diff --git a/.gitignore b/.gitignore index 549e00a..f72c5bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,3 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ +ordering/src/main/resources/application.properties +authentication/src/main/resources/application.properties +src/main/resources/application.properties \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..075303c --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..694ced6 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..b301a31 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..214cb49 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a2c32e8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/authentication/pom.xml b/authentication/pom.xml new file mode 100644 index 0000000..ddcaf97 --- /dev/null +++ b/authentication/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + com.coded.spring + Barrak + 0.0.1-SNAPSHOT + + + authentication + + + \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/authentication/src/main/kotlin/authentication/AuthenticationApplication.kt similarity index 61% rename from src/main/kotlin/com/coded/spring/ordering/Application.kt rename to authentication/src/main/kotlin/authentication/AuthenticationApplication.kt index 8554e49..eb6774c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/authentication/src/main/kotlin/authentication/AuthenticationApplication.kt @@ -1,11 +1,11 @@ -package com.coded.spring.ordering +package authentication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication -class Application +class AuthenticationApplication fun main(args: Array) { - runApplication(*args) -} + runApplication(*args) +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/AuthenticationController.kt b/authentication/src/main/kotlin/authentication/AuthenticationController.kt new file mode 100644 index 0000000..84e3c7f --- /dev/null +++ b/authentication/src/main/kotlin/authentication/AuthenticationController.kt @@ -0,0 +1,78 @@ +package authentication + +import RegistrationRequestDTO +import authentication.jwt.JwtService +import authentication.users.UsersService +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.* +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.* +import java.security.Principal + +@Tag(name = "AuthenticationAPI") +@RestController +@RequestMapping("/authentication") +class AuthController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService, + private val usersService: UsersService +) { + + // login page + @PostMapping("/login") + fun login(@RequestBody authRequest: AuthRequest): AuthResponse { + + // create a spring security authentication using the username and password from the request + val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password) + // then the credentials will be authenticated by authentication manager + val authentication = authenticationManager.authenticate(authToken) + + // check if authentication is successful + if (authentication.isAuthenticated) { + + // load the user details from the userDetailsService + val userDetails = userDetailsService.loadUserByUsername(authRequest.username) + // Uses a JWT (JSON Web Token) service to generate a token for the authenticated user. + val token = jwtService.generateToken(userDetails.username) + // Returns a response object containing the JWT token + return AuthResponse(token) + } else { + throw UsernameNotFoundException("Invalid user request!") + } + } + + // register + @PostMapping("/register") + fun addUser(@RequestBody request: RegistrationRequestDTO) { + usersService.registerUsers(request) + ResponseEntity.ok() + } + + + // check the token + @PostMapping("/check-token") + fun checkToken( + principal: Principal + ): CheckTokenResponse { + return CheckTokenResponse( + userId = usersService.findByUsername(principal.name) + ) + } + +} + +data class CheckTokenResponse( + val userId: Long +) + +data class AuthRequest( + val username: String, + val password: String +) + +data class AuthResponse( + val token: String +) \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/CustomUserDetailsClass.kt b/authentication/src/main/kotlin/authentication/CustomUserDetailsClass.kt new file mode 100644 index 0000000..d68bece --- /dev/null +++ b/authentication/src/main/kotlin/authentication/CustomUserDetailsClass.kt @@ -0,0 +1,28 @@ +package authentication + +import authentication.users.UserEntity +import authentication.users.UsersRepository +import jakarta.inject.Named +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.stereotype.Service + + +@Service +class CustomUserDetailsClass( + private val usersRepository: UsersRepository +): UserDetailsService{ + + override fun loadUserByUsername(username: String): UserDetails { + val user: UserEntity = usersRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User Not Found") + + return User.builder() + .username(user.username) + .password(user.password) + // .roles(user.role.toString()) + .build() + } +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/LoggingFilter.kt b/authentication/src/main/kotlin/authentication/LoggingFilter.kt new file mode 100644 index 0000000..46bb417 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/LoggingFilter.kt @@ -0,0 +1,40 @@ +package authentication + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper + + +@Component +class LoggingFilter: OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + + val cachedRequest = ContentCachingRequestWrapper(request) + val cachedResponse = ContentCachingResponseWrapper(response) + + filterChain.doFilter(cachedRequest, cachedResponse) + + logRequest(cachedRequest) + logResponse(cachedResponse) + cachedResponse.copyBodyToResponse() + } + + private fun logRequest(request: ContentCachingRequestWrapper){ + val requestBody = String(request.contentAsByteArray) + logger.info("Request: method=${request.method}, uri=${request.requestURI}, body=$requestBody") + } + + private fun logResponse(response: ContentCachingResponseWrapper) { + val responseBody = String(response.contentAsByteArray) + logger.info("Response: status=${response.status}, body=$responseBody") + } + +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/SecurityConfig.kt b/authentication/src/main/kotlin/authentication/SecurityConfig.kt new file mode 100644 index 0000000..184cbb3 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/SecurityConfig.kt @@ -0,0 +1,65 @@ +package authentication + +import authentication.jwt.JwtAuthFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.dao.DaoAuthenticationProvider +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val jwtAuthFilter: JwtAuthFilter, + private val userDetailsService: UserDetailsService +) { + + @Bean + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it + .requestMatchers("/authentication/**").permitAll() + .requestMatchers("/authentication/login").permitAll() + .requestMatchers("/hello").permitAll() + .requestMatchers("/authentication/check-token").authenticated() + // swagger + .requestMatchers("/api-docs").permitAll() + // registration endpoint + + // rest of endpoints will need authentication + .anyRequest().authenticated() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build(); + } + + @Bean + fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager = + config.authenticationManager + + @Bean + fun authenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(userDetailsService) + provider.setPasswordEncoder(passwordEncoder()) + return provider + } +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/jwt/JwtAuthFilter.kt b/authentication/src/main/kotlin/authentication/jwt/JwtAuthFilter.kt new file mode 100644 index 0000000..407bcb9 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/jwt/JwtAuthFilter.kt @@ -0,0 +1,45 @@ +package authentication.jwt + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.* +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter + +@Component +class JwtAuthFilter( + private val jwtService: JwtService, + private val userDetailsService: UserDetailsService +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val authHeader = request.getHeader("Authorization") + if (authHeader == null || !authHeader.startsWith("Bearer ")){ + filterChain.doFilter(request, response) + return + } + + val token = authHeader.substring(7) + val username = jwtService.extractUsername(token) + + if (SecurityContextHolder.getContext().authentication == null) { + if (jwtService.isTokenValid(token, username)) { + val userDetails = userDetailsService.loadUserByUsername(username) + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + } + } + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/jwt/JwtService.kt b/authentication/src/main/kotlin/authentication/jwt/JwtService.kt new file mode 100644 index 0000000..79d9387 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/jwt/JwtService.kt @@ -0,0 +1,50 @@ +package authentication.jwt + + +import io.jsonwebtoken.* +import java.util.* +import javax.crypto.SecretKey +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.security.Keys +import org.springframework.stereotype.Component +import java.util.* + + +@Component +class JwtService { + + private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private val expirationsMs: Long = 1000 * 60 * 60 + + // generate token for the user + fun generateToken(username: String): String { + val now = Date() + val expiry = Date(now.time + expirationsMs) + + return Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiry) + .signWith(secretKey) + .compact() + } + + // extract username from the token itself + fun extractUsername(token: String): String = + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .body + .subject + + // check if token is valid or not + fun isTokenValid(token: String, username: String): Boolean{ + return try { + extractUsername(token) == username + } catch (e: Exception){ + false + } + } + +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/users/UserRepository.kt b/authentication/src/main/kotlin/authentication/users/UserRepository.kt new file mode 100644 index 0000000..1a04034 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/users/UserRepository.kt @@ -0,0 +1,39 @@ +package authentication.users + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface UsersRepository : JpaRepository { + fun age(age: Int): MutableList + fun findByUsername(username: String): UserEntity? + fun existsByUsername(username: String): Boolean +} + +@Entity +@Table(name = "users") +data class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + var name: String, + var age: Int, + + @Column(unique = true) // make sure it's unique + var username: String, + + var password: String, + +// @Enumerated(EnumType.STRING) +// val role: Roles = Roles.USER + +){ + constructor() : this(null, "", 0,"username","password") +} + +enum class Roles { + USER, ADMIN +} + + diff --git a/authentication/src/main/kotlin/authentication/users/UsersController.kt b/authentication/src/main/kotlin/authentication/users/UsersController.kt new file mode 100644 index 0000000..0a08332 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/users/UsersController.kt @@ -0,0 +1,22 @@ +package authentication.users + +import RegistrationResponseDto +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import io.swagger.v3.oas.annotations.tags.Tag + +@Tag(name = "UserAPI") +@RestController +class UsersController( + private val usersService: UsersService, + +){ + + @GetMapping("/authentication/users/list") + fun users() = usersService.listUsers() + + +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/users/UsersDTO.kt b/authentication/src/main/kotlin/authentication/users/UsersDTO.kt new file mode 100644 index 0000000..8eac6a1 --- /dev/null +++ b/authentication/src/main/kotlin/authentication/users/UsersDTO.kt @@ -0,0 +1,14 @@ +// Change this: +data class RegistrationRequestDTO( + val name: String, + val age: Int, + val username: String, + val password: String +) +// To this: + + +data class RegistrationResponseDto( + val message: String, + val username: String +) \ No newline at end of file diff --git a/authentication/src/main/kotlin/authentication/users/UsersService.kt b/authentication/src/main/kotlin/authentication/users/UsersService.kt new file mode 100644 index 0000000..bf66bca --- /dev/null +++ b/authentication/src/main/kotlin/authentication/users/UsersService.kt @@ -0,0 +1,77 @@ +package authentication.users + +import RegistrationRequestDTO + +import jakarta.inject.Named +import org.springframework.security.crypto.password.PasswordEncoder + +@Named +class UsersService( + private val usersRepository: UsersRepository, + private val passwordEncoder: PasswordEncoder // Added missing dependency +) { + + fun findByUsername(username: String): Long { + return usersRepository.findByUsername(username)?.id + ?: throw IllegalArgumentException("User has no id") + } + + fun listUsers(): List = usersRepository.findAll().map { + User( + id = it.id, + name = it.name, + age = it.age, + ) + } + + fun registerUsers(request: RegistrationRequestDTO): UserEntity { + + //check if username exists already + if(usersRepository.existsByUsername(request.username)){ + throw IllegalArgumentException("user name already exists") + } + + // Validate the password + val password = request.password + + // check pass length + if (password.length < 6) { + throw IllegalArgumentException("Password must be at least 6 characters long") + } + + // check if there's any uppercase letter + if (!password.any { it.isUpperCase() }) { + throw IllegalArgumentException("Password must contain at least one capital letter") + } + + // check if there's any digits + if (!password.any { it.isDigit() }) { + throw IllegalArgumentException("Password must contain at least one number") + } + + // create user + val user = UserEntity( + name = request.name, + age = request.age, + username = request.username, + password = passwordEncoder.encode(request.password) + ) + + // save the user + return usersRepository.save(user) + } +} + +data class User( + val id: Long?, + val name: String, + val age: Int +) + +data class RegistrationRequest( + val name: String, + val age: Int, + val username: String, + val password: String +) + diff --git a/authentication/src/main/resources/application.properties b/authentication/src/main/resources/application.properties new file mode 100644 index 0000000..f0889b4 --- /dev/null +++ b/authentication/src/main/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port=8082 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/authentication/target/classes/META-INF/authentication.kotlin_module b/authentication/target/classes/META-INF/authentication.kotlin_module new file mode 100644 index 0000000..e0ddbc8 Binary files /dev/null and b/authentication/target/classes/META-INF/authentication.kotlin_module differ diff --git a/authentication/target/classes/RegistrationRequestDTO.class b/authentication/target/classes/RegistrationRequestDTO.class new file mode 100644 index 0000000..1f2dc52 Binary files /dev/null and b/authentication/target/classes/RegistrationRequestDTO.class differ diff --git a/authentication/target/classes/RegistrationResponseDto.class b/authentication/target/classes/RegistrationResponseDto.class new file mode 100644 index 0000000..06740b4 Binary files /dev/null and b/authentication/target/classes/RegistrationResponseDto.class differ diff --git a/authentication/target/classes/application.properties b/authentication/target/classes/application.properties new file mode 100644 index 0000000..f0889b4 --- /dev/null +++ b/authentication/target/classes/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port=8082 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/authentication/target/classes/authentication/AuthController.class b/authentication/target/classes/authentication/AuthController.class new file mode 100644 index 0000000..d688181 Binary files /dev/null and b/authentication/target/classes/authentication/AuthController.class differ diff --git a/authentication/target/classes/authentication/AuthRequest.class b/authentication/target/classes/authentication/AuthRequest.class new file mode 100644 index 0000000..0c1bc87 Binary files /dev/null and b/authentication/target/classes/authentication/AuthRequest.class differ diff --git a/authentication/target/classes/authentication/AuthResponse.class b/authentication/target/classes/authentication/AuthResponse.class new file mode 100644 index 0000000..d04e23a Binary files /dev/null and b/authentication/target/classes/authentication/AuthResponse.class differ diff --git a/authentication/target/classes/authentication/AuthenticationApplication.class b/authentication/target/classes/authentication/AuthenticationApplication.class new file mode 100644 index 0000000..e0c3943 Binary files /dev/null and b/authentication/target/classes/authentication/AuthenticationApplication.class differ diff --git a/authentication/target/classes/authentication/AuthenticationApplicationKt.class b/authentication/target/classes/authentication/AuthenticationApplicationKt.class new file mode 100644 index 0000000..4bb8e7a Binary files /dev/null and b/authentication/target/classes/authentication/AuthenticationApplicationKt.class differ diff --git a/authentication/target/classes/authentication/CheckTokenResponse.class b/authentication/target/classes/authentication/CheckTokenResponse.class new file mode 100644 index 0000000..977f339 Binary files /dev/null and b/authentication/target/classes/authentication/CheckTokenResponse.class differ diff --git a/authentication/target/classes/authentication/CustomUserDetailsClass.class b/authentication/target/classes/authentication/CustomUserDetailsClass.class new file mode 100644 index 0000000..8f84841 Binary files /dev/null and b/authentication/target/classes/authentication/CustomUserDetailsClass.class differ diff --git a/authentication/target/classes/authentication/LoggingFilter.class b/authentication/target/classes/authentication/LoggingFilter.class new file mode 100644 index 0000000..1f26aa4 Binary files /dev/null and b/authentication/target/classes/authentication/LoggingFilter.class differ diff --git a/authentication/target/classes/authentication/SecurityConfig.class b/authentication/target/classes/authentication/SecurityConfig.class new file mode 100644 index 0000000..3d0bec9 Binary files /dev/null and b/authentication/target/classes/authentication/SecurityConfig.class differ diff --git a/authentication/target/classes/authentication/jwt/JwtAuthFilter.class b/authentication/target/classes/authentication/jwt/JwtAuthFilter.class new file mode 100644 index 0000000..3a523cf Binary files /dev/null and b/authentication/target/classes/authentication/jwt/JwtAuthFilter.class differ diff --git a/authentication/target/classes/authentication/jwt/JwtService.class b/authentication/target/classes/authentication/jwt/JwtService.class new file mode 100644 index 0000000..271b692 Binary files /dev/null and b/authentication/target/classes/authentication/jwt/JwtService.class differ diff --git a/authentication/target/classes/authentication/users/RegistrationRequest.class b/authentication/target/classes/authentication/users/RegistrationRequest.class new file mode 100644 index 0000000..0f86da1 Binary files /dev/null and b/authentication/target/classes/authentication/users/RegistrationRequest.class differ diff --git a/authentication/target/classes/authentication/users/Roles.class b/authentication/target/classes/authentication/users/Roles.class new file mode 100644 index 0000000..f6d5609 Binary files /dev/null and b/authentication/target/classes/authentication/users/Roles.class differ diff --git a/authentication/target/classes/authentication/users/User.class b/authentication/target/classes/authentication/users/User.class new file mode 100644 index 0000000..0bd0301 Binary files /dev/null and b/authentication/target/classes/authentication/users/User.class differ diff --git a/authentication/target/classes/authentication/users/UserEntity.class b/authentication/target/classes/authentication/users/UserEntity.class new file mode 100644 index 0000000..f6dbfd1 Binary files /dev/null and b/authentication/target/classes/authentication/users/UserEntity.class differ diff --git a/authentication/target/classes/authentication/users/UsersController.class b/authentication/target/classes/authentication/users/UsersController.class new file mode 100644 index 0000000..ff8c1b7 Binary files /dev/null and b/authentication/target/classes/authentication/users/UsersController.class differ diff --git a/authentication/target/classes/authentication/users/UsersRepository.class b/authentication/target/classes/authentication/users/UsersRepository.class new file mode 100644 index 0000000..d3917d0 Binary files /dev/null and b/authentication/target/classes/authentication/users/UsersRepository.class differ diff --git a/authentication/target/classes/authentication/users/UsersService.class b/authentication/target/classes/authentication/users/UsersService.class new file mode 100644 index 0000000..e16cb77 Binary files /dev/null and b/authentication/target/classes/authentication/users/UsersService.class differ diff --git a/ordering/pom.xml b/ordering/pom.xml new file mode 100644 index 0000000..0cfd102 --- /dev/null +++ b/ordering/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + com.coded.spring + Barrak + 0.0.1-SNAPSHOT + + + ordering + + + com.coded.spring + authentication + 0.0.1-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/ordering/src/main/kotlin/LoggingFilter.kt b/ordering/src/main/kotlin/LoggingFilter.kt new file mode 100644 index 0000000..1dfcaed --- /dev/null +++ b/ordering/src/main/kotlin/LoggingFilter.kt @@ -0,0 +1,40 @@ +package ordering + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper + + +@Component +class LoggingFilter: OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + + val cachedRequest = ContentCachingRequestWrapper(request) + val cachedResponse = ContentCachingResponseWrapper(response) + + filterChain.doFilter(cachedRequest, cachedResponse) + + logRequest(cachedRequest) + logResponse(cachedResponse) + cachedResponse.copyBodyToResponse() + } + + private fun logRequest(request: ContentCachingRequestWrapper){ + val requestBody = String(request.contentAsByteArray) + logger.info("Request: method=${request.method}, uri=${request.requestURI}, body=$requestBody") + } + + private fun logResponse(response: ContentCachingResponseWrapper) { + val responseBody = String(response.contentAsByteArray) + logger.info("Response: status=${response.status}, body=$responseBody") + } + +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/OrderingApplication.kt b/ordering/src/main/kotlin/OrderingApplication.kt new file mode 100644 index 0000000..a1fc64b --- /dev/null +++ b/ordering/src/main/kotlin/OrderingApplication.kt @@ -0,0 +1,17 @@ +package ordering + +import com.hazelcast.config.Config +import com.hazelcast.core.Hazelcast +import com.hazelcast.core.HazelcastInstance +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class OrderingApplication + +fun main(args: Array) { + runApplication(*args) +} + +val orderingCacheConfig = Config("hello-world-cache") +val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(orderingCacheConfig) \ No newline at end of file diff --git a/ordering/src/main/kotlin/client/AuthenticationClient.kt b/ordering/src/main/kotlin/client/AuthenticationClient.kt new file mode 100644 index 0000000..ad85bf5 --- /dev/null +++ b/ordering/src/main/kotlin/client/AuthenticationClient.kt @@ -0,0 +1,33 @@ +package ordering.client + +import authentication.CheckTokenResponse +import jakarta.inject.Named +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.* +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.exchange + +@Named +class AuthenticationClient { + + fun checkToken(token: String): CheckTokenResponse { + val restTemplate = RestTemplate() + val url = "http://localhost:8082/authentication/check-token" + + // Create headers with token + val headers = HttpHeaders() + headers.set("Authorization", "Bearer $token") + + // Create request entity with headers + val requestEntity = HttpEntity(headers) + + val response = restTemplate.exchange( + url = url, + method = HttpMethod.POST, + requestEntity = requestEntity, + object : ParameterizedTypeReference() {} + ) + return response.body ?: throw IllegalStateException("Check token response has no body ...") + } + +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/items/ItemsController.kt b/ordering/src/main/kotlin/items/ItemsController.kt new file mode 100644 index 0000000..8def5d4 --- /dev/null +++ b/ordering/src/main/kotlin/items/ItemsController.kt @@ -0,0 +1,22 @@ +//package items +// +//import io.swagger.v3.oas.annotations.tags.Tag +//import org.springframework.web.bind.annotation.GetMapping +//import org.springframework.web.bind.annotation.PostMapping +//import org.springframework.web.bind.annotation.RequestBody +//import org.springframework.web.bind.annotation.RestController +// +//@Tag(name = "ItemAPI") +//@RestController +//class ItemsController( +// private val itemsService: ItemsService +//) { +// +// @GetMapping("/items/list") +// fun items() = itemsService.listItems() +// +// @PostMapping("/items/create") +// fun addItems(@RequestBody request: CreateItemRequestDTO): ItemDTO { +// return itemsService.createItem(request) +// } +//} \ No newline at end of file diff --git a/ordering/src/main/kotlin/items/ItemsDTO.kt b/ordering/src/main/kotlin/items/ItemsDTO.kt new file mode 100644 index 0000000..15a99ff --- /dev/null +++ b/ordering/src/main/kotlin/items/ItemsDTO.kt @@ -0,0 +1,17 @@ +package ordering.items + +data class CreateItemRequestDTO( + + var name: String, + var quantity: Int, + var order_id: Long +) + +data class ItemDTO( + val name: String, + val quantity: Int, +) + +data class ItemsListDTO( + val items: List +) \ No newline at end of file diff --git a/ordering/src/main/kotlin/items/ItemsRepository.kt b/ordering/src/main/kotlin/items/ItemsRepository.kt new file mode 100644 index 0000000..6865742 --- /dev/null +++ b/ordering/src/main/kotlin/items/ItemsRepository.kt @@ -0,0 +1,27 @@ +package ordering.items + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface ItemsRepository: JpaRepository + +@Entity +@Table(name = "items") +data class ItemEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + var name: String, + var quantity: Int, + + @Column(name = "order_id") + var orderId: Long, + +// @Column("'name'") + + +){ + constructor(): this(null, "", 0, 0) +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/items/ItemsService.kt b/ordering/src/main/kotlin/items/ItemsService.kt new file mode 100644 index 0000000..a9821fb --- /dev/null +++ b/ordering/src/main/kotlin/items/ItemsService.kt @@ -0,0 +1,44 @@ +//package items +// +//import orders.OrderRepository +//import jakarta.inject.Named +// +//@Named +//class ItemsService( +// private val itemsRepository: ItemsRepository, +// private val orderRepository: OrderRepository // Added to fetch orders +// +//) { +// +// fun listItems(): List = itemsRepository.findAll().map { +// ItemDTO( +// id = it.id, +// name = it.name, +// quantity = it.quantity, +// orderId = it.order.id +// ) +// +// } +// +// fun createItem(request: CreateItemRequestDTO): ItemDTO { +// val order = orderRepository.findById(request.order_id) +// .orElseThrow { IllegalArgumentException("Order not found with id: ${request.order_id}") } +// +// val item = ItemEntity( +// name = request.name, +// quantity = request.quantity, +// order = order +// ) +// +// val savedItem = itemsRepository.save(item) +// +// return ItemDTO( +// id = savedItem.id, +// name = savedItem.name, +// quantity = savedItem.quantity, +// orderId = savedItem.order.id +// ) +// } +// +// +//} \ No newline at end of file diff --git a/ordering/src/main/kotlin/orders/OrderController.kt b/ordering/src/main/kotlin/orders/OrderController.kt new file mode 100644 index 0000000..27d4190 --- /dev/null +++ b/ordering/src/main/kotlin/orders/OrderController.kt @@ -0,0 +1,45 @@ +package ordering.orders + +import authentication.users.UsersService +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.servlet.http.HttpServletRequest +import ordering.items.ItemDTO + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +//@Tag(name = "OrderAPI") +@RestController +class OrderController( + private val orderService: OrderService, + +) { + + @GetMapping("/orders/orders") + fun orders(): OrdersListDTO { + val ordersList = orderService.listOrders() + return OrdersListDTO(orders = ordersList) + } + + // create order + @PostMapping("/orders/orders") + fun createOrder( + request: HttpServletRequest, + @RequestBody body: CreateOrderRequest + ): CreateOrderResponse { + val userId = request.getAttribute("userId") as Long + val order = orderService.createOrder(userId, body.items) + return CreateOrderResponse( + order.id, + order.items + ) + } + +} + +data class CreateOrderResponse( + val id: Long?, + val items: List? +) diff --git a/ordering/src/main/kotlin/orders/OrderRepository.kt b/ordering/src/main/kotlin/orders/OrderRepository.kt new file mode 100644 index 0000000..b1a925c --- /dev/null +++ b/ordering/src/main/kotlin/orders/OrderRepository.kt @@ -0,0 +1,32 @@ +package ordering.orders + + +import jakarta.inject.Named +import jakarta.persistence.* +import ordering.items.ItemEntity +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface OrderRepository : JpaRepository { + fun findByUserId(userId: Long): List +} + +@Entity +@Table(name = "orders") +data class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "user_id") + val userId: Long, + + // join + @OneToMany + val items: List? = null + + +){ + constructor(): this(null, 0, listOf()) + +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/orders/OrderService.kt b/ordering/src/main/kotlin/orders/OrderService.kt new file mode 100644 index 0000000..03a6c07 --- /dev/null +++ b/ordering/src/main/kotlin/orders/OrderService.kt @@ -0,0 +1,50 @@ +package ordering.orders + +import authentication.users.UsersRepository + +import jakarta.inject.Named +import ordering.items.ItemDTO +import ordering.items.ItemEntity +import ordering.items.ItemsRepository + +@Named +class OrderService( + private val orderRepository: OrderRepository, + private val itemsRepository: ItemsRepository +) { + + // listing orders + fun listOrders(): List = orderRepository.findAll().map { order -> + OrderDTO( + id = order.id, //transfer the orderId to dto + items = order.items?.map { item -> + //for each item in the order, create an ItemDTO + ItemDTO( + name = item.name, + quantity = item.quantity + ) + } + ) + } + + // creating new order + fun createOrder(userId: Long, items: List): OrderDTO { + val newOrder = OrderEntity(userId = userId) + val savedOrder = orderRepository.save(newOrder) + + val newItems = items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + orderId = savedOrder.id!! + ) + } + itemsRepository.saveAll(newItems) + + return OrderDTO( + id = savedOrder.id, + items = newItems.map { ItemDTO(it.name, it.quantity) } + ) + } +} + diff --git a/ordering/src/main/kotlin/orders/OrdersDTO.kt b/ordering/src/main/kotlin/orders/OrdersDTO.kt new file mode 100644 index 0000000..5cd6086 --- /dev/null +++ b/ordering/src/main/kotlin/orders/OrdersDTO.kt @@ -0,0 +1,17 @@ +package ordering.orders + +import ordering.items.ItemDTO + +// return this when user asking for order list +data class OrderDTO( + val id: Long?, + val items: List? +) + +data class OrdersListDTO( + val orders: List +) + +data class CreateOrderRequest( + val items: List +) \ No newline at end of file diff --git a/ordering/src/main/kotlin/security/RemoteAuthenticationFilter.kt b/ordering/src/main/kotlin/security/RemoteAuthenticationFilter.kt new file mode 100644 index 0000000..bd700b2 --- /dev/null +++ b/ordering/src/main/kotlin/security/RemoteAuthenticationFilter.kt @@ -0,0 +1,32 @@ +package ordering.security + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.* +import ordering.client.AuthenticationClient +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter + +@Component +class RemoteAuthenticationFilter( + private val authenticationClient: AuthenticationClient, +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + logger.info("Remote authentication filter running...") + val authHeader = request.getHeader("Authorization") + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response) + return + } + + val token = authHeader.substring(7) + val result = authenticationClient.checkToken(token) + request.setAttribute("userId", result.userId) + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/security/SecurityConfig.kt b/ordering/src/main/kotlin/security/SecurityConfig.kt new file mode 100644 index 0000000..5586252 --- /dev/null +++ b/ordering/src/main/kotlin/security/SecurityConfig.kt @@ -0,0 +1,30 @@ +package ordering.security + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val remoteAuthFilter: RemoteAuthenticationFilter +) { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.anyRequest().permitAll() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .addFilterBefore(remoteAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build() + } +} \ No newline at end of file diff --git a/ordering/src/main/resources/application.properties b/ordering/src/main/resources/application.properties new file mode 100644 index 0000000..2bf2893 --- /dev/null +++ b/ordering/src/main/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port=8083 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/ordering/target/classes/META-INF/ordering.kotlin_module b/ordering/target/classes/META-INF/ordering.kotlin_module new file mode 100644 index 0000000..5d4bb7c Binary files /dev/null and b/ordering/target/classes/META-INF/ordering.kotlin_module differ diff --git a/ordering/target/classes/application.properties b/ordering/target/classes/application.properties new file mode 100644 index 0000000..2bf2893 --- /dev/null +++ b/ordering/target/classes/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port=8083 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/ordering/target/classes/ordering/LoggingFilter.class b/ordering/target/classes/ordering/LoggingFilter.class new file mode 100644 index 0000000..0ea4566 Binary files /dev/null and b/ordering/target/classes/ordering/LoggingFilter.class differ diff --git a/ordering/target/classes/ordering/OrderingApplication.class b/ordering/target/classes/ordering/OrderingApplication.class new file mode 100644 index 0000000..ff9a185 Binary files /dev/null and b/ordering/target/classes/ordering/OrderingApplication.class differ diff --git a/ordering/target/classes/ordering/OrderingApplicationKt.class b/ordering/target/classes/ordering/OrderingApplicationKt.class new file mode 100644 index 0000000..fee0751 Binary files /dev/null and b/ordering/target/classes/ordering/OrderingApplicationKt.class differ diff --git a/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$$inlined$exchange$1.class b/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$$inlined$exchange$1.class new file mode 100644 index 0000000..9bcc38a Binary files /dev/null and b/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$$inlined$exchange$1.class differ diff --git a/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$response$1.class b/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$response$1.class new file mode 100644 index 0000000..21161ce Binary files /dev/null and b/ordering/target/classes/ordering/client/AuthenticationClient$checkToken$response$1.class differ diff --git a/ordering/target/classes/ordering/client/AuthenticationClient.class b/ordering/target/classes/ordering/client/AuthenticationClient.class new file mode 100644 index 0000000..9567369 Binary files /dev/null and b/ordering/target/classes/ordering/client/AuthenticationClient.class differ diff --git a/ordering/target/classes/ordering/items/CreateItemRequestDTO.class b/ordering/target/classes/ordering/items/CreateItemRequestDTO.class new file mode 100644 index 0000000..f27631c Binary files /dev/null and b/ordering/target/classes/ordering/items/CreateItemRequestDTO.class differ diff --git a/ordering/target/classes/ordering/items/ItemDTO.class b/ordering/target/classes/ordering/items/ItemDTO.class new file mode 100644 index 0000000..9578e32 Binary files /dev/null and b/ordering/target/classes/ordering/items/ItemDTO.class differ diff --git a/ordering/target/classes/ordering/items/ItemEntity.class b/ordering/target/classes/ordering/items/ItemEntity.class new file mode 100644 index 0000000..a9897c7 Binary files /dev/null and b/ordering/target/classes/ordering/items/ItemEntity.class differ diff --git a/ordering/target/classes/ordering/items/ItemsListDTO.class b/ordering/target/classes/ordering/items/ItemsListDTO.class new file mode 100644 index 0000000..9220d6d Binary files /dev/null and b/ordering/target/classes/ordering/items/ItemsListDTO.class differ diff --git a/ordering/target/classes/ordering/items/ItemsRepository.class b/ordering/target/classes/ordering/items/ItemsRepository.class new file mode 100644 index 0000000..e073984 Binary files /dev/null and b/ordering/target/classes/ordering/items/ItemsRepository.class differ diff --git a/ordering/target/classes/ordering/orders/CreateOrderRequest.class b/ordering/target/classes/ordering/orders/CreateOrderRequest.class new file mode 100644 index 0000000..cd22645 Binary files /dev/null and b/ordering/target/classes/ordering/orders/CreateOrderRequest.class differ diff --git a/ordering/target/classes/ordering/orders/CreateOrderResponse.class b/ordering/target/classes/ordering/orders/CreateOrderResponse.class new file mode 100644 index 0000000..16fd8e2 Binary files /dev/null and b/ordering/target/classes/ordering/orders/CreateOrderResponse.class differ diff --git a/ordering/target/classes/ordering/orders/OrderController.class b/ordering/target/classes/ordering/orders/OrderController.class new file mode 100644 index 0000000..c922adf Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrderController.class differ diff --git a/ordering/target/classes/ordering/orders/OrderDTO.class b/ordering/target/classes/ordering/orders/OrderDTO.class new file mode 100644 index 0000000..c6e17cd Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrderDTO.class differ diff --git a/ordering/target/classes/ordering/orders/OrderEntity.class b/ordering/target/classes/ordering/orders/OrderEntity.class new file mode 100644 index 0000000..63393e1 Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrderEntity.class differ diff --git a/ordering/target/classes/ordering/orders/OrderRepository.class b/ordering/target/classes/ordering/orders/OrderRepository.class new file mode 100644 index 0000000..8a217d0 Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrderRepository.class differ diff --git a/ordering/target/classes/ordering/orders/OrderService.class b/ordering/target/classes/ordering/orders/OrderService.class new file mode 100644 index 0000000..124c693 Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrderService.class differ diff --git a/ordering/target/classes/ordering/orders/OrdersListDTO.class b/ordering/target/classes/ordering/orders/OrdersListDTO.class new file mode 100644 index 0000000..b61b493 Binary files /dev/null and b/ordering/target/classes/ordering/orders/OrdersListDTO.class differ diff --git a/ordering/target/classes/ordering/security/RemoteAuthenticationFilter.class b/ordering/target/classes/ordering/security/RemoteAuthenticationFilter.class new file mode 100644 index 0000000..f203fad Binary files /dev/null and b/ordering/target/classes/ordering/security/RemoteAuthenticationFilter.class differ diff --git a/ordering/target/classes/ordering/security/SecurityConfig.class b/ordering/target/classes/ordering/security/SecurityConfig.class new file mode 100644 index 0000000..eeb61af Binary files /dev/null and b/ordering/target/classes/ordering/security/SecurityConfig.class differ diff --git a/pom.xml b/pom.xml index 163ad53..5875045 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot @@ -8,9 +8,11 @@ 3.4.4 + com.coded.spring - Ordering + Barrak 0.0.1-SNAPSHOT + pom Kotlin.SpringbootV2 Kotlin.SpringbootV2 @@ -20,6 +22,10 @@ + + authentication + ordering + @@ -43,21 +49,80 @@ org.jetbrains.kotlin kotlin-reflect + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + org.jetbrains.kotlin kotlin-stdlib - + + org.springdoc + springdoc-openapi-starter-webmvc-api + 2.6.0 + + + org.postgresql + postgresql + compile + + + com.hazelcast + hazelcast + 5.3.8 + org.springframework.boot spring-boot-starter-test test + + com.h2database + h2 + test + org.jetbrains.kotlin kotlin-test-junit5 test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + + + + + + jakarta.inject + jakarta.inject-api + 2.0.1 + + + org.springframework.boot + spring-boot-starter-security + + + @@ -90,4 +155,4 @@ - + \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring - Shortcut.lnk b/src/main/kotlin/com/coded/spring - Shortcut.lnk new file mode 100644 index 0000000..f6e78c9 Binary files /dev/null and b/src/main/kotlin/com/coded/spring - Shortcut.lnk differ diff --git a/src/main/kotlin/com/coded/spring/orders/Application.kt b/src/main/kotlin/com/coded/spring/orders/Application.kt new file mode 100644 index 0000000..08a9aa1 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/Application.kt @@ -0,0 +1,18 @@ +package com.coded.spring.ordering + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast +import com.hazelcast.core.HazelcastInstance + + +@SpringBootApplication +class Application + +fun main(args: Array) { + runApplication(*args) +} + +val orderingConfig = Config("Ordering-config") +val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(orderingConfig) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/HelloWorldController.kt b/src/main/kotlin/com/coded/spring/orders/HelloWorldController.kt new file mode 100644 index 0000000..e0ce963 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/HelloWorldController.kt @@ -0,0 +1,86 @@ +package com.coded.spring.ordering + + +import com.coded.spring.ordering.orders.OrderEntity +import com.coded.spring.ordering.orders.OrderRepository +import com.coded.spring.ordering.users.UserEntity +import com.coded.spring.ordering.users.UsersRepository +import org.springframework.beans.factory.annotation.Value +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class HelloWorldController( + val usersRepository: UsersRepository, + val orderRepository: OrderRepository, + + // welcome endpoint + @Value("\${server-welcome-message:test}") + val welcomeMessage: String, + //the feature + @Value("\${feature.festive.enabled:false}") + val festiveStatus: Boolean, + + @Value("\${festive-welcome-message: Eidkom Mubarak}") + private val festiveWelcomeMessage: String +){ + + @GetMapping("/hello") + + fun helloWorld() = if(festiveStatus){ + festiveWelcomeMessage + }else { + "$welcomeMessage Barrak" + } + + + @GetMapping("/cart") + fun cart() = "Your cart" + + @GetMapping("/restaurants") + fun resturant() = "List of resturants" + + @GetMapping("/Settings") + fun settings() = "Settings!" + + // Post +// @PostMapping("/my-name") +// fun sayMyName( @RequestBody request: SayMyNameRequest) = usersRepository.save(UserEntity(name = request.name, age = request.age)) + + @GetMapping("/my-name") + fun getName(): MutableList { + return usersRepository.findAll() + } + +// @PostMapping("/orders") +// fun orders( @RequestBody request: OrderRequest): OrderEntity { +// val order = (OrderEntity(null,user)) +// return orderRepository.save(order) +// } + + @GetMapping("/orders") + fun getOrders(): MutableList{ + return orderRepository.findAll() + } + + + + + +} + +data class SayMyNameRequest( + val name: String, + val age: Int +) + +data class OrderRequest( + val user_id: Int, + + +) + + + diff --git a/src/main/kotlin/com/coded/spring/orders/files/Barrak-online-ordering-api-swagger-01.json b/src/main/kotlin/com/coded/spring/orders/files/Barrak-online-ordering-api-swagger-01.json new file mode 100644 index 0000000..195bf86 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/files/Barrak-online-ordering-api-swagger-01.json @@ -0,0 +1 @@ +{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:8081","description":"Generated server url"}],"paths":{"/register":{"post":{"tags":["UserAPI"],"operationId":"registerUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/profile":{"post":{"tags":["ProfileAPI"],"operationId":"createOrUpdateProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileRequestDto"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/orders/orders":{"get":{"tags":["OrderAPI"],"operationId":"orders","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderDTO"}}}}}}},"post":{"tags":["OrderAPI"],"operationId":"createOrders","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderRequest"}}},"required":true},"responses":{"200":{"description":"OK"}}}},"/items/create":{"post":{"tags":["ItemAPI"],"operationId":"addItems","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateItemRequestDTO"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ItemDTO"}}}}}}},"/authentication/login":{"post":{"tags":["AuthenticationAPI"],"operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}}}}},"/users/list":{"get":{"tags":["UserAPI"],"operationId":"users","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}}},"/restaurants":{"get":{"tags":["hello-world-controller"],"operationId":"resturant","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/orders":{"get":{"tags":["hello-world-controller"],"operationId":"getOrders","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderEntity"}}}}}}}},"/my-name":{"get":{"tags":["hello-world-controller"],"operationId":"getName","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserEntity"}}}}}}}},"/menu":{"get":{"tags":["MenuAPI"],"operationId":"menu","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MenuEntity"}}}}}}}},"/items/list":{"get":{"tags":["ItemAPI"],"operationId":"items","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Unit"}}}}}}}},"/hello":{"get":{"tags":["hello-world-controller"],"operationId":"helloWorld","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/cart":{"get":{"tags":["hello-world-controller"],"operationId":"cart","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/Settings":{"get":{"tags":["hello-world-controller"],"operationId":"settings","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"RegistrationRequest":{"required":["age","name","password","username"],"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer","format":"int32"},"username":{"type":"string"},"password":{"type":"string"}}},"ProfileRequestDto":{"required":["firstName","lastName","phoneNumber"],"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"phoneNumber":{"type":"string"}}},"CreateOrderRequest":{"required":["userId"],"type":"object","properties":{"userId":{"type":"integer","format":"int64"}}},"CreateItemRequestDTO":{"required":["name","order_id","quantity"],"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"order_id":{"type":"integer","format":"int64"}}},"ItemDTO":{"required":["name","quantity"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"orderId":{"type":"integer","format":"int64"}}},"AuthRequest":{"required":["password","username"],"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"AuthResponse":{"required":["token"],"type":"object","properties":{"token":{"type":"string"}}},"User":{"required":["age","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"age":{"type":"integer","format":"int32"}}},"ItemEntity":{"required":["name","order","quantity"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"order":{"$ref":"#/components/schemas/OrderEntity"}}},"OrderEntity":{"required":["user"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"user":{"$ref":"#/components/schemas/UserEntity"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemEntity"}}}},"UserEntity":{"required":["age","name","password","username"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"age":{"type":"integer","format":"int32"},"username":{"type":"string"},"password":{"type":"string"}}},"OrderDTO":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"userId":{"type":"integer","format":"int64"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemDTO"}}}},"MenuEntity":{"required":["description","name","price"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"price":{"type":"number"},"description":{"type":"string"}}},"Unit":{"type":"object"}}}} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/menu/MenuController.kt b/src/main/kotlin/com/coded/spring/orders/menu/MenuController.kt new file mode 100644 index 0000000..66044f1 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/menu/MenuController.kt @@ -0,0 +1,15 @@ +package com.coded.spring.ordering.menu + +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "MenuAPI") +@RestController +class MenuController( + private val menuService: MenuService +){ + + @GetMapping("menu") + fun menu() = menuService.listMenu() +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/menu/MenuRepository.kt b/src/main/kotlin/com/coded/spring/orders/menu/MenuRepository.kt new file mode 100644 index 0000000..9def669 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/menu/MenuRepository.kt @@ -0,0 +1,27 @@ +package com.coded.spring.ordering.menu + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import java.math.BigDecimal + +@Named +interface MenuRepository : JpaRepository{ + +} + +@Entity +@Table(name = "menu") +data class MenuEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + var name: String, + var price: BigDecimal, + var description: String + + +){ + constructor() : this(null, "", BigDecimal.ZERO, "") +} diff --git a/src/main/kotlin/com/coded/spring/orders/menu/MenuService.kt b/src/main/kotlin/com/coded/spring/orders/menu/MenuService.kt new file mode 100644 index 0000000..b40140c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/menu/MenuService.kt @@ -0,0 +1,30 @@ +package com.coded.spring.ordering.menu + +import jakarta.inject.Named +import org.springframework.beans.factory.annotation.Value +import java.math.BigDecimal +import java.math.RoundingMode + +@Named +class MenuService( + private val menuRepository: MenuRepository, + + @Value("\${feature.festive.enabled:false}") + val festiveStatus: Boolean, +) { + + fun listMenu(): List = menuRepository.findAll().map { + MenuEntity( + id = it.id, + name = it.name, + price = if(festiveStatus) { + // discount the price by 20% + it.price.multiply(BigDecimal("0.8")) + } else{ + it.price + }, + description = it.description + ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesController.kt b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesController.kt new file mode 100644 index 0000000..6329263 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesController.kt @@ -0,0 +1,24 @@ +package com.coded.spring.ordering.profiles + +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "ProfileAPI") +@RestController +class ProfilesController( + private val profilesRepository: ProfilesRepository, + private val profilesService: ProfilesService +){ + // user creating profile + @PostMapping("/profile") + fun createOrUpdateProfile(@RequestBody request: ProfileRequestDto): Any { + try{ + return profilesService.createOrUpdateProf(request) + } catch (e: IllegalArgumentException) { + return ResponseEntity.badRequest().body(mapOf("error" to e.message)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesDTO.kt b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesDTO.kt new file mode 100644 index 0000000..96fedea --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesDTO.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering.profiles + +import com.coded.spring.ordering.users.UserEntity + +data class ProfileRequestDto( + val firstName: String, + val lastName: String, + val phoneNumber: String +) + +data class ProfileResponseDto( + val userId: Long, + val firstName: String, + val lastName: String, + val phoneNumber: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesRepository.kt b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesRepository.kt new file mode 100644 index 0000000..5bbf828 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesRepository.kt @@ -0,0 +1,33 @@ +package com.coded.spring.ordering.profiles + + +import com.coded.spring.ordering.users.UserEntity +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + + + +@Named +interface ProfilesRepository : JpaRepository { + fun findByUserId(userId: UserEntity): ProfileEntity? +} + +@Entity +@Table(name = "profiles") +data class ProfileEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + //one user can have one profile + @OneToOne + @JoinColumn(name = "user_id")// foreign key for Users(Id) + var userId: UserEntity, + + var firstName: String, + var lastName: String, + var phoneNumber: String, + +){ constructor() : this(null, UserEntity(), "", "", "") +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesService.kt b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesService.kt new file mode 100644 index 0000000..b08210f --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/profiles/ProfilesService.kt @@ -0,0 +1,55 @@ +package com.coded.spring.ordering.profiles + +import com.coded.spring.ordering.users.UsersRepository +import jakarta.inject.Named +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Service + +@Service +open class ProfilesService( + private val profilesRepository: ProfilesRepository, + private val usersRepository: UsersRepository +) { + + // create or update info for profiles + fun createOrUpdateProf(request: ProfileRequestDto): Any{ + + // get the username from the security contextholer + val username = SecurityContextHolder.getContext().authentication.name + + // check if user exists + val user = usersRepository.findByUsername(username) + ?: throw IllegalArgumentException("user not found") + + // check if user have a profile + val existingProfile = profilesRepository.findByUserId(user) + + val profile = if( existingProfile != null){ + // update existing profile + val updateProfile = existingProfile.copy( + firstName = request.firstName, + lastName = request.lastName, + phoneNumber = request.phoneNumber + ) + profilesRepository.save(updateProfile) + + } else { // if user doesn't have a profile + // create a new profile + val createProfile = ProfileEntity( + userId = user, + firstName = request.firstName, + lastName = request.lastName, + phoneNumber = request.phoneNumber, + ) + profilesRepository.save(createProfile) + } + + return ProfileResponseDto( + userId = user.id!!, + firstName = profile.firstName, + lastName = profile.lastName, + phoneNumber = profile.phoneNumber + ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/orders/scripts/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/orders/scripts/InitUserRunner.kt new file mode 100644 index 0000000..a2bbb6d --- /dev/null +++ b/src/main/kotlin/com/coded/spring/orders/scripts/InitUserRunner.kt @@ -0,0 +1,34 @@ +package com.coded.spring.ordering.scripts + +import com.coded.spring.ordering.users.UserEntity +import com.coded.spring.ordering.users.UsersRepository +import org.springframework.boot.CommandLineRunner +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.security.crypto.password.PasswordEncoder + +@SpringBootApplication +class InitUserRunner { + + @Bean + fun initUsers(usersRepository: UsersRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { + val user = UserEntity( + name = "HelloUser", + username = "testuser", + password = passwordEncoder.encode("password123"), + age = 18, + + ) + if (usersRepository.findByUsername(user.username) == null) { + println("Creating user ${user.username}") + usersRepository.save(user) + } else { + println("User ${user.username} already exists") + } + } +} + +fun main(args: Array) { + runApplication(*args).close() +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..38fb0f0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,10 @@ spring.application.name=Kotlin.SpringbootV2 + +server.port=8081 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index b2e2320..18278f6 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -1,13 +1,347 @@ package com.coded.spring.ordering +import RegistrationResponseDto +import com.coded.spring.ordering.authentication.AuthRequest +import com.coded.spring.ordering.authentication.AuthResponse +import com.coded.spring.ordering.authentication.jwt.JwtService +import com.coded.spring.ordering.items.CreateItemRequestDTO +import com.coded.spring.ordering.items.ItemDTO +import com.coded.spring.ordering.items.ItemsListDTO +import com.coded.spring.ordering.items.ItemsRepository +import com.coded.spring.ordering.orders.CreateOrderRequest +import com.coded.spring.ordering.orders.OrderDTO +import com.coded.spring.ordering.orders.OrderRepository +import com.coded.spring.ordering.orders.OrdersListDTO +import com.coded.spring.ordering.profiles.ProfileRequestDto +import com.coded.spring.ordering.profiles.ProfileResponseDto +import com.coded.spring.ordering.users.RegistrationRequest +import com.coded.spring.ordering.users.UserEntity +import com.coded.spring.ordering.users.UsersRepository +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.boot.test.web.client.exchange +import org.springframework.boot.test.web.client.getForEntity +import org.springframework.boot.test.web.client.postForEntity +import org.springframework.context.annotation.Profile +import org.springframework.http.* +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.test.context.ActiveProfiles +import org.springframework.util.MultiValueMap +import kotlin.reflect.jvm.internal.impl.load.kotlin.JvmType +import kotlin.test.assertEquals +import kotlin.test.assertTrue -@SpringBootTest +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, +) class ApplicationTests { + companion object { + val testUser = "Yousef91" + val testPassword = "Test123456" + + @JvmStatic + @BeforeAll + fun setup( + + @Autowired usersRepository: UsersRepository, + @Autowired passwordEncoder: PasswordEncoder + ) { + + val test = UserEntity( + name = "Yousef Al Othman", + age = 25, + username = testUser, + password = passwordEncoder.encode(testPassword) + ) + usersRepository.save(test) + } + } + + @Autowired + lateinit var restTemplate: TestRestTemplate + @Autowired + lateinit var usersRepository: UsersRepository + + // a helper function to get jwt token + fun getAuthToken(): String { + // create a authentication request with test credentials + val login = AuthRequest( + username = testUser, + password = testPassword + ) + + // send an http post request to the login endpoint + val response: ResponseEntity = restTemplate.postForEntity( + "/authentication/login", + login, + AuthResponse::class.java + ) + + Assertions.assertEquals(HttpStatus.OK, response.statusCode) //check status code + assertNotNull(response.body) //check that response.body is not null + + return response.body?.token ?: throw AssertionError("Token is null") + + } + + // function to create the authentication request + fun authRequest(body: T, token: String): HttpEntity{ + val headers = HttpHeaders() + headers.setBearerAuth(token) + return HttpEntity(body, headers) + } + + + // 1st test + @Test + fun `As a user, I can login and get jwt token`(){ + // create a authentication request with test credentials + val login = AuthRequest( + username = testUser, + password = testPassword + ) + + // send a http post request to the login endpoint + val response: ResponseEntity = restTemplate.postForEntity( + "/authentication/login", + login, + AuthResponse::class.java + ) + + Assertions.assertEquals(HttpStatus.OK, response.statusCode) //check status code + assertNotNull(response.body) //check that response.body is not null + } + + //registration + @Test + fun `As a developer, I can test user regastration endpoint`(){ + // create a regastration request + val regastration = RegistrationRequest( + username = "userRegistration", + password = testPassword, + age = 30, + name = "Nemo" + ) + + // send http post request to the regastration endpoint + val response: ResponseEntity = restTemplate.postForEntity( + "/register", + regastration, + RegistrationResponseDto::class.java + ) + + Assertions.assertEquals(HttpStatus.OK, response.statusCode) //check status code + Assertions.assertEquals("userRegistration", response.body?.username) // check if username matches + // Verify the user was saved in database + val savedUser = usersRepository.findByUsername("userRegistration") + assertNotNull(savedUser) + + } + + + + // 3rd test + @Test + fun `As a developer, I can test creating orders`(){ + //get authentication token + val token = getAuthToken() + + //get user id + val user = usersRepository.findByUsername(testUser) + assertNotNull(user) + + // create order + val order = CreateOrderRequest( + userId = user?.id!! + ) + //1st order + val response1: ResponseEntity = restTemplate.postForEntity( + "/orders/orders", + authRequest(order, token), + Any::class.java + ) + assertEquals(HttpStatus.OK, response1.statusCode) // check status code + + //2nd order + val response2: ResponseEntity = restTemplate.postForEntity( + "/orders/orders", + authRequest(order, token), + Any::class.java + ) + assertEquals(HttpStatus.OK, response2.statusCode) // check status code + + + }// 4th test @Test - fun contextLoads() { + fun `As a developer, I can test reading orders list`(){ + //get authentication token + val token = getAuthToken() + + //get user id + val user = usersRepository.findByUsername(testUser) + assertNotNull(user) + + // create order +// val order = CreateOrderRequest( +// userId = user?.id!! +// ) +// //1st order +// val response1: ResponseEntity = restTemplate.postForEntity( +// "/orders/orders", +// authRequest(order, token), +// Any::class.java +// ) +// assertEquals(HttpStatus.OK, response1.statusCode) // check status code +// +// //2nd order +// val response2: ResponseEntity = restTemplate.postForEntity( +// "/orders/orders", +// authRequest(order, token), +// Any::class.java +// ) +// assertEquals(HttpStatus.OK, response2.statusCode) // check status code + + // create authenticated get request + val headers = HttpHeaders() + headers.setBearerAuth(token) + val getRequest = HttpEntity(headers) + + // get the orders + val response: ResponseEntity = restTemplate.exchange( + "/orders/orders", + HttpMethod.GET, + getRequest, + OrdersListDTO::class.java + ) + + assertEquals(HttpStatus.OK, response.statusCode) // check status code + val orders = response.body + assertNotNull(orders) // checking orders is not null + assertEquals(2, response.body?.orders?.size)// making sure there's 2 orders in the list + + //verify all orders belong to the same user + + } + + //5th test + @Test + fun `as a developer, I can test a user can create profile`(){ + val token = getAuthToken() + + // create profile + val createProfile = ProfileRequestDto( + firstName = "Harvey", + lastName = "Specter", + phoneNumber = "333-111-3333" + ) + //create response + val response: ResponseEntity = restTemplate.postForEntity( + "/profile", + authRequest(createProfile, token), + ProfileResponseDto::class.java + ) + //verify responses + assertEquals(HttpStatus.OK, response.statusCode) + assertEquals("Harvey", response.body?.firstName) + assertEquals("Specter", response.body?.lastName) + assertEquals("333-111-3333", response.body?.phoneNumber) + } + + //6th test + @Test + fun `as a developer, I can test a user can update profile`(){ + val token = getAuthToken() + + val updateProfile = ProfileRequestDto( + firstName = "Mike", + lastName = "Ross", + phoneNumber = "111-222-2222" + ) + val response2: ResponseEntity = restTemplate.postForEntity( + "/profile", + authRequest(updateProfile, token), + ProfileResponseDto::class.java + ) + + assertEquals(HttpStatus.OK, response2.statusCode) + assertEquals("Mike", response2.body?.firstName) + assertEquals("Ross", response2.body?.lastName) + assertEquals("111-222-2222", response2.body?.phoneNumber) + } + + //7th test + @Test + fun `As a developer, I can add items to an order`(){ + val token = getAuthToken() + + //get user id + val user = usersRepository.findByUsername(testUser) + assertNotNull(user) + + // Add items to the order + val item1 = CreateItemRequestDTO( + name = "Machboos", + quantity = 1, + order_id = user?.id!! + ) + + val item2 = CreateItemRequestDTO( + name = "Laban", + quantity = 4, + order_id = user.id!! + ) + + // Create items + restTemplate.postForEntity( + "/items/create", + authRequest(item1, token), + ItemDTO::class.java + ) + + restTemplate.postForEntity( + "/items/create", + authRequest(item2, token), + ItemDTO::class.java + ) + + // Get the order id + val headers = HttpHeaders() + headers.setBearerAuth(token) + val getRequest = HttpEntity(headers) + + // list items + val response: ResponseEntity = restTemplate.exchange( + "/orders/orders", + HttpMethod.GET, + getRequest, + ItemsListDTO::class.java + + ) + assertEquals(HttpStatus.OK, response.statusCode) + val items = response.body + assertNotNull(items) + } + + //8th test + @Test + fun `as a developer, I can test reading the menu`(){ + + val response = restTemplate.getForEntity( + "/menu", + Object::class.java + ) + assertEquals(HttpStatus.OK, response.statusCode) + assertNotNull(response) + } -} +} \ No newline at end of file diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..a3a4208 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console \ No newline at end of file diff --git a/target/classes/META-INF/Ordering.kotlin_module b/target/classes/META-INF/Ordering.kotlin_module new file mode 100644 index 0000000..7f5d569 Binary files /dev/null and b/target/classes/META-INF/Ordering.kotlin_module differ diff --git a/target/classes/RegistrationRequest.class b/target/classes/RegistrationRequest.class new file mode 100644 index 0000000..f3623e0 Binary files /dev/null and b/target/classes/RegistrationRequest.class differ diff --git a/target/classes/RegistrationRequestDTO.class b/target/classes/RegistrationRequestDTO.class new file mode 100644 index 0000000..74ee737 Binary files /dev/null and b/target/classes/RegistrationRequestDTO.class differ diff --git a/target/classes/RegistrationResponseDto.class b/target/classes/RegistrationResponseDto.class new file mode 100644 index 0000000..1d2ee6c Binary files /dev/null and b/target/classes/RegistrationResponseDto.class differ diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..38fb0f0 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port=8081 + +spring.datasource.url=jdbc:postgresql://localhost:8080/myHelloDatabase +spring.datasource.username=postgres +spring.datasource.password=Barrak2000# +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +springdoc.api-docs.path=/api-docs diff --git a/target/classes/com/coded/spring/ordering/Application.class b/target/classes/com/coded/spring/ordering/Application.class new file mode 100644 index 0000000..e3f99d2 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/Application.class differ diff --git a/target/classes/com/coded/spring/ordering/ApplicationKt.class b/target/classes/com/coded/spring/ordering/ApplicationKt.class new file mode 100644 index 0000000..0b09b10 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/ApplicationKt.class differ diff --git a/target/classes/com/coded/spring/ordering/HelloWorldController.class b/target/classes/com/coded/spring/ordering/HelloWorldController.class new file mode 100644 index 0000000..ed6e39d Binary files /dev/null and b/target/classes/com/coded/spring/ordering/HelloWorldController.class differ diff --git a/target/classes/com/coded/spring/ordering/LoggingFilter.class b/target/classes/com/coded/spring/ordering/LoggingFilter.class new file mode 100644 index 0000000..64c5e3a Binary files /dev/null and b/target/classes/com/coded/spring/ordering/LoggingFilter.class differ diff --git a/target/classes/com/coded/spring/ordering/OrderRequest.class b/target/classes/com/coded/spring/ordering/OrderRequest.class new file mode 100644 index 0000000..0708a13 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/OrderRequest.class differ diff --git a/target/classes/com/coded/spring/ordering/SayMyNameRequest.class b/target/classes/com/coded/spring/ordering/SayMyNameRequest.class new file mode 100644 index 0000000..77b2023 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/SayMyNameRequest.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/AuthController.class b/target/classes/com/coded/spring/ordering/authentication/AuthController.class new file mode 100644 index 0000000..be11d80 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/AuthController.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/AuthRequest.class b/target/classes/com/coded/spring/ordering/authentication/AuthRequest.class new file mode 100644 index 0000000..69e29e5 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/AuthRequest.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/AuthResponse.class b/target/classes/com/coded/spring/ordering/authentication/AuthResponse.class new file mode 100644 index 0000000..77e923f Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/AuthResponse.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/CustomUserDetailsClass.class b/target/classes/com/coded/spring/ordering/authentication/CustomUserDetailsClass.class new file mode 100644 index 0000000..2c6f1ee Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/CustomUserDetailsClass.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/SecurityConfig.class b/target/classes/com/coded/spring/ordering/authentication/SecurityConfig.class new file mode 100644 index 0000000..4c5b499 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/SecurityConfig.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/jwt/JwtAuthFilter.class b/target/classes/com/coded/spring/ordering/authentication/jwt/JwtAuthFilter.class new file mode 100644 index 0000000..148c5fc Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/jwt/JwtAuthFilter.class differ diff --git a/target/classes/com/coded/spring/ordering/authentication/jwt/JwtService.class b/target/classes/com/coded/spring/ordering/authentication/jwt/JwtService.class new file mode 100644 index 0000000..a28c871 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/authentication/jwt/JwtService.class differ diff --git a/target/classes/com/coded/spring/ordering/items/CreateItemRequestDTO.class b/target/classes/com/coded/spring/ordering/items/CreateItemRequestDTO.class new file mode 100644 index 0000000..fcc8905 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/CreateItemRequestDTO.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemDTO.class b/target/classes/com/coded/spring/ordering/items/ItemDTO.class new file mode 100644 index 0000000..b64ad17 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemDTO.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemEntity.class b/target/classes/com/coded/spring/ordering/items/ItemEntity.class new file mode 100644 index 0000000..afbaba0 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemEntity.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemsController.class b/target/classes/com/coded/spring/ordering/items/ItemsController.class new file mode 100644 index 0000000..27e6fee Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemsController.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemsListDTO.class b/target/classes/com/coded/spring/ordering/items/ItemsListDTO.class new file mode 100644 index 0000000..3baa7cb Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemsListDTO.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemsRepository.class b/target/classes/com/coded/spring/ordering/items/ItemsRepository.class new file mode 100644 index 0000000..e583b4f Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemsRepository.class differ diff --git a/target/classes/com/coded/spring/ordering/items/ItemsService.class b/target/classes/com/coded/spring/ordering/items/ItemsService.class new file mode 100644 index 0000000..f55b9eb Binary files /dev/null and b/target/classes/com/coded/spring/ordering/items/ItemsService.class differ diff --git a/target/classes/com/coded/spring/ordering/menu/MenuController.class b/target/classes/com/coded/spring/ordering/menu/MenuController.class new file mode 100644 index 0000000..05cc632 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/menu/MenuController.class differ diff --git a/target/classes/com/coded/spring/ordering/menu/MenuEntity.class b/target/classes/com/coded/spring/ordering/menu/MenuEntity.class new file mode 100644 index 0000000..2292ccc Binary files /dev/null and b/target/classes/com/coded/spring/ordering/menu/MenuEntity.class differ diff --git a/target/classes/com/coded/spring/ordering/menu/MenuRepository.class b/target/classes/com/coded/spring/ordering/menu/MenuRepository.class new file mode 100644 index 0000000..acf1906 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/menu/MenuRepository.class differ diff --git a/target/classes/com/coded/spring/ordering/menu/MenuService.class b/target/classes/com/coded/spring/ordering/menu/MenuService.class new file mode 100644 index 0000000..69c2a29 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/menu/MenuService.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/CreateOrderRequest.class b/target/classes/com/coded/spring/ordering/orders/CreateOrderRequest.class new file mode 100644 index 0000000..1b921d5 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/CreateOrderRequest.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrderController.class b/target/classes/com/coded/spring/ordering/orders/OrderController.class new file mode 100644 index 0000000..fb704e5 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrderController.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrderDTO.class b/target/classes/com/coded/spring/ordering/orders/OrderDTO.class new file mode 100644 index 0000000..3ec9afe Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrderDTO.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrderEntity.class b/target/classes/com/coded/spring/ordering/orders/OrderEntity.class new file mode 100644 index 0000000..9e5df7b Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrderEntity.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrderRepository.class b/target/classes/com/coded/spring/ordering/orders/OrderRepository.class new file mode 100644 index 0000000..14e7759 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrderRepository.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrderService.class b/target/classes/com/coded/spring/ordering/orders/OrderService.class new file mode 100644 index 0000000..2a09901 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrderService.class differ diff --git a/target/classes/com/coded/spring/ordering/orders/OrdersListDTO.class b/target/classes/com/coded/spring/ordering/orders/OrdersListDTO.class new file mode 100644 index 0000000..f4be656 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/orders/OrdersListDTO.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfileEntity.class b/target/classes/com/coded/spring/ordering/profiles/ProfileEntity.class new file mode 100644 index 0000000..b1d7fdc Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfileEntity.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfileRequestDto.class b/target/classes/com/coded/spring/ordering/profiles/ProfileRequestDto.class new file mode 100644 index 0000000..235ecf0 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfileRequestDto.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfileResponseDto.class b/target/classes/com/coded/spring/ordering/profiles/ProfileResponseDto.class new file mode 100644 index 0000000..e245afb Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfileResponseDto.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfilesController.class b/target/classes/com/coded/spring/ordering/profiles/ProfilesController.class new file mode 100644 index 0000000..2d2a34d Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfilesController.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfilesRepository.class b/target/classes/com/coded/spring/ordering/profiles/ProfilesRepository.class new file mode 100644 index 0000000..6ab7791 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfilesRepository.class differ diff --git a/target/classes/com/coded/spring/ordering/profiles/ProfilesService.class b/target/classes/com/coded/spring/ordering/profiles/ProfilesService.class new file mode 100644 index 0000000..1a8c8e6 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/profiles/ProfilesService.class differ diff --git a/target/classes/com/coded/spring/ordering/scripts/InitUserRunner.class b/target/classes/com/coded/spring/ordering/scripts/InitUserRunner.class new file mode 100644 index 0000000..fff4b84 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/scripts/InitUserRunner.class differ diff --git a/target/classes/com/coded/spring/ordering/scripts/InitUserRunnerKt.class b/target/classes/com/coded/spring/ordering/scripts/InitUserRunnerKt.class new file mode 100644 index 0000000..0816720 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/scripts/InitUserRunnerKt.class differ diff --git a/target/classes/com/coded/spring/ordering/users/RegistrationRequest.class b/target/classes/com/coded/spring/ordering/users/RegistrationRequest.class new file mode 100644 index 0000000..cbcd90d Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/RegistrationRequest.class differ diff --git a/target/classes/com/coded/spring/ordering/users/Roles.class b/target/classes/com/coded/spring/ordering/users/Roles.class new file mode 100644 index 0000000..41c35f8 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/Roles.class differ diff --git a/target/classes/com/coded/spring/ordering/users/User.class b/target/classes/com/coded/spring/ordering/users/User.class new file mode 100644 index 0000000..b477edb Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/User.class differ diff --git a/target/classes/com/coded/spring/ordering/users/UserEntity.class b/target/classes/com/coded/spring/ordering/users/UserEntity.class new file mode 100644 index 0000000..7a50ab4 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/UserEntity.class differ diff --git a/target/classes/com/coded/spring/ordering/users/UsersController.class b/target/classes/com/coded/spring/ordering/users/UsersController.class new file mode 100644 index 0000000..d26b022 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/UsersController.class differ diff --git a/target/classes/com/coded/spring/ordering/users/UsersRepository.class b/target/classes/com/coded/spring/ordering/users/UsersRepository.class new file mode 100644 index 0000000..23b64d5 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/UsersRepository.class differ diff --git a/target/classes/com/coded/spring/ordering/users/UsersService.class b/target/classes/com/coded/spring/ordering/users/UsersService.class new file mode 100644 index 0000000..e3edb67 Binary files /dev/null and b/target/classes/com/coded/spring/ordering/users/UsersService.class differ diff --git a/target/test-classes/META-INF/Ordering.kotlin_module b/target/test-classes/META-INF/Ordering.kotlin_module new file mode 100644 index 0000000..1e9f2ca Binary files /dev/null and b/target/test-classes/META-INF/Ordering.kotlin_module differ diff --git a/target/test-classes/application-test.properties b/target/test-classes/application-test.properties new file mode 100644 index 0000000..a3a4208 --- /dev/null +++ b/target/test-classes/application-test.properties @@ -0,0 +1,10 @@ +spring.application.name=Kotlin.SpringbootV2 +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console \ No newline at end of file diff --git a/target/test-classes/com/coded/spring/ordering/ApplicationTests$Companion.class b/target/test-classes/com/coded/spring/ordering/ApplicationTests$Companion.class new file mode 100644 index 0000000..e9f9714 Binary files /dev/null and b/target/test-classes/com/coded/spring/ordering/ApplicationTests$Companion.class differ diff --git a/target/test-classes/com/coded/spring/ordering/ApplicationTests.class b/target/test-classes/com/coded/spring/ordering/ApplicationTests.class new file mode 100644 index 0000000..0941ff6 Binary files /dev/null and b/target/test-classes/com/coded/spring/ordering/ApplicationTests.class differ