diff --git a/pom.xml b/pom.xml
index 163ad53..04235ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,41 @@
1.9.25
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ 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-test
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
org.springframework.boot
spring-boot-starter-web
@@ -43,23 +78,29 @@
org.jetbrains.kotlin
kotlin-reflect
+
+ com.hazelcast
+ hazelcast
+ 5.5.0
+
org.jetbrains.kotlin
kotlin-stdlib
- org.springframework.boot
- spring-boot-starter-test
- test
+ org.postgresql
+ postgresql
+ compile
- org.jetbrains.kotlin
- kotlin-test-junit5
+ com.h2database
+ h2
test
-
+
+
${project.basedir}/src/main/kotlin
${project.basedir}/src/test/kotlin
diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/Application.kt
similarity index 87%
rename from src/main/kotlin/com/coded/spring/ordering/Application.kt
rename to src/main/kotlin/Application.kt
index 8554e49..f5efc56 100644
--- a/src/main/kotlin/com/coded/spring/ordering/Application.kt
+++ b/src/main/kotlin/Application.kt
@@ -1,4 +1,5 @@
-package com.coded.spring.ordering
+package com.coded.spring
+
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@@ -8,4 +9,5 @@ class Application
fun main(args: Array) {
runApplication(*args)
+
}
diff --git a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt
new file mode 100644
index 0000000..465b654
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt
@@ -0,0 +1,36 @@
+package com.coded.spring.com.coded.spring.ordering
+
+import com.coded.spring.Application
+import com.coded.spring.ordering.users.Roles
+import com.coded.spring.ordering.users.UserEntity
+
+
+import com.coded.spring.ordering.users.UserRepository
+import org.springframework.boot.CommandLineRunner
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.security.crypto.password.PasswordEncoder
+
+// @SpringBootApplication
+class InitUserRunner {
+ @Bean
+ fun initUsers(userRepository: UserRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner {
+ val user = UserEntity(
+ name = "HelloUser",
+ username = "testuser",
+ password = passwordEncoder.encode("password123"),
+ role = Roles.USER
+
+ )
+ if (userRepository.findByUsername(user.username) == null) {
+ println("Creating user ${user.username}")
+ userRepository.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/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt
new file mode 100644
index 0000000..38c18d1
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt
@@ -0,0 +1,23 @@
+package com.coded.spring.com.coded.spring.ordering.authentication
+
+import com.coded.spring.ordering.users.UserRepository
+import org.springframework.security.core.userdetails.*
+import org.springframework.stereotype.Service
+
+
+@Service
+class CustomUserDetailsService(
+ private val userRepository: UserRepository
+) : UserDetailsService {
+ override fun loadUserByUsername(username: String): UserDetails {
+ val user = userRepository.findByUsername(username)
+ ?: throw UsernameNotFoundException("User not found!")
+
+ return User.builder()
+ .username(user.username)
+ .password(user.password)
+ .roles(user.role.toString())
+ .build()
+ }
+}
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt
new file mode 100644
index 0000000..f22038a
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt
@@ -0,0 +1,59 @@
+package com.coded.spring.com.coded.spring.ordering.authentication
+
+import com.coded.spring.com.coded.spring.ordering.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
+) {
+ @Bean
+ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+ http.csrf { it.disable() }
+ .authorizeHttpRequests {
+ it.requestMatchers("/user").permitAll()
+ .requestMatchers("/auth/**").permitAll()
+// .requestMatchers("/profile").authenticated()
+ .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/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtAuthenticationFilter.kt
new file mode 100644
index 0000000..38ed3ab
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtAuthenticationFilter.kt
@@ -0,0 +1,45 @@
+package com.coded.spring.com.coded.spring.ordering.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
+ ) {
+ 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/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtService.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtService.kt
new file mode 100644
index 0000000..8daa163
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtService.kt
@@ -0,0 +1,42 @@
+package com.coded.spring.com.coded.spring.ordering.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/src/main/kotlin/com/coded/spring/ordering/config/HazelcastConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/HazelcastConfig.kt
new file mode 100644
index 0000000..353b619
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/config/HazelcastConfig.kt
@@ -0,0 +1,19 @@
+package com.coded.spring.com.coded.spring.ordering.config
+
+
+import com.hazelcast.config.Config
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.core.HazelcastInstance
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+class HazelcastConfig {
+
+ @Bean
+ fun hazelcastInstance(): HazelcastInstance {
+ val config = Config("menu-cache")
+ config.getMapConfig("menus").timeToLiveSeconds = 5
+ return Hazelcast.newHazelcastInstance(config)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemController.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemController.kt
new file mode 100644
index 0000000..c04f3a5
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemController.kt
@@ -0,0 +1,2 @@
+package com.coded.spring.ordering.items
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt
new file mode 100644
index 0000000..6a0d86b
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt
@@ -0,0 +1,24 @@
+package com.coded.spring.com.coded.spring.ordering.items
+
+import com.coded.spring.ordering.orders.OrderEntity
+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
+
+){
+ constructor() : this(null, "", 0, OrderEntity() )
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemRepository.kt
new file mode 100644
index 0000000..3c382a3
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemRepository.kt
@@ -0,0 +1,6 @@
+package com.coded.spring.ordering.items
+
+import com.coded.spring.com.coded.spring.ordering.items.ItemEntity
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface ItemsRepository: JpaRepository
diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemRequest.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemRequest.kt
new file mode 100644
index 0000000..08bc4f7
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemRequest.kt
@@ -0,0 +1,2 @@
+package com.coded.spring.com.coded.spring.ordering.items
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemService.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemService.kt
new file mode 100644
index 0000000..08bc4f7
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemService.kt
@@ -0,0 +1,2 @@
+package com.coded.spring.com.coded.spring.ordering.items
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt
new file mode 100644
index 0000000..4c0fad0
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt
@@ -0,0 +1,22 @@
+package com.coded.spring.com.coded.spring.ordering.menu
+
+
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/menu")
+class MenuController(
+ private val menuService: MenuService
+) {
+ @PostMapping
+ fun createMenu(@RequestBody menu: MenuEntity): ResponseEntity {
+ return menuService.createMenu(menu)
+ }
+
+ @GetMapping
+ fun getAllMenus(): ResponseEntity> {
+ return menuService.getAllMenus()
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuEntity.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuEntity.kt
new file mode 100644
index 0000000..22d80e6
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuEntity.kt
@@ -0,0 +1,17 @@
+package com.coded.spring.com.coded.spring.ordering.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
+) {
+ constructor() : this(id = null, name = "", price = 0.0)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuProvider.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuProvider.kt
new file mode 100644
index 0000000..63b2452
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuProvider.kt
@@ -0,0 +1,25 @@
+package com.coded.spring.com.coded.spring.ordering.menu
+
+
+import com.hazelcast.core.HazelcastInstance
+import org.springframework.stereotype.Component
+
+@Component
+class MenuProvider(
+ private val menuRepository: MenuRepository,
+ private val hazelcastInstance: HazelcastInstance
+) {
+ private val menuCache = hazelcastInstance.getMap>("menus")
+
+ fun getMenus(): List {
+ val cachedMenus = menuCache["menus"]
+ if (cachedMenus.isNullOrEmpty()) {
+ println("No menus in cache. Fetching from DB...")
+ val menus = menuRepository.findAll()
+ menuCache["menus"] = menus
+ return menus
+ }
+ println("Returning ${cachedMenus.size} menus from cache")
+ return cachedMenus
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuRepository.kt
new file mode 100644
index 0000000..6d4d2a1
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuRepository.kt
@@ -0,0 +1,9 @@
+package com.coded.spring.com.coded.spring.ordering.menu
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface MenuRepository : JpaRepository{
+
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuRequest.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuRequest.kt
new file mode 100644
index 0000000..3fc73e9
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuRequest.kt
@@ -0,0 +1,2 @@
+package com.coded.spring.com.coded.spring.ordering.menu
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/menu/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/menu/MenuService.kt
new file mode 100644
index 0000000..9abf577
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/menu/MenuService.kt
@@ -0,0 +1,20 @@
+package com.coded.spring.com.coded.spring.ordering.menu
+
+import org.springframework.http.ResponseEntity
+import org.springframework.stereotype.Service
+
+
+@Service
+class MenuService(
+ private val menuRepository: MenuRepository,
+ private val menuProvider: MenuProvider
+) {
+ fun createMenu(menu: MenuEntity): ResponseEntity {
+ menuRepository.save(menu)
+ return ResponseEntity.ok("Menu item created")
+ }
+
+ fun getAllMenus(): ResponseEntity> {
+ return ResponseEntity.ok(menuProvider.getMenus())
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderController.kt
new file mode 100644
index 0000000..142ab15
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderController.kt
@@ -0,0 +1,22 @@
+package com.coded.spring.ordering.orders
+
+import com.coded.spring.com.coded.spring.ordering.orders.OrderRequest
+import com.coded.spring.ordering.OrderService
+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 OrderController(
+ private val orderService: OrderService
+) {
+
+ @GetMapping("/order")
+ fun homePage() = "Welcome to Ooreedoo, please place your order!"
+
+ @PostMapping("/order")
+ fun createOrder(@RequestBody request: OrderRequest) {
+ orderService.createOrder(request)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderEntity.kt
new file mode 100644
index 0000000..83c5ad9
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderEntity.kt
@@ -0,0 +1,25 @@
+package com.coded.spring.ordering.orders
+
+import com.coded.spring.com.coded.spring.ordering.items.ItemEntity
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "orders")
+data class OrderEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null,
+
+ val userId: Long,
+ //If the relationship is: Many Orders can belong to One User, then the User will be brought to the OrderEntity as an Object or as a whole UserEntity
+
+ val restaurant: String,
+
+ @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL])
+ var items: List = listOf()
+ //If the relationship is: One Order can have Many Items, then the Items will be brought to the OrderEntity as a List
+ //This lets the order list its items, but the actual foreign key is in the items table, not the orders table (only the child "the Many" holds the foreign key)
+ // this is saying: If I get an order from the database, and I want to see its items, look in the items table and find all the rows where order_id = this order’s id
+){
+ constructor() : this(null, 1, "", listOf())
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderRepository.kt
new file mode 100644
index 0000000..ac07b7c
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderRepository.kt
@@ -0,0 +1,11 @@
+package com.coded.spring.ordering.orders
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface OrdersRepository : JpaRepository{
+ fun findByUserId(userId: Long): List
+}
+
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderRequest.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderRequest.kt
new file mode 100644
index 0000000..846137a
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderRequest.kt
@@ -0,0 +1,12 @@
+package com.coded.spring.com.coded.spring.ordering.orders
+
+data class OrderRequest(
+ val userId: Long,
+ val restaurant: String,
+ val items: List-
+)
+
+data class Item(
+ val name: String,
+ val quantity: Int
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt
new file mode 100644
index 0000000..fadc50e
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt
@@ -0,0 +1,31 @@
+package com.coded.spring.ordering
+
+import com.coded.spring.com.coded.spring.ordering.items.ItemEntity
+import com.coded.spring.com.coded.spring.ordering.orders.OrderRequest
+import com.coded.spring.ordering.items.ItemsRepository
+import com.coded.spring.ordering.orders.OrderEntity
+import com.coded.spring.ordering.orders.OrdersRepository
+import com.coded.spring.ordering.users.UserRepository
+import org.springframework.stereotype.Service
+
+@Service
+class OrderService(
+ private val ordersRepository: OrdersRepository,
+ private val userRepository: UserRepository,
+ private val itemsRepository: ItemsRepository
+) {
+
+ fun createOrder(request: OrderRequest) {
+
+ val newOrder = OrderEntity(
+ userId = request.userId,
+ restaurant = request.restaurant
+ ).apply {
+ items = request.items.map {
+ ItemEntity(name = it.name, quantity = it.quantity, order = this)
+ }
+ }
+
+ ordersRepository.save(newOrder)
+ }
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileController.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileController.kt
new file mode 100644
index 0000000..c85e02e
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileController.kt
@@ -0,0 +1,19 @@
+package com.coded.spring.ordering.profiles
+
+
+import org.springframework.http.ResponseEntity
+import org.springframework.security.core.Authentication
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class ProfileController(
+ private val profileService: ProfileService
+) {
+
+ @PostMapping("/profile")
+ fun createProfile(@RequestBody request: ProfileRequest, authentication: Authentication): ResponseEntity {
+ return profileService.createProfile(request, authentication.name)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileEntity.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileEntity.kt
new file mode 100644
index 0000000..3195cac
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileEntity.kt
@@ -0,0 +1,30 @@
+package com.coded.spring.ordering.profiles
+
+import com.coded.spring.ordering.users.UserEntity
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "profiles")
+data class ProfileEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long = 0,
+
+ val firstName: String,
+
+ val lastName: String,
+
+ val phoneNumber: String,
+
+ @OneToOne
+ @JoinColumn(name = "user_id", referencedColumnName = "id")
+ val user:UserEntity? = null,
+
+) { constructor() : this(
+id = 0,
+firstName = "",
+lastName = "",
+phoneNumber = "",
+
+) }
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRepository.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRepository.kt
new file mode 100644
index 0000000..79e9c08
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRepository.kt
@@ -0,0 +1,10 @@
+package com.coded.spring.ordering.profiles
+
+import com.coded.spring.ordering.users.UserEntity
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface ProfileRepository : JpaRepository {
+ fun existsByUser(user: UserEntity): Boolean
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRequest.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRequest.kt
new file mode 100644
index 0000000..e3d3408
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRequest.kt
@@ -0,0 +1,7 @@
+package com.coded.spring.ordering.profiles
+
+data class ProfileRequest(
+ val firstName: String,
+ val lastName: String,
+ val phoneNumber: String
+)
diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt
new file mode 100644
index 0000000..75020a0
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt
@@ -0,0 +1,33 @@
+package com.coded.spring.ordering.profiles
+
+import com.coded.spring.ordering.users.UserRepository
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.stereotype.Service
+
+@Service
+class ProfileService(
+ private val userRepository: UserRepository,
+ private val profileRepository: ProfileRepository
+) {
+
+ fun createProfile(request: ProfileRequest, username: String): ResponseEntity {
+ val user = userRepository.findByUsername(username)
+ ?: return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found")
+
+ if (profileRepository.existsByUser(user)) {
+ return ResponseEntity.status(HttpStatus.CONFLICT).body("Profile already exists")
+ }
+
+ val userId = user.id
+
+ val profile = ProfileEntity(
+ firstName = request.firstName,
+ lastName = request.lastName,
+ phoneNumber = request.phoneNumber
+ )
+
+ profileRepository.save(profile)
+ return ResponseEntity.ok("Profile created successfully")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserController.kt
new file mode 100644
index 0000000..2dc9a14
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/users/UserController.kt
@@ -0,0 +1,38 @@
+package com.coded.spring.ordering.users
+
+import com.coded.spring.com.coded.spring.ordering.users.UserService
+import org.springframework.http.ResponseEntity
+import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class UserController(
+ private val userService: UserService
+) {
+ @PostMapping("/auth/register")
+ fun register(@RequestBody authRequest: AuthenticationRequest): ResponseEntity {
+ return try {
+ val response = userService.register(authRequest)
+ ResponseEntity.ok(response)
+ } catch (e: IllegalArgumentException) {
+ ResponseEntity.badRequest().body(e.message)
+ } catch (e: BadCredentialsException) {
+ ResponseEntity.status(401).body(e.message)
+ }
+
+ }
+
+ @PostMapping("/auth/login")
+ fun login(@RequestBody authRequest: AuthenticationRequest): ResponseEntity {
+ return try {
+ val response = userService.login(authRequest)
+ ResponseEntity.ok(response)
+ } catch (e: BadCredentialsException) {
+ ResponseEntity.status(401).body(mapOf("error" to e.message))
+ }
+ }
+}
+
diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserEntity.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserEntity.kt
new file mode 100644
index 0000000..1c1a82c
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/users/UserEntity.kt
@@ -0,0 +1,32 @@
+package com.coded.spring.ordering.users
+
+import com.coded.spring.ordering.profiles.ProfileEntity
+import jakarta.persistence.*
+
+
+@Entity
+@Table(name = "users")
+data class UserEntity(
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ val id: Long? = null,
+
+ val name: String = "",
+
+ var username: String,
+ var password: String,
+
+ @Enumerated(EnumType.STRING)
+ val role: Roles = Roles.USER,
+
+ @OneToOne(mappedBy = "user")
+ val profile: ProfileEntity? = null
+
+
+) {
+ constructor() : this(null, "", "", "",Roles.USER, ProfileEntity())
+}
+
+enum class Roles {
+ USER, ADMIN
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserRepository.kt
new file mode 100644
index 0000000..6ad7040
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/users/UserRepository.kt
@@ -0,0 +1,9 @@
+package com.coded.spring.ordering.users
+
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface UserRepository : JpaRepository {
+ fun findByUsername(username: String): UserEntity?
+}
diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserRequest.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserRequest.kt
new file mode 100644
index 0000000..85d8620
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/users/UserRequest.kt
@@ -0,0 +1,10 @@
+package com.coded.spring.ordering.users
+
+data class AuthenticationRequest(
+ val username: String,
+ val password: String,
+)
+
+data class AuthenticationResponse(
+ val token: String
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt
new file mode 100644
index 0000000..a608d45
--- /dev/null
+++ b/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt
@@ -0,0 +1,64 @@
+package com.coded.spring.com.coded.spring.ordering.users
+
+
+import com.coded.spring.com.coded.spring.ordering.authentication.jwt.JwtService
+import com.coded.spring.ordering.users.AuthenticationRequest
+import com.coded.spring.ordering.users.AuthenticationResponse
+import com.coded.spring.ordering.users.UserEntity
+import com.coded.spring.ordering.users.UserRepository
+import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.BadCredentialsException
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.stereotype.Service
+
+@Service
+class UserService(
+ private val userRepository: UserRepository,
+ private val userDetailsService : UserDetailsService,
+ private val passwordEncoder : PasswordEncoder,
+ private val authenticationManager: AuthenticationManager,
+ private val jwtService: JwtService
+) {
+ fun register(authRequest: AuthenticationRequest): AuthenticationResponse {
+
+ if (authRequest.username.isBlank()){
+ throw IllegalArgumentException("Username must not be blank!")
+ }
+
+ if (authRequest.password.length < 8) {
+ throw IllegalArgumentException("Password must be at least 8 characters long")
+ }
+
+ val newUser = UserEntity(
+ username = authRequest.username,
+ password = passwordEncoder.encode(authRequest.password)
+ )
+ userRepository.save(newUser)
+
+ 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 BadCredentialsException("Could not authenticate user!")
+ }
+ }
+
+ fun login(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 BadCredentialsException("Invalid credentials!")
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3704dc6..d43cb53 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,9 @@
spring.application.name=Kotlin.SpringbootV2
+
+
+server.port=8082
+
+spring.datasource.url=jdbc:postgresql://localhost:5433/orders
+spring.datasource.username=postgres
+spring.datasource.password=1193
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
\ 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..9365c1b 100644
--- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
+++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt
@@ -1,13 +1,24 @@
package com.coded.spring.ordering
+import org.junit.jupiter.api.Assertions.assertEquals
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.HttpStatus
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApplicationTests {
- @Test
- fun contextLoads() {
- }
+// @Autowired
+// lateinit var restTemplate: TestRestTemplate
+//
+// @Test
+// fun helloWorld() {
+// val result = restTemplate.getForEntity("/hello", String::class.java)
+// assertEquals("Hello World",result.body)
+// assertEquals(HttpStatus.OK,result?.statusCode)
+// }
+
}