From c64c7f0ba8ca5295bff8dd1168883022421fef41 Mon Sep 17 00:00:00 2001 From: A Date: Tue, 8 Apr 2025 18:22:55 +0300 Subject: [PATCH 1/8] Online Ordering - Post an Order --- pom.xml | 12 ++++++++ .../com/coded/spring/ordering/Controller.kt | 28 +++++++++++++++++++ .../coded/spring/ordering/OrdersRepository.kt | 21 ++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/Controller.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt diff --git a/pom.xml b/pom.xml index 163ad53..f7219c8 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,18 @@ kotlin-stdlib + + jakarta.inject + jakarta.inject-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/kotlin/com/coded/spring/ordering/Controller.kt b/src/main/kotlin/com/coded/spring/ordering/Controller.kt new file mode 100644 index 0000000..a483856 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/Controller.kt @@ -0,0 +1,28 @@ +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 Controller( + val ordersRepository: OrdersRepository +){ + + @GetMapping("/order") + fun homePage() = "Welcome to Ooreedoo, please place your order!" + + @GetMapping("/orders") + fun listOrders(): List = ordersRepository.findAll() + + @PostMapping("/order") + fun submitOrder( @RequestBody request: EnterOrderRequest) = ordersRepository.save(Order(username = request.user, restaurant = request.restaurant, items = request.items)) + +} + +data class EnterOrderRequest( + val user: String, + val restaurant: String, + val items: MutableList +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt new file mode 100644 index 0000000..469e852 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt @@ -0,0 +1,21 @@ +package com.coded.spring.ordering + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface OrdersRepository : JpaRepository + +@Entity +@Table(name = "orders") +data class Order( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + val username: String, + val restaurant: String, + val items: MutableList +){ + constructor() : this(null, "", "", mutableListOf()) +} \ No newline at end of file From 4f6881127f777d1d97a0324d376b5b807454f81d Mon Sep 17 00:00:00 2001 From: A Date: Thu, 10 Apr 2025 20:51:08 +0300 Subject: [PATCH 2/8] task 3 WIP --- pom.xml | 18 +++--- .../coded/spring/ordering => }/Application.kt | 2 +- .../com/coded/spring/ordering/Controller.kt | 28 --------- .../coded/spring/ordering/OrdersRepository.kt | 21 ------- .../spring/ordering/items/ItemsController.kt | 2 + .../spring/ordering/items/ItemsRepository.kt | 19 +++++++ .../ordering/orders/OrdersController.kt | 57 +++++++++++++++++++ .../ordering/orders/OrdersRepository.kt | 31 ++++++++++ .../spring/ordering/orders/OrdersService.kt | 30 ++++++++++ .../spring/ordering/users/UsersController.kt | 2 + .../spring/ordering/users/UsersRepository.kt | 20 +++++++ src/main/resources/application.properties | 7 +++ 12 files changed, 176 insertions(+), 61 deletions(-) rename src/main/kotlin/{com/coded/spring/ordering => }/Application.kt (87%) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/Controller.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt diff --git a/pom.xml b/pom.xml index f7219c8..9d6e046 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,10 @@ 1.9.25 + + org.springframework.boot + spring-boot-starter-data-jpa + org.springframework.boot spring-boot-starter-web @@ -49,16 +53,9 @@ - jakarta.inject - jakarta.inject-api - - - org.springframework.boot - spring-boot-starter-data-jpa - - - com.h2database - h2 + org.postgresql + postgresql + runtime org.springframework.boot @@ -71,7 +68,6 @@ 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..3fe2c8e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/Application.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering +package com.coded.spring import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/src/main/kotlin/com/coded/spring/ordering/Controller.kt b/src/main/kotlin/com/coded/spring/ordering/Controller.kt deleted file mode 100644 index a483856..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/Controller.kt +++ /dev/null @@ -1,28 +0,0 @@ -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 Controller( - val ordersRepository: OrdersRepository -){ - - @GetMapping("/order") - fun homePage() = "Welcome to Ooreedoo, please place your order!" - - @GetMapping("/orders") - fun listOrders(): List = ordersRepository.findAll() - - @PostMapping("/order") - fun submitOrder( @RequestBody request: EnterOrderRequest) = ordersRepository.save(Order(username = request.user, restaurant = request.restaurant, items = request.items)) - -} - -data class EnterOrderRequest( - val user: String, - val restaurant: String, - val items: MutableList -) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt deleted file mode 100644 index 469e852..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.coded.spring.ordering - -import jakarta.inject.Named -import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository - -@Named -interface OrdersRepository : JpaRepository - -@Entity -@Table(name = "orders") -data class Order( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, - val username: String, - val restaurant: String, - val items: MutableList -){ - constructor() : this(null, "", "", mutableListOf()) -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt new file mode 100644 index 0000000..c04f3a5 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt @@ -0,0 +1,2 @@ +package com.coded.spring.ordering.items + diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt new file mode 100644 index 0000000..e5c4105 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt @@ -0,0 +1,19 @@ +package com.coded.spring.ordering.items + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +interface ItemsRepository: JpaRepository + +@Entity +@Table(name="items") +data class ItemEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + val name: String, + val quantity: Int, + // val order_id: Long +){ + constructor() : this(null, "", 0) +} diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt new file mode 100644 index 0000000..8148ebd --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt @@ -0,0 +1,57 @@ +package com.coded.spring.ordering.orders + +import com.coded.spring.ordering.items.ItemEntity +import com.coded.spring.ordering.users.UserRepository +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 Controller( + val ordersRepository: OrdersRepository, + val userRepository: UserRepository, +) { + +// @GetMapping("/order") +// fun homePage() = "Welcome to Ooreedoo, please place your order!" +// +// @GetMapping("/orders/v1/list") +// fun orders() = ordersService.listOrders() +// +// @GetMapping("/orders") +// fun listOrders(): List = ordersRepository.findAll() + +// @PostMapping("/order") +// fun createOrder( @RequestBody request: EnterOrderRequest) = ordersRepository.save( +// com.coded.spring.ordering.orders.OrderEntity( +// user = request.user_id, +// restaurant = request.restaurant, items = null) +// ) +// ) + + @PostMapping("/order") + fun createOrder(@RequestBody request: EnterOrderRequest) { + val user = userRepository.findById(request.user_id).orElseThrow(); + var newOrder = OrderEntity( + user = user, + restaurant = request.restaurant, + items = request.items.map { ItemEntity(name = it.name, quantity = it.quantity) + } + ) + ordersRepository.save(newOrder) + + } + +} + +data class EnterOrderRequest( + val user_id: Long, + val restaurant: String, + val items: List +) + +data class ItemRequest( + val name: String, + val quantity: Int +) diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt new file mode 100644 index 0000000..854862b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt @@ -0,0 +1,31 @@ +package com.coded.spring.ordering.orders + +import com.coded.spring.ordering.items.ItemEntity +import com.coded.spring.ordering.users.UserEntity +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrdersRepository : JpaRepository{ + // fun findByUser_Id(user_id: Long): List +} + +@Entity +@Table(name = "orders") +data class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + @ManyToOne + val user: UserEntity, + + val restaurant: String, + + @OneToMany(mappedBy = "id", cascade = [CascadeType.ALL]) + val items: List? = null +){ + constructor() : this(null, UserEntity(), "", listOf()) +} + diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt new file mode 100644 index 0000000..7fc4e39 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt @@ -0,0 +1,30 @@ +package com.coded.spring.ordering.orders + + +import org.springframework.stereotype.Service + +//@Service +//class OrdersService( +// private val ordersRepository: OrdersRepository, +//) { +// +// fun listOrders(): List = ordersRepository.findAll().map { +// OrderEntity( +// user_id = it.user_id, +// restaurant = it.restaurant, +// items = it.items +// ) +// } +// +// fun createOrder(userId: Long){ +// val user = usersRepository.findById(user_id).get() +// val newOrder = OrderEntity(user=name) +// orderRepository.save(newOrder) +// +//} +// +//data class Order( +// val username: String, +// val restaurant: Int, +//) + diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt new file mode 100644 index 0000000..60d068d --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt @@ -0,0 +1,2 @@ +package com.coded.spring.ordering.users + diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt new file mode 100644 index 0000000..05517d0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering.users + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UserRepository : JpaRepository + +@Entity +@Table(name = "users") +data class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + val name: String, +// val items: MutableList +){ + constructor() : this(null, "") +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..0ef0063 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,8 @@ spring.application.name=Kotlin.SpringbootV2 + + + +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 From f5197ae974ab782b7cafdd6d37ad30cf53f8fc2a Mon Sep 17 00:00:00 2001 From: A Date: Fri, 11 Apr 2025 21:13:41 +0300 Subject: [PATCH 3/8] Studied and added some comments and explanations --- .../spring/ordering/items/ItemsRepository.kt | 11 ++++++++-- .../ordering/orders/OrdersController.kt | 4 ++-- .../ordering/orders/OrdersRepository.kt | 21 ++++++++++++------- .../spring/ordering/users/UsersRepository.kt | 9 ++++++-- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt index e5c4105..9220943 100644 --- a/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.items +import com.coded.spring.ordering.orders.OrderEntity import jakarta.persistence.* import org.springframework.data.jpa.repository.JpaRepository @@ -11,9 +12,15 @@ data class ItemEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, + val name: String, + val quantity: Int, - // val order_id: Long + + @ManyToOne + @JoinColumn(name = "order_id") + val order: OrderEntity + ){ - constructor() : this(null, "", 0) + constructor() : this(null, "", 0,OrderEntity() ) } diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt index 8148ebd..aef216e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController -@RestController -class Controller( +@RestController //this tells Spring that this class is in charge of listening to the internet and sending back answers +class Controller( //this is saying whenever Spring creates this controller, please give it these two objects to use: val ordersRepository: OrdersRepository, val userRepository: UserRepository, ) { diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt index 854862b..6d24e90 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt @@ -6,26 +6,31 @@ import jakarta.persistence.* import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -@Repository -interface OrdersRepository : JpaRepository{ - // fun findByUser_Id(user_id: Long): List +@Repository //This annotates that this file is gonna be the repository. I dont need to write it here since am using JpaRepository, I will need to write it if am creating my own custom repository class (not just an interface) +interface OrdersRepository : JpaRepository{ //This line creates the repository, and Jpa lets the repository talk to the DB and gives me all the ready functions to use (.save , .findall) + // means: This repository is for the orders table, and the ID type (the @Id column in the table) is a Long number +// fun findByUser_Id(user_id: Long): List } -@Entity +@Entity //this indicates that this class should become a table @Table(name = "orders") data class OrderEntity( - @Id + @Id //This is the unique identifier "Primary key" @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, @ManyToOne + @JoinColumn(name= "user_id") //Creates a column in the orders table called user_id. No need to declare a user_id cause Spring handles it automatically when i define the relationship val user: UserEntity, + //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 = "id", cascade = [CascadeType.ALL]) - val items: List? = null + @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL]) //cascade means: “If I save an order, also save their items. If I delete an order, delete their items too.”. without it I’d have to manually save the order then save its items one by one + val 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) ){ - constructor() : this(null, UserEntity(), "", listOf()) + constructor() : this(null, UserEntity(), "", listOf()) //Spring needs this to be able to create a blank User object behind the scenes. } diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt index 05517d0..da42e8c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.users +import com.coded.spring.ordering.orders.OrderEntity import jakarta.persistence.* import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -14,7 +15,11 @@ data class UserEntity( @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, val name: String, -// val items: MutableList + + @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL]) + val orders: List = listOf() +//This lets the user "know about" their orders, but the actual foreign key exists in the orders table, not the users table. (only the child "the Many" holds the foreign key) + ){ - constructor() : this(null, "") + constructor() : this(null, "", listOf()) } From 4aa5f8b5232232ab10ee3bdc128cfb162f9feece Mon Sep 17 00:00:00 2001 From: A Date: Fri, 11 Apr 2025 22:47:58 +0300 Subject: [PATCH 4/8] Create Online Ordering DB - Asma --- .../ordering/orders/OrdersController.kt | 38 ++++++++++--------- .../ordering/orders/OrdersRepository.kt | 5 ++- .../spring/ordering/users/UsersRepository.kt | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt index aef216e..a529a2b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt @@ -1,6 +1,7 @@ package com.coded.spring.ordering.orders import com.coded.spring.ordering.items.ItemEntity +import com.coded.spring.ordering.items.ItemsRepository import com.coded.spring.ordering.users.UserRepository import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping @@ -8,9 +9,10 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController //this tells Spring that this class is in charge of listening to the internet and sending back answers -class Controller( //this is saying whenever Spring creates this controller, please give it these two objects to use: +class Controller( //this is saying whenever Spring creates this controller, please give it these objects to use: val ordersRepository: OrdersRepository, val userRepository: UserRepository, + val itemsRepository: ItemsRepository ) { // @GetMapping("/order") @@ -22,30 +24,32 @@ class Controller( //this is saying whenever Spring creates this controller, plea // @GetMapping("/orders") // fun listOrders(): List = ordersRepository.findAll() -// @PostMapping("/order") -// fun createOrder( @RequestBody request: EnterOrderRequest) = ordersRepository.save( -// com.coded.spring.ordering.orders.OrderEntity( -// user = request.user_id, -// restaurant = request.restaurant, items = null) -// ) -// ) - @PostMapping("/order") - fun createOrder(@RequestBody request: EnterOrderRequest) { - val user = userRepository.findById(request.user_id).orElseThrow(); + fun createOrder(@RequestBody request: OrderRequest) { //this tells Spring: Expect the data to come in the body of the request, and convert it into this Kotlin object + val user = userRepository.findById(request.user_id).orElseThrow(); //this means: Go to the users table and try to find the user with the given user_id var newOrder = OrderEntity( user = user, - restaurant = request.restaurant, - items = request.items.map { ItemEntity(name = it.name, quantity = it.quantity) + restaurant = request.restaurant + ).apply { + items = request.items.map { + ItemEntity(name = it.name, quantity = it.quantity, order = this) } - ) - ordersRepository.save(newOrder) + } + //or use this direct assignment: + //items = listOf() + //) + //val items = request.items.map { + // ItemEntity(name = it.name, quantity = it.quantity, order = newOrder) + // } + // + // newOrder.items = items + ordersRepository.save(newOrder) //this tells spring to save this order in the DB, and because of cascade feature, also save all the items with it } } -data class EnterOrderRequest( +data class OrderRequest( val user_id: Long, val restaurant: String, val items: List @@ -54,4 +58,4 @@ data class EnterOrderRequest( data class ItemRequest( val name: String, val quantity: Int -) +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt index 6d24e90..4e4288b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt @@ -20,16 +20,17 @@ data class OrderEntity( var id: Long? = null, @ManyToOne - @JoinColumn(name= "user_id") //Creates a column in the orders table called user_id. No need to declare a user_id cause Spring handles it automatically when i define the relationship + @JoinColumn(name= "user_id") //Creates the foreign key, a column in the orders table called user_id. (No need to declare a user_id cause Spring handles it automatically when i define the relationship) val user: UserEntity, //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]) //cascade means: “If I save an order, also save their items. If I delete an order, delete their items too.”. without it I’d have to manually save the order then save its items one by one - val items: List = listOf() + 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, UserEntity(), "", listOf()) //Spring needs this to be able to create a blank User object behind the scenes. } diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt index da42e8c..40bcccd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt @@ -19,7 +19,7 @@ data class UserEntity( @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL]) val orders: List = listOf() //This lets the user "know about" their orders, but the actual foreign key exists in the orders table, not the users table. (only the child "the Many" holds the foreign key) - +// this is saying: If I get a user from the database, and I want to see their orders, look in the orders table and find all the rows where user_id = this user’s id ){ constructor() : this(null, "", listOf()) } From e9b52df0ca54bb3338d702c2cc493dee8be1e43b Mon Sep 17 00:00:00 2001 From: asomazz Date: Sun, 20 Apr 2025 20:01:34 +0300 Subject: [PATCH 5/8] user authentication task - asma --- pom.xml | 21 +++++++ .../coded/spring/ordering/InitUserRunner.kt | 34 +++++++++++ .../CustomUserDetailsService.kt | 23 ++++++++ .../ordering/authentication/SecurityConfig.kt | 57 +++++++++++++++++++ .../jwt/AuthenticationController.kt | 39 +++++++++++++ .../jwt/JwtAuthenticationFilter.kt | 45 +++++++++++++++ .../ordering/authentication/jwt/JwtService.kt | 42 ++++++++++++++ .../ordering/orders/OrdersController.kt | 10 ++-- .../ordering/orders/OrdersRepository.kt | 1 - .../spring/ordering/users/UsersController.kt | 45 +++++++++++++++ .../spring/ordering/users/UsersRepository.kt | 21 +++++-- src/main/resources/application.properties | 1 + 12 files changed, 328 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtAuthenticationFilter.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/jwt/JwtService.kt diff --git a/pom.xml b/pom.xml index 9d6e046..b159d6d 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,27 @@ 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-data-jpa 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..e5619df --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt @@ -0,0 +1,34 @@ +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.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.security.crypto.password.PasswordEncoder + +@SpringBootApplication +class InitUserRunner { + @Bean + fun initUsers(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..871ebbf --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt @@ -0,0 +1,57 @@ +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() + it.requestMatchers("/auth/**").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/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt new file mode 100644 index 0000000..62a933f --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt @@ -0,0 +1,39 @@ +package com.coded.spring.com.coded.spring.ordering.authentication.jwt + +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("/auth") +class AuthenticationController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService +) { + + @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!") + } + } +} + +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/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/orders/OrdersController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt index a529a2b..70ea061 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt @@ -15,12 +15,10 @@ class Controller( //this is saying whenever Spring creates this controller, plea val itemsRepository: ItemsRepository ) { -// @GetMapping("/order") -// fun homePage() = "Welcome to Ooreedoo, please place your order!" -// -// @GetMapping("/orders/v1/list") -// fun orders() = ordersService.listOrders() -// + @GetMapping("/order") + fun homePage() = "Welcome to Ooreedoo, please place your order!" + + // @GetMapping("/orders") // fun listOrders(): List = ordersRepository.findAll() diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt index 4e4288b..8ecc40a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt @@ -9,7 +9,6 @@ import org.springframework.stereotype.Repository @Repository //This annotates that this file is gonna be the repository. I dont need to write it here since am using JpaRepository, I will need to write it if am creating my own custom repository class (not just an interface) interface OrdersRepository : JpaRepository{ //This line creates the repository, and Jpa lets the repository talk to the DB and gives me all the ready functions to use (.save , .findall) // means: This repository is for the orders table, and the ID type (the @Id column in the table) is a Long number -// fun findByUser_Id(user_id: Long): List } @Entity //this indicates that this class should become a table diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt index 60d068d..9e89f8b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt @@ -1,2 +1,47 @@ package com.coded.spring.ordering.users +import com.coded.spring.com.coded.spring.ordering.authentication.jwt.JwtService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class UsersController( + private val usersRepository: UserRepository, + private val jwtService: JwtService, +) { + @PostMapping("/user") + fun register(@RequestBody request: UserRequest) { + val newUser = UserEntity( + username = request.username, + password = request.password + ) + usersRepository.save(newUser) + + } + + @PostMapping("/auth/login1") + fun login(@RequestBody request: LoginRequest): ResponseEntity { + val user = usersRepository.findByUsername(request.username) + ?: return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found") + + if (user.password != request.password) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid password") + } + val token = jwtService.generateToken(username = request.username) + return ResponseEntity.ok("Login successful for user: ${user.username}. token=${token}") + } + +} + +data class UserRequest( + val username: String, + val password: String, +) + +data class LoginRequest( + val username: String, + val password: String +) diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt index 40bcccd..efb080c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt @@ -6,20 +6,33 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface UserRepository : JpaRepository +interface UserRepository : JpaRepository { + fun findByUsername(username: String): UserEntity? +} @Entity @Table(name = "users") data class UserEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, - val name: String, + val id: Long? = null, + + val name: String = "", + + var username: String, + var password: String, + + @Enumerated(EnumType.STRING) + val role: Roles = Roles.USER, @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL]) val orders: List = listOf() //This lets the user "know about" their orders, but the actual foreign key exists in the orders table, not the users table. (only the child "the Many" holds the foreign key) // this is saying: If I get a user from the database, and I want to see their orders, look in the orders table and find all the rows where user_id = this user’s id ){ - constructor() : this(null, "", listOf()) + constructor() : this(null, "", "", "",Roles.USER, listOf()) } + +enum class Roles { + USER, ADMIN +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0ef0063..d43cb53 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,7 @@ spring.application.name=Kotlin.SpringbootV2 +server.port=8082 spring.datasource.url=jdbc:postgresql://localhost:5433/orders spring.datasource.username=postgres From aa4adc76641a141715585c44b2fe8ac906ad03bb Mon Sep 17 00:00:00 2001 From: asomazz Date: Sat, 3 May 2025 23:54:04 +0300 Subject: [PATCH 6/8] Finally able to clean code everything, done all tasks up to authentication --- pom.xml | 23 ++++--- .../coded/spring/ordering/InitUserRunner.kt | 6 +- .../ordering/authentication/SecurityConfig.kt | 4 +- .../jwt/AuthenticationController.kt | 39 ----------- .../{ItemsController.kt => ItemController.kt} | 0 .../{ItemsRepository.kt => ItemEntity.kt} | 6 +- .../spring/ordering/items/ItemRepository.kt | 6 ++ .../spring/ordering/items/ItemRequest.kt | 2 + .../spring/ordering/items/ItemService.kt | 2 + .../spring/ordering/orders/OrderController.kt | 22 +++++++ .../spring/ordering/orders/OrderEntity.kt | 25 ++++++++ .../spring/ordering/orders/OrderRepository.kt | 11 ++++ .../spring/ordering/orders/OrderRequest.kt | 12 ++++ .../spring/ordering/orders/OrderService.kt | 31 +++++++++ .../ordering/orders/OrdersController.kt | 59 ----------------- .../ordering/orders/OrdersRepository.kt | 36 ----------- .../spring/ordering/orders/OrdersService.kt | 30 --------- .../ordering/profiles/ProfileController.kt | 19 ++++++ .../spring/ordering/profiles/ProfileEntity.kt | 30 +++++++++ .../ordering/profiles/ProfileRepository.kt | 10 +++ .../ordering/profiles/ProfileRequest.kt | 7 ++ .../ordering/profiles/ProfileService.kt | 33 ++++++++++ .../spring/ordering/users/UserController.kt | 38 +++++++++++ .../coded/spring/ordering/users/UserEntity.kt | 32 ++++++++++ .../spring/ordering/users/UserRepository.kt | 9 +++ .../spring/ordering/users/UserRequest.kt | 10 +++ .../spring/ordering/users/UserService.kt | 64 +++++++++++++++++++ .../spring/ordering/users/UsersController.kt | 47 -------------- .../spring/ordering/users/UsersRepository.kt | 38 ----------- .../coded/spring/ordering/ApplicationTests.kt | 19 ++++-- 30 files changed, 402 insertions(+), 268 deletions(-) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt rename src/main/kotlin/com/coded/spring/ordering/items/{ItemsController.kt => ItemController.kt} (100%) rename src/main/kotlin/com/coded/spring/ordering/items/{ItemsRepository.kt => ItemEntity.kt} (62%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/items/ItemRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/items/ItemRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/items/ItemService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserService.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt diff --git a/pom.xml b/pom.xml index b159d6d..4e9d4de 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,16 @@ 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 @@ -76,18 +86,15 @@ org.postgresql postgresql - runtime - - - org.springframework.boot - spring-boot-starter-test - test + compile - org.jetbrains.kotlin - kotlin-test-junit5 + com.h2database + h2 test + + ${project.basedir}/src/main/kotlin diff --git a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt index e5619df..465b654 100644 --- a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt +++ b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt @@ -3,14 +3,15 @@ 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.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.annotation.Bean import org.springframework.security.crypto.password.PasswordEncoder -@SpringBootApplication +// @SpringBootApplication class InitUserRunner { @Bean fun initUsers(userRepository: UserRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { @@ -19,6 +20,7 @@ class InitUserRunner { username = "testuser", password = passwordEncoder.encode("password123"), role = Roles.USER + ) if (userRepository.findByUsername(user.username) == null) { println("Creating user ${user.username}") diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt index 871ebbf..f22038a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt @@ -27,8 +27,10 @@ class SecurityConfig( http.csrf { it.disable() } .authorizeHttpRequests { it.requestMatchers("/user").permitAll() - it.requestMatchers("/auth/**").permitAll() + .requestMatchers("/auth/**").permitAll() +// .requestMatchers("/profile").authenticated() .anyRequest().authenticated() + } .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt deleted file mode 100644 index 62a933f..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/authentication/jwt/AuthenticationController.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.coded.spring.com.coded.spring.ordering.authentication.jwt - -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("/auth") -class AuthenticationController( - private val authenticationManager: AuthenticationManager, - private val userDetailsService: UserDetailsService, - private val jwtService: JwtService -) { - - @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!") - } - } -} - -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/items/ItemsController.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemController.kt similarity index 100% rename from src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt rename to src/main/kotlin/com/coded/spring/ordering/items/ItemController.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt b/src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt similarity index 62% rename from src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt rename to src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt index 9220943..6a0d86b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/items/ItemsRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/items/ItemEntity.kt @@ -1,10 +1,8 @@ -package com.coded.spring.ordering.items +package com.coded.spring.com.coded.spring.ordering.items import com.coded.spring.ordering.orders.OrderEntity import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository -interface ItemsRepository: JpaRepository @Entity @Table(name="items") @@ -22,5 +20,5 @@ data class ItemEntity( val order: OrderEntity ){ - constructor() : this(null, "", 0,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/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/orders/OrdersController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt deleted file mode 100644 index 70ea061..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersController.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.coded.spring.ordering.orders - -import com.coded.spring.ordering.items.ItemEntity -import com.coded.spring.ordering.items.ItemsRepository -import com.coded.spring.ordering.users.UserRepository -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 //this tells Spring that this class is in charge of listening to the internet and sending back answers -class Controller( //this is saying whenever Spring creates this controller, please give it these objects to use: - val ordersRepository: OrdersRepository, - val userRepository: UserRepository, - val itemsRepository: ItemsRepository -) { - - @GetMapping("/order") - fun homePage() = "Welcome to Ooreedoo, please place your order!" - - -// @GetMapping("/orders") -// fun listOrders(): List = ordersRepository.findAll() - - @PostMapping("/order") - fun createOrder(@RequestBody request: OrderRequest) { //this tells Spring: Expect the data to come in the body of the request, and convert it into this Kotlin object - val user = userRepository.findById(request.user_id).orElseThrow(); //this means: Go to the users table and try to find the user with the given user_id - var newOrder = OrderEntity( - user = user, - restaurant = request.restaurant - ).apply { - items = request.items.map { - ItemEntity(name = it.name, quantity = it.quantity, order = this) - } - } - //or use this direct assignment: - //items = listOf() - //) - //val items = request.items.map { - // ItemEntity(name = it.name, quantity = it.quantity, order = newOrder) - // } - // - // newOrder.items = items - ordersRepository.save(newOrder) //this tells spring to save this order in the DB, and because of cascade feature, also save all the items with it - - } - -} - -data class OrderRequest( - val user_id: Long, - val restaurant: String, - val items: List -) - -data class ItemRequest( - val name: String, - val quantity: Int -) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt deleted file mode 100644 index 8ecc40a..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersRepository.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.coded.spring.ordering.orders - -import com.coded.spring.ordering.items.ItemEntity -import com.coded.spring.ordering.users.UserEntity -import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - -@Repository //This annotates that this file is gonna be the repository. I dont need to write it here since am using JpaRepository, I will need to write it if am creating my own custom repository class (not just an interface) -interface OrdersRepository : JpaRepository{ //This line creates the repository, and Jpa lets the repository talk to the DB and gives me all the ready functions to use (.save , .findall) - // means: This repository is for the orders table, and the ID type (the @Id column in the table) is a Long number -} - -@Entity //this indicates that this class should become a table -@Table(name = "orders") -data class OrderEntity( - @Id //This is the unique identifier "Primary key" - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, - - @ManyToOne - @JoinColumn(name= "user_id") //Creates the foreign key, a column in the orders table called user_id. (No need to declare a user_id cause Spring handles it automatically when i define the relationship) - val user: UserEntity, - //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]) //cascade means: “If I save an order, also save their items. If I delete an order, delete their items too.”. without it I’d have to manually save the order then save its items one by one - 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, UserEntity(), "", listOf()) //Spring needs this to be able to create a blank User object behind the scenes. -} - diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt deleted file mode 100644 index 7fc4e39..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrdersService.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.coded.spring.ordering.orders - - -import org.springframework.stereotype.Service - -//@Service -//class OrdersService( -// private val ordersRepository: OrdersRepository, -//) { -// -// fun listOrders(): List = ordersRepository.findAll().map { -// OrderEntity( -// user_id = it.user_id, -// restaurant = it.restaurant, -// items = it.items -// ) -// } -// -// fun createOrder(userId: Long){ -// val user = usersRepository.findById(user_id).get() -// val newOrder = OrderEntity(user=name) -// orderRepository.save(newOrder) -// -//} -// -//data class Order( -// val username: String, -// val restaurant: Int, -//) - 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/kotlin/com/coded/spring/ordering/users/UsersController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt deleted file mode 100644 index 9e89f8b..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersController.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.coded.spring.ordering.users - -import com.coded.spring.com.coded.spring.ordering.authentication.jwt.JwtService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RestController - -@RestController -class UsersController( - private val usersRepository: UserRepository, - private val jwtService: JwtService, -) { - @PostMapping("/user") - fun register(@RequestBody request: UserRequest) { - val newUser = UserEntity( - username = request.username, - password = request.password - ) - usersRepository.save(newUser) - - } - - @PostMapping("/auth/login1") - fun login(@RequestBody request: LoginRequest): ResponseEntity { - val user = usersRepository.findByUsername(request.username) - ?: return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found") - - if (user.password != request.password) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid password") - } - val token = jwtService.generateToken(username = request.username) - return ResponseEntity.ok("Login successful for user: ${user.username}. token=${token}") - } - -} - -data class UserRequest( - val username: String, - val password: String, -) - -data class LoginRequest( - val username: String, - val password: String -) diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt deleted file mode 100644 index efb080c..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/users/UsersRepository.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.coded.spring.ordering.users - -import com.coded.spring.ordering.orders.OrderEntity -import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - -@Repository -interface UserRepository : JpaRepository { - fun findByUsername(username: String): UserEntity? -} - -@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, - - @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL]) - val orders: List = listOf() -//This lets the user "know about" their orders, but the actual foreign key exists in the orders table, not the users table. (only the child "the Many" holds the foreign key) -// this is saying: If I get a user from the database, and I want to see their orders, look in the orders table and find all the rows where user_id = this user’s id -){ - constructor() : this(null, "", "", "",Roles.USER, listOf()) -} - -enum class Roles { - USER, ADMIN -} \ 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) +// } + } From a5933d434829f2ef8cb416f56624b5b5511a50c2 Mon Sep 17 00:00:00 2001 From: asomazz Date: Sun, 4 May 2025 11:10:35 +0300 Subject: [PATCH 7/8] Just started Cacheing --- pom.xml | 5 +++++ src/main/kotlin/Application.kt | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 4e9d4de..04235ee 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,11 @@ org.jetbrains.kotlin kotlin-reflect + + com.hazelcast + hazelcast + 5.5.0 + org.jetbrains.kotlin kotlin-stdlib diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt index 3fe2c8e..f347e0d 100644 --- a/src/main/kotlin/Application.kt +++ b/src/main/kotlin/Application.kt @@ -1,5 +1,9 @@ package com.coded.spring + +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast +import com.hazelcast.core.HazelcastInstance import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @@ -8,4 +12,8 @@ class Application fun main(args: Array) { runApplication(*args) + helloWorldConfig.getMapConfig("pets").setTimeToLiveSeconds(5) } + +val menuConfig = StatusLogger.Config("menu-cache") +val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(menuConfig) \ No newline at end of file From 7a17144dde7945a84cb8306ca7229893ee54e5c1 Mon Sep 17 00:00:00 2001 From: asomazz Date: Sun, 4 May 2025 14:49:37 +0300 Subject: [PATCH 8/8] Finished Cacheing - Menu Endpoint --- src/main/kotlin/Application.kt | 8 +----- .../spring/ordering/config/HazelcastConfig.kt | 19 ++++++++++++++ .../spring/ordering/menu/MenuController.kt | 22 ++++++++++++++++ .../coded/spring/ordering/menu/MenuEntity.kt | 17 +++++++++++++ .../spring/ordering/menu/MenuProvider.kt | 25 +++++++++++++++++++ .../spring/ordering/menu/MenuRepository.kt | 9 +++++++ .../coded/spring/ordering/menu/MenuRequest.kt | 2 ++ .../coded/spring/ordering/menu/MenuService.kt | 20 +++++++++++++++ 8 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/config/HazelcastConfig.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuProvider.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/menu/MenuService.kt diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt index f347e0d..f5efc56 100644 --- a/src/main/kotlin/Application.kt +++ b/src/main/kotlin/Application.kt @@ -1,9 +1,6 @@ package com.coded.spring -import com.hazelcast.config.Config; -import com.hazelcast.core.Hazelcast -import com.hazelcast.core.HazelcastInstance import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @@ -12,8 +9,5 @@ class Application fun main(args: Array) { runApplication(*args) - helloWorldConfig.getMapConfig("pets").setTimeToLiveSeconds(5) -} -val menuConfig = StatusLogger.Config("menu-cache") -val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(menuConfig) \ 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/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