From 4946c4487e2d26c31639e168ce498d69504a1c84 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Tue, 8 Apr 2025 11:31:53 +0300 Subject: [PATCH 01/26] Exercise 1 & 2 --- pom.xml | 13 +++++++ .../coded/spring/ordering/FoodieController.kt | 37 +++++++++++++++++++ .../coded/spring/ordering/OrdersRepository.kt | 32 ++++++++++++++++ src/main/resources/application.properties | 9 +++++ 4 files changed, 91 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/FoodieController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt diff --git a/pom.xml b/pom.xml index 163ad53..9d2dd5b 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,19 @@ kotlin-test-junit5 test + + jakarta.inject + jakarta.inject-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + runtime + diff --git a/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt b/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt new file mode 100644 index 0000000..e7c6453 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt @@ -0,0 +1,37 @@ +package com.coded.spring.ordering + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class FoodieController(val ordersRepository: OrderRepository) { + + // Exercise 1 + @GetMapping("/welcome") + fun chooseDeliveryOrPickUp() = "Delivery or Pick-Up?" + + + // Exercise 2 + @PostMapping("/order") + fun makeAnOrder(@RequestBody request: OrderRequest): Order { + return ordersRepository.save( + Order( + user = request.user, + restaurant = request.restaurant, + items = request.items + ) + ) + } + + @GetMapping("/order") + fun getAllOrders() = ordersRepository.findAll() +} + data class OrderRequest( + val user: String, + val restaurant: String, + val items: List + ) 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..0fbcb83 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt @@ -0,0 +1,32 @@ +package com.coded.spring.ordering + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderRepository : JpaRepository + +@Entity +@Table(name = "orders") +data class Order( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "orderid") + val orderID: Long? = null, + + @Column(name = "user") + val user: String, + + @Column(name = "restaurant") + val restaurant: String, + + @ElementCollection // Since the specified schema requires a list of items I had to make a seperate table +// linked by id to the main table + @CollectionTable(name = "order_items", joinColumns = [JoinColumn(name = "orderID")]) + @Column(name ="item") + val items: List +){ + constructor() : this(null,"","", emptyList()) +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..dd75031 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,10 @@ spring.application.name=Kotlin.SpringbootV2 +server.port = 4444 + +# Force consistent naming strategy +spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl +spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl + +# Show SQL for debugging +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file From 540996dddf13c48f25fc70af376ed859c4b632d9 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Tue, 8 Apr 2025 11:39:58 +0300 Subject: [PATCH 02/26] Exercise 1 & 2 --- src/main/resources/application.properties | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index dd75031..dcfce35 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,10 +1,2 @@ spring.application.name=Kotlin.SpringbootV2 server.port = 4444 - -# Force consistent naming strategy -spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl -spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl - -# Show SQL for debugging -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file From 74a44677d69811423a76be2a3ad7515a175d0c27 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Tue, 8 Apr 2025 17:10:02 +0300 Subject: [PATCH 03/26] Exercise 1 & 2 --- .../com/coded/spring/ordering/Application.kt | 1 + .../coded/spring/ordering/FoodieController.kt | 26 ++++++++----------- .../coded/spring/ordering/OrdersRepository.kt | 24 +++++++---------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/ordering/Application.kt index 8554e49..871c1df 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/ordering/Application.kt @@ -9,3 +9,4 @@ class Application fun main(args: Array) { runApplication(*args) } + diff --git a/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt b/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt index e7c6453..b0f0627 100644 --- a/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt @@ -1,7 +1,5 @@ package com.coded.spring.ordering -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -14,24 +12,22 @@ class FoodieController(val ordersRepository: OrderRepository) { @GetMapping("/welcome") fun chooseDeliveryOrPickUp() = "Delivery or Pick-Up?" - // Exercise 2 @PostMapping("/order") fun makeAnOrder(@RequestBody request: OrderRequest): Order { - return ordersRepository.save( - Order( - user = request.user, - restaurant = request.restaurant, - items = request.items - ) + val newOrder = Order( + user = request.user, + restaurant = request.restaurant, + items = request.items ) + return ordersRepository.save(newOrder) } - @GetMapping("/order") + @GetMapping("/order") fun getAllOrders() = ordersRepository.findAll() } - data class OrderRequest( - val user: String, - val restaurant: String, - val items: List - ) +data class OrderRequest( + val user: String, + val restaurant: String, + val items: MutableList +) diff --git a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt index 0fbcb83..5942a4f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt @@ -5,7 +5,7 @@ import jakarta.persistence.* import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -@Repository +@Named interface OrderRepository : JpaRepository @Entity @@ -13,20 +13,16 @@ interface OrderRepository : JpaRepository data class Order( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "orderid") - val orderID: Long? = null, + var orderID: Long? = null, + // Thanks to Mohammed Sheshter he figured out we need to use back ticks to resolve the 500 server error caused by JPA getting confused by "users" + @Column(name = "`user`") + var user: String = "", - @Column(name = "user") - val user: String, + var restaurant: String = "", - @Column(name = "restaurant") - val restaurant: String, - - @ElementCollection // Since the specified schema requires a list of items I had to make a seperate table -// linked by id to the main table - @CollectionTable(name = "order_items", joinColumns = [JoinColumn(name = "orderID")]) - @Column(name ="item") - val items: List + // Since the specified schema requires a list of items I had to make a seperate table + @CollectionTable(name = "order_items") + var items: MutableList = mutableListOf() ){ - constructor() : this(null,"","", emptyList()) + constructor() : this(null,"","", mutableListOf()) } \ No newline at end of file From f400a3e93f4357b556472d1c3f304e60a24af4e6 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Thu, 10 Apr 2025 20:32:04 +0300 Subject: [PATCH 04/26] Thursdays work --- pom.xml | 10 ++++-- .../spring/{ordering => }/Application.kt | 2 +- .../coded/spring/controller/UserController.kt | 20 +++++++++++ .../com/coded/spring/entity/ItemEntity.kt | 20 +++++++++++ .../com/coded/spring/entity/OrderEntity.kt | 21 ++++++++++++ .../com/coded/spring/entity/UserEntity.kt | 21 ++++++++++++ .../coded/spring/repository/ItemRepository.kt | 8 +++++ .../spring/repository/OrderRepository.kt | 9 +++++ .../coded/spring/repository/UserRepository.kt | 9 +++++ .../com/coded/spring/service/OrderService.kt | 33 +++++++++++++++++++ .../com/coded/spring/service/UserService.kt | 14 ++++++++ .../ordering/OrdersController.kt} | 2 +- .../ordering/OrdersRepository.kt | 7 ++-- .../com/legacy/ordering/UsersController.kt | 14 ++++++++ .../com/legacy/ordering/UsersRepository.kt | 25 ++++++++++++++ src/main/resources/application.properties | 6 ++++ 16 files changed, 214 insertions(+), 7 deletions(-) rename src/main/kotlin/com/coded/spring/{ordering => }/Application.kt (87%) create mode 100644 src/main/kotlin/com/coded/spring/controller/UserController.kt create mode 100644 src/main/kotlin/com/coded/spring/entity/ItemEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/entity/OrderEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/entity/UserEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/repository/ItemRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/repository/OrderRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/repository/UserRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/service/OrderService.kt create mode 100644 src/main/kotlin/com/coded/spring/service/UserService.kt rename src/main/kotlin/com/{coded/spring/ordering/FoodieController.kt => legacy/ordering/OrdersController.kt} (93%) rename src/main/kotlin/com/{coded/spring => legacy}/ordering/OrdersRepository.kt (85%) create mode 100644 src/main/kotlin/com/legacy/ordering/UsersController.kt create mode 100644 src/main/kotlin/com/legacy/ordering/UsersRepository.kt diff --git a/pom.xml b/pom.xml index 9d2dd5b..04d92f8 100644 --- a/pom.xml +++ b/pom.xml @@ -66,10 +66,14 @@ org.springframework.boot spring-boot-starter-data-jpa + + + + + - com.h2database - h2 - runtime + org.postgresql + postgresql diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/Application.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/ordering/Application.kt rename to src/main/kotlin/com/coded/spring/Application.kt index 871c1df..946ceab 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/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/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt new file mode 100644 index 0000000..182c0d0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -0,0 +1,20 @@ +package com.coded.spring.controller + +import com.coded.spring.entity.UserEntity +import com.coded.spring.service.UserService +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class UserController (private val userService: UserService){ + + @PostMapping("/users/create") + fun newUser(user: UserEntity): UserEntity { + return userService.createUser(user)} + + @GetMapping("/users/v1/list") + fun listUsers() = userService.findAllUsers() +} + + diff --git a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt new file mode 100644 index 0000000..039b559 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt @@ -0,0 +1,20 @@ +package com.coded.spring.entity + +import jakarta.persistence.* +import java.math.BigDecimal + +@Entity +@Table(name = "items") +class ItemEntity ( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + var name: String, + + var quantity: Int, + + var price : BigDecimal) + + + diff --git a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt new file mode 100644 index 0000000..7868994 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt @@ -0,0 +1,21 @@ +package com.coded.spring.entity + +import jakarta.persistence.* + +@Entity +@Table(name = "orders") +data class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + @ManyToOne // this is for fk + val user: UserEntity, + + @Column(name = "restaurant") + var restaurant: String, + + @OneToMany(mappedBy = "id") + val items: MutableList + +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt new file mode 100644 index 0000000..cecc316 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt @@ -0,0 +1,21 @@ +package com.coded.spring.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.OneToMany +import jakarta.persistence.Table + +@Entity +@Table(name = "users") +data class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + @Column(name = "name") + var name: String, + @Column(name = "email") + var email: String, +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt b/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt new file mode 100644 index 0000000..34123ac --- /dev/null +++ b/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt @@ -0,0 +1,8 @@ +package com.coded.spring.repository + +import com.coded.spring.entity.ItemEntity +import jakarta.inject.Named +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface ItemRepository : JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt new file mode 100644 index 0000000..0c21fed --- /dev/null +++ b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt @@ -0,0 +1,9 @@ +package com.coded.spring.repository + +import com.coded.spring.entity.OrderEntity +import jakarta.inject.Named +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface OrderRepository : JpaRepository{ +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt new file mode 100644 index 0000000..476d901 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt @@ -0,0 +1,9 @@ +package com.coded.spring.repository + + +import com.coded.spring.entity.UserEntity +import jakarta.inject.Named +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface UserRepository : JpaRepository diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt new file mode 100644 index 0000000..51f30fd --- /dev/null +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -0,0 +1,33 @@ +package com.coded.spring.service + +import com.coded.spring.entity.ItemEntity +import com.coded.spring.entity.OrderEntity +import com.coded.spring.repository.ItemRepository +import com.coded.spring.repository.OrderRepository +import com.coded.spring.repository.UserRepository +import org.springframework.stereotype.Service + +@Service +class OrderService ( + private var orderRepository: OrderRepository, + private var userRepository: UserRepository, + private var itemRepository: ItemRepository +){ + + fun createOrder(userId: Long, restaurant: String, items: MutableList) { + val user = userRepository.findById(userId).get() + val newOrder = OrderEntity( + user = user, restaurant = restaurant, + id = user.id, + items = items + ) + orderRepository.save(newOrder) + itemRepository.saveAll(items) + } + + fun listOrders(){ + + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/src/main/kotlin/com/coded/spring/service/UserService.kt new file mode 100644 index 0000000..7a00b7a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/service/UserService.kt @@ -0,0 +1,14 @@ +package com.coded.spring.service + +import com.coded.spring.entity.UserEntity +import com.coded.spring.repository.UserRepository +import org.springframework.stereotype.Service + +@Service +class UserService (private val userRepository: UserRepository){ + + fun findAllUsers() = userRepository.findAll() + fun createUser(user: UserEntity) = userRepository.save(user) + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt b/src/main/kotlin/com/legacy/ordering/OrdersController.kt similarity index 93% rename from src/main/kotlin/com/coded/spring/ordering/FoodieController.kt rename to src/main/kotlin/com/legacy/ordering/OrdersController.kt index b0f0627..54761f8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/FoodieController.kt +++ b/src/main/kotlin/com/legacy/ordering/OrdersController.kt @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController -class FoodieController(val ordersRepository: OrderRepository) { +class OrdersController(val ordersRepository: OrderRepository) { // Exercise 1 @GetMapping("/welcome") diff --git a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt b/src/main/kotlin/com/legacy/ordering/OrdersRepository.kt similarity index 85% rename from src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt rename to src/main/kotlin/com/legacy/ordering/OrdersRepository.kt index 5942a4f..6db68ee 100644 --- a/src/main/kotlin/com/coded/spring/ordering/OrdersRepository.kt +++ b/src/main/kotlin/com/legacy/ordering/OrdersRepository.kt @@ -13,15 +13,18 @@ interface OrderRepository : JpaRepository data class Order( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") var orderID: Long? = null, // Thanks to Mohammed Sheshter he figured out we need to use back ticks to resolve the 500 server error caused by JPA getting confused by "users" - @Column(name = "`user`") +// @Column(name = "`user`") + @Column(name = "name") var user: String = "", var restaurant: String = "", // Since the specified schema requires a list of items I had to make a seperate table - @CollectionTable(name = "order_items") + @CollectionTable(name = "items") + @JoinColumn() var items: MutableList = mutableListOf() ){ constructor() : this(null,"","", mutableListOf()) diff --git a/src/main/kotlin/com/legacy/ordering/UsersController.kt b/src/main/kotlin/com/legacy/ordering/UsersController.kt new file mode 100644 index 0000000..7840ff3 --- /dev/null +++ b/src/main/kotlin/com/legacy/ordering/UsersController.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering + + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class UsersController( + private val usersRepository: UsersRepository +){ + + @GetMapping("/users/v1/list") + fun users() = usersRepository.findAll() +} \ No newline at end of file diff --git a/src/main/kotlin/com/legacy/ordering/UsersRepository.kt b/src/main/kotlin/com/legacy/ordering/UsersRepository.kt new file mode 100644 index 0000000..16be2ee --- /dev/null +++ b/src/main/kotlin/com/legacy/ordering/UsersRepository.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface UsersRepository : JpaRepository + +@Entity +@Table(name = "users") +data class User( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") + var orderID: Long? = null, + + @Column(name = "name") + var user: String = "", + @Column(name = "age") + var age: Int? = null + +){ + constructor() : this(null, "", null) +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index dcfce35..8dada97 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,8 @@ spring.application.name=Kotlin.SpringbootV2 server.port = 4444 +spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB +spring.datasource.username=postgres +spring.datasource.password=alix +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file From 540ea0807186d3a760804f405a8567af1850ae8d Mon Sep 17 00:00:00 2001 From: ahjadi Date: Fri, 11 Apr 2025 13:07:57 +0300 Subject: [PATCH 05/26] Added SQL code used to create TABLES --- OrderingDB.sql | 22 +++++++++++++++ .../spring/controller/OrderController.kt | 26 ++++++++++++++++++ .../coded/spring/controller/UserController.kt | 3 ++- .../com/coded/spring/entity/ItemEntity.kt | 6 ++++- .../com/coded/spring/entity/OrderEntity.kt | 5 +++- .../com/coded/spring/entity/UserEntity.kt | 6 +++-- .../coded/spring/repository/ItemRepository.kt | 8 +++--- .../spring/repository/OrderRepository.kt | 6 ++++- .../coded/spring/repository/UserRepository.kt | 4 +-- .../com/coded/spring/service/OrderService.kt | 27 ++++++++++++------- .../com/coded/spring/service/UserService.kt | 1 + 11 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 OrderingDB.sql create mode 100644 src/main/kotlin/com/coded/spring/controller/OrderController.kt diff --git a/OrderingDB.sql b/OrderingDB.sql new file mode 100644 index 0000000..3d78ceb --- /dev/null +++ b/OrderingDB.sql @@ -0,0 +1,22 @@ +-- SQL code used to create the local postgres@17 DB +create table users ( + id serial primary key, + name varchar(255) not null, + email varchar(255) not null unique +) + +create table orders ( + id serial primary key, + user_id bigint not null, + restaurant varchar(255) not null, + foreign key (user_id) references users(id) on delete cascade +) + +create table items ( +id serial primary key, +order_id bigint not null, +name varchar(255) not null, +quantity int check(quantity >= 0) not null, +price decimal(10,3) check(price >= 0) not null, +foreign key (order_id) references orders(id) on delete cascade +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt new file mode 100644 index 0000000..447f705 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -0,0 +1,26 @@ +package com.coded.spring.controller + +import com.coded.spring.entity.OrderEntity +import com.coded.spring.service.OrderService +import org.springframework.web.bind.annotation.* + +@RestController +class OrderController(val orderService: OrderService){ + + @PostMapping("/orders/create") + fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) { + orderService.createOrder(orderRequest) + } + + @GetMapping("/orders/user/orders/{userId}") + fun listOrdersByUserId(@PathVariable userId: Long): List { + return orderService.listOrdersByUserID(userId)} + + } + + + + + + + diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 182c0d0..2ffbadf 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -4,13 +4,14 @@ import com.coded.spring.entity.UserEntity import com.coded.spring.service.UserService 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 UserController (private val userService: UserService){ @PostMapping("/users/create") - fun newUser(user: UserEntity): UserEntity { + fun newUser(@RequestBody user: UserEntity): UserEntity { return userService.createUser(user)} @GetMapping("/users/v1/list") diff --git a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt index 039b559..1636239 100644 --- a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt @@ -14,7 +14,11 @@ class ItemEntity ( var quantity: Int, - var price : BigDecimal) + var price : BigDecimal +) +{ + constructor(): this(null, "", 0, BigDecimal.ZERO ) +} diff --git a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt index 7868994..7ca587b 100644 --- a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt @@ -17,5 +17,8 @@ data class OrderEntity( @OneToMany(mappedBy = "id") val items: MutableList +) -) \ No newline at end of file +{ + constructor() : this(null, UserEntity(), "", mutableListOf()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt index cecc316..a4ef34c 100644 --- a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt @@ -5,7 +5,6 @@ import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id -import jakarta.persistence.OneToMany import jakarta.persistence.Table @Entity @@ -18,4 +17,7 @@ data class UserEntity( var name: String, @Column(name = "email") var email: String, -) \ No newline at end of file +) +{ + constructor() : this(null,"","") +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt b/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt index 34123ac..327cb57 100644 --- a/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt @@ -1,8 +1,10 @@ package com.coded.spring.repository import com.coded.spring.entity.ItemEntity -import jakarta.inject.Named import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -@Named -interface ItemRepository : JpaRepository \ No newline at end of file +@Repository +interface ItemRepository : JpaRepository{ + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt index 0c21fed..add1ffc 100644 --- a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt @@ -1,9 +1,13 @@ package com.coded.spring.repository import com.coded.spring.entity.OrderEntity +import com.coded.spring.entity.UserEntity import jakarta.inject.Named import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -@Named +@Repository interface OrderRepository : JpaRepository{ + fun findAllByUser(user: UserEntity): List + fun findAllByUserId(userId: Long): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt index 476d901..7ce6c14 100644 --- a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt @@ -2,8 +2,8 @@ package com.coded.spring.repository import com.coded.spring.entity.UserEntity -import jakarta.inject.Named import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -@Named +@Repository interface UserRepository : JpaRepository diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 51f30fd..6b6c60b 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -2,10 +2,12 @@ package com.coded.spring.service import com.coded.spring.entity.ItemEntity import com.coded.spring.entity.OrderEntity +import com.coded.spring.entity.UserEntity import com.coded.spring.repository.ItemRepository import com.coded.spring.repository.OrderRepository import com.coded.spring.repository.UserRepository import org.springframework.stereotype.Service +import java.math.BigDecimal @Service class OrderService ( @@ -14,20 +16,27 @@ class OrderService ( private var itemRepository: ItemRepository ){ - fun createOrder(userId: Long, restaurant: String, items: MutableList) { - val user = userRepository.findById(userId).get() + fun createOrder(orderRequest: OrderRequestDTO) { + val user = userRepository.findById(orderRequest.userId).get() val newOrder = OrderEntity( - user = user, restaurant = restaurant, - id = user.id, - items = items + user = user, restaurant = orderRequest.restaurant, + items = orderRequest.items ) orderRepository.save(newOrder) - itemRepository.saveAll(items) + itemRepository.saveAll(orderRequest.items) } - fun listOrders(){ - - } + fun listOrdersByUserID(user_ID: Long) = orderRepository.findAllByUserId(user_ID) + data class ItemDTO( + val name: String, + val quantity: Int, + val price: BigDecimal + ) + data class OrderRequestDTO( + val userId: Long, + val restaurant: String, + val items: MutableList + ) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/src/main/kotlin/com/coded/spring/service/UserService.kt index 7a00b7a..10aac68 100644 --- a/src/main/kotlin/com/coded/spring/service/UserService.kt +++ b/src/main/kotlin/com/coded/spring/service/UserService.kt @@ -1,5 +1,6 @@ package com.coded.spring.service +//import com.coded.spring.entity.UserEntity import com.coded.spring.entity.UserEntity import com.coded.spring.repository.UserRepository import org.springframework.stereotype.Service From 9c5753fd7c44a765e26f7c42da14f54f0e3fd6a2 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Wed, 16 Apr 2025 20:47:06 +0300 Subject: [PATCH 06/26] added securit --- pom.xml | 4 +++ .../kotlin/com/coded/spring/InitUserRunner.kt | 34 +++++++++++++++++++ .../CustomUserDetailsService.kt | 21 ++++++++++++ .../spring/authentication/SecurityConfig.kt | 32 +++++++++++++++++ .../spring/controller/OrderController.kt | 2 +- .../coded/spring/controller/UserController.kt | 15 ++++++-- .../com/coded/spring/entity/UserEntity.kt | 7 +++- .../coded/spring/repository/UserRepository.kt | 4 ++- .../com/coded/spring/service/UserService.kt | 1 + 9 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/InitUserRunner.kt create mode 100644 src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt create mode 100644 src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt diff --git a/pom.xml b/pom.xml index 04d92f8..01404bb 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,10 @@ kotlin-test-junit5 test + + org.springframework.boot + spring-boot-starter-security + jakarta.inject jakarta.inject-api diff --git a/src/main/kotlin/com/coded/spring/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/InitUserRunner.kt new file mode 100644 index 0000000..0c245d4 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/InitUserRunner.kt @@ -0,0 +1,34 @@ +package com.coded.spring + + + +import com.coded.spring.entity.UserEntity +import com.coded.spring.repository.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"), + email = "test@coded.com", + ) + 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/authentication/CustomUserDetailsService.kt b/src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt new file mode 100644 index 0000000..b906427 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt @@ -0,0 +1,21 @@ +package com.coded.spring.authentication + + +import com.coded.spring.repository.UserRepository +import org.springframework.security.core.userdetails.* +import org.springframework.stereotype.Service + +@Service +class CustomUserDetailsService( + private val usersRepository: UserRepository +) : UserDetailsService { + override fun loadUserByUsername(username: String): UserDetails { + val user = usersRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User not found") + + return User.builder() + .username(user.username) + .password(user.password) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt new file mode 100644 index 0000000..d6e758b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -0,0 +1,32 @@ +package com.coded.spring.authentication + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.web.SecurityFilterChain + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val userDetailsService: UserDetailsService +) { + + @Bean + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.requestMatchers("/public/**").permitAll() + .anyRequest().authenticated() + } + .formLogin { it.defaultSuccessUrl("/hello", true) } + .userDetailsService(userDetailsService) + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index 447f705..ef890db 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.* @RestController class OrderController(val orderService: OrderService){ - @PostMapping("/orders/create") + @PostMapping("/orders/submit") fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) { orderService.createOrder(orderRequest) } diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 2ffbadf..14f408b 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -8,11 +8,22 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController -class UserController (private val userService: UserService){ +class UserController(private val userService: UserService) { + + @GetMapping("/hello") + fun hello()= """ + + +

hello yo

+ + + + """.trimIndent() @PostMapping("/users/create") fun newUser(@RequestBody user: UserEntity): UserEntity { - return userService.createUser(user)} + return userService.createUser(user) + } @GetMapping("/users/v1/list") fun listUsers() = userService.findAllUsers() diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt index a4ef34c..22d0814 100644 --- a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt @@ -17,7 +17,12 @@ data class UserEntity( var name: String, @Column(name = "email") var email: String, + + val username: String, + val password: String +//ALTER TABLE users ADD username varchar(255) unique +//ALTER TABLE users ADD password varchar(255) ) { - constructor() : this(null,"","") + constructor() : this(null,"","", "", "") } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt index 7ce6c14..4ceae82 100644 --- a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt @@ -6,4 +6,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface UserRepository : JpaRepository +interface UserRepository : JpaRepository { + fun findByUsername(username: String) : UserEntity? +} diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/src/main/kotlin/com/coded/spring/service/UserService.kt index 10aac68..0cc62d8 100644 --- a/src/main/kotlin/com/coded/spring/service/UserService.kt +++ b/src/main/kotlin/com/coded/spring/service/UserService.kt @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service class UserService (private val userRepository: UserRepository){ fun findAllUsers() = userRepository.findAll() + fun createUser(user: UserEntity) = userRepository.save(user) From b86fab152f9b1bf1dd0edd8f49482928908070ff Mon Sep 17 00:00:00 2001 From: ahjadi Date: Thu, 17 Apr 2025 17:48:07 +0300 Subject: [PATCH 07/26] Did menu table and finished Online Ordering - User Authentication --- .../spring/authentication/SecurityConfig.kt | 3 +- .../coded/spring/controller/MenuController.kt | 29 +++++++++++++++++++ .../spring/controller/OrderController.kt | 2 +- .../coded/spring/controller/UserController.kt | 2 +- .../com/coded/spring/entity/ItemEntity.kt | 2 +- .../com/coded/spring/entity/MenuEntity.kt | 20 +++++++++++++ .../coded/spring/repository/MenuRepository.kt | 10 +++++++ .../coded/spring/repository/UserRepository.kt | 1 + .../com/coded/spring/service/MenuService.kt | 12 ++++++++ 9 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/controller/MenuController.kt create mode 100644 src/main/kotlin/com/coded/spring/entity/MenuEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/repository/MenuRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/service/MenuService.kt diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index d6e758b..9dcb9a1 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -22,8 +22,9 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/public/**").permitAll() + it.requestMatchers("/public/**").permitAll().requestMatchers("/orders/v1/**").authenticated() .anyRequest().authenticated() + } .formLogin { it.defaultSuccessUrl("/hello", true) } .userDetailsService(userDetailsService) diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/src/main/kotlin/com/coded/spring/controller/MenuController.kt new file mode 100644 index 0000000..c6ba0cf --- /dev/null +++ b/src/main/kotlin/com/coded/spring/controller/MenuController.kt @@ -0,0 +1,29 @@ +package com.coded.spring.controller + +import com.coded.spring.entity.MenuEntity +import com.coded.spring.service.MenuService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal + +@RestController +class MenuController(val menuService: MenuService) { + + @PostMapping("/menu/add") + fun addItemsToMenu(@RequestBody menuRequest: MenuRequest) : Any { + val menuEntity = MenuEntity(name = menuRequest.name, price = menuRequest.price) + menuService.addItems(menuEntity) + return ResponseEntity.ok().body("Menu items added successfully.") + } + + @GetMapping("/public/menu/list") + fun listItems() = menuService.listMenuItems() +} + +data class MenuRequest( + val name: String, + val price: BigDecimal +) diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index ef890db..e55b700 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.* @RestController class OrderController(val orderService: OrderService){ - @PostMapping("/orders/submit") + @PostMapping("/orders/v1/submit") fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) { orderService.createOrder(orderRequest) } diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 14f408b..a06f668 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -20,7 +20,7 @@ class UserController(private val userService: UserService) { """.trimIndent() - @PostMapping("/users/create") + @PostMapping("/public/users/create") fun newUser(@RequestBody user: UserEntity): UserEntity { return userService.createUser(user) } diff --git a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt index 1636239..721b49c 100644 --- a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt @@ -5,7 +5,7 @@ import java.math.BigDecimal @Entity @Table(name = "items") -class ItemEntity ( +data class ItemEntity ( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, diff --git a/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt b/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt new file mode 100644 index 0000000..971ddde --- /dev/null +++ b/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt @@ -0,0 +1,20 @@ +package com.coded.spring.entity + +import jakarta.persistence.* +import java.math.BigDecimal + +@Entity +@Table(name = "menu") +data class MenuEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + var name: String, + var price: BigDecimal +) { + constructor() : this(null, "", BigDecimal.ZERO) { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/MenuRepository.kt b/src/main/kotlin/com/coded/spring/repository/MenuRepository.kt new file mode 100644 index 0000000..f6cef4c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/repository/MenuRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.repository + +import com.coded.spring.entity.MenuEntity +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/repository/UserRepository.kt b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt index 4ceae82..c0cd69e 100644 --- a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/UserRepository.kt @@ -8,4 +8,5 @@ import org.springframework.stereotype.Repository @Repository interface UserRepository : JpaRepository { fun findByUsername(username: String) : UserEntity? + } diff --git a/src/main/kotlin/com/coded/spring/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt new file mode 100644 index 0000000..1f64e16 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/service/MenuService.kt @@ -0,0 +1,12 @@ +package com.coded.spring.service + +import com.coded.spring.entity.MenuEntity +import com.coded.spring.repository.MenuRepository +import org.springframework.stereotype.Service + +@Service +class MenuService(val menuRepository: MenuRepository){ + + fun addItems(menuEntity: MenuEntity) = menuRepository.save(menuEntity) + fun listMenuItems() = menuRepository.findAll() +} \ No newline at end of file From c6af91e0ffa5c7e3886894bfa243dbf14d60e92e Mon Sep 17 00:00:00 2001 From: ahjadi Date: Thu, 17 Apr 2025 18:17:46 +0300 Subject: [PATCH 08/26] BONUS Did password validation for Online Ordering - User Authentication --- .../coded/spring/controller/UserController.kt | 16 +++++++++++++--- .../com/coded/spring/service/UserService.kt | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index a06f668..7824176 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -2,13 +2,15 @@ package com.coded.spring.controller import com.coded.spring.entity.UserEntity import com.coded.spring.service.UserService +import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController -class UserController(private val userService: UserService) { +class UserController(private val userService: UserService, + private val encoder: PasswordEncoder) { @GetMapping("/hello") fun hello()= """ @@ -21,12 +23,20 @@ class UserController(private val userService: UserService) { """.trimIndent() @PostMapping("/public/users/create") - fun newUser(@RequestBody user: UserEntity): UserEntity { - return userService.createUser(user) + fun newUser(@RequestBody userRequest: UserRequest): Any { + userService.validatePassword(userRequest.password) + return userService.createUser(UserEntity(name = userRequest.name, email = userRequest.email, username = userRequest.username, password = encoder.encode(userRequest.password) )) } @GetMapping("/users/v1/list") fun listUsers() = userService.findAllUsers() } +data class UserRequest( + val name: String, + val email: String, + val username: String, + val password: String +) + diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/src/main/kotlin/com/coded/spring/service/UserService.kt index 0cc62d8..2fc28ee 100644 --- a/src/main/kotlin/com/coded/spring/service/UserService.kt +++ b/src/main/kotlin/com/coded/spring/service/UserService.kt @@ -12,5 +12,10 @@ class UserService (private val userRepository: UserRepository){ fun createUser(user: UserEntity) = userRepository.save(user) + fun validatePassword(password: String) { + require(password.length >= 6) { "Password must be at least 6 characters long" } + require(password.any { it.isUpperCase() }) { "Password must contain at least one uppercase letter" } + require(password.any { it.isDigit() }) { "Password must contain at least one number" } + } } \ No newline at end of file From 570d841098d003437ade2cee26e338ddbdef1a77 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Fri, 18 Apr 2025 20:22:13 +0300 Subject: [PATCH 09/26] some of autherization --- pom.xml | 17 +++++++++++++++++ .../spring/authentication/SecurityConfig.kt | 3 +-- .../jwt/JwtAuthenticationFilter.kt | 4 ++++ .../spring/authentication/jwt/JwtService.kt | 4 ++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt create mode 100644 src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt diff --git a/pom.xml b/pom.xml index 01404bb..81444e6 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,23 @@ kotlin-test-junit5 test
+ + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + org.springframework.boot spring-boot-starter-security diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index 9dcb9a1..c9ab03a 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -22,8 +22,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/public/**").permitAll().requestMatchers("/orders/v1/**").authenticated() - .anyRequest().authenticated() + it.requestMatchers("/public/**").permitAll().requestMatchers("/orders/v1/**").authenticated().anyRequest().authenticated() } .formLogin { it.defaultSuccessUrl("/hello", true) } diff --git a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..f718fee --- /dev/null +++ b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt @@ -0,0 +1,4 @@ +package com.coded.spring.authentication.jwt + +class JwtAuthenticationFilter { +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt new file mode 100644 index 0000000..20ff0b0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt @@ -0,0 +1,4 @@ +package com.coded.spring.authentication.jwt + +class JwtService { +} \ No newline at end of file From 6c66f4f5e73d5e67e02e365d6e5c9b84d072b800 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Fri, 18 Apr 2025 21:20:30 +0300 Subject: [PATCH 10/26] added compiler plugin for no-arg constructor --- OrderingDB.sql | 4 +-- pom.xml | 9 +++++ .../kotlin/com/coded/spring/InitUserRunner.kt | 34 ------------------- .../com/coded/spring/entity/ItemEntity.kt | 8 ++--- .../com/coded/spring/entity/MenuEntity.kt | 11 +++--- .../com/coded/spring/entity/OrderEntity.kt | 6 ++-- .../com/coded/spring/entity/UserEntity.kt | 6 ++-- src/main/resources/application.properties | 2 +- 8 files changed, 28 insertions(+), 52 deletions(-) delete mode 100644 src/main/kotlin/com/coded/spring/InitUserRunner.kt diff --git a/OrderingDB.sql b/OrderingDB.sql index 3d78ceb..3c9376b 100644 --- a/OrderingDB.sql +++ b/OrderingDB.sql @@ -3,14 +3,14 @@ create table users ( id serial primary key, name varchar(255) not null, email varchar(255) not null unique -) +); create table orders ( id serial primary key, user_id bigint not null, restaurant varchar(255) not null, foreign key (user_id) references users(id) on delete cascade -) +); create table items ( id serial primary key, diff --git a/pom.xml b/pom.xml index 81444e6..b19f681 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,16 @@ spring + + jpa + all-open + + + + + + diff --git a/src/main/kotlin/com/coded/spring/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/InitUserRunner.kt deleted file mode 100644 index 0c245d4..0000000 --- a/src/main/kotlin/com/coded/spring/InitUserRunner.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.coded.spring - - - -import com.coded.spring.entity.UserEntity -import com.coded.spring.repository.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"), - email = "test@coded.com", - ) - 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/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt index 721b49c..50cb294 100644 --- a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt @@ -16,9 +16,9 @@ data class ItemEntity ( var price : BigDecimal ) -{ - constructor(): this(null, "", 0, BigDecimal.ZERO ) - -} +//{ +// constructor(): this(null, "", 0, BigDecimal.ZERO ) +// +//} diff --git a/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt b/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt index 971ddde..7ddfb8a 100644 --- a/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt @@ -13,8 +13,9 @@ data class MenuEntity( var name: String, var price: BigDecimal -) { - constructor() : this(null, "", BigDecimal.ZERO) { - - } -} \ No newline at end of file +) +//) { +// constructor() : this(null, "", BigDecimal.ZERO) { +// +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt index 7ca587b..fa012ec 100644 --- a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt @@ -19,6 +19,6 @@ data class OrderEntity( val items: MutableList ) -{ - constructor() : this(null, UserEntity(), "", mutableListOf()) -} \ No newline at end of file +//{ +// constructor() : this(null, UserEntity(), "", mutableListOf()) +//} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt index 22d0814..5d0219e 100644 --- a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt @@ -23,6 +23,6 @@ data class UserEntity( //ALTER TABLE users ADD username varchar(255) unique //ALTER TABLE users ADD password varchar(255) ) -{ - constructor() : this(null,"","", "", "") -} \ No newline at end of file +//{ +// constructor() : this(null,"","", "", "") +//} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8dada97..00a43e7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=Kotlin.SpringbootV2 -server.port = 4444 +#server.port = 4444 spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB spring.datasource.username=postgres spring.datasource.password=alix From a5f4b41da7c5b8b72b000a3507008402d9e95aab Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sat, 19 Apr 2025 11:45:00 +0300 Subject: [PATCH 11/26] simplified entity relationships user direct foreign keys and removed JPA associations --- OrderingDB.sql | 38 ++++++++-------- pom.xml | 3 ++ .../spring/authentication/SecurityConfig.kt | 42 ++++++++++++++---- .../jwt/AuthenticationController.kt | 40 +++++++++++++++++ .../jwt/JwtAuthenticationFilter.kt | 44 ++++++++++++++++++- .../spring/authentication/jwt/JwtService.kt | 39 ++++++++++++++++ .../com/coded/spring/entity/ItemEntity.kt | 9 ++-- .../com/coded/spring/entity/OrderEntity.kt | 11 +---- .../com/coded/spring/entity/UserEntity.kt | 12 ++--- .../com/coded/spring/service/OrderService.kt | 10 ++++- src/main/resources/application.properties | 4 +- 11 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt diff --git a/OrderingDB.sql b/OrderingDB.sql index 3c9376b..7802adc 100644 --- a/OrderingDB.sql +++ b/OrderingDB.sql @@ -1,22 +1,24 @@ --- SQL code used to create the local postgres@17 DB -create table users ( - id serial primary key, - name varchar(255) not null, - email varchar(255) not null unique +CREATE TABLE users ( + id serial PRIMARY KEY, + name VARCHAR(255), + email VARCHAR(255), + username VARCHAR(255) unique, + password VARCHAR(255) ); -create table orders ( - id serial primary key, - user_id bigint not null, - restaurant varchar(255) not null, - foreign key (user_id) references users(id) on delete cascade +CREATE TABLE orders ( + id serial PRIMARY KEY, + user_id BIGINT, + restaurant VARCHAR(255), + FOREIGN KEY (user_id) REFERENCES users(id) ); -create table items ( -id serial primary key, -order_id bigint not null, -name varchar(255) not null, -quantity int check(quantity >= 0) not null, -price decimal(10,3) check(price >= 0) not null, -foreign key (order_id) references orders(id) on delete cascade -) \ No newline at end of file +CREATE TABLE items ( + id serial PRIMARY KEY, + name VARCHAR(255), + quantity INT, + price DECIMAL(9, 3), + order_id BIGINT, + FOREIGN KEY (order_id) REFERENCES orders(id) +); +-- added menu too \ No newline at end of file diff --git a/pom.xml b/pom.xml index b19f681..2021664 100644 --- a/pom.xml +++ b/pom.xml @@ -118,9 +118,11 @@ jpa all-open + no-arg + @@ -134,6 +136,7 @@ +
diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index c9ab03a..0074cfc 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -1,32 +1,58 @@ package com.coded.spring.authentication + +import com.coded.spring.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 passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() - @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/public/**").permitAll().requestMatchers("/orders/v1/**").authenticated().anyRequest().authenticated() - + it.requestMatchers("/auth/**").permitAll() + .anyRequest().authenticated() } - .formLogin { it.defaultSuccessUrl("/hello", true) } - .userDetailsService(userDetailsService) - return http.build(); + .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/authentication/jwt/AuthenticationController.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt new file mode 100644 index 0000000..009f412 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt @@ -0,0 +1,40 @@ +package com.coded.spring.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/authentication/jwt/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt index f718fee..20e39cf 100644 --- a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt +++ b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt @@ -1,4 +1,46 @@ package com.coded.spring.authentication.jwt -class JwtAuthenticationFilter { + +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/authentication/jwt/JwtService.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt index 20ff0b0..9d711df 100644 --- a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt +++ b/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt @@ -1,4 +1,43 @@ package com.coded.spring.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/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt index 50cb294..67d28e3 100644 --- a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt @@ -11,14 +11,11 @@ data class ItemEntity ( var id: Long? = null, var name: String, - var quantity: Int, + var price : BigDecimal, + + var orderId: Long - var price : BigDecimal ) -//{ -// constructor(): this(null, "", 0, BigDecimal.ZERO ) -// -//} diff --git a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt index fa012ec..2bb0e2c 100644 --- a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt @@ -9,16 +9,7 @@ data class OrderEntity( @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, - @ManyToOne // this is for fk - val user: UserEntity, - - @Column(name = "restaurant") var restaurant: String, - @OneToMany(mappedBy = "id") - val items: MutableList + var userId: Long ) - -//{ -// constructor() : this(null, UserEntity(), "", mutableListOf()) -//} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt index 5d0219e..606d770 100644 --- a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/UserEntity.kt @@ -5,6 +5,7 @@ import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.OneToMany import jakarta.persistence.Table @Entity @@ -13,16 +14,9 @@ data class UserEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, - @Column(name = "name") + var name: String, - @Column(name = "email") var email: String, - val username: String, - val password: String -//ALTER TABLE users ADD username varchar(255) unique -//ALTER TABLE users ADD password varchar(255) + val password: String, ) -//{ -// constructor() : this(null,"","", "", "") -//} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 6b6c60b..794be19 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -20,10 +20,16 @@ class OrderService ( val user = userRepository.findById(orderRequest.userId).get() val newOrder = OrderEntity( user = user, restaurant = orderRequest.restaurant, - items = orderRequest.items +// items = orderRequest.items ) + val itemsWithOrder = orderRequest.items.map { + it.order = newOrder + it + } + newOrder.items.addAll(itemsWithOrder) + orderRepository.save(newOrder) - itemRepository.saveAll(orderRequest.items) +// itemRepository.saveAll(orderRequest.items) } fun listOrdersByUserID(user_ID: Long) = orderRepository.findAllByUserId(user_ID) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 00a43e7..9d37fbf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,8 @@ spring.application.name=Kotlin.SpringbootV2 -#server.port = 4444 +server.port = 4444 spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB spring.datasource.username=postgres spring.datasource.password=alix spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file +spring.jpa.properties.hibe1rnate.format_sql=true \ No newline at end of file From 75126348c82c013236bd76892a7851a8427611ea Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sat, 19 Apr 2025 12:35:18 +0300 Subject: [PATCH 12/26] added validaion for user identity before order submission to prevent unauthorized access --- .../spring/authentication/SecurityConfig.kt | 2 +- .../spring/controller/OrderController.kt | 3 +- .../spring/repository/OrderRepository.kt | 1 - .../com/coded/spring/service/OrderService.kt | 54 ++++++++++++------- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index 0074cfc..d5ab904 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -29,7 +29,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/auth/**").permitAll() + it.requestMatchers("/auth/**", "/public/users/create").permitAll() .anyRequest().authenticated() } .sessionManagement { diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index e55b700..b97211e 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -8,8 +8,9 @@ import org.springframework.web.bind.annotation.* class OrderController(val orderService: OrderService){ @PostMapping("/orders/v1/submit") - fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) { + fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) : OrderService.OrderResponseDTO{ orderService.createOrder(orderRequest) + return OrderService.OrderResponseDTO(orderRequest.restaurant, orderRequest.items) } @GetMapping("/orders/user/orders/{userId}") diff --git a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt index add1ffc..444d6f2 100644 --- a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt @@ -8,6 +8,5 @@ import org.springframework.stereotype.Repository @Repository interface OrderRepository : JpaRepository{ - fun findAllByUser(user: UserEntity): List fun findAllByUserId(userId: Long): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 794be19..43fdf2e 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -2,34 +2,46 @@ package com.coded.spring.service import com.coded.spring.entity.ItemEntity import com.coded.spring.entity.OrderEntity -import com.coded.spring.entity.UserEntity import com.coded.spring.repository.ItemRepository import com.coded.spring.repository.OrderRepository import com.coded.spring.repository.UserRepository +import org.springframework.http.HttpStatus +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service +import org.springframework.web.server.ResponseStatusException import java.math.BigDecimal @Service -class OrderService ( +class OrderService( private var orderRepository: OrderRepository, - private var userRepository: UserRepository, - private var itemRepository: ItemRepository -){ - - fun createOrder(orderRequest: OrderRequestDTO) { - val user = userRepository.findById(orderRequest.userId).get() - val newOrder = OrderEntity( - user = user, restaurant = orderRequest.restaurant, -// items = orderRequest.items + private var itemRepository: ItemRepository, + private var userRepository: UserRepository +) { + + fun createOrder(request: OrderRequestDTO) { + val user = userRepository.findById(request.userId) + .orElseThrow { IllegalArgumentException("User Not Found") } + + val tokenUsername = SecurityContextHolder.getContext().authentication.name + val requestUsername = userRepository.findById(request.userId).get().username + if (tokenUsername != requestUsername) + throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") + + val order = OrderEntity( + restaurant = request.restaurant, + userId = request.userId ) - val itemsWithOrder = orderRequest.items.map { - it.order = newOrder - it + val savedOrder = orderRepository.save(order) + val items = request.items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + price = it.price, + orderId = savedOrder.id!! + ) } - newOrder.items.addAll(itemsWithOrder) - orderRepository.save(newOrder) -// itemRepository.saveAll(orderRequest.items) + itemRepository.saveAll(items) } fun listOrdersByUserID(user_ID: Long) = orderRepository.findAllByUserId(user_ID) @@ -40,9 +52,15 @@ class OrderService ( val quantity: Int, val price: BigDecimal ) + data class OrderRequestDTO( val userId: Long, val restaurant: String, - val items: MutableList + val items: MutableList + ) + + data class OrderResponseDTO( + val restaurant: String, + val items: List ) } \ No newline at end of file From 1975497f848f617f88ef7ab3701a2c6219b3165f Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sat, 19 Apr 2025 19:47:23 +0300 Subject: [PATCH 13/26] Did Online Ordering - Profiles with BONUS but no testing yet --- .../coded/spring/controller/UserController.kt | 43 ++++++++++++++----- .../com/coded/spring/entity/ProfileEntity.kt | 22 ++++++++++ .../spring/repository/ProfileRepository.kt | 8 ++++ .../coded/spring/service/ProfileService.kt | 41 ++++++++++++++++++ .../com/coded/spring/service/UserService.kt | 7 ++- 5 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/service/ProfileService.kt diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 7824176..9600900 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -1,7 +1,12 @@ package com.coded.spring.controller +import com.coded.spring.entity.ProfileEntity import com.coded.spring.entity.UserEntity +import com.coded.spring.service.ProfileRequest +import com.coded.spring.service.ProfileService +import com.coded.spring.service.UserRequest import com.coded.spring.service.UserService +import org.springframework.http.ResponseEntity import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping @@ -9,11 +14,14 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @RestController -class UserController(private val userService: UserService, - private val encoder: PasswordEncoder) { +class UserController( + private val userService: UserService, + private val encoder: PasswordEncoder, + private val profileService: ProfileService +) { @GetMapping("/hello") - fun hello()= """ + fun hello() = """

hello yo

@@ -25,18 +33,33 @@ class UserController(private val userService: UserService, @PostMapping("/public/users/create") fun newUser(@RequestBody userRequest: UserRequest): Any { userService.validatePassword(userRequest.password) - return userService.createUser(UserEntity(name = userRequest.name, email = userRequest.email, username = userRequest.username, password = encoder.encode(userRequest.password) )) + return userService.createUser( + UserEntity( + name = userRequest.name, + email = userRequest.email, + username = userRequest.username, + password = encoder.encode(userRequest.password) + ) + ) } @GetMapping("/users/v1/list") fun listUsers() = userService.findAllUsers() + + @PostMapping("/auth/profile/save") + fun saveProfile(@RequestBody request: ProfileRequest): Any { + + val newProfile = ProfileEntity( + firstName = request.firstName, + lastName = request.lastName, phoneNumber = request.phoneNumber, + userId = request.userId + ) + profileService.save(newProfile) + return ResponseEntity.ok().body("Success") + } + } -data class UserRequest( - val name: String, - val email: String, - val username: String, - val password: String -) + diff --git a/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt b/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt new file mode 100644 index 0000000..4c7db48 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt @@ -0,0 +1,22 @@ +package com.coded.spring.entity + +import com.coded.spring.service.ProfileRequest +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name = "profiles") +data class ProfileEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + var firstName: String, + var lastName: String, + var phoneNumber: String, + + var userId: Long, +) diff --git a/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt b/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt new file mode 100644 index 0000000..dee2be0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt @@ -0,0 +1,8 @@ +package com.coded.spring.repository + +import com.coded.spring.entity.ProfileEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ProfileRepository : JpaRepository {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/ProfileService.kt b/src/main/kotlin/com/coded/spring/service/ProfileService.kt new file mode 100644 index 0000000..0747bee --- /dev/null +++ b/src/main/kotlin/com/coded/spring/service/ProfileService.kt @@ -0,0 +1,41 @@ +package com.coded.spring.service + +import com.coded.spring.entity.ProfileEntity +import com.coded.spring.repository.ProfileRepository +import com.coded.spring.repository.UserRepository +import org.springframework.http.HttpStatus +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Service +import org.springframework.web.server.ResponseStatusException +import java.lang.IllegalArgumentException + + +@Service +class ProfileService(private val profileRepository: ProfileRepository, + private val userRepository: UserRepository,) { + + + + fun save(profile: ProfileEntity) { + val user = userRepository.findById(profile.userId).orElseThrow { IllegalArgumentException("No user with id ${profile.userId}") } + + require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) {"Phone number must be 8 digits" } + require(profile.firstName.all { it.isLetter() }) {"First name must be letters" } + require(profile.lastName.all { it.isLetter() }) {"Last name must be letters" } + + + // To avoid the wrong user from profile saving + val tokenUsername = SecurityContextHolder.getContext().authentication.name + val requestUsername = userRepository.findById(profile.userId).get().username + if (tokenUsername != requestUsername) + throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") + + profileRepository.save(profile) + } + +} +data class ProfileRequest( + val userId: Long, + val firstName: String, + val lastName: String, + val phoneNumber: String) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/src/main/kotlin/com/coded/spring/service/UserService.kt index 2fc28ee..cb8f1b0 100644 --- a/src/main/kotlin/com/coded/spring/service/UserService.kt +++ b/src/main/kotlin/com/coded/spring/service/UserService.kt @@ -18,4 +18,9 @@ class UserService (private val userRepository: UserRepository){ require(password.any { it.isDigit() }) { "Password must contain at least one number" } } -} \ No newline at end of file +}data class UserRequest( + val name: String, + val email: String, + val username: String, + val password: String +) \ No newline at end of file From d40f6b7e6dcc81e42f883c23fba39a9319d2cebd Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sun, 20 Apr 2025 09:53:35 +0300 Subject: [PATCH 14/26] exception hanfling for no user found in Profile service and entity --- .../com/coded/spring/entity/ProfileEntity.kt | 7 +------ .../coded/spring/service/ProfileService.kt | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt b/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt index 4c7db48..edf3665 100644 --- a/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt +++ b/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt @@ -1,11 +1,6 @@ package com.coded.spring.entity -import com.coded.spring.service.ProfileRequest -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.Table +import jakarta.persistence.* @Entity @Table(name = "profiles") diff --git a/src/main/kotlin/com/coded/spring/service/ProfileService.kt b/src/main/kotlin/com/coded/spring/service/ProfileService.kt index 0747bee..640cf72 100644 --- a/src/main/kotlin/com/coded/spring/service/ProfileService.kt +++ b/src/main/kotlin/com/coded/spring/service/ProfileService.kt @@ -7,21 +7,22 @@ import org.springframework.http.HttpStatus import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service import org.springframework.web.server.ResponseStatusException -import java.lang.IllegalArgumentException @Service -class ProfileService(private val profileRepository: ProfileRepository, - private val userRepository: UserRepository,) { - +class ProfileService( + private val profileRepository: ProfileRepository, + private val userRepository: UserRepository, +) { fun save(profile: ProfileEntity) { - val user = userRepository.findById(profile.userId).orElseThrow { IllegalArgumentException("No user with id ${profile.userId}") } + val user = userRepository.findById(profile.userId) + .orElseThrow { IllegalArgumentException("No user with id ${profile.userId}") } - require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) {"Phone number must be 8 digits" } - require(profile.firstName.all { it.isLetter() }) {"First name must be letters" } - require(profile.lastName.all { it.isLetter() }) {"Last name must be letters" } + require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) { "Phone number must be 8 digits" } + require(profile.firstName.all { it.isLetter() }) { "First name must be letters" } + require(profile.lastName.all { it.isLetter() }) { "Last name must be letters" } // To avoid the wrong user from profile saving @@ -34,8 +35,10 @@ class ProfileService(private val profileRepository: ProfileRepository, } } + data class ProfileRequest( val userId: Long, val firstName: String, val lastName: String, - val phoneNumber: String) \ No newline at end of file + val phoneNumber: String +) \ No newline at end of file From 361c61851b790842cecd07ac6d53854bf92179e7 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sun, 20 Apr 2025 20:25:57 +0300 Subject: [PATCH 15/26] removed userId from requests and instead gets extracted from token --- .../spring/authentication/SecurityConfig.kt | 2 +- .../spring/controller/OrderController.kt | 11 +++++--- .../coded/spring/controller/UserController.kt | 25 +++++++++++------ .../spring/repository/ProfileRepository.kt | 4 ++- .../com/coded/spring/service/MenuService.kt | 1 + .../com/coded/spring/service/OrderService.kt | 15 ++++++----- .../coded/spring/service/ProfileService.kt | 27 ++++++++++++++----- 7 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index d5ab904..c2ec2c7 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -24,7 +24,7 @@ class SecurityConfig( private val jwtAuthFilter: JwtAuthenticationFilter, private val userDetailsService: UserDetailsService ) { - + // For authorization you need to add code in security config, customuserdetails, and jwtservice @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index b97211e..c192747 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -9,14 +9,19 @@ class OrderController(val orderService: OrderService){ @PostMapping("/orders/v1/submit") fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) : OrderService.OrderResponseDTO{ - orderService.createOrder(orderRequest) + try { + orderService.createOrder(orderRequest) + } catch (e: IllegalArgumentException) {"error submitting the order: ${e.message}"} return OrderService.OrderResponseDTO(orderRequest.restaurant, orderRequest.items) } @GetMapping("/orders/user/orders/{userId}") - fun listOrdersByUserId(@PathVariable userId: Long): List { - return orderService.listOrdersByUserID(userId)} + fun listOrdersByUserId(@PathVariable userId: Long){ + try { + orderService.listOrdersByUserID(userId)} + catch (e: IllegalArgumentException) {"error occurred while listing orders: ${e.message}"} + } } diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 9600900..d24ba75 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -1,6 +1,5 @@ package com.coded.spring.controller -import com.coded.spring.entity.ProfileEntity import com.coded.spring.entity.UserEntity import com.coded.spring.service.ProfileRequest import com.coded.spring.service.ProfileService @@ -48,14 +47,24 @@ class UserController( @PostMapping("/auth/profile/save") fun saveProfile(@RequestBody request: ProfileRequest): Any { + return try { + profileService.save(request) + ResponseEntity.ok().body("Profile saved successfully") + } catch (e: IllegalArgumentException) { + ResponseEntity.badRequest().body("Error while saving profile ${e.message}") + } + + } + + @GetMapping("/auth/profile/view/") + fun viewProfile(): Any { + return try { + profileService.view() + } catch (e: IllegalArgumentException) { + "error: ${e.message}" + } + - val newProfile = ProfileEntity( - firstName = request.firstName, - lastName = request.lastName, phoneNumber = request.phoneNumber, - userId = request.userId - ) - profileService.save(newProfile) - return ResponseEntity.ok().body("Success") } } diff --git a/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt b/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt index dee2be0..1cd0dfe 100644 --- a/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt +++ b/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt @@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface ProfileRepository : JpaRepository {} \ No newline at end of file +interface ProfileRepository : JpaRepository { + fun findByUserId(userId: Long): ProfileEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt index 1f64e16..526e479 100644 --- a/src/main/kotlin/com/coded/spring/service/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/service/MenuService.kt @@ -8,5 +8,6 @@ import org.springframework.stereotype.Service class MenuService(val menuRepository: MenuRepository){ fun addItems(menuEntity: MenuEntity) = menuRepository.save(menuEntity) + fun listMenuItems() = menuRepository.findAll() } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 43fdf2e..98d1177 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -19,17 +19,18 @@ class OrderService( ) { fun createOrder(request: OrderRequestDTO) { - val user = userRepository.findById(request.userId) - .orElseThrow { IllegalArgumentException("User Not Found") } + val userName = SecurityContextHolder.getContext().authentication.name + val userId = userRepository.findByUsername(userName)?.id + ?: throw IllegalArgumentException("User Not Found") val tokenUsername = SecurityContextHolder.getContext().authentication.name - val requestUsername = userRepository.findById(request.userId).get().username + val requestUsername = userRepository.findById(userId).get().username if (tokenUsername != requestUsername) throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") val order = OrderEntity( restaurant = request.restaurant, - userId = request.userId + userId = userId ) val savedOrder = orderRepository.save(order) val items = request.items.map { @@ -44,7 +45,10 @@ class OrderService( itemRepository.saveAll(items) } - fun listOrdersByUserID(user_ID: Long) = orderRepository.findAllByUserId(user_ID) + fun listOrdersByUserID(userId: Long) { + val user = userRepository.findById(userId).orElseThrow { IllegalArgumentException("User Not Found") } + orderRepository.findAllByUserId(userId) + } data class ItemDTO( @@ -54,7 +58,6 @@ class OrderService( ) data class OrderRequestDTO( - val userId: Long, val restaurant: String, val items: MutableList ) diff --git a/src/main/kotlin/com/coded/spring/service/ProfileService.kt b/src/main/kotlin/com/coded/spring/service/ProfileService.kt index 640cf72..d3af683 100644 --- a/src/main/kotlin/com/coded/spring/service/ProfileService.kt +++ b/src/main/kotlin/com/coded/spring/service/ProfileService.kt @@ -16,9 +16,10 @@ class ProfileService( ) { - fun save(profile: ProfileEntity) { - val user = userRepository.findById(profile.userId) - .orElseThrow { IllegalArgumentException("No user with id ${profile.userId}") } + fun save(profile: ProfileRequest) { + val userName = SecurityContextHolder.getContext().authentication.name + val userId = userRepository.findByUsername(userName)?.id + ?: throw IllegalArgumentException("User Not Found") require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) { "Phone number must be 8 digits" } require(profile.firstName.all { it.isLetter() }) { "First name must be letters" } @@ -27,17 +28,31 @@ class ProfileService( // To avoid the wrong user from profile saving val tokenUsername = SecurityContextHolder.getContext().authentication.name - val requestUsername = userRepository.findById(profile.userId).get().username + val requestUsername = userRepository.findById(userId).get().username if (tokenUsername != requestUsername) throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") + val newProfile = ProfileEntity( userId = userId, firstName = profile.firstName, lastName = profile.lastName, phoneNumber = profile.phoneNumber) + profileRepository.save(newProfile) + } - profileRepository.save(profile) + fun view() : ProfileResponse { + val userName = SecurityContextHolder.getContext().authentication.name + val userId = userRepository.findByUsername(userName)?.id + ?: throw IllegalArgumentException("User Not Found") + val profile = profileRepository.findByUserId(userId) ?: throw IllegalArgumentException("No profile with id $userId") + return ProfileResponse(profile.firstName, profile.lastName,profile.phoneNumber) } + } data class ProfileRequest( - val userId: Long, + val firstName: String, + val lastName: String, + val phoneNumber: String +) + +data class ProfileResponse( val firstName: String, val lastName: String, val phoneNumber: String From 536347b58bb48a38a58f5b7d34e8e0235c144ab6 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sun, 20 Apr 2025 21:49:22 +0300 Subject: [PATCH 16/26] Minor polishing --- .../spring/controller/OrderController.kt | 21 +++++++++++-------- .../coded/spring/controller/UserController.kt | 2 -- .../com/coded/spring/service/OrderService.kt | 6 +++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index c192747..d6c63c9 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -1,28 +1,31 @@ package com.coded.spring.controller -import com.coded.spring.entity.OrderEntity import com.coded.spring.service.OrderService import org.springframework.web.bind.annotation.* @RestController -class OrderController(val orderService: OrderService){ +class OrderController(val orderService: OrderService) { @PostMapping("/orders/v1/submit") - fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequestDTO) : OrderService.OrderResponseDTO{ + fun submitOrder(@RequestBody orderRequest: OrderService.OrderRequest): OrderService.OrderResponse { try { orderService.createOrder(orderRequest) - } catch (e: IllegalArgumentException) {"error submitting the order: ${e.message}"} - return OrderService.OrderResponseDTO(orderRequest.restaurant, orderRequest.items) + } catch (e: IllegalArgumentException) { + "error submitting the order: ${e.message}" + } + return OrderService.OrderResponse(orderRequest.restaurant, orderRequest.items) } @GetMapping("/orders/user/orders/{userId}") - fun listOrdersByUserId(@PathVariable userId: Long){ + fun listOrdersByUserId(@PathVariable userId: Long) { try { - orderService.listOrdersByUserID(userId)} - catch (e: IllegalArgumentException) {"error occurred while listing orders: ${e.message}"} - + orderService.listOrdersByUserID(userId) + } catch (e: IllegalArgumentException) { + "error occurred while listing orders: ${e.message}" } + } +} diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index d24ba75..1ec3448 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -63,8 +63,6 @@ class UserController( } catch (e: IllegalArgumentException) { "error: ${e.message}" } - - } } diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 98d1177..f29bd49 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -18,7 +18,7 @@ class OrderService( private var userRepository: UserRepository ) { - fun createOrder(request: OrderRequestDTO) { + fun createOrder(request: OrderRequest) { val userName = SecurityContextHolder.getContext().authentication.name val userId = userRepository.findByUsername(userName)?.id ?: throw IllegalArgumentException("User Not Found") @@ -57,12 +57,12 @@ class OrderService( val price: BigDecimal ) - data class OrderRequestDTO( + data class OrderRequest( val restaurant: String, val items: MutableList ) - data class OrderResponseDTO( + data class OrderResponse( val restaurant: String, val items: List ) From 29de2dd614ee154941ccc64a4327f71908b6aa66 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 21 Apr 2025 20:25:32 +0300 Subject: [PATCH 17/26] Did 1 GET test --- pom.xml | 22 ++++++++++------- .../spring/authentication/SecurityConfig.kt | 8 +++++-- .../coded/spring/controller/UserController.kt | 11 +++------ .../com/coded/spring/service/OrderService.kt | 6 ++--- .../coded/spring/service/ProfileService.kt | 16 +++++++++---- .../coded/spring/ordering/ApplicationTests.kt | 24 +++++++++++++++++-- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 2021664..35c441b 100644 --- a/pom.xml +++ b/pom.xml @@ -87,14 +87,15 @@ org.springframework.boot spring-boot-starter-data-jpa - - - - - + + com.h2database + h2 + test + org.postgresql postgresql + compile @@ -114,9 +115,8 @@ -Xjsr305=strict - spring - jpa + spring all-open no-arg @@ -129,10 +129,16 @@ + + + org.jetbrains.kotlin + kotlin-maven-noarg + 1.9.25 + org.jetbrains.kotlin kotlin-maven-allopen - ${kotlin.version} + 1.9.25 diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index c2ec2c7..8fe6771 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -29,8 +29,12 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/auth/**", "/public/users/create").permitAll() - .anyRequest().authenticated() + it + .anyRequest().permitAll() // permit all + +// .requestMatchers("/auth/**", "/public/users/create").permitAll() +// .anyRequest() +// .authenticated() } .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 1ec3448..8445923 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -20,14 +20,9 @@ class UserController( ) { @GetMapping("/hello") - fun hello() = """ - - -

hello yo

- - - - """.trimIndent() + fun hello(): String { + return "Hello World!" + } @PostMapping("/public/users/create") fun newUser(@RequestBody userRequest: UserRequest): Any { diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index f29bd49..4bc32e7 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -51,7 +51,7 @@ class OrderService( } - data class ItemDTO( + data class ItemDto( val name: String, val quantity: Int, val price: BigDecimal @@ -59,11 +59,11 @@ class OrderService( data class OrderRequest( val restaurant: String, - val items: MutableList + val items: MutableList ) data class OrderResponse( val restaurant: String, - val items: List + val items: List ) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/ProfileService.kt b/src/main/kotlin/com/coded/spring/service/ProfileService.kt index d3af683..c0d88f8 100644 --- a/src/main/kotlin/com/coded/spring/service/ProfileService.kt +++ b/src/main/kotlin/com/coded/spring/service/ProfileService.kt @@ -21,7 +21,8 @@ class ProfileService( val userId = userRepository.findByUsername(userName)?.id ?: throw IllegalArgumentException("User Not Found") - require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) { "Phone number must be 8 digits" } + require(profile.phoneNumber.length == 8 && profile.phoneNumber.all { it.isDigit() }) + { "Phone number must be 8 digits" } require(profile.firstName.all { it.isLetter() }) { "First name must be letters" } require(profile.lastName.all { it.isLetter() }) { "Last name must be letters" } @@ -31,16 +32,21 @@ class ProfileService( val requestUsername = userRepository.findById(userId).get().username if (tokenUsername != requestUsername) throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") - val newProfile = ProfileEntity( userId = userId, firstName = profile.firstName, lastName = profile.lastName, phoneNumber = profile.phoneNumber) + val newProfile = ProfileEntity( + userId = userId, firstName = profile.firstName, lastName = profile.lastName, + phoneNumber = profile.phoneNumber + ) profileRepository.save(newProfile) } - fun view() : ProfileResponse { + fun view(): ProfileResponse { val userName = SecurityContextHolder.getContext().authentication.name val userId = userRepository.findByUsername(userName)?.id ?: throw IllegalArgumentException("User Not Found") - val profile = profileRepository.findByUserId(userId) ?: throw IllegalArgumentException("No profile with id $userId") - return ProfileResponse(profile.firstName, profile.lastName,profile.phoneNumber) + val profile = + profileRepository.findByUserId(userId) ?: throw IllegalArgumentException("No profile with id $userId") + return ProfileResponse(profile.firstName, profile.lastName, + profile.phoneNumber) } diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index b2e2320..da6cf29 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -1,13 +1,33 @@ package com.coded.spring.ordering +import com.coded.spring.service.OrderService 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 +import kotlin.test.assertEquals -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApplicationTests { + @Autowired + lateinit var restTemplate: TestRestTemplate + @Test - fun contextLoads() { + fun testHelloWorld(){ + val result = restTemplate.getForEntity("/hello", String::class.java) + val expected = "Hello World!" + + assertEquals(HttpStatus.OK, result.statusCode) + assertEquals(expected, result.body) + } + @Test + fun testSubmitOrder(){ + + val result = restTemplate.postForEntity("/orders/v1/submit", ) + } + } From 8d020f102f80c9fa1a59221834ea4b2d28711bef Mon Sep 17 00:00:00 2001 From: ahjadi Date: Sun, 27 Apr 2025 18:55:53 +0300 Subject: [PATCH 18/26] Implemented logging --- .../kotlin/com/coded/spring/LoggingFilter.kt | 43 ++++++++++++ .../spring/authentication/SecurityConfig.kt | 8 +-- src/main/resources/application.properties | 2 + .../coded/spring/ordering/ApplicationTests.kt | 67 ++++++++++++++----- 4 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/LoggingFilter.kt diff --git a/src/main/kotlin/com/coded/spring/LoggingFilter.kt b/src/main/kotlin/com/coded/spring/LoggingFilter.kt new file mode 100644 index 0000000..f951a92 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/LoggingFilter.kt @@ -0,0 +1,43 @@ +package com.coded.spring + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.core.Ordered +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper + +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +class LoggingFilter : OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val cachedRequest= ContentCachingRequestWrapper(request) + val cachedResponse= ContentCachingResponseWrapper(response) + filterChain.doFilter(cachedRequest, cachedResponse) + + logRequest(cachedRequest) + logResponse(cachedResponse) + cachedResponse.copyBodyToResponse() + + } + + private fun logRequest(request: ContentCachingRequestWrapper) { + val requestBody = String(request.contentAsByteArray) + // Thread safe logging for multi processing + logger.info("Request: method=${request.method}, uri=${request.requestURI}, body=$requestBody") + } + + private fun logResponse(response: ContentCachingResponseWrapper) { + val responseBody = String(response.contentAsByteArray) + logger.info("Response: status=${response.status}, body=$responseBody") + } + + +} diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index 8fe6771..cb941bc 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -30,11 +30,11 @@ class SecurityConfig( http.csrf { it.disable() } .authorizeHttpRequests { it - .anyRequest().permitAll() // permit all +// .anyRequest().permitAll() // permit all -// .requestMatchers("/auth/**", "/public/users/create").permitAll() -// .anyRequest() -// .authenticated() + .requestMatchers("/auth/**", "/public/users/create").permitAll() + .anyRequest() + .authenticated() } .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9d37fbf..c490ac8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,7 @@ spring.application.name=Kotlin.SpringbootV2 + server.port = 4444 + spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB spring.datasource.username=postgres spring.datasource.password=alix diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index da6cf29..eccad6b 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -1,33 +1,70 @@ package com.coded.spring.ordering -import com.coded.spring.service.OrderService +import com.coded.spring.authentication.jwt.JwtService +import com.coded.spring.entity.UserEntity +import com.coded.spring.repository.UserRepository +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.test.context.ActiveProfiles +import org.springframework.util.MultiValueMap import kotlin.test.assertEquals @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") class ApplicationTests { +// MOCK TRIGGER ASSERT +// GIVEN WHEN THEN + companion object { + @JvmStatic +// @BeforeAll + fun setUp( + @Autowired userRepository: UserRepository, + @Autowired passwordEncoder: PasswordEncoder + ) { - @Autowired - lateinit var restTemplate: TestRestTemplate + userRepository.deleteAll() - @Test - fun testHelloWorld(){ - val result = restTemplate.getForEntity("/hello", String::class.java) - val expected = "Hello World!" + val testUser = + UserEntity( + name = "coded", + email = "test@test.com", + username = "coded", + password = passwordEncoder.encode("Password123") + ) + userRepository.save(testUser) + } + } - assertEquals(HttpStatus.OK, result.statusCode) - assertEquals(expected, result.body) - } - @Test - fun testSubmitOrder(){ + // Tests go here + @Autowired + lateinit var restTemplate: TestRestTemplate - val result = restTemplate.postForEntity("/orders/v1/submit", ) + @Test + fun testHelloWorld(@Autowired jwtService: JwtService) { + val token = jwtService.generateToken("coded") + val headers = HttpHeaders( + MultiValueMap.fromSingleValue(mapOf("Authorization" to "Bearer $token")) + ) + val requestEntity = HttpEntity(headers) + + val result = restTemplate.exchange( + "/hello", + HttpMethod.GET, + requestEntity, + String::class.java + ) + assertEquals(HttpStatus.OK, result.statusCode) + assertEquals("Hello World!", result.body) + } +} - } -} From e43253593b5c9ee1b4fcbc2361d56f10215d269e Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 28 Apr 2025 17:32:33 +0300 Subject: [PATCH 19/26] swagger --- pom.xml | 6 ++++++ .../com/coded/spring/authentication/SecurityConfig.kt | 2 +- src/main/resources/application.properties | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 35c441b..57da2d5 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,12 @@ postgresql compile + + org.springdoc + springdoc-openapi-starter-webmvc-api + 2.6.0 + + diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index cb941bc..028aad3 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -32,7 +32,7 @@ class SecurityConfig( it // .anyRequest().permitAll() // permit all - .requestMatchers("/auth/**", "/public/users/create").permitAll() + .requestMatchers("/auth/**", "/public/users/create", "/api-docs").permitAll() .anyRequest() .authenticated() } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c490ac8..36873dc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,4 +7,6 @@ spring.datasource.username=postgres spring.datasource.password=alix spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=true -spring.jpa.properties.hibe1rnate.format_sql=true \ No newline at end of file +spring.jpa.properties.hibe1rnate.format_sql=true + +springdoc.api-docs.path=/api-docs \ No newline at end of file From 6672261569eaf20c5b129c30bce9f08c8d9b4752 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 28 Apr 2025 18:41:22 +0300 Subject: [PATCH 20/26] swagger --- .../resources/Ali-Aljadi-online-ordering-api-swagger-01.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json diff --git a/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json b/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json new file mode 100644 index 0000000..556dc60 --- /dev/null +++ b/src/main/resources/Ali-Aljadi-online-ordering-api-swagger-01.json @@ -0,0 +1 @@ +{"openapi":"3.0.1","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"http://localhost:4444","description":"Generated server url"}],"paths":{"/public/users/create":{"post":{"tags":["CustomerAPI"],"operationId":"newUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/orders/v1/submit":{"post":{"tags":["OrderAPI"],"operationId":"submitOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}}}}},"/menu/add":{"post":{"tags":["MenuAPI"],"operationId":"addItemsToMenu","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/auth/profile/save":{"post":{"tags":["CustomerAPI"],"operationId":"saveProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}},"/auth/login":{"post":{"tags":["authentication-controller"],"operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticationRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AuthenticationResponse"}}}}}}},"/users/v1/list":{"get":{"tags":["CustomerAPI"],"operationId":"listUsers","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserEntity"}}}}}}}},"/public/menu/list":{"get":{"tags":["MenuAPI"],"operationId":"listItems","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MenuEntity"}}}}}}}},"/orders/user/orders/{userId}":{"get":{"tags":["OrderAPI"],"operationId":"listOrdersByUserId","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/hello":{"get":{"tags":["CustomerAPI"],"operationId":"hello","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/auth/profile/view/":{"get":{"tags":["CustomerAPI"],"operationId":"viewProfile","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object"}}}}}}}},"components":{"schemas":{"UserRequest":{"required":["email","name","password","username"],"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"ItemDto":{"required":["name","price","quantity"],"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"price":{"type":"number"}}},"OrderRequest":{"required":["items","restaurant"],"type":"object","properties":{"restaurant":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemDto"}}}},"OrderResponse":{"required":["items","restaurant"],"type":"object","properties":{"restaurant":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItemDto"}}}},"MenuRequest":{"required":["name","price"],"type":"object","properties":{"name":{"type":"string"},"price":{"type":"number"}}},"ProfileRequest":{"required":["firstName","lastName","phoneNumber"],"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"phoneNumber":{"type":"string"}}},"AuthenticationRequest":{"required":["password","username"],"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"AuthenticationResponse":{"required":["token"],"type":"object","properties":{"token":{"type":"string"}}},"UserEntity":{"required":["email","name","password","username"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"MenuEntity":{"required":["name","price"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"price":{"type":"number"}}}}}} \ No newline at end of file From a3371593503a29b479f46b0fe390dbdadb352931 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 28 Apr 2025 18:41:33 +0300 Subject: [PATCH 21/26] swagger --- .../com/coded/spring/authentication/SecurityConfig.kt | 2 +- .../com/coded/spring/controller/MenuController.kt | 3 ++- .../com/coded/spring/controller/OrderController.kt | 3 ++- .../com/coded/spring/controller/UserController.kt | 10 +++++++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index 028aad3..4af0678 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -32,7 +32,7 @@ class SecurityConfig( it // .anyRequest().permitAll() // permit all - .requestMatchers("/auth/**", "/public/users/create", "/api-docs").permitAll() + .requestMatchers("/auth/**", "/public/users/create", "/api-docs", "/hello").permitAll() .anyRequest() .authenticated() } diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/src/main/kotlin/com/coded/spring/controller/MenuController.kt index c6ba0cf..d56f29e 100644 --- a/src/main/kotlin/com/coded/spring/controller/MenuController.kt +++ b/src/main/kotlin/com/coded/spring/controller/MenuController.kt @@ -2,13 +2,14 @@ package com.coded.spring.controller import com.coded.spring.entity.MenuEntity import com.coded.spring.service.MenuService +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController import java.math.BigDecimal - +@Tag(name = "MenuAPI") @RestController class MenuController(val menuService: MenuService) { diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/controller/OrderController.kt index d6c63c9..d3fcc76 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/controller/OrderController.kt @@ -1,8 +1,9 @@ package com.coded.spring.controller import com.coded.spring.service.OrderService +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.web.bind.annotation.* - +@Tag(name = "OrderAPI") @RestController class OrderController(val orderService: OrderService) { diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 8445923..40b2ab5 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -5,23 +5,27 @@ import com.coded.spring.service.ProfileRequest import com.coded.spring.service.ProfileService import com.coded.spring.service.UserRequest import com.coded.spring.service.UserService +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.beans.factory.annotation.Value import org.springframework.http.ResponseEntity import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController - +@Tag(name = "CustomerAPI") @RestController class UserController( private val userService: UserService, private val encoder: PasswordEncoder, - private val profileService: ProfileService + private val profileService: ProfileService, + @Value("\${hello_world}") + private val fromEnvVarMessage: String ) { @GetMapping("/hello") fun hello(): String { - return "Hello World!" + return fromEnvVarMessage } @PostMapping("/public/users/create") From 8b85ff35ff44eb1b050cf1072bbab6dc6df30552 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 28 Apr 2025 20:06:03 +0300 Subject: [PATCH 22/26] Online Ordering - Configuration --- .../com/coded/spring/controller/UserController.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/src/main/kotlin/com/coded/spring/controller/UserController.kt index 40b2ab5..d089f5e 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/src/main/kotlin/com/coded/spring/controller/UserController.kt @@ -19,13 +19,18 @@ class UserController( private val userService: UserService, private val encoder: PasswordEncoder, private val profileService: ProfileService, - @Value("\${hello_world}") - private val fromEnvVarMessage: String + @Value("\${company_name}") + private val fromEnvVarMessage: String, + @Value("\${festive.feature}") + private val festiveFeature: Boolean ) { @GetMapping("/hello") fun hello(): String { - return fromEnvVarMessage + if(festiveFeature) + return "Eidkom Mubarak, Summer Sale is here!" + else + return "Welcome to Online Ordering by $fromEnvVarMessage" } @PostMapping("/public/users/create") From 2e71a857100378e4402cbd94b05a799d1d028093 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Mon, 28 Apr 2025 20:57:03 +0300 Subject: [PATCH 23/26] Online Ordering - Configuration and discount --- .../coded/spring/controller/MenuController.kt | 5 ++- .../com/coded/spring/service/MenuService.kt | 14 ++++++-- .../com/coded/spring/service/OrderService.kt | 34 +++++++++++++------ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/src/main/kotlin/com/coded/spring/controller/MenuController.kt index d56f29e..6d13067 100644 --- a/src/main/kotlin/com/coded/spring/controller/MenuController.kt +++ b/src/main/kotlin/com/coded/spring/controller/MenuController.kt @@ -15,9 +15,8 @@ class MenuController(val menuService: MenuService) { @PostMapping("/menu/add") fun addItemsToMenu(@RequestBody menuRequest: MenuRequest) : Any { - val menuEntity = MenuEntity(name = menuRequest.name, price = menuRequest.price) - menuService.addItems(menuEntity) - return ResponseEntity.ok().body("Menu items added successfully.") + + return ResponseEntity.ok().body(menuService.addItems(menuRequest)) } @GetMapping("/public/menu/list") diff --git a/src/main/kotlin/com/coded/spring/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt index 526e479..75d427c 100644 --- a/src/main/kotlin/com/coded/spring/service/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/service/MenuService.kt @@ -1,13 +1,23 @@ package com.coded.spring.service +import com.coded.spring.controller.MenuRequest import com.coded.spring.entity.MenuEntity import com.coded.spring.repository.MenuRepository +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service +import java.math.BigDecimal @Service -class MenuService(val menuRepository: MenuRepository){ +class MenuService( + val menuRepository: MenuRepository, +){ - fun addItems(menuEntity: MenuEntity) = menuRepository.save(menuEntity) + fun addItems(menu: MenuRequest) { + + menuRepository.save(MenuEntity( + name = menu.name, + price = menu.price)) + } fun listMenuItems() = menuRepository.findAll() } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt index 4bc32e7..9c16a04 100644 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/service/OrderService.kt @@ -5,6 +5,7 @@ import com.coded.spring.entity.OrderEntity import com.coded.spring.repository.ItemRepository import com.coded.spring.repository.OrderRepository import com.coded.spring.repository.UserRepository +import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpStatus import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service @@ -15,7 +16,9 @@ import java.math.BigDecimal class OrderService( private var orderRepository: OrderRepository, private var itemRepository: ItemRepository, - private var userRepository: UserRepository + private var userRepository: UserRepository, + @Value("\${discount.feature}") + val discount: Boolean ) { fun createOrder(request: OrderRequest) { @@ -33,16 +36,27 @@ class OrderService( userId = userId ) val savedOrder = orderRepository.save(order) - val items = request.items.map { - ItemEntity( - name = it.name, - quantity = it.quantity, - price = it.price, - orderId = savedOrder.id!! - ) - } - itemRepository.saveAll(items) + if (discount){ + itemRepository.saveAll(request.items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + price = it.price.multiply(BigDecimal(0.8)), + orderId = savedOrder.id!! + ) + }) + } + else { + itemRepository.saveAll(request.items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + price = it.price, + orderId = savedOrder.id!! + ) + }) + } } fun listOrdersByUserID(userId: Long) { From 628b4242c73a2004baacb9802d67859b5a749c42 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Tue, 29 Apr 2025 17:58:46 +0300 Subject: [PATCH 24/26] Menu Endpoint with cache --- pom.xml | 8 ++++++++ .../kotlin/com/coded/spring/Application.kt | 9 +++++++++ .../spring/authentication/SecurityConfig.kt | 2 +- .../coded/spring/controller/MenuController.kt | 2 +- .../com/coded/spring/service/MenuService.kt | 19 ++++++++++++++++++- 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 57da2d5..069ee2d 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,14 @@ 2.6.0 + + com.hazelcast + hazelcast + 5.3.8 + + + + diff --git a/src/main/kotlin/com/coded/spring/Application.kt b/src/main/kotlin/com/coded/spring/Application.kt index 946ceab..7a4b1ce 100644 --- a/src/main/kotlin/com/coded/spring/Application.kt +++ b/src/main/kotlin/com/coded/spring/Application.kt @@ -1,12 +1,21 @@ 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 +import java.io.ObjectInputFilter @SpringBootApplication class Application fun main(args: Array) { runApplication(*args) + orderingConfig.getMapConfig("menu").setTimeToLiveSeconds(60) } +val orderingConfig = Config("online-ordering-cache") +val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(orderingConfig) + diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt index 4af0678..9339113 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt @@ -32,7 +32,7 @@ class SecurityConfig( it // .anyRequest().permitAll() // permit all - .requestMatchers("/auth/**", "/public/users/create", "/api-docs", "/hello").permitAll() + .requestMatchers("/auth/**", "/public/**", "/api-docs", "/hello").permitAll() .anyRequest() .authenticated() } diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/src/main/kotlin/com/coded/spring/controller/MenuController.kt index 6d13067..62d4c7a 100644 --- a/src/main/kotlin/com/coded/spring/controller/MenuController.kt +++ b/src/main/kotlin/com/coded/spring/controller/MenuController.kt @@ -13,7 +13,7 @@ import java.math.BigDecimal @RestController class MenuController(val menuService: MenuService) { - @PostMapping("/menu/add") + @PostMapping("/auth/menu/add") fun addItemsToMenu(@RequestBody menuRequest: MenuRequest) : Any { return ResponseEntity.ok().body(menuService.addItems(menuRequest)) diff --git a/src/main/kotlin/com/coded/spring/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt index 75d427c..11acb1c 100644 --- a/src/main/kotlin/com/coded/spring/service/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/service/MenuService.kt @@ -3,6 +3,7 @@ package com.coded.spring.service import com.coded.spring.controller.MenuRequest import com.coded.spring.entity.MenuEntity import com.coded.spring.repository.MenuRepository +import com.coded.spring.serverCache import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.math.BigDecimal @@ -19,5 +20,21 @@ class MenuService( price = menu.price)) } - fun listMenuItems() = menuRepository.findAll() + fun listMenuItems(): List { + val cachedMenu = menuCache["menu"] + if (!cachedMenu.isNullOrEmpty()) + { + println() + println() + println("From cache") + println() + return cachedMenu + + } + val uncachedMenu = menuRepository.findAll() + menuCache["menu"] = uncachedMenu + return uncachedMenu + } + + val menuCache = serverCache.getMap< String,List>("menu") } \ No newline at end of file From 8046a5395ec6190a012738abd08c81969ed890f9 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Tue, 29 Apr 2025 18:40:36 +0300 Subject: [PATCH 25/26] Menu Endpoint with cache With BONUS 1 --- .../coded/spring/controller/MenuController.kt | 3 ++- .../com/coded/spring/service/MenuService.kt | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/src/main/kotlin/com/coded/spring/controller/MenuController.kt index 62d4c7a..a5973a6 100644 --- a/src/main/kotlin/com/coded/spring/controller/MenuController.kt +++ b/src/main/kotlin/com/coded/spring/controller/MenuController.kt @@ -7,6 +7,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.math.BigDecimal @Tag(name = "MenuAPI") @@ -20,7 +21,7 @@ class MenuController(val menuService: MenuService) { } @GetMapping("/public/menu/list") - fun listItems() = menuService.listMenuItems() + fun listItems(@RequestParam(required = false) search: String?) = menuService.listMenuItems(search) } data class MenuRequest( diff --git a/src/main/kotlin/com/coded/spring/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt index 11acb1c..62b0adb 100644 --- a/src/main/kotlin/com/coded/spring/service/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/service/MenuService.kt @@ -20,21 +20,28 @@ class MenuService( price = menu.price)) } - fun listMenuItems(): List { + fun listMenuItems(search: String?): List { + val cachedMenu = menuCache["menu"] + if (!cachedMenu.isNullOrEmpty()) { - println() - println() - println("From cache") - println() - return cachedMenu + println("\nFrom cache\n") + if (search != null) + return cachedMenu.filter { it.name.trim().lowercase().contains(search, true) } + else + return cachedMenu } val uncachedMenu = menuRepository.findAll() menuCache["menu"] = uncachedMenu - return uncachedMenu + if (search != null) + return uncachedMenu.filter { it.name.trim().lowercase().contains(search, true) } + else + return uncachedMenu } val menuCache = serverCache.getMap< String,List>("menu") -} \ No newline at end of file +} + + From d2fe915910e46a7d2eb7f2ea5431602cd0268386 Mon Sep 17 00:00:00 2001 From: ahjadi Date: Fri, 2 May 2025 19:47:04 +0300 Subject: [PATCH 26/26] split monolith two authentication microservice and ordering microservice --- authentication/pom.xml | 16 ++++ .../AuthenticationApplication.kt | 11 +++ .../AuthenticationController.kt | 72 ++++++++++++++++ .../CustomUserDetailsService.kt | 4 +- .../com/ali}/authentication/SecurityConfig.kt | 6 +- .../jwt/JwtAuthenticationFilter.kt | 3 +- .../com/ali}/authentication/jwt/JwtService.kt | 2 +- .../authentication/profile}/ProfileEntity.kt | 10 ++- .../profile}/ProfileRepository.kt | 3 +- .../authentication/profile}/ProfileService.kt | 7 +- .../authentication/user}/UserController.kt | 16 ++-- .../ali/authentication/user}/UserEntity.kt | 6 +- .../authentication/user}/UserRepository.kt | 6 +- .../ali/authentication/user}/UserService.kt | 17 ++-- .../src/main/resources/application.properties | 12 +++ order/pom.xml | 23 +++++ .../coded/spring/OrderingApplication.kt | 14 ++++ .../spring/client/AuthenticationClient.kt | 31 +++++++ .../coded/spring/controller/MenuController.kt | 13 +-- .../spring/controller/OrderController.kt | 4 +- .../kotlin}/coded/spring/entity/ItemEntity.kt | 2 +- .../kotlin}/coded/spring/entity/MenuEntity.kt | 7 +- .../coded/spring/entity/OrderEntity.kt | 2 +- .../coded/spring/repository/ItemRepository.kt | 4 +- .../coded/spring/repository/MenuRepository.kt | 4 +- .../spring/repository/OrderRepository.kt | 6 +- .../security/RemoteAuthenticationFilter.kt | 36 ++++++++ .../coded/spring/security/SecurityConfig.kt | 32 +++++++ .../coded/spring/service/MenuService.kt | 29 +++++++ .../coded/spring/service/OrderService.kt | 72 ++++++++++++++++ .../src/main/resources/application.properties | 12 +++ pom.xml | 7 +- .../kotlin/com/coded/spring/Application.kt | 21 ----- .../kotlin/com/coded/spring/LoggingFilter.kt | 43 ---------- .../jwt/AuthenticationController.kt | 40 --------- .../com/coded/spring/service/MenuService.kt | 47 ----------- .../com/coded/spring/service/OrderService.kt | 83 ------------------- 37 files changed, 424 insertions(+), 299 deletions(-) create mode 100644 authentication/pom.xml create mode 100644 authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt create mode 100644 authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt rename {src/main/kotlin/com/coded/spring => authentication/src/main/kotlin/com/ali}/authentication/CustomUserDetailsService.kt (85%) rename {src/main/kotlin/com/coded/spring => authentication/src/main/kotlin/com/ali}/authentication/SecurityConfig.kt (92%) rename {src/main/kotlin/com/coded/spring => authentication/src/main/kotlin/com/ali}/authentication/jwt/JwtAuthenticationFilter.kt (93%) rename {src/main/kotlin/com/coded/spring => authentication/src/main/kotlin/com/ali}/authentication/jwt/JwtService.kt (96%) rename {src/main/kotlin/com/coded/spring/entity => authentication/src/main/kotlin/com/ali/authentication/profile}/ProfileEntity.kt (53%) rename {src/main/kotlin/com/coded/spring/repository => authentication/src/main/kotlin/com/ali/authentication/profile}/ProfileRepository.kt (75%) rename {src/main/kotlin/com/coded/spring/service => authentication/src/main/kotlin/com/ali/authentication/profile}/ProfileService.kt (92%) rename {src/main/kotlin/com/coded/spring/controller => authentication/src/main/kotlin/com/ali/authentication/user}/UserController.kt (89%) rename {src/main/kotlin/com/coded/spring/entity => authentication/src/main/kotlin/com/ali/authentication/user}/UserEntity.kt (80%) rename {src/main/kotlin/com/coded/spring/repository => authentication/src/main/kotlin/com/ali/authentication/user}/UserRepository.kt (74%) rename {src/main/kotlin/com/coded/spring/service => authentication/src/main/kotlin/com/ali/authentication/user}/UserService.kt (68%) create mode 100644 authentication/src/main/resources/application.properties create mode 100644 order/pom.xml create mode 100644 order/src/main/kotlin/coded/spring/OrderingApplication.kt create mode 100644 order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/controller/MenuController.kt (71%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/controller/OrderController.kt (91%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/entity/ItemEntity.kt (90%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/entity/MenuEntity.kt (70%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/entity/OrderEntity.kt (87%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/repository/ItemRepository.kt (70%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/repository/MenuRepository.kt (70%) rename {src/main/kotlin/com => order/src/main/kotlin}/coded/spring/repository/OrderRepository.kt (62%) create mode 100644 order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt create mode 100644 order/src/main/kotlin/coded/spring/security/SecurityConfig.kt create mode 100644 order/src/main/kotlin/coded/spring/service/MenuService.kt create mode 100644 order/src/main/kotlin/coded/spring/service/OrderService.kt create mode 100644 order/src/main/resources/application.properties delete mode 100644 src/main/kotlin/com/coded/spring/Application.kt delete mode 100644 src/main/kotlin/com/coded/spring/LoggingFilter.kt delete mode 100644 src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt delete mode 100644 src/main/kotlin/com/coded/spring/service/MenuService.kt delete mode 100644 src/main/kotlin/com/coded/spring/service/OrderService.kt diff --git a/authentication/pom.xml b/authentication/pom.xml new file mode 100644 index 0000000..f693b1c --- /dev/null +++ b/authentication/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + com.coded.spring + monolith + 0.0.1-SNAPSHOT + + + authentication + + + + \ No newline at end of file diff --git a/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt new file mode 100644 index 0000000..50f7e45 --- /dev/null +++ b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationApplication.kt @@ -0,0 +1,11 @@ +package com.ali.authentication + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class AuthenticationApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt new file mode 100644 index 0000000..9cc768c --- /dev/null +++ b/authentication/src/main/kotlin/com/ali/authentication/AuthenticationController.kt @@ -0,0 +1,72 @@ +package com.ali.authentication + + +import com.ali.authentication.jwt.JwtService +import com.ali.authentication.user.UserService +import org.springframework.http.HttpStatus +import org.springframework.security.authentication.* +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.* +import org.springframework.web.server.ResponseStatusException +import java.security.Principal + + +@RestController +@RequestMapping("/auth") +class AuthenticationController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService, + private val userService: UserService +) { + + @PostMapping("/login") + fun login(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse { + val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password) + val authentication = authenticationManager.authenticate(authToken) + + if (authentication.isAuthenticated) { + val userDetails = userDetailsService.loadUserByUsername(authRequest.username) + val token = jwtService.generateToken(userDetails.username) + return AuthenticationResponse (token) + } else { + throw UsernameNotFoundException("Invalid user request!") + } + } + + @PostMapping("/check-token") + fun checkToken( + principal: Principal + ): CheckTokenResponse { + return CheckTokenResponse( + userId = userService.findByUsername(principal.name) + ) + } +// @PostMapping("/check-token") +// fun checkToken(): CheckTokenResponse { +// val auth = SecurityContextHolder.getContext().authentication +// if (auth == null || !auth.isAuthenticated) { +// throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token") +// } +// val username = auth.name +// val userId = userService.findByUsername(username) +// return CheckTokenResponse(userId) +// } +} + +data class CheckTokenResponse( + val userId: Long +) + +data class AuthenticationRequest( + val username: String, + val password: String +) + +data class AuthenticationResponse( + val token: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt b/authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt similarity index 85% rename from src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt rename to authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt index b906427..3140a07 100644 --- a/src/main/kotlin/com/coded/spring/authentication/CustomUserDetailsService.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/CustomUserDetailsService.kt @@ -1,7 +1,7 @@ -package com.coded.spring.authentication +package com.ali.authentication -import com.coded.spring.repository.UserRepository +import com.ali.authentication.user.UserRepository import org.springframework.security.core.userdetails.* import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt b/authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt similarity index 92% rename from src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt rename to authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt index 9339113..fc453bb 100644 --- a/src/main/kotlin/com/coded/spring/authentication/SecurityConfig.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/SecurityConfig.kt @@ -1,7 +1,7 @@ -package com.coded.spring.authentication +package com.ali.authentication -import com.coded.spring.authentication.jwt.JwtAuthenticationFilter +import com.ali.authentication.jwt.JwtAuthenticationFilter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationManager @@ -32,7 +32,7 @@ class SecurityConfig( it // .anyRequest().permitAll() // permit all - .requestMatchers("/auth/**", "/public/**", "/api-docs", "/hello").permitAll() + .requestMatchers("/auth/login", "/public/**", "/api-docs", "/hello").permitAll() .anyRequest() .authenticated() } diff --git a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtAuthenticationFilter.kt similarity index 93% rename from src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt rename to authentication/src/main/kotlin/com/ali/authentication/jwt/JwtAuthenticationFilter.kt index 20e39cf..d19dc66 100644 --- a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtAuthenticationFilter.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtAuthenticationFilter.kt @@ -1,4 +1,4 @@ -package com.coded.spring.authentication.jwt +package com.ali.authentication.jwt import jakarta.servlet.FilterChain @@ -21,6 +21,7 @@ class JwtAuthenticationFilter( response: HttpServletResponse, filterChain: FilterChain ) { + logger.info("JwtAuthenticationFilter running for: ${request.requestURI}") val authHeader = request.getHeader("Authorization") if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response) diff --git a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtService.kt similarity index 96% rename from src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt rename to authentication/src/main/kotlin/com/ali/authentication/jwt/JwtService.kt index 9d711df..e2af8c8 100644 --- a/src/main/kotlin/com/coded/spring/authentication/jwt/JwtService.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/jwt/JwtService.kt @@ -1,4 +1,4 @@ -package com.coded.spring.authentication.jwt +package com.ali.authentication.jwt import io.jsonwebtoken.* diff --git a/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileEntity.kt similarity index 53% rename from src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt rename to authentication/src/main/kotlin/com/ali/authentication/profile/ProfileEntity.kt index edf3665..ca5f586 100644 --- a/src/main/kotlin/com/coded/spring/entity/ProfileEntity.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileEntity.kt @@ -1,6 +1,10 @@ -package com.coded.spring.entity +package com.ali.authentication.profile -import jakarta.persistence.* +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table @Entity @Table(name = "profiles") @@ -14,4 +18,4 @@ data class ProfileEntity( var phoneNumber: String, var userId: Long, -) +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt similarity index 75% rename from src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt rename to authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt index 1cd0dfe..9cc3bd0 100644 --- a/src/main/kotlin/com/coded/spring/repository/ProfileRepository.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileRepository.kt @@ -1,6 +1,5 @@ -package com.coded.spring.repository +package com.ali.authentication.profile -import com.coded.spring.entity.ProfileEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/src/main/kotlin/com/coded/spring/service/ProfileService.kt b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt similarity index 92% rename from src/main/kotlin/com/coded/spring/service/ProfileService.kt rename to authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt index c0d88f8..06c2641 100644 --- a/src/main/kotlin/com/coded/spring/service/ProfileService.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/profile/ProfileService.kt @@ -1,8 +1,7 @@ -package com.coded.spring.service +package com.ali.authentication.profile -import com.coded.spring.entity.ProfileEntity -import com.coded.spring.repository.ProfileRepository -import com.coded.spring.repository.UserRepository + +import com.ali.authentication.user.UserRepository import org.springframework.http.HttpStatus import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/coded/spring/controller/UserController.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt similarity index 89% rename from src/main/kotlin/com/coded/spring/controller/UserController.kt rename to authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt index d089f5e..e9399ab 100644 --- a/src/main/kotlin/com/coded/spring/controller/UserController.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserController.kt @@ -1,10 +1,7 @@ -package com.coded.spring.controller +package com.ali.authentication.user -import com.coded.spring.entity.UserEntity -import com.coded.spring.service.ProfileRequest -import com.coded.spring.service.ProfileService -import com.coded.spring.service.UserRequest -import com.coded.spring.service.UserService +import com.ali.authentication.profile.ProfileRequest +import com.ali.authentication.profile.ProfileService import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.beans.factory.annotation.Value import org.springframework.http.ResponseEntity @@ -13,6 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController + @Tag(name = "CustomerAPI") @RestController class UserController( @@ -69,8 +67,4 @@ class UserController( } } -} - - - - +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt similarity index 80% rename from src/main/kotlin/com/coded/spring/entity/UserEntity.kt rename to authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt index 606d770..5973aa5 100644 --- a/src/main/kotlin/com/coded/spring/entity/UserEntity.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserEntity.kt @@ -1,11 +1,9 @@ -package com.coded.spring.entity +package com.ali.authentication.user -import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id -import jakarta.persistence.OneToMany import jakarta.persistence.Table @Entity @@ -19,4 +17,4 @@ data class UserEntity( var email: String, val username: String, val password: String, -) +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt similarity index 74% rename from src/main/kotlin/com/coded/spring/repository/UserRepository.kt rename to authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt index c0cd69e..0eb646e 100644 --- a/src/main/kotlin/com/coded/spring/repository/UserRepository.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserRepository.kt @@ -1,7 +1,5 @@ -package com.coded.spring.repository +package com.ali.authentication.user - -import com.coded.spring.entity.UserEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -9,4 +7,4 @@ import org.springframework.stereotype.Repository interface UserRepository : JpaRepository { fun findByUsername(username: String) : UserEntity? -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/service/UserService.kt b/authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt similarity index 68% rename from src/main/kotlin/com/coded/spring/service/UserService.kt rename to authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt index cb8f1b0..a0c17ff 100644 --- a/src/main/kotlin/com/coded/spring/service/UserService.kt +++ b/authentication/src/main/kotlin/com/ali/authentication/user/UserService.kt @@ -1,12 +1,11 @@ -package com.coded.spring.service +package com.ali.authentication.user + -//import com.coded.spring.entity.UserEntity -import com.coded.spring.entity.UserEntity -import com.coded.spring.repository.UserRepository import org.springframework.stereotype.Service @Service -class UserService (private val userRepository: UserRepository){ +class UserService(private val userRepository: UserRepository) { + fun findAllUsers() = userRepository.findAll() @@ -18,7 +17,13 @@ class UserService (private val userRepository: UserRepository){ require(password.any { it.isDigit() }) { "Password must contain at least one number" } } -}data class UserRequest( + + fun findByUsername(username: String): Long = + userRepository.findByUsername(username)?.id ?: throw IllegalStateException("User has no id...") + +} + +data class UserRequest( val name: String, val email: String, val username: String, diff --git a/authentication/src/main/resources/application.properties b/authentication/src/main/resources/application.properties new file mode 100644 index 0000000..824e8b2 --- /dev/null +++ b/authentication/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port = 8080 + +spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB +spring.datasource.username=postgres +spring.datasource.password=alix +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true +spring.jpa.properties.hibe1rnate.format_sql=true + +springdoc.api-docs.path=/api-docs \ No newline at end of file diff --git a/order/pom.xml b/order/pom.xml new file mode 100644 index 0000000..3ffc190 --- /dev/null +++ b/order/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + com.coded.spring + monolith + 0.0.1-SNAPSHOT + + + order + + + com.coded.spring + authentication + 0.0.1-SNAPSHOT + compile + + + + + \ No newline at end of file diff --git a/order/src/main/kotlin/coded/spring/OrderingApplication.kt b/order/src/main/kotlin/coded/spring/OrderingApplication.kt new file mode 100644 index 0000000..e937af3 --- /dev/null +++ b/order/src/main/kotlin/coded/spring/OrderingApplication.kt @@ -0,0 +1,14 @@ +package coded.spring + + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + + +@SpringBootApplication +class OrderingApplication + +fun main(args: Array) { + runApplication(*args) +} + diff --git a/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt b/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt new file mode 100644 index 0000000..1d72033 --- /dev/null +++ b/order/src/main/kotlin/coded/spring/client/AuthenticationClient.kt @@ -0,0 +1,31 @@ +package coded.spring.client + + +import com.ali.authentication.CheckTokenResponse +import jakarta.inject.Named +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.* +import org.springframework.util.MultiValueMap +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.exchange + +@Named +class AuthenticationClient { + + fun checkToken(token: String): CheckTokenResponse { + val restTemplate = RestTemplate() + val url = "http://localhost:8080/auth/check-token" + val response = restTemplate.exchange( + url = url, + method = HttpMethod.POST, + requestEntity = HttpEntity( + MultiValueMap.fromMultiValue(mapOf("Authorization" to listOf("Bearer $token"))) + ), + object : ParameterizedTypeReference() { + } + ) + return response.body ?: throw IllegalStateException("Check token response has no body ...") + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/controller/MenuController.kt b/order/src/main/kotlin/coded/spring/controller/MenuController.kt similarity index 71% rename from src/main/kotlin/com/coded/spring/controller/MenuController.kt rename to order/src/main/kotlin/coded/spring/controller/MenuController.kt index a5973a6..e2d69b3 100644 --- a/src/main/kotlin/com/coded/spring/controller/MenuController.kt +++ b/order/src/main/kotlin/coded/spring/controller/MenuController.kt @@ -1,7 +1,6 @@ -package com.coded.spring.controller +package coded.spring.controller -import com.coded.spring.entity.MenuEntity -import com.coded.spring.service.MenuService +import coded.spring.service.MenuService import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping @@ -10,20 +9,24 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.math.BigDecimal + + @Tag(name = "MenuAPI") @RestController class MenuController(val menuService: MenuService) { @PostMapping("/auth/menu/add") - fun addItemsToMenu(@RequestBody menuRequest: MenuRequest) : Any { + fun addItemsToMenu(@RequestBody menuRequest: MenuRequest): Any { return ResponseEntity.ok().body(menuService.addItems(menuRequest)) } @GetMapping("/public/menu/list") - fun listItems(@RequestParam(required = false) search: String?) = menuService.listMenuItems(search) + fun listItems() = menuService.listMenuItems() + } + data class MenuRequest( val name: String, val price: BigDecimal diff --git a/src/main/kotlin/com/coded/spring/controller/OrderController.kt b/order/src/main/kotlin/coded/spring/controller/OrderController.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/controller/OrderController.kt rename to order/src/main/kotlin/coded/spring/controller/OrderController.kt index d3fcc76..d60fd3a 100644 --- a/src/main/kotlin/com/coded/spring/controller/OrderController.kt +++ b/order/src/main/kotlin/coded/spring/controller/OrderController.kt @@ -1,6 +1,6 @@ -package com.coded.spring.controller +package coded.spring.controller -import com.coded.spring.service.OrderService +import coded.spring.service.OrderService import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.web.bind.annotation.* @Tag(name = "OrderAPI") diff --git a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt b/order/src/main/kotlin/coded/spring/entity/ItemEntity.kt similarity index 90% rename from src/main/kotlin/com/coded/spring/entity/ItemEntity.kt rename to order/src/main/kotlin/coded/spring/entity/ItemEntity.kt index 67d28e3..5b62231 100644 --- a/src/main/kotlin/com/coded/spring/entity/ItemEntity.kt +++ b/order/src/main/kotlin/coded/spring/entity/ItemEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.entity +package coded.spring.entity import jakarta.persistence.* import java.math.BigDecimal diff --git a/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt b/order/src/main/kotlin/coded/spring/entity/MenuEntity.kt similarity index 70% rename from src/main/kotlin/com/coded/spring/entity/MenuEntity.kt rename to order/src/main/kotlin/coded/spring/entity/MenuEntity.kt index 7ddfb8a..2524cc1 100644 --- a/src/main/kotlin/com/coded/spring/entity/MenuEntity.kt +++ b/order/src/main/kotlin/coded/spring/entity/MenuEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.entity +package coded.spring.entity import jakarta.persistence.* import java.math.BigDecimal @@ -14,8 +14,3 @@ data class MenuEntity( var name: String, var price: BigDecimal ) -//) { -// constructor() : this(null, "", BigDecimal.ZERO) { -// -// } -//} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt b/order/src/main/kotlin/coded/spring/entity/OrderEntity.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/entity/OrderEntity.kt rename to order/src/main/kotlin/coded/spring/entity/OrderEntity.kt index 2bb0e2c..e10b509 100644 --- a/src/main/kotlin/com/coded/spring/entity/OrderEntity.kt +++ b/order/src/main/kotlin/coded/spring/entity/OrderEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.entity +package coded.spring.entity import jakarta.persistence.* diff --git a/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt b/order/src/main/kotlin/coded/spring/repository/ItemRepository.kt similarity index 70% rename from src/main/kotlin/com/coded/spring/repository/ItemRepository.kt rename to order/src/main/kotlin/coded/spring/repository/ItemRepository.kt index 327cb57..6a29197 100644 --- a/src/main/kotlin/com/coded/spring/repository/ItemRepository.kt +++ b/order/src/main/kotlin/coded/spring/repository/ItemRepository.kt @@ -1,6 +1,6 @@ -package com.coded.spring.repository +package coded.spring.repository -import com.coded.spring.entity.ItemEntity +import coded.spring.entity.ItemEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/src/main/kotlin/com/coded/spring/repository/MenuRepository.kt b/order/src/main/kotlin/coded/spring/repository/MenuRepository.kt similarity index 70% rename from src/main/kotlin/com/coded/spring/repository/MenuRepository.kt rename to order/src/main/kotlin/coded/spring/repository/MenuRepository.kt index f6cef4c..f0f4b04 100644 --- a/src/main/kotlin/com/coded/spring/repository/MenuRepository.kt +++ b/order/src/main/kotlin/coded/spring/repository/MenuRepository.kt @@ -1,6 +1,6 @@ -package com.coded.spring.repository +package coded.spring.repository -import com.coded.spring.entity.MenuEntity +import coded.spring.entity.MenuEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt b/order/src/main/kotlin/coded/spring/repository/OrderRepository.kt similarity index 62% rename from src/main/kotlin/com/coded/spring/repository/OrderRepository.kt rename to order/src/main/kotlin/coded/spring/repository/OrderRepository.kt index 444d6f2..4edb2c2 100644 --- a/src/main/kotlin/com/coded/spring/repository/OrderRepository.kt +++ b/order/src/main/kotlin/coded/spring/repository/OrderRepository.kt @@ -1,8 +1,6 @@ -package com.coded.spring.repository +package coded.spring.repository -import com.coded.spring.entity.OrderEntity -import com.coded.spring.entity.UserEntity -import jakarta.inject.Named +import coded.spring.entity.OrderEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt b/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt new file mode 100644 index 0000000..d2cdf59 --- /dev/null +++ b/order/src/main/kotlin/coded/spring/security/RemoteAuthenticationFilter.kt @@ -0,0 +1,36 @@ +package coded.spring.security + +import coded.spring.client.AuthenticationClient +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter +import kotlin.text.startsWith +import kotlin.text.substring + +@Component +class RemoteAuthenticationFilter( + private val authenticationClient: AuthenticationClient, +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + + logger.info("Remote authentication filter running...") + val authHeader = request.getHeader("Authorization") + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response) + return + } + + val token = authHeader.substring(7) + val result = authenticationClient.checkToken(token) + request.setAttribute("userId", result.userId) + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt b/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt new file mode 100644 index 0000000..f5e1dac --- /dev/null +++ b/order/src/main/kotlin/coded/spring/security/SecurityConfig.kt @@ -0,0 +1,32 @@ +package coded.spring.security + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + + + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val remoteAuthFilter: RemoteAuthenticationFilter +) { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.anyRequest().permitAll() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .addFilterBefore(remoteAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build() + } +} \ No newline at end of file diff --git a/order/src/main/kotlin/coded/spring/service/MenuService.kt b/order/src/main/kotlin/coded/spring/service/MenuService.kt new file mode 100644 index 0000000..1699f37 --- /dev/null +++ b/order/src/main/kotlin/coded/spring/service/MenuService.kt @@ -0,0 +1,29 @@ +package coded.spring.service +import coded.spring.controller.MenuRequest +import coded.spring.entity.MenuEntity +import coded.spring.repository.MenuRepository +import org.springframework.stereotype.Service +import kotlin.text.set + +@Service +class MenuService( + val menuRepository: MenuRepository, +) { + + fun addItems(menu: MenuRequest) { + + menuRepository.save( + MenuEntity( + name = menu.name, + price = menu.price + ) + ) + } + + fun listMenuItems(): List = menuRepository.findAll() + + + +} + + diff --git a/order/src/main/kotlin/coded/spring/service/OrderService.kt b/order/src/main/kotlin/coded/spring/service/OrderService.kt new file mode 100644 index 0000000..a79b9c0 --- /dev/null +++ b/order/src/main/kotlin/coded/spring/service/OrderService.kt @@ -0,0 +1,72 @@ +package coded.spring.service + +import coded.spring.entity.ItemEntity +import coded.spring.entity.OrderEntity +import coded.spring.repository.ItemRepository +import coded.spring.repository.OrderRepository +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import java.math.BigDecimal + +@Service +class OrderService( + private var orderRepository: OrderRepository, + private var itemRepository: ItemRepository, + @Value("\${discount.feature}") + val discount: Boolean +) { + + fun createOrder(request: OrderRequest) { + + val savedOrder =orderRepository.save(OrderEntity( + restaurant = request.restaurant, + userId = request.userId)) + + + + if (discount){ + itemRepository.saveAll(request.items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + price = it.price.multiply(BigDecimal(0.8)), + orderId = savedOrder.id!! + ) + }) + } + else { + itemRepository.saveAll(request.items.map { + ItemEntity( + name = it.name, + quantity = it.quantity, + price = it.price, + orderId = savedOrder.id!! + ) + }) + } + + } + + + fun listOrdersByUserID(userId: Long) { + orderRepository.findAllByUserId(userId) + } + + + data class ItemDto( + val name: String, + val quantity: Int, + val price: BigDecimal + ) + + data class OrderRequest( + val userId: Long, + val restaurant: String, + val items: MutableList + ) + + data class OrderResponse( + val restaurant: String, + val items: List + ) +} \ No newline at end of file diff --git a/order/src/main/resources/application.properties b/order/src/main/resources/application.properties new file mode 100644 index 0000000..36873dc --- /dev/null +++ b/order/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.application.name=Kotlin.SpringbootV2 + +server.port = 4444 + +spring.datasource.url=jdbc:postgresql://localhost:5432/OrderingDB +spring.datasource.username=postgres +spring.datasource.password=alix +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true +spring.jpa.properties.hibe1rnate.format_sql=true + +springdoc.api-docs.path=/api-docs \ No newline at end of file diff --git a/pom.xml b/pom.xml index 069ee2d..572ee68 100644 --- a/pom.xml +++ b/pom.xml @@ -9,8 +9,9 @@ com.coded.spring - Ordering + monolith 0.0.1-SNAPSHOT + pom Kotlin.SpringbootV2 Kotlin.SpringbootV2 @@ -20,6 +21,10 @@ + + authentication + order + diff --git a/src/main/kotlin/com/coded/spring/Application.kt b/src/main/kotlin/com/coded/spring/Application.kt deleted file mode 100644 index 7a4b1ce..0000000 --- a/src/main/kotlin/com/coded/spring/Application.kt +++ /dev/null @@ -1,21 +0,0 @@ -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 -import java.io.ObjectInputFilter - -@SpringBootApplication -class Application - -fun main(args: Array) { - runApplication(*args) - orderingConfig.getMapConfig("menu").setTimeToLiveSeconds(60) -} - -val orderingConfig = Config("online-ordering-cache") -val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(orderingConfig) - diff --git a/src/main/kotlin/com/coded/spring/LoggingFilter.kt b/src/main/kotlin/com/coded/spring/LoggingFilter.kt deleted file mode 100644 index f951a92..0000000 --- a/src/main/kotlin/com/coded/spring/LoggingFilter.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.coded.spring - -import jakarta.servlet.FilterChain -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.springframework.core.Ordered -import org.springframework.core.annotation.Order -import org.springframework.stereotype.Component -import org.springframework.web.filter.OncePerRequestFilter -import org.springframework.web.util.ContentCachingRequestWrapper -import org.springframework.web.util.ContentCachingResponseWrapper - -@Component -@Order(Ordered.LOWEST_PRECEDENCE) -class LoggingFilter : OncePerRequestFilter() { - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - val cachedRequest= ContentCachingRequestWrapper(request) - val cachedResponse= ContentCachingResponseWrapper(response) - filterChain.doFilter(cachedRequest, cachedResponse) - - logRequest(cachedRequest) - logResponse(cachedResponse) - cachedResponse.copyBodyToResponse() - - } - - private fun logRequest(request: ContentCachingRequestWrapper) { - val requestBody = String(request.contentAsByteArray) - // Thread safe logging for multi processing - logger.info("Request: method=${request.method}, uri=${request.requestURI}, body=$requestBody") - } - - private fun logResponse(response: ContentCachingResponseWrapper) { - val responseBody = String(response.contentAsByteArray) - logger.info("Response: status=${response.status}, body=$responseBody") - } - - -} diff --git a/src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt b/src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt deleted file mode 100644 index 009f412..0000000 --- a/src/main/kotlin/com/coded/spring/authentication/jwt/AuthenticationController.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.coded.spring.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/service/MenuService.kt b/src/main/kotlin/com/coded/spring/service/MenuService.kt deleted file mode 100644 index 62b0adb..0000000 --- a/src/main/kotlin/com/coded/spring/service/MenuService.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.coded.spring.service - -import com.coded.spring.controller.MenuRequest -import com.coded.spring.entity.MenuEntity -import com.coded.spring.repository.MenuRepository -import com.coded.spring.serverCache -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Service -import java.math.BigDecimal - -@Service -class MenuService( - val menuRepository: MenuRepository, -){ - - fun addItems(menu: MenuRequest) { - - menuRepository.save(MenuEntity( - name = menu.name, - price = menu.price)) - } - - fun listMenuItems(search: String?): List { - - val cachedMenu = menuCache["menu"] - - if (!cachedMenu.isNullOrEmpty()) - { - println("\nFrom cache\n") - - if (search != null) - return cachedMenu.filter { it.name.trim().lowercase().contains(search, true) } - else - return cachedMenu - } - val uncachedMenu = menuRepository.findAll() - menuCache["menu"] = uncachedMenu - if (search != null) - return uncachedMenu.filter { it.name.trim().lowercase().contains(search, true) } - else - return uncachedMenu - } - - val menuCache = serverCache.getMap< String,List>("menu") -} - - diff --git a/src/main/kotlin/com/coded/spring/service/OrderService.kt b/src/main/kotlin/com/coded/spring/service/OrderService.kt deleted file mode 100644 index 9c16a04..0000000 --- a/src/main/kotlin/com/coded/spring/service/OrderService.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.coded.spring.service - -import com.coded.spring.entity.ItemEntity -import com.coded.spring.entity.OrderEntity -import com.coded.spring.repository.ItemRepository -import com.coded.spring.repository.OrderRepository -import com.coded.spring.repository.UserRepository -import org.springframework.beans.factory.annotation.Value -import org.springframework.http.HttpStatus -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.stereotype.Service -import org.springframework.web.server.ResponseStatusException -import java.math.BigDecimal - -@Service -class OrderService( - private var orderRepository: OrderRepository, - private var itemRepository: ItemRepository, - private var userRepository: UserRepository, - @Value("\${discount.feature}") - val discount: Boolean -) { - - fun createOrder(request: OrderRequest) { - val userName = SecurityContextHolder.getContext().authentication.name - val userId = userRepository.findByUsername(userName)?.id - ?: throw IllegalArgumentException("User Not Found") - - val tokenUsername = SecurityContextHolder.getContext().authentication.name - val requestUsername = userRepository.findById(userId).get().username - if (tokenUsername != requestUsername) - throw ResponseStatusException(HttpStatus.FORBIDDEN, "Username mismatch") - - val order = OrderEntity( - restaurant = request.restaurant, - userId = userId - ) - val savedOrder = orderRepository.save(order) - - if (discount){ - itemRepository.saveAll(request.items.map { - ItemEntity( - name = it.name, - quantity = it.quantity, - price = it.price.multiply(BigDecimal(0.8)), - orderId = savedOrder.id!! - ) - }) - } - else { - itemRepository.saveAll(request.items.map { - ItemEntity( - name = it.name, - quantity = it.quantity, - price = it.price, - orderId = savedOrder.id!! - ) - }) - } - } - - fun listOrdersByUserID(userId: Long) { - val user = userRepository.findById(userId).orElseThrow { IllegalArgumentException("User Not Found") } - orderRepository.findAllByUserId(userId) - } - - - data class ItemDto( - val name: String, - val quantity: Int, - val price: BigDecimal - ) - - data class OrderRequest( - val restaurant: String, - val items: MutableList - ) - - data class OrderResponse( - val restaurant: String, - val items: List - ) -} \ No newline at end of file