diff --git a/auth/pom.xml b/auth/pom.xml
new file mode 100644
index 0000000..547c47f
--- /dev/null
+++ b/auth/pom.xml
@@ -0,0 +1,92 @@
+
+
+ 4.0.0
+
+ com.coded.spring
+ Ordering
+ 0.0.1-SNAPSHOT
+
+
+ auth
+
+
+ UTF-8
+ official
+ 1.8
+
+
+
+
+ mavenCentral
+ https://repo1.maven.org/maven2/
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ maven-surefire-plugin
+ 2.22.2
+
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+ MainKt
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ 2.1.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.0
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 2.1.0
+
+
+
+
+
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/auth/src/main/kotlin/auth-service/AuthApplication.kt
similarity index 66%
rename from src/main/kotlin/com/coded/spring/ordering/Application.kt
rename to auth/src/main/kotlin/auth-service/AuthApplication.kt
index 8554e49..fb67b5c 100644
--- a/src/main/kotlin/com/coded/spring/ordering/Application.kt
+++ b/auth/src/main/kotlin/auth-service/AuthApplication.kt
@@ -1,11 +1,11 @@
-package com.coded.spring.ordering
+package `auth-service`
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
-class Application
+class AuthApplication
fun main(args: Array) {
- runApplication(*args)
+ runApplication(*args)
}
diff --git a/auth/src/main/kotlin/auth-service/AuthenticationController.kt b/auth/src/main/kotlin/auth-service/AuthenticationController.kt
new file mode 100644
index 0000000..66eb4cb
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/AuthenticationController.kt
@@ -0,0 +1,38 @@
+package `auth-service`
+
+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.*
+
+@RestController
+@RequestMapping("/authentication")
+class AuthenticationController(
+ private val authenticationManager: AuthenticationManager,
+ private val userDetailsService: UserDetailsService,
+ private val jwtService: JwtService
+) {
+
+ @PostMapping("/login")
+ fun login(@RequestBody request: AuthenticationRequest): AuthenticationResponse {
+ val authenticationToken = UsernamePasswordAuthenticationToken(request.username, request.password)
+ val authentication = authenticationManager.authenticate(authenticationToken)
+
+ if (authentication.isAuthenticated) {
+ val userDetails = userDetailsService.loadUserByUsername(request.username)
+ val token = jwtService.generateToken(userDetails.username)
+ return AuthenticationResponse(token)
+ } else {
+ throw UsernameNotFoundException("Invalid login")
+ }
+ }
+}
+
+data class AuthenticationRequest(
+ val username: String,
+ val password: String
+)
+
+data class AuthenticationResponse(
+ val token: String
+)
diff --git a/auth/src/main/kotlin/auth-service/SecurityConfig.kt b/auth/src/main/kotlin/auth-service/SecurityConfig.kt
new file mode 100644
index 0000000..4546bbc
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/SecurityConfig.kt
@@ -0,0 +1,46 @@
+package `auth-service`
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.AuthenticationManager
+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 userDetailsService: UserDetailsService,
+ private val jwtAuthenticationFilter: JwtAuthenticationFilter
+) {
+
+ @Bean
+ fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
+
+ @Bean
+ fun authenticationManager(authConfig: AuthenticationConfiguration): AuthenticationManager {
+ return authConfig.authenticationManager
+ }
+
+ @Bean
+ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+ http
+ .csrf { it.disable() }
+ .authorizeHttpRequests {
+ it.requestMatchers("/menu", "/clients", "/welcome", "/authentication/**","/api-docs").permitAll()
+ it.anyRequest().authenticated()
+ }
+ .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
+ .httpBasic { it.disable() }
+ .formLogin { it.disable() }
+
+ return http.build()
+ }
+}
diff --git a/auth/src/main/kotlin/auth-service/TalabatRepository.kt b/auth/src/main/kotlin/auth-service/TalabatRepository.kt
new file mode 100644
index 0000000..5a16427
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/TalabatRepository.kt
@@ -0,0 +1,23 @@
+package `auth-service`
+
+import jakarta.persistence.*
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface TalabatRepository : JpaRepository{
+ fun findByUsername(username: String): UserEntity?
+}
+
+@Entity
+@Table(name = "users")
+data class UserEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+ var name: String,
+ @Column(unique = true)
+ var username: String,
+
+ var password: String
+) {
+ constructor() : this(null, "", "","")
+}
\ No newline at end of file
diff --git a/auth/src/main/kotlin/auth-service/UserDetailsService.kt b/auth/src/main/kotlin/auth-service/UserDetailsService.kt
new file mode 100644
index 0000000..8320cbb
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/UserDetailsService.kt
@@ -0,0 +1,22 @@
+package `auth-service`
+
+
+import `auth-service`.TalabatRepository
+import org.springframework.security.core.userdetails.*
+import org.springframework.stereotype.Service
+
+@Service
+class CustomUserDetailsService(
+ private val talabatRepository: TalabatRepository
+) : UserDetailsService {
+ override fun loadUserByUsername(username: String): UserDetails {
+ val user = talabatRepository.findByUsername(username)
+ ?: throw UsernameNotFoundException("User not found")
+
+ return User.builder()
+ .username(user.username)
+ .password(user.password)
+ .roles("USER")
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/kotlin/auth-service/customUserDetails.kt b/auth/src/main/kotlin/auth-service/customUserDetails.kt
new file mode 100644
index 0000000..e58e7ea
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/customUserDetails.kt
@@ -0,0 +1,21 @@
+package `auth-service`
+
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+
+data class CustomUserDetails(
+ val id: Long,
+ private val username: String,
+ private val password: String
+) : UserDetails {
+ override fun getAuthorities(): Collection =
+ listOf(SimpleGrantedAuthority("ROLE_USER"))
+
+ override fun getPassword(): String = password
+ override fun getUsername(): String = username
+ override fun isAccountNonExpired(): Boolean = true
+ override fun isAccountNonLocked(): Boolean = true
+ override fun isCredentialsNonExpired(): Boolean = true
+ override fun isEnabled(): Boolean = true
+}
\ No newline at end of file
diff --git a/auth/src/main/kotlin/auth-service/jwtAuthenticationFilter.kt b/auth/src/main/kotlin/auth-service/jwtAuthenticationFilter.kt
new file mode 100644
index 0000000..0cc5bc9
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/jwtAuthenticationFilter.kt
@@ -0,0 +1,45 @@
+package `auth-service`
+
+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
+ ) {
+ 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)
+ }
+}
diff --git a/auth/src/main/kotlin/auth-service/jwtService.kt b/auth/src/main/kotlin/auth-service/jwtService.kt
new file mode 100644
index 0000000..259ec02
--- /dev/null
+++ b/auth/src/main/kotlin/auth-service/jwtService.kt
@@ -0,0 +1,42 @@
+package `auth-service`
+
+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
+ }
+ }
+}
diff --git a/ordercenter/pom.xml b/ordercenter/pom.xml
new file mode 100644
index 0000000..3448249
--- /dev/null
+++ b/ordercenter/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ com.coded.spring
+ Ordering
+ 0.0.1-SNAPSHOT
+
+
+ ordercenter
+
+
+ UTF-8
+ official
+ 1.8
+
+
+
+
+ mavenCentral
+ https://repo1.maven.org/maven2/
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ maven-surefire-plugin
+ 2.22.2
+
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+ MainKt
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ 2.1.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.0
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 2.1.0
+
+
+
+
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/TalabatController.kt b/ordercenter/src/main/kotlin/ordering-service/TalabatController.kt
new file mode 100644
index 0000000..8ebbc79
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/TalabatController.kt
@@ -0,0 +1,49 @@
+package `ordering-service`
+
+import org.springframework.web.bind.annotation.*
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.userdetails.UserDetails
+
+@RestController
+class TalabatController(
+ private val talabatRepository: TalabatRepository,
+ private val passwordEncoder: PasswordEncoder,
+ private val appProperties: AppProperties
+) {
+
+ @PostMapping("/clients")
+ fun clients(@RequestBody request: ClientsRequest): UserEntity {
+ val encodedPassword = passwordEncoder.encode(request.password)
+ return talabatRepository.save(
+ UserEntity(
+ name = request.name,
+ username = request.username,
+ password = encodedPassword
+ )
+ )
+ }
+
+ @PostMapping("/order")
+ fun submitOrder(
+ @RequestBody request: OrderRequest,
+ @AuthenticationPrincipal user: UserDetails
+ ): String {
+ return "Order received from ${user.username} for items: ${request.items.joinToString(", ")}"
+ }
+}
+
+data class ClientsRequest(
+ val name: String,
+ val username: String,
+ val password: String
+)
+
+data class OrderRequest(
+ val items: List
+)
+
+data class MenuItem(
+ val name: String,
+ val price: Double
+)
diff --git a/ordercenter/src/main/kotlin/ordering-service/cache/hazelCastInstance.kt b/ordercenter/src/main/kotlin/ordering-service/cache/hazelCastInstance.kt
new file mode 100644
index 0000000..21251f9
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/cache/hazelCastInstance.kt
@@ -0,0 +1,11 @@
+package `ordering-service`.cache
+
+import com.hazelcast.config.Config
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.core.HazelcastInstance
+
+val menuCacheConfig = Config("menu-cache").apply {
+ getMapConfig("menu").timeToLiveSeconds = 60
+}
+
+val menuHazelcastInstance: HazelcastInstance = Hazelcast.newHazelcastInstance(menuCacheConfig)
diff --git a/ordercenter/src/main/kotlin/ordering-service/menu/menuCache.kt b/ordercenter/src/main/kotlin/ordering-service/menu/menuCache.kt
new file mode 100644
index 0000000..ac88c0f
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/menu/menuCache.kt
@@ -0,0 +1,5 @@
+package `ordering-service`.menu
+
+import `ordering-service`.cache.menuHazelcastInstance
+
+val menuCache = menuHazelcastInstance.getMap>("menu")
diff --git a/ordercenter/src/main/kotlin/ordering-service/menu/menuController.kt b/ordercenter/src/main/kotlin/ordering-service/menu/menuController.kt
new file mode 100644
index 0000000..fedb874
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/menu/menuController.kt
@@ -0,0 +1,22 @@
+package `ordering-service`.menu
+
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/menu")
+class MenuController(private val menuService: MenuService) {
+
+ @GetMapping
+ fun getMenu(): List = menuService.getMenu()
+
+ @PostMapping
+ fun addMenuItem(@RequestBody request: MenuRequest): MenuEntity {
+ return menuService.addMenuItem(request)
+ }
+}
+
+data class MenuRequest(
+ val name: String,
+ val price: Double
+)
+
diff --git a/ordercenter/src/main/kotlin/ordering-service/menu/menuEntity.kt b/ordercenter/src/main/kotlin/ordering-service/menu/menuEntity.kt
new file mode 100644
index 0000000..5cfce72
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/menu/menuEntity.kt
@@ -0,0 +1,16 @@
+package `ordering-service`.menu
+
+
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "menu")
+data class MenuEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long? = null,
+
+ val name: String = "",
+
+ val price: Double = 0.0
+)
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/menu/menuRepository.kt b/ordercenter/src/main/kotlin/ordering-service/menu/menuRepository.kt
new file mode 100644
index 0000000..7c99400
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/menu/menuRepository.kt
@@ -0,0 +1,6 @@
+package `ordering-service`.menu
+
+import `ordering-service`.menu.MenuEntity
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface MenuRepository : JpaRepository
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/menu/menuService.kt b/ordercenter/src/main/kotlin/ordering-service/menu/menuService.kt
new file mode 100644
index 0000000..3052cf0
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/menu/menuService.kt
@@ -0,0 +1,41 @@
+package `ordering-service`.menu
+
+import com.coded.spring.ordering.AppProperties
+import org.springframework.stereotype.Service
+
+
+@Service
+class MenuService(
+ private val menuRepository: MenuRepository,
+ private val appProperties: AppProperties
+) {
+
+ fun getMenu(): List {
+ val cached = menuCache["all"]
+ val menu = if (cached != null) {
+ println("Returning menu from cache")
+ cached
+ } else {
+ println("Fetching menu from DB...")
+ val dbMenu = menuRepository.findAll()
+ menuCache["all"] = dbMenu
+ dbMenu
+ }
+
+ return if (appProperties.festive.enabled) {
+ menu.map { it.copy(price = (it.price * 0.8)) }
+ } else {
+ menu
+ }
+ }
+
+ fun addMenuItem(request: MenuRequest): MenuEntity {
+ val newItem = MenuEntity(name = request.name, price = request.price)
+ val saved = menuRepository.save(newItem)
+
+ menuCache.remove("all")
+ println("Cleared menu cache after adding new item")
+
+ return saved
+ }
+}
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/CreateOrderRequest.kt b/ordercenter/src/main/kotlin/ordering-service/orders/CreateOrderRequest.kt
new file mode 100644
index 0000000..21cc481
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/CreateOrderRequest.kt
@@ -0,0 +1,12 @@
+package `ordering-service`.orders
+
+data class CreateOrderRequest(
+ val userId: Long,
+ val restaurant: String,
+ val items: List
+)
+
+data class ItemRequest(
+ val name: String,
+ val quantity: Int
+)
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/ItemsEntity.kt b/ordercenter/src/main/kotlin/ordering-service/orders/ItemsEntity.kt
new file mode 100644
index 0000000..75fafa9
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/ItemsEntity.kt
@@ -0,0 +1,19 @@
+package `ordering-service`.orders
+
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "items")
+data class ItemEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long? = null,
+
+ val name: String,
+ val quantity: Int,
+
+ @ManyToOne
+ @JoinColumn(name = "order_id")
+ val order: OrderEntity
+)
+
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/ItemsRepository.kt b/ordercenter/src/main/kotlin/ordering-service/orders/ItemsRepository.kt
new file mode 100644
index 0000000..275998c
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/ItemsRepository.kt
@@ -0,0 +1,7 @@
+package `ordering-service`.orders
+
+import org.springframework.data.jpa.repository.JpaRepository
+import `ordering-service`.orders.ItemEntity
+
+interface ItemsRepository : JpaRepository
+
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/OrderEntity.kt b/ordercenter/src/main/kotlin/ordering-service/orders/OrderEntity.kt
new file mode 100644
index 0000000..d0c97fc
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/OrderEntity.kt
@@ -0,0 +1,27 @@
+package `ordering-service`.orders
+
+import com.coded.spring.ordering.UserEntity
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "orders")
+data class OrderEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long? = null,
+
+ @ManyToOne
+ @JoinColumn(name = "name_id")
+ val user: UserEntity,
+
+ val restaurant: String,
+
+ @Column(columnDefinition = "TEXT")
+ val items: String = ""
+)
+
+ {
+ constructor() : this(null, UserEntity(), "", "")
+}
+
+
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/OrdersController.kt b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersController.kt
new file mode 100644
index 0000000..2840f0e
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersController.kt
@@ -0,0 +1,19 @@
+package `ordering-service`.orders
+
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/orders")
+class OrdersController(
+ private val ordersService: OrdersService
+) {
+ @PostMapping
+ fun createOrder(@RequestBody request: CreateOrderRequest): OrderEntity {
+ return ordersService.createOrder(request)
+ }
+ @GetMapping("/v1/list")
+ fun listOrders(): List {
+ return ordersService.listOrders()
+ }
+}
+
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/OrdersRepository.kt b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersRepository.kt
new file mode 100644
index 0000000..949b207
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersRepository.kt
@@ -0,0 +1,6 @@
+package `ordering-service`.orders
+
+import org.springframework.data.jpa.repository.JpaRepository
+import `ordering-service`.orders.OrderEntity
+
+interface OrderRepository : JpaRepository
diff --git a/ordercenter/src/main/kotlin/ordering-service/orders/OrdersService.kt b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersService.kt
new file mode 100644
index 0000000..028091e
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/orders/OrdersService.kt
@@ -0,0 +1,31 @@
+package `ordering-service`.orders
+
+import com.coded.spring.ordering.TalabatRepository
+import `ordering-service`.orders.OrderEntity
+import com.fasterxml.jackson.databind.ObjectMapper
+import `ordering-service`.orders.CreateOrderRequest
+import `ordering-service`.orders.OrderRepository
+import org.springframework.stereotype.Service
+
+@Service
+class OrdersService(
+ private val orderRepository: OrderRepository,
+ private val userRepository: TalabatRepository,
+ private val objectMapper: ObjectMapper
+) {
+ fun createOrder(request: CreateOrderRequest): OrderEntity {
+ val user = userRepository.findById(request.userId).orElseThrow()
+ val itemsJson = objectMapper.writeValueAsString(request.items)
+
+ val order = OrderEntity(
+ user = user,
+ restaurant = request.restaurant,
+ items = itemsJson
+ )
+
+ return orderRepository.save(order)
+ }
+ fun listOrders(): List {
+ return orderRepository.findAll()
+ }
+}
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/profiles/profileController.kt b/ordercenter/src/main/kotlin/ordering-service/profiles/profileController.kt
new file mode 100644
index 0000000..2fdc67c
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/profiles/profileController.kt
@@ -0,0 +1,36 @@
+package `ordering-service`.profiles
+
+import com.coded.spring.ordering.TalabatRepository
+import org.springframework.web.bind.annotation.*
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.userdetails.UserDetails
+
+@RestController
+class ProfileController(
+ private val talabatRepository: TalabatRepository,
+ private val profileRepository: ProfileRepository
+) {
+
+ @PostMapping("/profile")
+ fun createProfile(
+ @RequestBody profileRequest: ProfileRequest,
+ @AuthenticationPrincipal userDetails: UserDetails
+ ): ProfileEntity {
+ val user = talabatRepository.findByUsername(userDetails.username)
+ ?: throw RuntimeException("User not found")
+
+ val profile = ProfileEntity(
+ user = user,
+ firstName = profileRequest.firstName,
+ lastName = profileRequest.lastName,
+ phoneNumber = profileRequest.phoneNumber
+ )
+
+ return profileRepository.save(profile)
+ }
+}
+data class ProfileRequest(
+ val firstName: String,
+ val lastName: String,
+ val phoneNumber: String
+)
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/profiles/profileEntity.kt b/ordercenter/src/main/kotlin/ordering-service/profiles/profileEntity.kt
new file mode 100644
index 0000000..278fcb3
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/profiles/profileEntity.kt
@@ -0,0 +1,19 @@
+package `ordering-service`.profiles
+
+import com.coded.spring.ordering.UserEntity
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "profiles")
+data class ProfileEntity(
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ @OneToOne
+ @JoinColumn(name = "user_id", referencedColumnName = "id")
+ var user: UserEntity? = null,
+
+ var firstName: String = "",
+ var lastName: String = "",
+ var phoneNumber: String = ""
+)
\ No newline at end of file
diff --git a/ordercenter/src/main/kotlin/ordering-service/profiles/profileRepository.kt b/ordercenter/src/main/kotlin/ordering-service/profiles/profileRepository.kt
new file mode 100644
index 0000000..4ac31ed
--- /dev/null
+++ b/ordercenter/src/main/kotlin/ordering-service/profiles/profileRepository.kt
@@ -0,0 +1,8 @@
+package `ordering-service`.profiles
+
+import jakarta.inject.Named
+import `ordering-service`.profiles.ProfileEntity
+import org.springframework.data.jpa.repository.JpaRepository
+
+@Named
+interface ProfileRepository : JpaRepository
diff --git a/pom.xml b/pom.xml
index 163ad53..1b0088f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
com.coded.spring
Ordering
0.0.1-SNAPSHOT
+ pom
Kotlin.SpringbootV2
Kotlin.SpringbootV2
@@ -20,6 +21,11 @@
+
+ auth
+ ordercenter
+ welcoming
+
@@ -43,11 +49,114 @@
org.jetbrains.kotlin
kotlin-reflect
+
+ com.hazelcast
+ hazelcast
+ 5.5.0
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-api
+ 2.6.0
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ io.cucumber
+ cucumber-java
+ 7.20.1
+ test
+
+
+
+
+ io.cucumber
+ cucumber-spring
+ 7.14.0
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ io.cucumber
+ cucumber-junit-platform-engine
+ 7.20.1
+ test
+
+
+ org.junit.platform
+ junit-platform-suite-api
+ 1.10.0
+ test
+
+
+
+ com.h2database
+ h2
+ test
+
org.jetbrains.kotlin
kotlin-stdlib
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ 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-data-jpa
+
+
+ com.h2database
+ h2
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index 3704dc6..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-spring.application.name=Kotlin.SpringbootV2
diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
index b2e2320..81fba0d 100644
--- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
+++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
@@ -1,13 +1,106 @@
-package com.coded.spring.ordering
-
-import org.junit.jupiter.api.Test
-import org.springframework.boot.test.context.SpringBootTest
-
-@SpringBootTest
-class ApplicationTests {
-
- @Test
- fun contextLoads() {
- }
-
-}
+//package com.coded.spring.ordering
+//
+//import com.coded.spring.ordering.orders.OrderRepository
+//import com.coded.spring.ordering.profiles.ProfileEntity
+//import com.coded.spring.ordering.profiles.ProfileRepository
+//import com.coded.spring.ordering.profiles.ProfileRequest
+//import org.junit.jupiter.api.Assertions.*
+//import org.junit.jupiter.api.BeforeAll
+//import org.junit.jupiter.api.Test
+//import org.junit.jupiter.api.TestInstance
+//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.boot.test.web.server.LocalServerPort
+//import org.springframework.http.HttpEntity
+//import org.springframework.http.HttpHeaders
+//import org.springframework.http.HttpStatus
+//import org.springframework.http.MediaType
+//import org.springframework.security.crypto.password.PasswordEncoder
+//import org.springframework.test.context.ActiveProfiles
+//
+//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+//@ActiveProfiles("test")
+//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+//class ProfileControllerTest {
+//
+// @LocalServerPort
+// var port: Int = 0
+//
+// @Autowired
+// lateinit var usersRepository: TalabatRepository
+//
+// @Autowired
+// lateinit var ordersRepository: OrderRepository
+//
+// @Autowired
+// lateinit var profileRepository: ProfileRepository
+//
+// @Autowired
+// lateinit var passwordEncoder: PasswordEncoder
+//
+// @Autowired
+// lateinit var restTemplate: TestRestTemplate
+//
+// lateinit var tokenUsername: String
+// lateinit var tokenPassword: String
+//
+// lateinit var token: String
+//
+// @BeforeAll
+// fun setUp() {
+// val username = "ZainabAlS"
+// val rawPassword = "joincoded"
+//
+// if (usersRepository.findByUsername(username) == null) {
+// val testUser = UserEntity(
+// name = "Zainab",
+// username = username,
+// password = passwordEncoder.encode(rawPassword)
+// )
+// usersRepository.save(testUser)
+// }
+//
+// val loginRequest = AuthenticationRequest(username, rawPassword)
+// val loginResponse = restTemplate.postForEntity(
+// "http://localhost:$port/authentication/login",
+// loginRequest,
+// AuthenticationResponse::class.java
+// )
+//
+// assertEquals(HttpStatus.OK, loginResponse.statusCode)
+// token = loginResponse.body?.token ?: throw IllegalStateException("Token was not returned")
+//
+// tokenUsername = username
+// tokenPassword = rawPassword
+// }
+//
+//
+// @Test
+// fun `authenticated user can create profile`() {
+// val request = ProfileRequest(
+// firstName = "Zainab",
+// lastName = "AlSaffar",
+// phoneNumber = "12345678"
+// )
+//
+// val headers = HttpHeaders().apply {
+// contentType = MediaType.APPLICATION_JSON
+// setBearerAuth(token)
+// }
+//
+// val entity = HttpEntity(request, headers)
+//
+// val response = restTemplate.postForEntity(
+// "http://localhost:$port/profile",
+// entity,
+// ProfileEntity::class.java
+// )
+//
+// assertEquals(HttpStatus.OK, response.statusCode)
+// assertNotNull(response.body)
+// assertEquals("Zainab", response.body?.firstName)
+// assertEquals("AlSaffar", response.body?.lastName)
+// }
+//
+//}
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
new file mode 100644
index 0000000..db57135
--- /dev/null
+++ b/src/test/resources/application-test.yml
@@ -0,0 +1,17 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ show-sql: true
+ properties:
+ hibernate:
+ dialect: org.hibernate.dialect.H2Dialect
+
+sql:
+init:
+mode: never
\ No newline at end of file
diff --git a/welcoming/pom.xml b/welcoming/pom.xml
new file mode 100644
index 0000000..e017ff7
--- /dev/null
+++ b/welcoming/pom.xml
@@ -0,0 +1,96 @@
+
+
+ 4.0.0
+
+ com.coded.spring
+ Ordering
+ 0.0.1-SNAPSHOT
+
+
+ welcoming
+
+
+ UTF-8
+ official
+ 1.8
+
+
+
+
+ mavenCentral
+ https://repo1.maven.org/maven2/
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ maven-surefire-plugin
+ 2.22.2
+
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+ MainKt
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ 2.1.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.0
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 2.1.0
+
+
+ com.coded.spring
+ auth
+ 0.0.1-SNAPSHOT
+
+
+
+
+
\ No newline at end of file
diff --git a/welcoming/src/main/kotlin/welcome-service/Application.kt b/welcoming/src/main/kotlin/welcome-service/Application.kt
new file mode 100644
index 0000000..8da59dc
--- /dev/null
+++ b/welcoming/src/main/kotlin/welcome-service/Application.kt
@@ -0,0 +1,13 @@
+package com.coded.spring.`welcome-service`
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+@EnableConfigurationProperties(AppProperties::class)
+class Application
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/welcoming/src/main/kotlin/welcome-service/appConfiguration.kt b/welcoming/src/main/kotlin/welcome-service/appConfiguration.kt
new file mode 100644
index 0000000..afa2f64
--- /dev/null
+++ b/welcoming/src/main/kotlin/welcome-service/appConfiguration.kt
@@ -0,0 +1,16 @@
+package com.coded.spring.`welcome-service`
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+@ConfigurationProperties(prefix = "app")
+class AppProperties {
+ lateinit var companyName: String
+ var festive: Festive = Festive()
+
+ class Festive {
+ var enabled: Boolean = false
+ var message: String = ""
+ }
+}
diff --git a/welcoming/src/main/kotlin/welcome-service/resources/application.properties b/welcoming/src/main/kotlin/welcome-service/resources/application.properties
new file mode 100644
index 0000000..a7bf29c
--- /dev/null
+++ b/welcoming/src/main/kotlin/welcome-service/resources/application.properties
@@ -0,0 +1,16 @@
+spring.application.name=Kotlin.SpringbootV2
+
+spring.datasource.url=jdbc:postgresql://localhost:5432/talabatDB
+spring.datasource.username=postgres
+spring.datasource.password=NBKPostgres
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+
+#server-welcome-message=Welcome to Talabat! Where all your favourite restaurants are a click away
+
+server-welcome-message=Welcome to Online Ordering by ${app.company-name}
+
+app.company-name=Zainab Foods
+app.festive.enabled=true
+app.festive.message=Eidkom Mubarak
+
+springdoc.api-docs.path=/api-docs
\ No newline at end of file
diff --git a/welcoming/src/main/kotlin/welcome-service/welcomeController.kt b/welcoming/src/main/kotlin/welcome-service/welcomeController.kt
new file mode 100644
index 0000000..846a65e
--- /dev/null
+++ b/welcoming/src/main/kotlin/welcome-service/welcomeController.kt
@@ -0,0 +1,21 @@
+package com.coded.spring.`welcome-service`
+
+import com.coded.spring.`welcome-service`.AppProperties
+import org.springframework.web.bind.annotation.*
+import org.springframework.security.crypto.password.PasswordEncoder
+
+@RestController
+class TalabatController(
+ private val talabatRepository: TalabatRepository,
+ private val passwordEncoder: PasswordEncoder,
+ private val appProperties: AppProperties
+) {
+
+ @GetMapping("/welcome")
+ fun welcomeToTalabat(): String {
+ return if (appProperties.festive.enabled) {
+ appProperties.festive.message
+ } else {
+ "Welcome to Online Ordering by ${appProperties.companyName}"
+ }
+ }
\ No newline at end of file
diff --git a/zainab-alsaffar-online-ordering-api-swagger-01.json b/zainab-alsaffar-online-ordering-api-swagger-01.json
new file mode 100644
index 0000000..73ab2c9
--- /dev/null
+++ b/zainab-alsaffar-online-ordering-api-swagger-01.json
@@ -0,0 +1 @@
+{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:8080","description":"Generated server url"}],"paths":{"/profile":{"post":{"tags":["profile-controller"],"operationId":"createProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProfileEntity"}}}}}}},"/orders":{"post":{"tags":["orders-controller"],"operationId":"createOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OrderEntity"}}}}}}},"/order":{"post":{"tags":["talabat-controller"],"operationId":"submitOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/menu":{"get":{"tags":["menu-controller"],"operationId":"getMenu","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MenuEntity"}}}}}}},"post":{"tags":["menu-controller"],"operationId":"addMenuItem","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MenuEntity"}}}}}}},"/clients":{"post":{"tags":["talabat-controller"],"operationId":"clients","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserEntity"}}}}}}},"/authentication/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"}}}}}}},"/welcome":{"get":{"tags":["talabat-controller"],"operationId":"welcomeToTalabat","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/orders/v1/list":{"get":{"tags":["orders-controller"],"operationId":"listOrders","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderEntity"}}}}}}}}},"components":{"schemas":{"ProfileRequest":{"required":["firstName","lastName","phoneNumber"],"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"phoneNumber":{"type":"string"}}},"ProfileEntity":{"required":["firstName","lastName","phoneNumber"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"user":{"$ref":"#/components/schemas/UserEntity"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phoneNumber":{"type":"string"}}},"UserEntity":{"required":["name","password","username"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"CreateOrderRequest":{"required":["items","restaurant","userId"],"type":"object","properties":{"userId":{"type":"integer","format":"int64"},"restaurant":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemRequest"}}}},"ItemRequest":{"required":["name","quantity"],"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"}}},"OrderEntity":{"required":["items","restaurant","user"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"user":{"$ref":"#/components/schemas/UserEntity"},"restaurant":{"type":"string"},"items":{"type":"string"}}},"OrderRequest":{"required":["items"],"type":"object","properties":{"items":{"type":"array","items":{"type":"string"}}}},"MenuRequest":{"required":["name","price"],"type":"object","properties":{"name":{"type":"string"},"price":{"type":"number","format":"double"}}},"MenuEntity":{"required":["name","price"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"price":{"type":"number","format":"double"}}},"ClientsRequest":{"required":["name","password","username"],"type":"object","properties":{"name":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"AuthenticationRequest":{"required":["password","username"],"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"AuthenticationResponse":{"required":["token"],"type":"object","properties":{"token":{"type":"string"}}}}}}
\ No newline at end of file