diff --git a/OrderingDB.sql b/OrderingDB.sql
new file mode 100644
index 0000000..7802adc
--- /dev/null
+++ b/OrderingDB.sql
@@ -0,0 +1,24 @@
+CREATE TABLE users (
+ id serial PRIMARY KEY,
+ name VARCHAR(255),
+ email VARCHAR(255),
+ username VARCHAR(255) unique,
+ password VARCHAR(255)
+);
+
+CREATE TABLE orders (
+ id serial PRIMARY KEY,
+ user_id BIGINT,
+ restaurant VARCHAR(255),
+ FOREIGN KEY (user_id) REFERENCES users(id)
+);
+
+CREATE TABLE items (
+ id serial PRIMARY KEY,
+ name VARCHAR(255),
+ quantity INT,
+ price DECIMAL(9, 3),
+ order_id BIGINT,
+ FOREIGN KEY (order_id) REFERENCES orders(id)
+);
+-- added menu too
\ No newline at end of file
diff --git a/authentication/pom.xml b/authentication/pom.xml
new file mode 100644
index 0000000..f693b1c
--- /dev/null
+++ b/authentication/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+ com.coded.spring
+ monolith
+ 0.0.1-SNAPSHOT
+
+
+ authentication
+
+
+
+
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt
new file mode 100644
index 0000000..50f7e45
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt
@@ -0,0 +1,11 @@
+package com.ali.authentication
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+class AuthenticationApplication
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt
new file mode 100644
index 0000000..9cc768c
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt
@@ -0,0 +1,72 @@
+package com.ali.authentication
+
+
+import com.ali.authentication.jwt.JwtService
+import com.ali.authentication.user.UserService
+import org.springframework.http.HttpStatus
+import org.springframework.security.authentication.*
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.core.userdetails.UsernameNotFoundException
+import org.springframework.web.bind.annotation.*
+import org.springframework.web.server.ResponseStatusException
+import java.security.Principal
+
+
+@RestController
+@RequestMapping("/auth")
+class AuthenticationController(
+ private val authenticationManager: AuthenticationManager,
+ private val userDetailsService: UserDetailsService,
+ private val jwtService: JwtService,
+ private val userService: UserService
+) {
+
+ @PostMapping("/login")
+ fun login(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse {
+ val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password)
+ val authentication = authenticationManager.authenticate(authToken)
+
+ if (authentication.isAuthenticated) {
+ val userDetails = userDetailsService.loadUserByUsername(authRequest.username)
+ val token = jwtService.generateToken(userDetails.username)
+ return AuthenticationResponse (token)
+ } else {
+ throw UsernameNotFoundException("Invalid user request!")
+ }
+ }
+
+ @PostMapping("/check-token")
+ fun checkToken(
+ principal: Principal
+ ): CheckTokenResponse {
+ return CheckTokenResponse(
+ userId = userService.findByUsername(principal.name)
+ )
+ }
+// @PostMapping("/check-token")
+// fun checkToken(): CheckTokenResponse {
+// val auth = SecurityContextHolder.getContext().authentication
+// if (auth == null || !auth.isAuthenticated) {
+// throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token")
+// }
+// val username = auth.name
+// val userId = userService.findByUsername(username)
+// return CheckTokenResponse(userId)
+// }
+}
+
+data class CheckTokenResponse(
+ val userId: Long
+)
+
+data class AuthenticationRequest(
+ val username: String,
+ val password: String
+)
+
+data class AuthenticationResponse(
+ val token: String
+)
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt b/authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt
new file mode 100644
index 0000000..3140a07
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt
@@ -0,0 +1,21 @@
+package com.ali.authentication
+
+
+import com.ali.authentication.user.UserRepository
+import org.springframework.security.core.userdetails.*
+import org.springframework.stereotype.Service
+
+@Service
+class CustomUserDetailsService(
+ private val usersRepository: UserRepository
+) : UserDetailsService {
+ override fun loadUserByUsername(username: String): UserDetails {
+ val user = usersRepository.findByUsername(username)
+ ?: throw UsernameNotFoundException("User not found")
+
+ return User.builder()
+ .username(user.username)
+ .password(user.password)
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt b/authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt
new file mode 100644
index 0000000..fc453bb
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt
@@ -0,0 +1,62 @@
+package com.ali.authentication
+
+
+import com.ali.authentication.jwt.JwtAuthenticationFilter
+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: JwtAuthenticationFilter,
+ private val userDetailsService: UserDetailsService
+) {
+ // For authorization you need to add code in security config, customuserdetails, and jwtservice
+ @Bean
+ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+ http.csrf { it.disable() }
+ .authorizeHttpRequests {
+ it
+// .anyRequest().permitAll() // permit all
+
+ .requestMatchers("/auth/login", "/public/**", "/api-docs", "/hello").permitAll()
+ .anyRequest()
+ .authenticated()
+ }
+ .sessionManagement {
+ it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ }
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java)
+
+ return http.build()
+ }
+
+ @Bean
+ fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
+
+ @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/com/ali/authentication/jwt/JwtAuthenticationFilter.kt b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtAuthenticationFilter.kt
new file mode 100644
index 0000000..d19dc66
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtAuthenticationFilter.kt
@@ -0,0 +1,47 @@
+package com.ali.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 JwtAuthenticationFilter(
+ private val jwtService: JwtService,
+ private val userDetailsService: UserDetailsService
+) : OncePerRequestFilter() {
+
+ override fun doFilterInternal(
+ request: HttpServletRequest,
+ response: HttpServletResponse,
+ filterChain: FilterChain
+ ) {
+ logger.info("JwtAuthenticationFilter running for: ${request.requestURI}")
+ 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/com/ali/authentication/jwt/JwtService.kt b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtService.kt
new file mode 100644
index 0000000..e2af8c8
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtService.kt
@@ -0,0 +1,43 @@
+package com.ali.authentication.jwt
+
+
+import io.jsonwebtoken.*
+import io.jsonwebtoken.security.Keys
+import org.springframework.stereotype.Component
+import java.util.*
+import javax.crypto.SecretKey
+
+@Component
+class JwtService {
+
+ private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
+ private val expirationMs: Long = 1000 * 60 * 60
+
+ fun generateToken(username: String): String {
+ val now = Date()
+ val expiry = Date(now.time + expirationMs)
+
+ return Jwts.builder()
+ .setSubject(username)
+ .setIssuedAt(now)
+ .setExpiration(expiry)
+ .signWith(secretKey)
+ .compact()
+ }
+
+ fun extractUsername(token: String): String =
+ Jwts.parserBuilder()
+ .setSigningKey(secretKey)
+ .build()
+ .parseClaimsJws(token)
+ .body
+ .subject
+
+ 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/com/ali/authentication/profile/ProfileEntity.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileEntity.kt
new file mode 100644
index 0000000..ca5f586
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileEntity.kt
@@ -0,0 +1,21 @@
+package com.ali.authentication.profile
+
+import jakarta.persistence.Entity
+import jakarta.persistence.GeneratedValue
+import jakarta.persistence.GenerationType
+import jakarta.persistence.Id
+import jakarta.persistence.Table
+
+@Entity
+@Table(name = "profiles")
+data class ProfileEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ var firstName: String,
+ var lastName: String,
+ var phoneNumber: String,
+
+ var userId: Long,
+)
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt
new file mode 100644
index 0000000..9cc3bd0
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt
@@ -0,0 +1,9 @@
+package com.ali.authentication.profile
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface ProfileRepository : JpaRepository {
+ fun findByUserId(userId: Long): ProfileEntity?
+}
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt
new file mode 100644
index 0000000..06c2641
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt
@@ -0,0 +1,64 @@
+package com.ali.authentication.profile
+
+
+import com.ali.authentication.user.UserRepository
+import org.springframework.http.HttpStatus
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.stereotype.Service
+import org.springframework.web.server.ResponseStatusException
+
+
+@Service
+class ProfileService(
+ private val profileRepository: ProfileRepository,
+ private val userRepository: UserRepository,
+) {
+
+
+ fun save(profile: ProfileRequest) {
+ val userName = SecurityContextHolder.getContext().authentication.name
+ val userId = userRepository.findByUsername(userName)?.id
+ ?: throw IllegalArgumentException("User Not Found")
+
+ require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() })
+ { "Phone number must be 8 digits" }
+ require(profile.firstName.all { it.isLetter() }) { "First name must be letters" }
+ require(profile.lastName.all { it.isLetter() }) { "Last name must be letters" }
+
+
+ // To avoid the wrong user from profile saving
+ val tokenUsername = SecurityContextHolder.getContext().authentication.name
+ val requestUsername = userRepository.findById(userId).get().username
+ if (tokenUsername != requestUsername)
+ throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch")
+ val newProfile = ProfileEntity(
+ userId = userId, firstName = profile.firstName, lastName = profile.lastName,
+ phoneNumber = profile.phoneNumber
+ )
+ profileRepository.save(newProfile)
+ }
+
+ fun view(): ProfileResponse {
+ val userName = SecurityContextHolder.getContext().authentication.name
+ val userId = userRepository.findByUsername(userName)?.id
+ ?: throw IllegalArgumentException("User Not Found")
+ val profile =
+ profileRepository.findByUserId(userId) ?: throw IllegalArgumentException("No profile with id $userId")
+ return ProfileResponse(profile.firstName, profile.lastName,
+ profile.phoneNumber)
+ }
+
+
+}
+
+data class ProfileRequest(
+ val firstName: String,
+ val lastName: String,
+ val phoneNumber: String
+)
+
+data class ProfileResponse(
+ val firstName: String,
+ val lastName: String,
+ val phoneNumber: String
+)
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt
new file mode 100644
index 0000000..e9399ab
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt
@@ -0,0 +1,70 @@
+package com.ali.authentication.user
+
+import com.ali.authentication.profile.ProfileRequest
+import com.ali.authentication.profile.ProfileService
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.http.ResponseEntity
+import org.springframework.security.crypto.password.PasswordEncoder
+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 = "CustomerAPI")
+@RestController
+class UserController(
+ private val userService: UserService,
+ private val encoder: PasswordEncoder,
+ private val profileService: ProfileService,
+ @Value("\${company_name}")
+ private val fromEnvVarMessage: String,
+ @Value("\${festive.feature}")
+ private val festiveFeature: Boolean
+) {
+
+ @GetMapping("/hello")
+ fun hello(): String {
+ if(festiveFeature)
+ return "Eidkom Mubarak, Summer Sale is here!"
+ else
+ return "Welcome to Online Ordering by $fromEnvVarMessage"
+ }
+
+ @PostMapping("/public/users/create")
+ fun newUser(@RequestBody userRequest: UserRequest): Any {
+ userService.validatePassword(userRequest.password)
+ return userService.createUser(
+ UserEntity(
+ name = userRequest.name,
+ email = userRequest.email,
+ username = userRequest.username,
+ password = encoder.encode(userRequest.password)
+ )
+ )
+ }
+
+ @GetMapping("/users/v1/list")
+ fun listUsers() = userService.findAllUsers()
+
+ @PostMapping("/auth/profile/save")
+ fun saveProfile(@RequestBody request: ProfileRequest): Any {
+ return try {
+ profileService.save(request)
+ ResponseEntity.ok().body("Profile saved successfully")
+ } catch (e: IllegalArgumentException) {
+ ResponseEntity.badRequest().body("Error while saving profile ${e.message}")
+ }
+
+ }
+
+ @GetMapping("/auth/profile/view/")
+ fun viewProfile(): Any {
+ return try {
+ profileService.view()
+ } catch (e: IllegalArgumentException) {
+ "error: ${e.message}"
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt
new file mode 100644
index 0000000..5973aa5
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt
@@ -0,0 +1,20 @@
+package com.ali.authentication.user
+
+import jakarta.persistence.Entity
+import jakarta.persistence.GeneratedValue
+import jakarta.persistence.GenerationType
+import jakarta.persistence.Id
+import jakarta.persistence.Table
+
+@Entity
+@Table(name = "users")
+data class UserEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ var name: String,
+ var email: String,
+ val username: String,
+ val password: String,
+)
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt
new file mode 100644
index 0000000..0eb646e
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt
@@ -0,0 +1,10 @@
+package com.ali.authentication.user
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface UserRepository : JpaRepository {
+ fun findByUsername(username: String) : UserEntity?
+
+}
\ No newline at end of file
diff --git a/authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt
new file mode 100644
index 0000000..a0c17ff
--- /dev/null
+++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt
@@ -0,0 +1,31 @@
+package com.ali.authentication.user
+
+
+import org.springframework.stereotype.Service
+
+@Service
+class UserService(private val userRepository: UserRepository) {
+
+
+ fun findAllUsers() = userRepository.findAll()
+
+ fun createUser(user: UserEntity) = userRepository.save(user)
+
+ fun validatePassword(password: String) {
+ require(password.length >= 6) { "Password must be at least 6 characters long" }
+ require(password.any { it.isUpperCase() }) { "Password must contain at least one uppercase letter" }
+ require(password.any { it.isDigit() }) { "Password must contain at least one number" }
+ }
+
+
+ fun findByUsername(username: String): Long =
+ userRepository.findByUsername(username)?.id ?: throw IllegalStateException("User has no id...")
+
+}
+
+data class UserRequest(
+ val name: String,
+ val email: String,
+ val username: String,
+ val password: String
+)
\ No newline at end of file
diff --git a/authentication/src/main/resources/application.properties b/authentication/src/main/resources/application.properties
new file mode 100644
index 0000000..824e8b2
--- /dev/null
+++ b/authentication/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.application.name=Kotlin.SpringbootV2
+
+server.port = 8080
+
+spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB
+spring.datasource.username=postgres
+spring.datasource.password=alix
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.show-sql=true
+spring.jpa.properties.hibe1rnate.format_sql=true
+
+springdoc.api-docs.path=/api-docs
\ No newline at end of file
diff --git a/order/pom.xml b/order/pom.xml
new file mode 100644
index 0000000..3ffc190
--- /dev/null
+++ b/order/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ com.coded.spring
+ monolith
+ 0.0.1-SNAPSHOT
+
+
+ order
+
+
+ com.coded.spring
+ authentication
+ 0.0.1-SNAPSHOT
+ compile
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/order/src/main/kotlin/coded/spring/OrderingApplication.kt
similarity index 64%
rename from src/main/kotlin/com/coded/spring/ordering/Application.kt
rename to order/src/main/kotlin/coded/spring/OrderingApplication.kt
index 8554e49..e937af3 100644
--- a/src/main/kotlin/com/coded/spring/ordering/Application.kt
+++ b/order/src/main/kotlin/coded/spring/OrderingApplication.kt
@@ -1,11 +1,14 @@
-package com.coded.spring.ordering
+package coded.spring
+
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
+
@SpringBootApplication
-class Application
+class OrderingApplication
fun main(args: Array) {
- runApplication(*args)
+ runApplication(*args)
}
+
diff --git a/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt b/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt
new file mode 100644
index 0000000..1d72033
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt
@@ -0,0 +1,31 @@
+package coded.spring.client
+
+
+import com.ali.authentication.CheckTokenResponse
+import jakarta.inject.Named
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.*
+import org.springframework.util.MultiValueMap
+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:8080/auth/check-token"
+ val response = restTemplate.exchange(
+ url = url,
+ method = HttpMethod.POST,
+ requestEntity = HttpEntity(
+ MultiValueMap.fromMultiValue(mapOf("Authorization" to listOf("Bearer $token")))
+ ),
+ object : ParameterizedTypeReference() {
+ }
+ )
+ return response.body ?: throw IllegalStateException("Check token response has no body ...")
+ }
+
+
+}
\ No newline at end of file
diff --git a/order/src/main/kotlin/coded/spring/controller/MenuController.kt b/order/src/main/kotlin/coded/spring/controller/MenuController.kt
new file mode 100644
index 0000000..e2d69b3
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/controller/MenuController.kt
@@ -0,0 +1,33 @@
+package coded.spring.controller
+
+import coded.spring.service.MenuService
+import io.swagger.v3.oas.annotations.tags.Tag
+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.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.math.BigDecimal
+
+
+@Tag(name = "MenuAPI")
+@RestController
+class MenuController(val menuService: MenuService) {
+
+ @PostMapping("/auth/menu/add")
+ fun addItemsToMenu(@RequestBody menuRequest: MenuRequest): Any {
+
+ return ResponseEntity.ok().body(menuService.addItems(menuRequest))
+ }
+
+ @GetMapping("/public/menu/list")
+ fun listItems() = menuService.listMenuItems()
+
+}
+
+
+data class MenuRequest(
+ val name: String,
+ val price: BigDecimal
+)
diff --git a/order/src/main/kotlin/coded/spring/controller/OrderController.kt b/order/src/main/kotlin/coded/spring/controller/OrderController.kt
new file mode 100644
index 0000000..d60fd3a
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/controller/OrderController.kt
@@ -0,0 +1,36 @@
+package coded.spring.controller
+
+import coded.spring.service.OrderService
+import io.swagger.v3.oas.annotations.tags.Tag
+import org.springframework.web.bind.annotation.*
+@Tag(name = "OrderAPI")
+@RestController
+class OrderController(val orderService: OrderService) {
+
+ @PostMapping("/orders/v1/submit")
+ fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequest): OrderService.OrderResponse {
+ try {
+ orderService.createOrder(orderRequest)
+ } catch (e: IllegalArgumentException) {
+ "error submitting the order: ${e.message}"
+ }
+ return OrderService.OrderResponse(orderRequest.restaurant, orderRequest.items)
+ }
+
+ @GetMapping("/orders/user/orders/{userId}")
+ fun listOrdersByUserId(@PathVariable userId: Long) {
+ try {
+ orderService.listOrdersByUserID(userId)
+ } catch (e: IllegalArgumentException) {
+ "error occurred while listing orders: ${e.message}"
+ }
+
+ }
+}
+
+
+
+
+
+
+
diff --git a/order/src/main/kotlin/coded/spring/entity/ItemEntity.kt b/order/src/main/kotlin/coded/spring/entity/ItemEntity.kt
new file mode 100644
index 0000000..5b62231
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/entity/ItemEntity.kt
@@ -0,0 +1,21 @@
+package coded.spring.entity
+
+import jakarta.persistence.*
+import java.math.BigDecimal
+
+@Entity
+@Table(name = "items")
+data class ItemEntity (
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ var name: String,
+ var quantity: Int,
+ var price : BigDecimal,
+
+ var orderId: Long
+
+)
+
+
diff --git a/order/src/main/kotlin/coded/spring/entity/MenuEntity.kt b/order/src/main/kotlin/coded/spring/entity/MenuEntity.kt
new file mode 100644
index 0000000..2524cc1
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/entity/MenuEntity.kt
@@ -0,0 +1,16 @@
+package coded.spring.entity
+
+import jakarta.persistence.*
+import java.math.BigDecimal
+
+@Entity
+@Table(name = "menu")
+data class MenuEntity(
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ var name: String,
+ var price: BigDecimal
+)
diff --git a/order/src/main/kotlin/coded/spring/entity/OrderEntity.kt b/order/src/main/kotlin/coded/spring/entity/OrderEntity.kt
new file mode 100644
index 0000000..e10b509
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/entity/OrderEntity.kt
@@ -0,0 +1,15 @@
+package coded.spring.entity
+
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "orders")
+data class OrderEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ var restaurant: String,
+
+ var userId: Long
+)
diff --git a/order/src/main/kotlin/coded/spring/repository/ItemRepository.kt b/order/src/main/kotlin/coded/spring/repository/ItemRepository.kt
new file mode 100644
index 0000000..6a29197
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/repository/ItemRepository.kt
@@ -0,0 +1,10 @@
+package coded.spring.repository
+
+import coded.spring.entity.ItemEntity
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface ItemRepository : JpaRepository{
+
+}
\ No newline at end of file
diff --git a/order/src/main/kotlin/coded/spring/repository/MenuRepository.kt b/order/src/main/kotlin/coded/spring/repository/MenuRepository.kt
new file mode 100644
index 0000000..f0f4b04
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/repository/MenuRepository.kt
@@ -0,0 +1,10 @@
+package coded.spring.repository
+
+import coded.spring.entity.MenuEntity
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface MenuRepository : JpaRepository{
+
+}
diff --git a/order/src/main/kotlin/coded/spring/repository/OrderRepository.kt b/order/src/main/kotlin/coded/spring/repository/OrderRepository.kt
new file mode 100644
index 0000000..4edb2c2
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/repository/OrderRepository.kt
@@ -0,0 +1,10 @@
+package coded.spring.repository
+
+import coded.spring.entity.OrderEntity
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface OrderRepository : JpaRepository{
+ fun findAllByUserId(userId: Long): List
+}
\ No newline at end of file
diff --git a/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt b/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt
new file mode 100644
index 0000000..d2cdf59
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt
@@ -0,0 +1,36 @@
+package coded.spring.security
+
+import coded.spring.client.AuthenticationClient
+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 kotlin.text.startsWith
+import kotlin.text.substring
+
+@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/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt b/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt
new file mode 100644
index 0000000..f5e1dac
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt
@@ -0,0 +1,32 @@
+package coded.spring.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/order/src/main/kotlin/coded/spring/service/MenuService.kt b/order/src/main/kotlin/coded/spring/service/MenuService.kt
new file mode 100644
index 0000000..1699f37
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/service/MenuService.kt
@@ -0,0 +1,29 @@
+package coded.spring.service
+import coded.spring.controller.MenuRequest
+import coded.spring.entity.MenuEntity
+import coded.spring.repository.MenuRepository
+import org.springframework.stereotype.Service
+import kotlin.text.set
+
+@Service
+class MenuService(
+ val menuRepository: MenuRepository,
+) {
+
+ fun addItems(menu: MenuRequest) {
+
+ menuRepository.save(
+ MenuEntity(
+ name = menu.name,
+ price = menu.price
+ )
+ )
+ }
+
+ fun listMenuItems(): List = menuRepository.findAll()
+
+
+
+}
+
+
diff --git a/order/src/main/kotlin/coded/spring/service/OrderService.kt b/order/src/main/kotlin/coded/spring/service/OrderService.kt
new file mode 100644
index 0000000..a79b9c0
--- /dev/null
+++ b/order/src/main/kotlin/coded/spring/service/OrderService.kt
@@ -0,0 +1,72 @@
+package coded.spring.service
+
+import coded.spring.entity.ItemEntity
+import coded.spring.entity.OrderEntity
+import coded.spring.repository.ItemRepository
+import coded.spring.repository.OrderRepository
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.stereotype.Service
+import java.math.BigDecimal
+
+@Service
+class OrderService(
+ private var orderRepository: OrderRepository,
+ private var itemRepository: ItemRepository,
+ @Value("\${discount.feature}")
+ val discount: Boolean
+) {
+
+ fun createOrder(request: OrderRequest) {
+
+ val savedOrder =orderRepository.save(OrderEntity(
+ restaurant = request.restaurant,
+ userId = request.userId))
+
+
+
+ if (discount){
+ itemRepository.saveAll(request.items.map {
+ ItemEntity(
+ name = it.name,
+ quantity = it.quantity,
+ price = it.price.multiply(BigDecimal(0.8)),
+ orderId = savedOrder.id!!
+ )
+ })
+ }
+ else {
+ itemRepository.saveAll(request.items.map {
+ ItemEntity(
+ name = it.name,
+ quantity = it.quantity,
+ price = it.price,
+ orderId = savedOrder.id!!
+ )
+ })
+ }
+
+ }
+
+
+ fun listOrdersByUserID(userId: Long) {
+ orderRepository.findAllByUserId(userId)
+ }
+
+
+ data class ItemDto(
+ val name: String,
+ val quantity: Int,
+ val price: BigDecimal
+ )
+
+ data class OrderRequest(
+ val userId: Long,
+ val restaurant: String,
+ val items: MutableList
+ )
+
+ data class OrderResponse(
+ val restaurant: String,
+ val items: List
+ )
+}
\ No newline at end of file
diff --git a/order/src/main/resources/application.properties b/order/src/main/resources/application.properties
new file mode 100644
index 0000000..36873dc
--- /dev/null
+++ b/order/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.application.name=Kotlin.SpringbootV2
+
+server.port = 4444
+
+spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB
+spring.datasource.username=postgres
+spring.datasource.password=alix
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.show-sql=true
+spring.jpa.properties.hibe1rnate.format_sql=true
+
+springdoc.api-docs.path=/api-docs
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 163ad53..572ee68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,8 +9,9 @@
com.coded.spring
- Ordering
+ monolith
0.0.1-SNAPSHOT
+ pom
Kotlin.SpringbootV2
Kotlin.SpringbootV2
@@ -20,6 +21,10 @@
+
+ authentication
+ order
+
@@ -58,6 +63,59 @@
kotlin-test-junit5
test
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ runtime
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+ 0.11.5
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ test
+
+
+ org.postgresql
+ postgresql
+ compile
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-api
+ 2.6.0
+
+
+
+ com.hazelcast
+ hazelcast
+ 5.3.8
+
+
+
+
@@ -76,17 +134,34 @@
-Xjsr305=strict
+ jpa
spring
+ all-open
+ no-arg
+
+
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-noarg
+ 1.9.25
+
org.jetbrains.kotlin
kotlin-maven-allopen
- ${kotlin.version}
+ 1.9.25
+
diff --git a/src/main/kotlin/com/legacy/ordering/OrdersController.kt b/src/main/kotlin/com/legacy/ordering/OrdersController.kt
new file mode 100644
index 0000000..54761f8
--- /dev/null
+++ b/src/main/kotlin/com/legacy/ordering/OrdersController.kt
@@ -0,0 +1,33 @@
+package com.coded.spring.ordering
+
+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 OrdersController(val ordersRepository: OrderRepository) {
+
+ // Exercise 1
+ @GetMapping("/welcome")
+ fun chooseDeliveryOrPickUp() = "Delivery or Pick-Up?"
+
+ // Exercise 2
+ @PostMapping("/order")
+ fun makeAnOrder(@RequestBody request: OrderRequest): Order {
+ val newOrder = Order(
+ user = request.user,
+ restaurant = request.restaurant,
+ items = request.items
+ )
+ return ordersRepository.save(newOrder)
+ }
+
+ @GetMapping("/order")
+ fun getAllOrders() = ordersRepository.findAll()
+}
+data class OrderRequest(
+ val user: String,
+ val restaurant: String,
+ val items: MutableList
+)
diff --git a/src/main/kotlin/com/legacy/ordering/OrdersRepository.kt b/src/main/kotlin/com/legacy/ordering/OrdersRepository.kt
new file mode 100644
index 0000000..6db68ee
--- /dev/null
+++ b/src/main/kotlin/com/legacy/ordering/OrdersRepository.kt
@@ -0,0 +1,31 @@
+package com.coded.spring.ordering
+
+import jakarta.inject.Named
+import jakarta.persistence.*
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Named
+interface OrderRepository : JpaRepository
+
+@Entity
+@Table(name = "orders")
+data class Order(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name="id")
+ var orderID: Long? = null,
+ // Thanks to Mohammed Sheshter he figured out we need to use back ticks to resolve the 500 server error caused by JPA getting confused by "users"
+// @Column(name = "`user`")
+ @Column(name = "name")
+ var user: String = "",
+
+ var restaurant: String = "",
+
+ // Since the specified schema requires a list of items I had to make a seperate table
+ @CollectionTable(name = "items")
+ @JoinColumn()
+ var items: MutableList = mutableListOf()
+){
+ constructor() : this(null,"","", mutableListOf())
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/legacy/ordering/UsersController.kt b/src/main/kotlin/com/legacy/ordering/UsersController.kt
new file mode 100644
index 0000000..7840ff3
--- /dev/null
+++ b/src/main/kotlin/com/legacy/ordering/UsersController.kt
@@ -0,0 +1,14 @@
+package com.coded.spring.ordering
+
+
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class UsersController(
+ private val usersRepository: UsersRepository
+){
+
+ @GetMapping("/users/v1/list")
+ fun users() = usersRepository.findAll()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/legacy/ordering/UsersRepository.kt b/src/main/kotlin/com/legacy/ordering/UsersRepository.kt
new file mode 100644
index 0000000..16be2ee
--- /dev/null
+++ b/src/main/kotlin/com/legacy/ordering/UsersRepository.kt
@@ -0,0 +1,25 @@
+package com.coded.spring.ordering
+
+import jakarta.inject.Named
+import jakarta.persistence.*
+import org.springframework.data.jpa.repository.JpaRepository
+
+@Named
+interface UsersRepository : JpaRepository
+
+@Entity
+@Table(name = "users")
+data class User(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name="id")
+ var orderID: Long? = null,
+
+ @Column(name = "name")
+ var user: String = "",
+ @Column(name = "age")
+ var age: Int? = null
+
+){
+ constructor() : this(null, "", null)
+}
\ No newline at end of file
diff --git a/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json b/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json
new file mode 100644
index 0000000..556dc60
--- /dev/null
+++ b/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json
@@ -0,0 +1 @@
+{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:4444","description":"Generated server url"}],"paths":{"/public/users/create":{"post":{"tags":["CustomerAPI"],"operationId":"newUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/orders/v1/submit":{"post":{"tags":["OrderAPI"],"operationId":"submitOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}}}}},"/menu/add":{"post":{"tags":["MenuAPI"],"operationId":"addItemsToMenu","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/auth/profile/save":{"post":{"tags":["CustomerAPI"],"operationId":"saveProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/auth/login":{"post":{"tags":["authentication-controller"],"operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthenticationResponse"}}}}}}},"/users/v1/list":{"get":{"tags":["CustomerAPI"],"operationId":"listUsers","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserEntity"}}}}}}}},"/public/menu/list":{"get":{"tags":["MenuAPI"],"operationId":"listItems","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MenuEntity"}}}}}}}},"/orders/user/orders/{userId}":{"get":{"tags":["OrderAPI"],"operationId":"listOrdersByUserId","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/hello":{"get":{"tags":["CustomerAPI"],"operationId":"hello","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/auth/profile/view/":{"get":{"tags":["CustomerAPI"],"operationId":"viewProfile","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}}},"components":{"schemas":{"UserRequest":{"required":["email","name","password","username"],"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"ItemDto":{"required":["name","price","quantity"],"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"price":{"type":"number"}}},"OrderRequest":{"required":["items","restaurant"],"type":"object","properties":{"restaurant":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemDto"}}}},"OrderResponse":{"required":["items","restaurant"],"type":"object","properties":{"restaurant":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemDto"}}}},"MenuRequest":{"required":["name","price"],"type":"object","properties":{"name":{"type":"string"},"price":{"type":"number"}}},"ProfileRequest":{"required":["firstName","lastName","phoneNumber"],"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"phoneNumber":{"type":"string"}}},"AuthenticationRequest":{"required":["password","username"],"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"AuthenticationResponse":{"required":["token"],"type":"object","properties":{"token":{"type":"string"}}},"UserEntity":{"required":["email","name","password","username"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"MenuEntity":{"required":["name","price"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"price":{"type":"number"}}}}}}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3704dc6..36873dc 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,12 @@
spring.application.name=Kotlin.SpringbootV2
+
+server.port = 4444
+
+spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB
+spring.datasource.username=postgres
+spring.datasource.password=alix
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.show-sql=true
+spring.jpa.properties.hibe1rnate.format_sql=true
+
+springdoc.api-docs.path=/api-docs
\ No newline at end of file
diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
index b2e2320..eccad6b 100644
--- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
+++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
@@ -1,13 +1,70 @@
package com.coded.spring.ordering
+import com.coded.spring.authentication.jwt.JwtService
+import com.coded.spring.entity.UserEntity
+import com.coded.spring.repository.UserRepository
+import org.junit.jupiter.api.BeforeAll
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.web.client.TestRestTemplate
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.http.HttpStatus
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.test.context.ActiveProfiles
+import org.springframework.util.MultiValueMap
+import kotlin.test.assertEquals
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("test")
class ApplicationTests {
+// MOCK TRIGGER ASSERT
+// GIVEN WHEN THEN
+ companion object {
+ @JvmStatic
+// @BeforeAll
+ fun setUp(
+ @Autowired userRepository: UserRepository,
+ @Autowired passwordEncoder: PasswordEncoder
+ ) {
- @Test
- fun contextLoads() {
- }
+ userRepository.deleteAll()
+ val testUser =
+ UserEntity(
+ name = "coded",
+ email = "test@test.com",
+ username = "coded",
+ password = passwordEncoder.encode("Password123")
+ )
+ userRepository.save(testUser)
+ }
+ }
+
+ // Tests go here
+ @Autowired
+ lateinit var restTemplate: TestRestTemplate
+
+ @Test
+ fun testHelloWorld(@Autowired jwtService: JwtService) {
+ val token = jwtService.generateToken("coded")
+ val headers = HttpHeaders(
+ MultiValueMap.fromSingleValue(mapOf("Authorization" to "Bearer $token"))
+ )
+ val requestEntity = HttpEntity(headers)
+
+ val result = restTemplate.exchange(
+ "/hello",
+ HttpMethod.GET,
+ requestEntity,
+ String::class.java
+ )
+ assertEquals(HttpStatus.OK, result.statusCode)
+ assertEquals("Hello World!", result.body)
+ }
}
+
+
+