From a3272e5cf811495e6b14e7de9c50764921cabcf2 Mon Sep 17 00:00:00 2001 From: Yousef Date: Mon, 7 Apr 2025 23:28:32 +0300 Subject: [PATCH 01/29] task 1 - welcome to online ordering --- curl.sh | 2 ++ .../ordering/controllers/HelloWorldApiController.kt | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 curl.sh create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt diff --git a/curl.sh b/curl.sh new file mode 100644 index 0000000..f16e5f0 --- /dev/null +++ b/curl.sh @@ -0,0 +1,2 @@ +curl localhost:8080/api/v1/hello-world + diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt new file mode 100644 index 0000000..1903fbd --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt @@ -0,0 +1,12 @@ +package com.coded.spring.ordering.controllers + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + + +@RestController +class HelloWorldApiController { + @GetMapping("/api/v1/hello-world") + fun helloWorld(): String = "Hello World!" + +} \ No newline at end of file From 55aed9aa4045440cdc696d4eca2234e2ad17cacb Mon Sep 17 00:00:00 2001 From: Yousef Date: Tue, 8 Apr 2025 00:50:47 +0300 Subject: [PATCH 02/29] task 2 - Post an order --- curl.sh | 7 ++++ pom.xml | 12 +++++++ .../controllers/HelloWorldApiController.kt | 4 ++- .../controllers/OrderApiController.kt | 36 +++++++++++++++++++ .../spring/ordering/domain/Extensions.kt | 19 ++++++++++ .../domain/dtos/OrderCreateRequestDto.kt | 7 ++++ .../spring/ordering/domain/dtos/OrderDto.kt | 7 ++++ .../spring/ordering/domain/entities/Order.kt | 25 +++++++++++++ .../ordering/domain/entities/OrderItem.kt | 22 ++++++++++++ 9 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt diff --git a/curl.sh b/curl.sh index f16e5f0..a4f0f17 100644 --- a/curl.sh +++ b/curl.sh @@ -1,2 +1,9 @@ curl localhost:8080/api/v1/hello-world +curl localhost:8080/api/v1/orders + +curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"user":"Sultan","restaurant":"Meme Curry", "items": ["2x Meme Monster", "1x Tempura Appetizer"]}' \ + http://localhost:8080/api/v1/orders + diff --git a/pom.xml b/pom.xml index 163ad53..ad6d32a 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,18 @@ org.jetbrains.kotlin kotlin-stdlib + + jakarta.inject + jakarta.inject-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + org.springframework.boot diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt index 1903fbd..7058d14 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt @@ -1,12 +1,14 @@ package com.coded.spring.ordering.controllers import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController +@RequestMapping("/api/v1/hello-world") class HelloWorldApiController { - @GetMapping("/api/v1/hello-world") + @GetMapping fun helloWorld(): String = "Hello World!" } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt new file mode 100644 index 0000000..bd394fb --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -0,0 +1,36 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.dtos.OrderCreateRequestDto +import com.coded.spring.ordering.domain.dtos.OrderDto +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.OrderItemRepository +import com.coded.spring.ordering.domain.entities.OrderRepository +import com.coded.spring.ordering.domain.toDto +import com.coded.spring.ordering.domain.toEntity +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.RequestMapping +import org.springframework.web.bind.annotation.RestController + + +@RestController +@RequestMapping("/api/v1/orders") +class OrderApiController( + private val orderRepository: OrderRepository, + private val orderItemRepository: OrderItemRepository +){ + + @GetMapping + fun getOrders(): List = orderRepository.findAll().map { it.toDto() } + + @PostMapping + fun createOrder(@RequestBody orderCreateRequestDto: OrderCreateRequestDto): OrderDto { + val orderEntity = orderCreateRequestDto.toEntity() + val order = orderRepository.save(orderEntity) + val items = orderItemRepository.saveAll(orderEntity.items.map { it.copy(order = order) }) + + return order.copy(items = items).toDto() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt b/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt new file mode 100644 index 0000000..0a0c353 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt @@ -0,0 +1,19 @@ +package com.coded.spring.ordering.domain + +import com.coded.spring.ordering.domain.dtos.OrderCreateRequestDto +import com.coded.spring.ordering.domain.dtos.OrderDto +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.OrderItem + +fun OrderCreateRequestDto.toEntity() = + Order( + user=this.user, + restaurant = this.restaurant, + items = this.items.map { OrderItem(product = it) } + ) + +fun Order.toDto() = OrderDto( + user=this.user, + restaurant=this.restaurant, + items=this.items.map { it.product } +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt new file mode 100644 index 0000000..974ef98 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.domain.dtos + +data class OrderCreateRequestDto( + val user: String, + val restaurant: String, + val items: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt new file mode 100644 index 0000000..4b36c44 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.domain.dtos + +data class OrderDto( + val user: String, + val restaurant: String, + val items: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt new file mode 100644 index 0000000..8407322 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface OrderRepository: JpaRepository + +@Entity +@Table(name = "orders") +data class Order( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name="username", nullable = false) + val user: String, + val restaurant: String, + + @OneToMany(mappedBy = "order", cascade = [CascadeType.DETACH], fetch = FetchType.EAGER) + val items: List +) { + constructor(): this(null, "", "", emptyList()) +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt new file mode 100644 index 0000000..5df17bd --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt @@ -0,0 +1,22 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.inject.Named +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository + +@Named +interface OrderItemRepository: JpaRepository + +@Entity +@Table(name = "order_items") +data class OrderItem( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + val product: String = "", + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "order_id") + val order: Order? = null, +) \ No newline at end of file From e165a7645d0c15a9cada7586e222901963729b72 Mon Sep 17 00:00:00 2001 From: Yousef Date: Wed, 9 Apr 2025 12:02:19 +0300 Subject: [PATCH 03/29] working on relations and custom queries --- docker-compose.yml | 8 ++++ pom.xml | 16 +++++++ .../ordering/controllers/MenuApiController.kt | 26 +++++++++++ .../controllers/OrderApiController.kt | 42 +++++++++-------- .../controllers/RestaurantApiController.kt | 23 ++++++++++ .../ordering/controllers/UserApiController.kt | 23 ++++++++++ .../spring/ordering/domain/Extensions.kt | 30 ++++++++----- .../spring/ordering/domain/dtos/OrderDto.kt | 8 +++- .../ordering/domain/dtos/OrderItemDto.kt | 1 + .../ordering/domain/dtos/OrderResponse.kt | 11 +++++ .../ordering/domain/entities/MenuItem.kt | 21 +++++++++ .../spring/ordering/domain/entities/Order.kt | 28 +++++++----- .../ordering/domain/entities/OrderItem.kt | 20 +++++---- .../ordering/domain/entities/Restaurant.kt | 20 +++++++++ .../spring/ordering/domain/entities/User.kt | 20 +++++++++ .../domain/projections/MenuItemSummary.kt | 6 +++ .../domain/projections/RestaurantDetail.kt | 7 +++ .../domain/projections/RestaurantSummary.kt | 6 +++ .../requests/MenuItemCreateRequestDto.kt | 6 +++ .../OrderCreateRequestDto.kt | 2 +- .../requests/RestaurantCreateRequestDto.kt | 6 +++ .../domain/requests/UserCreateRequestDto.kt | 7 +++ .../respositories/MenuItemRepository.kt | 10 +++++ .../respositories/OrderItemRepository.kt | 8 ++++ .../ordering/respositories/OrderRepository.kt | 9 ++++ .../respositories/RestaurantRepository.kt | 16 +++++++ .../ordering/respositories/UserRepository.kt | 10 +++++ .../ordering/services/MenuItemService.kt | 4 ++ .../ordering/services/OderItemService.kt | 4 ++ .../spring/ordering/services/OrderService.kt | 5 +++ .../ordering/services/RestaurantService.kt | 13 ++++++ .../spring/ordering/services/UserService.kt | 10 +++++ .../services/impl/RestaurantServiceImpl.kt | 35 +++++++++++++++ .../ordering/services/impl/UserServiceImpl.kt | 29 ++++++++++++ src/main/resources/application.properties | 8 ++++ .../resources/db/mirations/V1__init_db.sql | 38 ++++++++++++++++ src/main/resources/sample.sql | 45 +++++++++++++++++++ 37 files changed, 526 insertions(+), 55 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt rename src/main/kotlin/com/coded/spring/ordering/domain/{dtos => requests}/OrderCreateRequestDto.kt (69%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/UserService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt create mode 100644 src/main/resources/db/mirations/V1__init_db.sql create mode 100644 src/main/resources/sample.sql diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1c3e607 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + db: + image: postgres:latest + ports: + - "5432:5432" + restart: always + environment: + POSTGRES_PASSWORD: changemelater \ No newline at end of file diff --git a/pom.xml b/pom.xml index ad6d32a..665fb6f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,22 @@ com.h2database h2 + test + + + org.postgresql + postgresql + runtime + + + org.flywaydb + flyway-core + 10.20.1 + + + org.flywaydb + flyway-database-postgresql + 10.20.1 diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt new file mode 100644 index 0000000..8d9a93b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -0,0 +1,26 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.requests.MenuItemCreateRequestDto +import com.coded.spring.ordering.domain.entities.MenuItem +import com.coded.spring.ordering.services.MenuItemService +import com.coded.spring.ordering.services.RestaurantService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/v1/menus") +class MenuApiController( + +) { +// @GetMapping +// fun getMenus() = menuItemRepository.findAll() +// +// @PostMapping +// fun addToMenu(@RequestBody menuItem: MenuItemCreateRequestDto): ResponseEntity { +// val restaurant = restaurantRepository.findByName(menuItem.restaurantName) +// ?: return ResponseEntity.badRequest().build() +// +// val savedMenuItem = menuItemRepository.save(MenuItem(name = menuItem.name, restaurant = restaurant)) +// return ResponseEntity.ok(savedMenuItem) +// } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt index bd394fb..55e8101 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -1,12 +1,11 @@ package com.coded.spring.ordering.controllers -import com.coded.spring.ordering.domain.dtos.OrderCreateRequestDto -import com.coded.spring.ordering.domain.dtos.OrderDto -import com.coded.spring.ordering.domain.entities.Order -import com.coded.spring.ordering.domain.entities.OrderItemRepository -import com.coded.spring.ordering.domain.entities.OrderRepository -import com.coded.spring.ordering.domain.toDto -import com.coded.spring.ordering.domain.toEntity +import com.coded.spring.ordering.domain.requests.OrderCreateRequestDto +import com.coded.spring.ordering.domain.dtos.OrderResponseSummary +import com.coded.spring.ordering.domain.entities.* +//import com.coded.spring.ordering.domain.toDto +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 @@ -17,20 +16,25 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/orders") class OrderApiController( - private val orderRepository: OrderRepository, - private val orderItemRepository: OrderItemRepository -){ - @GetMapping - fun getOrders(): List = orderRepository.findAll().map { it.toDto() } +){ - @PostMapping - fun createOrder(@RequestBody orderCreateRequestDto: OrderCreateRequestDto): OrderDto { - val orderEntity = orderCreateRequestDto.toEntity() - val order = orderRepository.save(orderEntity) - val items = orderItemRepository.saveAll(orderEntity.items.map { it.copy(order = order) }) +// @GetMapping +// fun getOrders(): List = orderRepository.findOrdersSummaries() +// .map { it.toDto() } - return order.copy(items = items).toDto() - } +// @PostMapping +// fun createOrder(@RequestBody orderCreateRequestDto: OrderCreateRequestDto) +// : ResponseEntity> { +// val customer = userRepository.findByUsername(orderCreateRequestDto.user) +// val restaurant = restaurantRepository.findByName(orderCreateRequestDto.restaurant) +// if (customer == null || restaurant == null) return ResponseEntity.badRequest().build() +// val items = menuItemRepository.findByNameIn(orderCreateRequestDto.items) +// +// val order = orderRepository.save(Order(restaurant = restaurant, user = customer)) +// orderItemRepository.saveAll(orderCreateRequestDto.items.map { OrderItem(name=it, order=order) }) +// +// return ResponseEntity(items, HttpStatus.OK) +// } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt new file mode 100644 index 0000000..cca51ac --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt @@ -0,0 +1,23 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.requests.RestaurantCreateRequestDto +import com.coded.spring.ordering.domain.toEntity +import com.coded.spring.ordering.services.RestaurantService +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.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/restaurants") +class RestaurantApiController( + private val restaurantService: RestaurantService +) { + @GetMapping + fun getRestaurants() = restaurantService.allSummaries() + + @PostMapping + fun createRestaurant(@RequestBody restaurant: RestaurantCreateRequestDto) = + restaurantService.save(restaurant.toEntity()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt new file mode 100644 index 0000000..8adf11f --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt @@ -0,0 +1,23 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.requests.UserCreateRequestDto +import com.coded.spring.ordering.domain.toEntity +import com.coded.spring.ordering.services.UserService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/v1/users") +class UserApiController + (private val userService: UserService) +{ + @GetMapping + fun getUsers() = ResponseEntity.ok(userService.getUsers()) + + @PostMapping + fun createUser( + @RequestBody user: UserCreateRequestDto + ) = ResponseEntity.ok( + userService.createUser(user.toEntity()) + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt b/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt index 0a0c353..1f53caa 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt @@ -1,19 +1,25 @@ package com.coded.spring.ordering.domain -import com.coded.spring.ordering.domain.dtos.OrderCreateRequestDto -import com.coded.spring.ordering.domain.dtos.OrderDto +import com.coded.spring.ordering.domain.dtos.* +import com.coded.spring.ordering.domain.entities.MenuItem import com.coded.spring.ordering.domain.entities.Order -import com.coded.spring.ordering.domain.entities.OrderItem +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.projections.RestaurantSummary +import com.coded.spring.ordering.domain.requests.MenuItemCreateRequestDto +import com.coded.spring.ordering.domain.requests.OrderCreateRequestDto +import com.coded.spring.ordering.domain.requests.RestaurantCreateRequestDto +import com.coded.spring.ordering.domain.requests.UserCreateRequestDto -fun OrderCreateRequestDto.toEntity() = +fun UserCreateRequestDto.toEntity() = User(username = this.username, name = this.name) + +fun OrderCreateRequestDto.toEntity(user: User, restaurant: Restaurant) = Order( - user=this.user, - restaurant = this.restaurant, - items = this.items.map { OrderItem(product = it) } + user=user, + restaurant = restaurant, ) -fun Order.toDto() = OrderDto( - user=this.user, - restaurant=this.restaurant, - items=this.items.map { it.product } -) \ No newline at end of file +fun RestaurantCreateRequestDto.toEntity() = Restaurant(name = name) + +fun MenuItemCreateRequestDto.toEntity(restaurant: Restaurant) = + MenuItem(name = name, restaurant = restaurant) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt index 4b36c44..a2aed96 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt @@ -1,7 +1,11 @@ package com.coded.spring.ordering.domain.dtos +import com.coded.spring.ordering.domain.entities.OrderItem +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.User + data class OrderDto( - val user: String, - val restaurant: String, + val user: User, + val restaurant: Restaurant, val items: List ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt new file mode 100644 index 0000000..b8f4771 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt @@ -0,0 +1 @@ +package com.coded.spring.ordering.domain.dtos diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt new file mode 100644 index 0000000..847d705 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt @@ -0,0 +1,11 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.Order +import org.springframework.data.jpa.repository.JpaRepository + + +interface OrderResponseSummary { + val id: Long + fun getUserName(): String + fun getOrderItemsMenuItemsName(): List +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt new file mode 100644 index 0000000..bda18f3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt @@ -0,0 +1,21 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.persistence.* + +@Entity +@Table(name = "menu_items") +class MenuItem( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") + val id: Long? = null, + + @Column(name="name", nullable = false) + val name: String = "", + + @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) + @JoinColumn(name="restaurant_id") + val restaurant: Restaurant? = null +) { + constructor(): this(null, "", null) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt index 8407322..6f467c1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt @@ -1,25 +1,29 @@ package com.coded.spring.ordering.domain.entities -import jakarta.inject.Named +import com.coded.spring.ordering.domain.dtos.OrderResponseSummary import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository -@Named -interface OrderRepository: JpaRepository @Entity @Table(name = "orders") -data class Order( +class Order( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") val id: Long? = null, - @Column(name="username", nullable = false) - val user: String, - val restaurant: String, + @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) + @JoinColumn(name="user_id") + val user: User?, - @OneToMany(mappedBy = "order", cascade = [CascadeType.DETACH], fetch = FetchType.EAGER) - val items: List + @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) + @JoinColumn(name="restaurant_id") + val restaurant: Restaurant?, + + + @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) + val orderItems: List? = null ) { - constructor(): this(null, "", "", emptyList()) -} + constructor(): this(null, null, null) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt index 5df17bd..52485bc 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt @@ -1,22 +1,24 @@ package com.coded.spring.ordering.domain.entities -import jakarta.inject.Named import jakarta.persistence.* -import org.springframework.data.jpa.repository.JpaRepository - -@Named -interface OrderItemRepository: JpaRepository @Entity -@Table(name = "order_items") -data class OrderItem( +@Table(name = "orders_items") +class OrderItem( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, - val product: String = "", + @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) + @JoinColumn(name = "menu_item_id") + val menuItem: MenuItem? = null, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") val order: Order? = null, -) \ No newline at end of file + + @Column(name = "quantity") + val quantity: Int? = null +) { + constructor(): this(null, null, null, null) +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt new file mode 100644 index 0000000..ee1a939 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.persistence.* + +@Entity +@Table(name = "restaurants") +class Restaurant( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") + val id: Long? = null, + + @Column(name="name", nullable = false) + val name: String = "", + + @OneToMany() + val menuItems: List? = emptyList() +) { + constructor(): this(null, "", emptyList()) +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt new file mode 100644 index 0000000..11fec44 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.persistence.* + +@Entity +@Table(name = "users") +class User( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") + val id: Long? = null, + + @Column(name="username", nullable = false, unique = true) + val username: String = "", + + @Column(name="name", nullable = false) + val name: String = "" +) { + constructor(): this(null, "", "") +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt new file mode 100644 index 0000000..e5a3b5e --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.domain.projections + +interface MenuItemSummary { + val id: Long + val name: String +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt new file mode 100644 index 0000000..f104bd0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.domain.projections + +interface RestaurantDetail { + val getId: Long + val getName: String + val getItems: Iterable +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt new file mode 100644 index 0000000..c2e924a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.domain.projections + +interface RestaurantSummary { + val id: Long + val name: String +} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt new file mode 100644 index 0000000..bab7b28 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.domain.requests + +data class MenuItemCreateRequestDto( + val name: String, + val restaurantName: String +) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt similarity index 69% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 974ef98..18fb4b7 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.domain.requests data class OrderCreateRequestDto( val user: String, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt new file mode 100644 index 0000000..cc4addc --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.domain.requests + + +data class RestaurantCreateRequestDto( + val name: String +) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt new file mode 100644 index 0000000..604a0b4 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.domain.requests + + +data class UserCreateRequestDto( + val name: String, + val username: String +) diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt new file mode 100644 index 0000000..35c8024 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.respositories + +import com.coded.spring.ordering.domain.entities.MenuItem +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MenuItemRepository: JpaRepository { + fun findByName(name: String): MenuItem? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt new file mode 100644 index 0000000..f7a33d0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt @@ -0,0 +1,8 @@ +package com.coded.spring.ordering.respositories + +import com.coded.spring.ordering.domain.entities.OrderItem +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderItemRepository: JpaRepository diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt new file mode 100644 index 0000000..faf65b1 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt @@ -0,0 +1,9 @@ +package com.coded.spring.ordering.respositories + + +import com.coded.spring.ordering.domain.entities.Order +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderRepository: JpaRepository {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt new file mode 100644 index 0000000..3202d68 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering.respositories + +import com.coded.spring.ordering.domain.projections.RestaurantSummary +import com.coded.spring.ordering.domain.entities.Restaurant +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.stereotype.Repository + +@Repository +interface RestaurantRepository: JpaRepository { + + @Query("SELECT (r.id, r.name) FROM Restaurant r") + fun all(): List + + fun findByName(name: String): Restaurant? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt new file mode 100644 index 0000000..8a720df --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.respositories + +import com.coded.spring.ordering.domain.entities.User +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UserRepository: JpaRepository { + fun findByUsername(username: String): User? +} diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt new file mode 100644 index 0000000..38354a2 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt @@ -0,0 +1,4 @@ +package com.coded.spring.ordering.services + + +interface MenuItemService {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt new file mode 100644 index 0000000..3f51a5e --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt @@ -0,0 +1,4 @@ +package com.coded.spring.ordering.services + +interface OderItemService { +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt new file mode 100644 index 0000000..97bcdbe --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering.services + +interface OrderService { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt new file mode 100644 index 0000000..d600c01 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt @@ -0,0 +1,13 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.projections.RestaurantSummary + + +interface RestaurantService { + fun save(restaurant: Restaurant): Restaurant + fun allSummaries(): Iterable + fun getById(id: Long): Restaurant? + fun getByName(name: String): Restaurant? + fun create(restaurant: Restaurant): Restaurant +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt new file mode 100644 index 0000000..6d81880 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.User + +interface UserService { + fun createUser(name: User): User + fun getUsers(): Iterable + fun getUserById(id: Long): User? + fun getUserByNameOrNull(name: String): User? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt new file mode 100644 index 0000000..d1830d3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt @@ -0,0 +1,35 @@ +package com.coded.spring.ordering.services.impl + +import com.coded.spring.ordering.domain.projections.RestaurantSummary +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.respositories.RestaurantRepository +import com.coded.spring.ordering.services.RestaurantService +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class RestaurantServiceImpl ( + private val restaurantRepository: RestaurantRepository +): RestaurantService { + override fun save(restaurant: Restaurant): Restaurant { + return restaurantRepository.save(restaurant) + } + + override fun allSummaries(): Iterable { + return restaurantRepository.all() + } + + override fun getById(id: Long): Restaurant? { + return restaurantRepository.findByIdOrNull(id) + } + + override fun getByName(name: String): Restaurant? { + return restaurantRepository.findByName(name= name) + } + + override fun create(restaurant: Restaurant): Restaurant { + return restaurantRepository.save(restaurant) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt new file mode 100644 index 0000000..af57799 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt @@ -0,0 +1,29 @@ +package com.coded.spring.ordering.services.impl + +import org.springframework.stereotype.Service +import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.respositories.UserRepository +import com.coded.spring.ordering.services.UserService +import org.springframework.data.repository.findByIdOrNull + +@Service +class UserServiceImpl( + private val userRepository: UserRepository +): UserService { + override fun createUser(user: User): User { + return userRepository.save(user) + } + + override fun getUsers(): Iterable { + return userRepository.findAll() + } + + override fun getUserById(id: Long): User? { + return userRepository.findByIdOrNull(id) + } + + override fun getUserByNameOrNull(name: String): User? { + return userRepository.findByUsername(name) + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..698b40e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,9 @@ spring.application.name=Kotlin.SpringbootV2 + +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/shopping +spring.datasource.username=postgres +spring.datasource.password=changemelater +spring.flyway.baseline-on-migrate=true +spring.flyway.enabled=true +spring.jpa.show-sql=true \ No newline at end of file diff --git a/src/main/resources/db/mirations/V1__init_db.sql b/src/main/resources/db/mirations/V1__init_db.sql new file mode 100644 index 0000000..0d1ba75 --- /dev/null +++ b/src/main/resources/db/mirations/V1__init_db.sql @@ -0,0 +1,38 @@ +DROP TABLE IF EXISTS users; +CREATE TABLE "users" ( + "id" SERIAL PRIMARY KEY , + "username" VARCHAR(200), + "name" VARCHAR(200) +); + +DROP TABLE IF EXISTS "restaurants"; +CREATE TABLE "restaurants" ( + "id" SERIAL PRIMARY KEY , + "name" VARCHAR(200) +); + +DROP TABLE IF EXISTS "menu_items"; +CREATE TABLE "menu_items" ( + "id" SERIAL PRIMARY KEY, + "name" VARCHAR(255), + "restaurant_id" INT REFERENCES restaurants (id) +); + +DROP TABLE IF EXISTS "orders"; +CREATE TABLE "orders" ( + "id" SERIAL PRIMARY KEY, + "user_id" INT REFERENCES users (id), + "restaurant_id" INT REFERENCES restaurants (id) +); + +DROP TABLE IF EXISTS "orders_items"; +CREATE TABLE "orders_items" ( + "id" SERIAL PRIMARY KEY, + "menu_item_id" INT REFERENCES restaurants (id), + "order_id" INT REFERENCES orders (id), + "quantity" INT NOT NULL, + CONSTRAINT quantity_non_negative CHECK(quantity>=0) +); + + + diff --git a/src/main/resources/sample.sql b/src/main/resources/sample.sql new file mode 100644 index 0000000..83e4cc1 --- /dev/null +++ b/src/main/resources/sample.sql @@ -0,0 +1,45 @@ +INSERT INTO public.users (username, name) +VALUES + ('dude', 'some dude'), + ('bro', 'some bro'), + ('chief', 'master chief'), + ('spart', 'leonidas k'), + ('bama', 'pr. obama'), + ('santa', 'st. clause'), + ('py', 'pythonista'); + +SELECT * FROM public.users; + + +INSERT INTO public.restaurants (name) +VALUES + ('memes curry'), + ('five guys'), + ('pick'); + +SELECT * FROM public.restaurants; + +INSERT INTO public.menu_items (name, restaurant_id) +VALUES + ('noodles', 1), + ('ramen', 1), + ('burgers', 2); + +SELECT * FROM public.menu_items; + +INSERT INTO public.orders (user_id, restaurant_id) +VALUES + (1, 1), + (1, 2); + +SELECT * FROM public.orders; + + +INSERT INTO public.orders_items (menu_item_id, order_id, quantity) +VALUES + (1, 1, 3), + (2, 2, 5), + (1, 1, 1); + +SELECT * FROM public.orders_items; + From 30384a91d04e39a9577a48d2371575697a343191 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 10 Apr 2025 17:04:13 +0300 Subject: [PATCH 04/29] task 3 & 4 - ordering db and menu endpoint --- pom.xml | 10 ---- .../ordering/controllers/MenuApiController.kt | 35 ++++++------ .../controllers/OrderApiController.kt | 53 +++++++++---------- .../controllers/RestaurantApiController.kt | 21 ++++++-- .../ordering/controllers/UserApiController.kt | 14 ++++- .../spring/ordering/domain/Extensions.kt | 25 --------- .../ordering/domain/dtos/OrderCreateDto.kt | 16 ++++++ .../spring/ordering/domain/dtos/OrderDto.kt | 11 ---- .../ordering/domain/dtos/OrderItemCreate.kt | 6 +++ .../ordering/domain/dtos/OrderItemDto.kt | 1 - .../domain/entities/{MenuItem.kt => Menu.kt} | 11 ++-- .../spring/ordering/domain/entities/Order.kt | 2 - .../ordering/domain/entities/OrderItem.kt | 4 +- .../ordering/domain/entities/Restaurant.kt | 4 +- ...mSummary.kt => MenuBasicInfoProjection.kt} | 7 +-- .../domain/projections/OrderInfoProjection.kt | 39 ++++++++++++++ .../domain/projections/RestaurantDetail.kt | 7 --- .../domain/projections/RestaurantSummary.kt | 6 --- .../requests/MenuItemCreateRequestDto.kt | 14 ++++- .../domain/requests/OrderCreateRequestDto.kt | 26 +++++++-- .../requests/RestaurantCreateRequestDto.kt | 4 ++ .../domain/requests/UserCreateRequestDto.kt | 4 ++ .../ordering/repositories/MenuRepository.kt | 12 +++++ .../OrderItemRepository.kt | 6 ++- .../ordering/repositories/OrderRepository.kt | 18 +++++++ .../repositories/RestaurantRepository.kt | 10 ++++ .../UserRepository.kt | 5 +- .../respositories/MenuItemRepository.kt | 10 ---- .../ordering/respositories/OrderRepository.kt | 9 ---- .../respositories/RestaurantRepository.kt | 16 ------ .../ordering/services/MenuItemService.kt | 4 -- .../spring/ordering/services/MenuService.kt | 14 +++++ .../ordering/services/MenuServiceImpl.kt | 24 +++++++++ .../ordering/services/OderItemService.kt | 4 -- .../spring/ordering/services/OrderService.kt | 10 +++- .../ordering/services/OrderServiceImpl.kt | 53 +++++++++++++++++++ .../ordering/services/RestaurantService.kt | 9 ++-- .../services/RestaurantServiceImpl.kt | 14 +++++ .../spring/ordering/services/UserService.kt | 8 +-- .../ordering/services/UserServiceImpl.kt | 14 +++++ .../services/impl/RestaurantServiceImpl.kt | 35 ------------ .../ordering/services/impl/UserServiceImpl.kt | 29 ---------- src/main/resources/application.properties | 6 +-- .../{mirations => migration}/V1__init_db.sql | 9 ++-- src/main/resources/sample.sql | 14 ++--- 45 files changed, 388 insertions(+), 265 deletions(-) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{MenuItem.kt => Menu.kt} (68%) rename src/main/kotlin/com/coded/spring/ordering/domain/projections/{MenuItemSummary.kt => MenuBasicInfoProjection.kt} (60%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt rename src/main/kotlin/com/coded/spring/ordering/{respositories => repositories}/OrderItemRepository.kt (61%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt rename src/main/kotlin/com/coded/spring/ordering/{respositories => repositories}/UserRepository.kt (70%) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt rename src/main/resources/db/{mirations => migration}/V1__init_db.sql (80%) diff --git a/pom.xml b/pom.xml index 665fb6f..3292b7a 100644 --- a/pom.xml +++ b/pom.xml @@ -65,16 +65,6 @@ postgresql runtime - - org.flywaydb - flyway-core - 10.20.1 - - - org.flywaydb - flyway-database-postgresql - 10.20.1 - org.springframework.boot diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt index 8d9a93b..de90077 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -1,26 +1,31 @@ package com.coded.spring.ordering.controllers -import com.coded.spring.ordering.domain.requests.MenuItemCreateRequestDto -import com.coded.spring.ordering.domain.entities.MenuItem -import com.coded.spring.ordering.services.MenuItemService +import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.requests.MenuCreateRequestDto +import com.coded.spring.ordering.domain.requests.toEntity +import com.coded.spring.ordering.repositories.MenuRepository import com.coded.spring.ordering.services.RestaurantService +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/api/v1/menus") class MenuApiController( - + private val menuRepository: MenuRepository, + private val restaurantService: RestaurantService ) { -// @GetMapping -// fun getMenus() = menuItemRepository.findAll() -// -// @PostMapping -// fun addToMenu(@RequestBody menuItem: MenuItemCreateRequestDto): ResponseEntity { -// val restaurant = restaurantRepository.findByName(menuItem.restaurantName) -// ?: return ResponseEntity.badRequest().build() -// -// val savedMenuItem = menuItemRepository.save(MenuItem(name = menuItem.name, restaurant = restaurant)) -// return ResponseEntity.ok(savedMenuItem) -// } + @GetMapping + fun getAll(): ResponseEntity> = ResponseEntity.ok(menuRepository.findAll()) + + @PostMapping + fun create( + @RequestBody menuCreateRequestDto: MenuCreateRequestDto + ): ResponseEntity { + val restaurant: Restaurant = restaurantService.findById(menuCreateRequestDto.restaurantId) + ?: return ResponseEntity.badRequest().build() + val newMenu = menuRepository.save(menuCreateRequestDto.toEntity(restaurant)) + return ResponseEntity(newMenu, HttpStatus.CREATED) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt index 55e8101..316cd71 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -1,40 +1,39 @@ package com.coded.spring.ordering.controllers import com.coded.spring.ordering.domain.requests.OrderCreateRequestDto -import com.coded.spring.ordering.domain.dtos.OrderResponseSummary -import com.coded.spring.ordering.domain.entities.* -//import com.coded.spring.ordering.domain.toDto +import com.coded.spring.ordering.domain.requests.toCreateDto +import com.coded.spring.ordering.services.OrderService +import com.coded.spring.ordering.services.RestaurantService +import com.coded.spring.ordering.services.UserService 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.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/api/v1/orders") class OrderApiController( - + private val orderService: OrderService, + private val userService: UserService, + private val restaurantService: RestaurantService, ){ + @GetMapping + fun order() = ResponseEntity.ok(orderService.getAllOrders()) -// @GetMapping -// fun getOrders(): List = orderRepository.findOrdersSummaries() -// .map { it.toDto() } - -// @PostMapping -// fun createOrder(@RequestBody orderCreateRequestDto: OrderCreateRequestDto) -// : ResponseEntity> { -// val customer = userRepository.findByUsername(orderCreateRequestDto.user) -// val restaurant = restaurantRepository.findByName(orderCreateRequestDto.restaurant) -// if (customer == null || restaurant == null) return ResponseEntity.badRequest().build() -// val items = menuItemRepository.findByNameIn(orderCreateRequestDto.items) -// -// val order = orderRepository.save(Order(restaurant = restaurant, user = customer)) -// orderItemRepository.saveAll(orderCreateRequestDto.items.map { OrderItem(name=it, order=order) }) -// -// return ResponseEntity(items, HttpStatus.OK) -// } - + @PostMapping + fun create(@RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { + println(newOrderDto) + val user = userService.findById(newOrderDto.userId) + ?: return ResponseEntity(HttpStatus.BAD_REQUEST) + val restaurant = restaurantService.findById(newOrderDto.restaurantId) + ?: return ResponseEntity(HttpStatus.NOT_FOUND) + orderService.create( + newOrderDto.toCreateDto( + user, + restaurant, + newOrderDto.items.map { it.toCreateDto() } + ) + ) + return ResponseEntity(HttpStatus.OK) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt index cca51ac..e4907d4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt @@ -1,9 +1,13 @@ package com.coded.spring.ordering.controllers +import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.requests.RestaurantCreateRequestDto -import com.coded.spring.ordering.domain.toEntity +import com.coded.spring.ordering.domain.requests.toEntity +import com.coded.spring.ordering.services.MenuService import com.coded.spring.ordering.services.RestaurantService +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -12,12 +16,21 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/restaurants") class RestaurantApiController( - private val restaurantService: RestaurantService + private val restaurantService: RestaurantService, + private val menuService: MenuService, ) { @GetMapping - fun getRestaurants() = restaurantService.allSummaries() + fun getRestaurants() = restaurantService.findAll() @PostMapping fun createRestaurant(@RequestBody restaurant: RestaurantCreateRequestDto) = - restaurantService.save(restaurant.toEntity()) + restaurantService.create(restaurant.toEntity()) + + @GetMapping(path = ["/{id}/menu"]) + fun getRestaurantMenu(@PathVariable("id") id: Long): ResponseEntity> { + val restaurant = restaurantService.findById(id) + ?: return ResponseEntity.badRequest().build() + val menus = menuService.findByRestaurantId(restaurantId = restaurant.id!!) + return ResponseEntity.ok(menus) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt index 8adf11f..24a270f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt @@ -1,7 +1,8 @@ package com.coded.spring.ordering.controllers +import com.coded.spring.ordering.domain.entities.User import com.coded.spring.ordering.domain.requests.UserCreateRequestDto -import com.coded.spring.ordering.domain.toEntity +import com.coded.spring.ordering.domain.requests.toEntity import com.coded.spring.ordering.services.UserService import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -12,7 +13,7 @@ class UserApiController (private val userService: UserService) { @GetMapping - fun getUsers() = ResponseEntity.ok(userService.getUsers()) + fun getUsers() = ResponseEntity.ok(userService.findAll()) @PostMapping fun createUser( @@ -20,4 +21,13 @@ class UserApiController ) = ResponseEntity.ok( userService.createUser(user.toEntity()) ) + + @GetMapping(path=["/{id}"]) + fun getUser(@PathVariable("id") id: Long): ResponseEntity { + println(id) + val user = userService.findById(id) + println(user) + if (user == null) return ResponseEntity.badRequest().build() + return ResponseEntity.ok(user) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt b/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt deleted file mode 100644 index 1f53caa..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/Extensions.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.coded.spring.ordering.domain - -import com.coded.spring.ordering.domain.dtos.* -import com.coded.spring.ordering.domain.entities.MenuItem -import com.coded.spring.ordering.domain.entities.Order -import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.domain.entities.User -import com.coded.spring.ordering.domain.projections.RestaurantSummary -import com.coded.spring.ordering.domain.requests.MenuItemCreateRequestDto -import com.coded.spring.ordering.domain.requests.OrderCreateRequestDto -import com.coded.spring.ordering.domain.requests.RestaurantCreateRequestDto -import com.coded.spring.ordering.domain.requests.UserCreateRequestDto - -fun UserCreateRequestDto.toEntity() = User(username = this.username, name = this.name) - -fun OrderCreateRequestDto.toEntity(user: User, restaurant: Restaurant) = - Order( - user=user, - restaurant = restaurant, - ) - -fun RestaurantCreateRequestDto.toEntity() = Restaurant(name = name) - -fun MenuItemCreateRequestDto.toEntity(restaurant: Restaurant) = - MenuItem(name = name, restaurant = restaurant) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt new file mode 100644 index 0000000..3887cab --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.User + +data class OrderCreateDto( + val user: User, + val restaurant: Restaurant, + val items: List +) + +fun OrderCreateDto.toEntity(): Order = Order( + user = user, + restaurant = restaurant +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt deleted file mode 100644 index a2aed96..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderDto.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.coded.spring.ordering.domain.dtos - -import com.coded.spring.ordering.domain.entities.OrderItem -import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.domain.entities.User - -data class OrderDto( - val user: User, - val restaurant: Restaurant, - val items: List -) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt new file mode 100644 index 0000000..8c968b8 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.domain.dtos + +data class OrderItemCreateDto( + val itemId: Long, + val quantity: Int, +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt deleted file mode 100644 index b8f4771..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemDto.kt +++ /dev/null @@ -1 +0,0 @@ -package com.coded.spring.ordering.domain.dtos diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt similarity index 68% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt index bda18f3..e22d98e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuItem.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt @@ -3,8 +3,8 @@ package com.coded.spring.ordering.domain.entities import jakarta.persistence.* @Entity -@Table(name = "menu_items") -class MenuItem( +@Table(name = "menus") +data class Menu( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") @@ -15,7 +15,10 @@ class MenuItem( @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name="restaurant_id") - val restaurant: Restaurant? = null + val restaurant: Restaurant? = null, + + @Column(name="price") + val price: Double = 0.0, ) { - constructor(): this(null, "", null) + constructor(): this(null, "", null, 0.0) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt index 6f467c1..3ff1941 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt @@ -1,9 +1,7 @@ package com.coded.spring.ordering.domain.entities -import com.coded.spring.ordering.domain.dtos.OrderResponseSummary import jakarta.persistence.* - @Entity @Table(name = "orders") class Order( diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt index 52485bc..e47d58e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt @@ -10,8 +10,8 @@ class OrderItem( val id: Long? = null, @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) - @JoinColumn(name = "menu_item_id") - val menuItem: MenuItem? = null, + @JoinColumn(name = "menu_id") + val item: Menu? = null, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt index ee1a939..40de8d1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt @@ -13,8 +13,6 @@ class Restaurant( @Column(name="name", nullable = false) val name: String = "", - @OneToMany() - val menuItems: List? = emptyList() ) { - constructor(): this(null, "", emptyList()) + constructor(): this(null, "") } diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt similarity index 60% rename from src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt index e5a3b5e..3350683 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuItemSummary.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt @@ -1,6 +1,7 @@ package com.coded.spring.ordering.domain.projections -interface MenuItemSummary { - val id: Long +interface MenuBasicInfoProjection { val name: String -} + val id: Long + val price: Double +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt new file mode 100644 index 0000000..e94274f --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt @@ -0,0 +1,39 @@ +package com.coded.spring.ordering.domain.projections + +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.User + +data class ItemResponse( + val id: Long, + val name: String, +) + +data class OrderItemResponse( + val item: ItemResponse, + val quantity: Int, +) + +data class OrderInfoResponse( + val id: Long, + val user: User, + val restaurant: Restaurant, + val items: List +) + +interface OrderInfoProjection { + val id: Long + val user: User + val restaurant: Restaurant + val orderItems: List + + interface OrderItemInfo { + val item: MenuInfo + val quantity: Int + } + + interface MenuInfo { + val id: Long + val name: String + val price: Double + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt deleted file mode 100644 index f104bd0..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantDetail.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.coded.spring.ordering.domain.projections - -interface RestaurantDetail { - val getId: Long - val getName: String - val getItems: Iterable -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt deleted file mode 100644 index c2e924a..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantSummary.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.coded.spring.ordering.domain.projections - -interface RestaurantSummary { - val id: Long - val name: String -} diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt index bab7b28..596e032 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -1,6 +1,16 @@ package com.coded.spring.ordering.domain.requests -data class MenuItemCreateRequestDto( +import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.Restaurant + +data class MenuCreateRequestDto( val name: String, - val restaurantName: String + val restaurantId: Long, + val price: Double ) + +fun MenuCreateRequestDto.toEntity(restaurant: Restaurant): Menu = Menu( + name=name, + restaurant=restaurant, + price=price +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 18fb4b7..1dfc480 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -1,7 +1,25 @@ package com.coded.spring.ordering.domain.requests +import com.coded.spring.ordering.domain.dtos.OrderCreateDto +import com.coded.spring.ordering.domain.dtos.OrderItemCreateDto +import com.coded.spring.ordering.domain.entities.* + +data class OrderItemCreateRequestDto ( + val itemId: Long, + val quantity: Int, +) + data class OrderCreateRequestDto( - val user: String, - val restaurant: String, - val items: List -) \ No newline at end of file + val userId: Long, + val restaurantId: Long, + val items: List, +) + +fun OrderCreateRequestDto.toCreateDto( + user: User, + restaurant: Restaurant, + items: List, +) = OrderCreateDto(user=user, restaurant=restaurant, items=items) + +fun OrderItemCreateRequestDto.toCreateDto( +) = OrderItemCreateDto(itemId=itemId, quantity=quantity) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index cc4addc..bb6adbb 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -1,6 +1,10 @@ package com.coded.spring.ordering.domain.requests +import com.coded.spring.ordering.domain.entities.Restaurant + data class RestaurantCreateRequestDto( val name: String ) + +fun RestaurantCreateRequestDto.toEntity() = Restaurant(name = name) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 604a0b4..8c6e865 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -1,7 +1,11 @@ package com.coded.spring.ordering.domain.requests +import com.coded.spring.ordering.domain.entities.User + data class UserCreateRequestDto( val name: String, val username: String ) + +fun UserCreateRequestDto.toEntity() = User(name = name, username = username) diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt new file mode 100644 index 0000000..6e657e8 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt @@ -0,0 +1,12 @@ +package com.coded.spring.ordering.repositories + +import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MenuRepository: JpaRepository { + fun findByRestaurant_Id(restaurantId: Long): List + fun findAllByIdIn(menuIds: List): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt similarity index 61% rename from src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt rename to src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt index f7a33d0..44cb51c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderItemRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt @@ -1,8 +1,10 @@ -package com.coded.spring.ordering.respositories +package com.coded.spring.ordering.repositories import com.coded.spring.ordering.domain.entities.OrderItem import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository + @Repository -interface OrderItemRepository: JpaRepository +interface OrderItemRepository: JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt new file mode 100644 index 0000000..6330f70 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt @@ -0,0 +1,18 @@ +package com.coded.spring.ordering.repositories + +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.stereotype.Repository + + +@Repository +interface OrderRepository: JpaRepository { + @Query("SELECT o FROM Order o") + fun findAllProjectedBy(): List + + @Query("SELECT o FROM Order o WHERE o.id = :id") + fun findProjectedById(@Param("id") id: Long): OrderInfoProjection? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt new file mode 100644 index 0000000..186a8dd --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.repositories + +import com.coded.spring.ordering.domain.entities.Restaurant +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface RestaurantRepository: JpaRepository { + fun findByName(name: String): Restaurant? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt similarity index 70% rename from src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt rename to src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt index 8a720df..a352af7 100644 --- a/src/main/kotlin/com/coded/spring/ordering/respositories/UserRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.respositories +package com.coded.spring.ordering.repositories import com.coded.spring.ordering.domain.entities.User import org.springframework.data.jpa.repository.JpaRepository @@ -6,5 +6,4 @@ import org.springframework.stereotype.Repository @Repository interface UserRepository: JpaRepository { - fun findByUsername(username: String): User? -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt deleted file mode 100644 index 35c8024..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/respositories/MenuItemRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.coded.spring.ordering.respositories - -import com.coded.spring.ordering.domain.entities.MenuItem -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - -@Repository -interface MenuItemRepository: JpaRepository { - fun findByName(name: String): MenuItem? -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt deleted file mode 100644 index faf65b1..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/respositories/OrderRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.coded.spring.ordering.respositories - - -import com.coded.spring.ordering.domain.entities.Order -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - -@Repository -interface OrderRepository: JpaRepository {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt deleted file mode 100644 index 3202d68..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/respositories/RestaurantRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.coded.spring.ordering.respositories - -import com.coded.spring.ordering.domain.projections.RestaurantSummary -import com.coded.spring.ordering.domain.entities.Restaurant -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.data.jpa.repository.Query -import org.springframework.stereotype.Repository - -@Repository -interface RestaurantRepository: JpaRepository { - - @Query("SELECT (r.id, r.name) FROM Restaurant r") - fun all(): List - - fun findByName(name: String): Restaurant? -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt deleted file mode 100644 index 38354a2..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuItemService.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.coded.spring.ordering.services - - -interface MenuItemService {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt new file mode 100644 index 0000000..8e6b1a9 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection + + +interface MenuService { + fun findAll(): List + fun create(menuItem: Menu): Menu + fun findById(id: Long): Menu? + fun findAllIn(items: List): List + fun findByRestaurantId(restaurantId: Long): List + fun getMenusInRequestOrder(menuIds: List): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt new file mode 100644 index 0000000..42061e0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt @@ -0,0 +1,24 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.spring.ordering.repositories.MenuRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class MenuServiceImpl(private val menuRepository: MenuRepository) : MenuService { + override fun findAll(): List = menuRepository.findAll() + override fun create(menuItem: Menu): Menu = menuRepository.save(menuItem) + override fun findById(id: Long): Menu? = menuRepository.findByIdOrNull(id) + override fun findAllIn(items: List): List { + return menuRepository.findAllByIdIn(items) + } + + override fun findByRestaurantId(restaurantId: Long) + : List = menuRepository.findByRestaurant_Id(restaurantId) + + override fun getMenusInRequestOrder(menuIds: List): List { + return menuRepository.findAllByIdIn(menuIds) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt deleted file mode 100644 index 3f51a5e..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/services/OderItemService.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.coded.spring.ordering.services - -interface OderItemService { -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt index 97bcdbe..afb10c9 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt @@ -1,5 +1,13 @@ package com.coded.spring.ordering.services -interface OrderService { +import com.coded.spring.ordering.domain.dtos.OrderCreateDto +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import com.coded.spring.ordering.domain.projections.OrderInfoResponse +interface OrderService { + fun findAll(): List + fun create(newOrder: OrderCreateDto) + fun findById(id: Long): Order? + fun getAllOrders(): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt new file mode 100644 index 0000000..39817f9 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt @@ -0,0 +1,53 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.dtos.OrderCreateDto +import com.coded.spring.ordering.domain.dtos.toEntity +import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.OrderItem +import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import com.coded.spring.ordering.domain.projections.OrderInfoResponse +import com.coded.spring.ordering.domain.projections.OrderItemResponse +import com.coded.spring.ordering.repositories.MenuRepository +import com.coded.spring.ordering.repositories.OrderItemRepository +import com.coded.spring.ordering.repositories.OrderRepository +import jakarta.transaction.Transactional +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class OrderServiceImpl( + private val orderRepository: OrderRepository, + private val orderItemRepository: OrderItemRepository, + private val menuRepository: MenuRepository, +) : OrderService { + override fun findAll(): List = orderRepository.findAll() + + @Transactional + override fun create( + newOrder: OrderCreateDto + ) { + val menuIds = newOrder.items.map { it.itemId } + val foundMenus = menuRepository.findAllByIdIn(menuIds) + + val foundIds = foundMenus.mapNotNull { it.id }.toSet() + val missingIds = menuIds.filterNot { it in foundIds } + if (missingIds.isNotEmpty()) { + throw IllegalStateException("Menus not found: $missingIds") + } + + val order: Order = orderRepository.save(newOrder.toEntity()) + val orderItems = newOrder.items.map { itemDto -> + val menu = foundMenus.find { menu -> menu.id == itemDto.itemId } + ?: throw IllegalStateException("Menu not found for id: ${itemDto.itemId}") + OrderItem( + item = menu, + order = order, + quantity = itemDto.quantity + ) + } + orderItemRepository.saveAll(orderItems) + } + + override fun findById(id: Long): Order? = orderRepository.findByIdOrNull(id) + override fun getAllOrders(): List = orderRepository.findAllProjectedBy() +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt index d600c01..2f20fde 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt @@ -1,13 +1,10 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.domain.projections.RestaurantSummary - interface RestaurantService { - fun save(restaurant: Restaurant): Restaurant - fun allSummaries(): Iterable - fun getById(id: Long): Restaurant? - fun getByName(name: String): Restaurant? + fun findAll(): List fun create(restaurant: Restaurant): Restaurant + fun findById(id: Long): Restaurant? + fun findByName(name: String): Restaurant? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt new file mode 100644 index 0000000..8d56db7 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.repositories.RestaurantRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class RestaurantServiceImpl(private val restaurantRepository: RestaurantRepository) : RestaurantService { + override fun findAll(): List = restaurantRepository.findAll() + override fun create(restaurant: Restaurant): Restaurant = restaurantRepository.save(restaurant) + override fun findById(id: Long): Restaurant? = restaurantRepository.findByIdOrNull(id) + override fun findByName(name: String): Restaurant? = restaurantRepository.findByName(name) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt index 6d81880..28a682a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt @@ -2,9 +2,9 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.entities.User + interface UserService { - fun createUser(name: User): User - fun getUsers(): Iterable - fun getUserById(id: Long): User? - fun getUserByNameOrNull(name: String): User? + fun findAll(): List + fun createUser(user: User): User + fun findById(id: Long): User? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt new file mode 100644 index 0000000..c4e9f9d --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.repositories.UserRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + + +@Service +class UserServiceImpl (private val userRepository: UserRepository): UserService { + override fun findAll(): List = userRepository.findAll() + override fun createUser(user: User): User = userRepository.save(user) + override fun findById(id: Long): User? = userRepository.findByIdOrNull(id) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt deleted file mode 100644 index d1830d3..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/services/impl/RestaurantServiceImpl.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.coded.spring.ordering.services.impl - -import com.coded.spring.ordering.domain.projections.RestaurantSummary -import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.respositories.RestaurantRepository -import com.coded.spring.ordering.services.RestaurantService -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service - -@Service -class RestaurantServiceImpl ( - private val restaurantRepository: RestaurantRepository -): RestaurantService { - override fun save(restaurant: Restaurant): Restaurant { - return restaurantRepository.save(restaurant) - } - - override fun allSummaries(): Iterable { - return restaurantRepository.all() - } - - override fun getById(id: Long): Restaurant? { - return restaurantRepository.findByIdOrNull(id) - } - - override fun getByName(name: String): Restaurant? { - return restaurantRepository.findByName(name= name) - } - - override fun create(restaurant: Restaurant): Restaurant { - return restaurantRepository.save(restaurant) - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt deleted file mode 100644 index af57799..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/services/impl/UserServiceImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.coded.spring.ordering.services.impl - -import org.springframework.stereotype.Service -import com.coded.spring.ordering.domain.entities.User -import com.coded.spring.ordering.respositories.UserRepository -import com.coded.spring.ordering.services.UserService -import org.springframework.data.repository.findByIdOrNull - -@Service -class UserServiceImpl( - private val userRepository: UserRepository -): UserService { - override fun createUser(user: User): User { - return userRepository.save(user) - } - - override fun getUsers(): Iterable { - return userRepository.findAll() - } - - override fun getUserById(id: Long): User? { - return userRepository.findByIdOrNull(id) - } - - override fun getUserByNameOrNull(name: String): User? { - return userRepository.findByUsername(name) - } - -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 698b40e..2268237 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +1,9 @@ spring.application.name=Kotlin.SpringbootV2 +logging.level.org.springframework.web= DEBUG + spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/shopping spring.datasource.username=postgres spring.datasource.password=changemelater -spring.flyway.baseline-on-migrate=true -spring.flyway.enabled=true -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file diff --git a/src/main/resources/db/mirations/V1__init_db.sql b/src/main/resources/db/migration/V1__init_db.sql similarity index 80% rename from src/main/resources/db/mirations/V1__init_db.sql rename to src/main/resources/db/migration/V1__init_db.sql index 0d1ba75..b885324 100644 --- a/src/main/resources/db/mirations/V1__init_db.sql +++ b/src/main/resources/db/migration/V1__init_db.sql @@ -11,11 +11,12 @@ CREATE TABLE "restaurants" ( "name" VARCHAR(200) ); -DROP TABLE IF EXISTS "menu_items"; -CREATE TABLE "menu_items" ( +DROP TABLE IF EXISTS "menus"; +CREATE TABLE "menus" ( "id" SERIAL PRIMARY KEY, "name" VARCHAR(255), - "restaurant_id" INT REFERENCES restaurants (id) + "restaurant_id" INT REFERENCES restaurants (id), + "price" NUMERIC NOT NULL ); DROP TABLE IF EXISTS "orders"; @@ -28,7 +29,7 @@ CREATE TABLE "orders" ( DROP TABLE IF EXISTS "orders_items"; CREATE TABLE "orders_items" ( "id" SERIAL PRIMARY KEY, - "menu_item_id" INT REFERENCES restaurants (id), + "menu_id" INT REFERENCES menus (id), "order_id" INT REFERENCES orders (id), "quantity" INT NOT NULL, CONSTRAINT quantity_non_negative CHECK(quantity>=0) diff --git a/src/main/resources/sample.sql b/src/main/resources/sample.sql index 83e4cc1..13a271e 100644 --- a/src/main/resources/sample.sql +++ b/src/main/resources/sample.sql @@ -19,23 +19,23 @@ VALUES SELECT * FROM public.restaurants; -INSERT INTO public.menu_items (name, restaurant_id) +INSERT INTO public.menus (name, restaurant_id, price) VALUES - ('noodles', 1), - ('ramen', 1), - ('burgers', 2); + ('noodles', 3, 5.5), + ('ramen', 1, 6.9), + ('burgers', 2, 3.5); -SELECT * FROM public.menu_items; +SELECT * FROM public.menus; INSERT INTO public.orders (user_id, restaurant_id) VALUES (1, 1), - (1, 2); + (1, 3); SELECT * FROM public.orders; -INSERT INTO public.orders_items (menu_item_id, order_id, quantity) +INSERT INTO public.orders_items (menu_id, order_id, quantity) VALUES (1, 1, 3), (2, 2, 5), From b7a519c5880400541cd2d716c1bf5a00deedd9e8 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 10 Apr 2025 19:05:41 +0300 Subject: [PATCH 05/29] added email, password and table migration and dtos for user object --- pom.xml | 7 +++++-- .../ordering/controllers/UserApiController.kt | 13 ++++++++---- .../ordering/domain/dtos/UserResponseDto.kt | 18 ++++++++++++++++ .../spring/ordering/domain/entities/Menu.kt | 7 ++++--- .../spring/ordering/domain/entities/User.kt | 10 +++++++-- .../projections/MenuBasicInfoProjection.kt | 4 +++- .../domain/projections/OrderInfoProjection.kt | 3 ++- .../requests/MenuItemCreateRequestDto.kt | 3 ++- .../domain/requests/UserCreateRequestDto.kt | 11 ++++++++-- src/main/resources/application.properties | 3 ++- .../db/migration/V2__menu_column_change.sql | 21 +++++++++++++++++++ 11 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt create mode 100644 src/main/resources/db/migration/V2__menu_column_change.sql diff --git a/pom.xml b/pom.xml index 3292b7a..00b2af7 100644 --- a/pom.xml +++ b/pom.xml @@ -58,12 +58,10 @@ com.h2database h2 - test org.postgresql postgresql - runtime @@ -76,6 +74,11 @@ kotlin-test-junit5 test + + org.springframework.boot + spring-boot-devtools + runtime + diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt index 24a270f..29cc868 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt @@ -1,5 +1,7 @@ package com.coded.spring.ordering.controllers +import com.coded.spring.ordering.domain.dtos.UserResponseDto +import com.coded.spring.ordering.domain.dtos.toDto import com.coded.spring.ordering.domain.entities.User import com.coded.spring.ordering.domain.requests.UserCreateRequestDto import com.coded.spring.ordering.domain.requests.toEntity @@ -13,21 +15,24 @@ class UserApiController (private val userService: UserService) { @GetMapping - fun getUsers() = ResponseEntity.ok(userService.findAll()) + fun getUsers() = ResponseEntity.ok( + userService.findAll() + .map { it.toDto() } + ) @PostMapping fun createUser( @RequestBody user: UserCreateRequestDto ) = ResponseEntity.ok( - userService.createUser(user.toEntity()) + userService.createUser(user.toEntity()).toDto() ) @GetMapping(path=["/{id}"]) - fun getUser(@PathVariable("id") id: Long): ResponseEntity { + fun getUser(@PathVariable("id") id: Long): ResponseEntity { println(id) val user = userService.findById(id) println(user) if (user == null) return ResponseEntity.badRequest().build() - return ResponseEntity.ok(user) + return ResponseEntity.ok(user.toDto()) } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt new file mode 100644 index 0000000..2fe6f28 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt @@ -0,0 +1,18 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.User + + +data class UserResponseDto( + val id: Long, + val email: String, + val username: String, + val name: String +) + +fun User.toDto() = UserResponseDto( + id = id!!, + email = email, + username = username, + name = name +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt index e22d98e..a326889 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt @@ -1,6 +1,7 @@ package com.coded.spring.ordering.domain.entities import jakarta.persistence.* +import java.math.BigDecimal @Entity @Table(name = "menus") @@ -17,8 +18,8 @@ data class Menu( @JoinColumn(name="restaurant_id") val restaurant: Restaurant? = null, - @Column(name="price") - val price: Double = 0.0, + @Column(name="price", precision = 10, scale = 2, nullable = false) + val price: BigDecimal = BigDecimal(0) ) { - constructor(): this(null, "", null, 0.0) + constructor(): this(null, "", null, BigDecimal(0.0)) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt index 11fec44..e8a59e9 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt @@ -14,7 +14,13 @@ class User( val username: String = "", @Column(name="name", nullable = false) - val name: String = "" + val name: String = "", + + @Column(name="email", unique = true) + val email: String = "", + + @Column(name="password") + val password: String = "", ) { - constructor(): this(null, "", "") + constructor(): this(null, "", "", "", "") } diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt index 3350683..5970218 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt @@ -1,7 +1,9 @@ package com.coded.spring.ordering.domain.projections +import java.math.BigDecimal + interface MenuBasicInfoProjection { val name: String val id: Long - val price: Double + val price: BigDecimal } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt index e94274f..f4fcc6b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt @@ -2,6 +2,7 @@ package com.coded.spring.ordering.domain.projections import com.coded.spring.ordering.domain.entities.Restaurant import com.coded.spring.ordering.domain.entities.User +import java.math.BigDecimal data class ItemResponse( val id: Long, @@ -34,6 +35,6 @@ interface OrderInfoProjection { interface MenuInfo { val id: Long val name: String - val price: Double + val price: BigDecimal } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt index 596e032..8976196 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -2,11 +2,12 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.entities.Restaurant +import java.math.BigDecimal data class MenuCreateRequestDto( val name: String, val restaurantId: Long, - val price: Double + val price: BigDecimal, ) fun MenuCreateRequestDto.toEntity(restaurant: Restaurant): Menu = Menu( diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 8c6e865..95d1d3a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -5,7 +5,14 @@ import com.coded.spring.ordering.domain.entities.User data class UserCreateRequestDto( val name: String, - val username: String + val username: String, + val email: String, + val password: String ) -fun UserCreateRequestDto.toEntity() = User(name = name, username = username) +fun UserCreateRequestDto.toEntity() = User( + name = name, + username = username, + email = email, + password = password +) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2268237..f33fd58 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,4 +6,5 @@ spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/shopping spring.datasource.username=postgres spring.datasource.password=changemelater -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__menu_column_change.sql b/src/main/resources/db/migration/V2__menu_column_change.sql new file mode 100644 index 0000000..9ab41dc --- /dev/null +++ b/src/main/resources/db/migration/V2__menu_column_change.sql @@ -0,0 +1,21 @@ +ALTER TABLE public.users + ADD email VARCHAR(255) + CONSTRAINT unique_user_email UNIQUE + DEFAULT NULL; + +ALTER TABLE public.users + ADD password VARCHAR(255) + DEFAULT NULL; + +SELECT * FROM public.users; + + +DO $FN$ + BEGIN + FOR counter IN 1..100 LOOP + UPDATE public.users + SET email= 'someEmail' || counter || '@exmaple.com', password='secretPassword123' + WHERE id = counter; + END LOOP; + END; +$FN$ From d94f6f5c0dc0eeae100424e0b90e8cf7d6853af4 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 13 Apr 2025 11:44:22 +0300 Subject: [PATCH 06/29] added query to menus, added validation to some create requests, refactoring --- pom.xml | 16 ++++++++++ .../ordering/controllers/MenuApiController.kt | 32 +++++++++++++++---- .../controllers/OrderApiController.kt | 4 +-- .../ordering/controllers/UserApiController.kt | 9 +++--- .../domain/dtos/MenuDetailResponse.kt | 18 +++++++++++ .../domain/dtos/RestaurantInfoResponse.kt | 13 ++++++++ .../spring/ordering/domain/entities/Menu.kt | 2 ++ .../ordering/domain/entities/Restaurant.kt | 7 +++- .../projections/MenuBasicInfoProjection.kt | 10 ++++++ .../domain/projections/OrderInfoProjection.kt | 8 ++++- .../projections/RestaurantInfoProjection.kt | 7 ++++ .../domain/requests/OrderCreateRequestDto.kt | 14 ++++++++ .../requests/RestaurantCreateRequestDto.kt | 6 ++++ .../domain/requests/UserCreateRequestDto.kt | 20 ++++++++++++ .../ordering/repositories/MenuRepository.kt | 30 +++++++++++++++++ .../repositories/RestaurantRepository.kt | 8 +++++ .../spring/ordering/services/MenuService.kt | 6 +++- .../ordering/services/MenuServiceImpl.kt | 32 +++++++++++++++++-- .../spring/ordering/services/OrderService.kt | 2 +- .../ordering/services/OrderServiceImpl.kt | 2 +- .../ordering/services/RestaurantService.kt | 2 ++ .../services/RestaurantServiceImpl.kt | 5 +++ .../resources/{sample.sql => V1_sample.sql} | 0 src/main/resources/V2_sample.sql | 12 +++++++ .../resources/db/migration/V1__init_db.sql | 2 +- .../db/migration/V2__menu_column_change.sql | 11 ------- 26 files changed, 245 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt rename src/main/resources/{sample.sql => V1_sample.sql} (100%) create mode 100644 src/main/resources/V2_sample.sql diff --git a/pom.xml b/pom.xml index 00b2af7..e2e61b8 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,15 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-validation + + + jakarta.validation + jakarta.validation-api + 3.1.0 + com.h2database h2 @@ -85,6 +94,13 @@ ${project.basedir}/src/main/kotlin ${project.basedir}/src/test/kotlin + + org.apache.maven.plugins + maven-surefire-plugin + + true + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt index de90077..177f79d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -1,10 +1,12 @@ package com.coded.spring.ordering.controllers +import com.coded.spring.ordering.domain.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.domain.requests.MenuCreateRequestDto import com.coded.spring.ordering.domain.requests.toEntity -import com.coded.spring.ordering.repositories.MenuRepository +import com.coded.spring.ordering.services.MenuService import com.coded.spring.ordering.services.RestaurantService import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -13,19 +15,37 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/api/v1/menus") class MenuApiController( - private val menuRepository: MenuRepository, + private val menuService: MenuService, private val restaurantService: RestaurantService ) { @GetMapping - fun getAll(): ResponseEntity> = ResponseEntity.ok(menuRepository.findAll()) + fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) @PostMapping - fun create( + fun createMenu( @RequestBody menuCreateRequestDto: MenuCreateRequestDto ): ResponseEntity { val restaurant: Restaurant = restaurantService.findById(menuCreateRequestDto.restaurantId) ?: return ResponseEntity.badRequest().build() - val newMenu = menuRepository.save(menuCreateRequestDto.toEntity(restaurant)) + val newMenu = menuService.create(menuCreateRequestDto.toEntity(restaurant)) return ResponseEntity(newMenu, HttpStatus.CREATED) } -} \ No newline at end of file + + @GetMapping(path=["details/{menuId}"]) + fun getMenu(@PathVariable("menuId") menuId: Long): ResponseEntity { + val foundMenu = menuService.findById(menuId) + ?: return ResponseEntity.notFound().build() + return ResponseEntity(foundMenu, HttpStatus.OK) + } + + @GetMapping(path=["/search"]) + fun search( + @RequestParam("restName") restName: String?=null, + @RequestParam("menuName") foodName: String?=null + ): ResponseEntity> { + return ResponseEntity( + menuService.searchMenus(foodName, restName), + HttpStatus.OK + ) + } +} diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt index 316cd71..ba19878 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -18,10 +18,10 @@ class OrderApiController( private val restaurantService: RestaurantService, ){ @GetMapping - fun order() = ResponseEntity.ok(orderService.getAllOrders()) + fun getAllOrders() = ResponseEntity.ok(orderService.getAllOrders()) @PostMapping - fun create(@RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { + fun createOrder(@RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { println(newOrderDto) val user = userService.findById(newOrderDto.userId) ?: return ResponseEntity(HttpStatus.BAD_REQUEST) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt index 29cc868..a439870 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt @@ -2,13 +2,14 @@ package com.coded.spring.ordering.controllers import com.coded.spring.ordering.domain.dtos.UserResponseDto import com.coded.spring.ordering.domain.dtos.toDto -import com.coded.spring.ordering.domain.entities.User import com.coded.spring.ordering.domain.requests.UserCreateRequestDto import com.coded.spring.ordering.domain.requests.toEntity import com.coded.spring.ordering.services.UserService +import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* + @RestController @RequestMapping("/api/v1/users") class UserApiController @@ -22,17 +23,15 @@ class UserApiController @PostMapping fun createUser( - @RequestBody user: UserCreateRequestDto + @Valid @RequestBody user: UserCreateRequestDto ) = ResponseEntity.ok( userService.createUser(user.toEntity()).toDto() ) @GetMapping(path=["/{id}"]) fun getUser(@PathVariable("id") id: Long): ResponseEntity { - println(id) val user = userService.findById(id) - println(user) - if (user == null) return ResponseEntity.badRequest().build() + ?: return ResponseEntity.badRequest().build() return ResponseEntity.ok(user.toDto()) } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt new file mode 100644 index 0000000..0a8c799 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt @@ -0,0 +1,18 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.Menu +import java.math.BigDecimal + +data class MenuDetailResponse ( + val id: Long, + val name: String, + val price: BigDecimal, + val restaurant: RestaurantInfoResponse, +) + +fun Menu.toResponse() = MenuDetailResponse( + id = id!!, + name = name, + price = price, + restaurant = restaurant!!.toResponse(), +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt new file mode 100644 index 0000000..13d97a4 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt @@ -0,0 +1,13 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.Restaurant + +data class RestaurantInfoResponse( + val id: Long, + val name: String +) + +fun Restaurant.toResponse() = RestaurantInfoResponse( + id=id!!, + name=name +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt index a326889..a7260d7 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.domain.entities +import com.fasterxml.jackson.annotation.JsonBackReference import jakarta.persistence.* import java.math.BigDecimal @@ -16,6 +17,7 @@ data class Menu( @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name="restaurant_id") + @JsonBackReference val restaurant: Restaurant? = null, @Column(name="price", precision = 10, scale = 2, nullable = false) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt index 40de8d1..13277a4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.domain.entities +import com.fasterxml.jackson.annotation.JsonManagedReference import jakarta.persistence.* @Entity @@ -13,6 +14,10 @@ class Restaurant( @Column(name="name", nullable = false) val name: String = "", + @OneToMany(mappedBy = "restaurant", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) + @JsonManagedReference + val menus: List? = emptyList() + ) { - constructor(): this(null, "") + constructor(): this(null, "", emptyList()) } diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt index 5970218..4e63482 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt @@ -6,4 +6,14 @@ interface MenuBasicInfoProjection { val name: String val id: Long val price: BigDecimal +} + +interface MenuInfoSearchProjection : MenuBasicInfoProjection { +// val restaurant: RestaurantInfoProjection + val restaurant:RestaurantInfoProjection + + interface RestaurantInfoProjection { + val id: Float + val name: String + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt index f4fcc6b..3735f67 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt @@ -23,10 +23,16 @@ data class OrderInfoResponse( interface OrderInfoProjection { val id: Long - val user: User + val user: UserInfo val restaurant: Restaurant val orderItems: List + interface UserInfo { + val username: String + val email: String + val id: Long + } + interface OrderItemInfo { val item: MenuInfo val quantity: Int diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt new file mode 100644 index 0000000..51c59c6 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.domain.projections + +interface RestaurantInfoProjection { + val id: Float + val name: String + val menus: List +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 1dfc480..60dab9e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -3,6 +3,11 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.dtos.OrderCreateDto import com.coded.spring.ordering.domain.dtos.OrderItemCreateDto import com.coded.spring.ordering.domain.entities.* +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Positive +import jakarta.validation.constraints.PositiveOrZero +import org.hibernate.validator.constraints.Length +import org.jetbrains.annotations.NotNull data class OrderItemCreateRequestDto ( val itemId: Long, @@ -10,8 +15,17 @@ data class OrderItemCreateRequestDto ( ) data class OrderCreateRequestDto( + @field:NotBlank(message = "User Id is required") + @field:NotNull + @field:Positive(message = "User Id is must be positive") val userId: Long, + + @field:NotBlank(message = "Restaurant Id is required") + @field:NotNull + @field:Positive(message = "Restaurant Id is must be positive") val restaurantId: Long, + + val items: List, ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index bb6adbb..f3ce6ba 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -1,9 +1,15 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.Restaurant +import jakarta.validation.constraints.NotBlank +import org.hibernate.validator.constraints.Length +import org.jetbrains.annotations.NotNull data class RestaurantCreateRequestDto( + @field:NotBlank(message = "Name is required") + @field:NotNull + @field:Length(max = 3, message = "Name is too short") val name: String ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 95d1d3a..084056d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -1,12 +1,32 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.User +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import org.hibernate.validator.constraints.Length data class UserCreateRequestDto( + @field:NotBlank(message = "Name is required") + @field:NotNull val name: String, + + @field:NotBlank(message = "Email is required") + @field:NotNull val username: String, + + @field:NotBlank(message = "Password is required") + @field:Email(message = "Email is too short") + @field:NotBlank(message = "Password is too short") val email: String, + + @field:NotBlank(message = "Password is required") + @field:NotNull(message = "Password is required") + @field:Length(min = 6, message = "Password is too short") + @field:Pattern(regexp = """(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*""", message = "Password is too simple") val password: String ) diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt index 6e657e8..2cf5105 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt @@ -2,11 +2,41 @@ package com.coded.spring.ordering.repositories import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param import org.springframework.stereotype.Repository @Repository interface MenuRepository: JpaRepository { + +// +// @Query("SELECT m FROM Menu m") +// fun getMenuById(id: Long): Menu? + fun findByRestaurant_Id(restaurantId: Long): List fun findAllByIdIn(menuIds: List): List + + @Query(""" + SELECT m FROM Menu m + WHERE LOWER(m.name) LIKE LOWER(CONCAT('%', :menuName, '%')) + AND LOWER(m.restaurant.name) LIKE LOWER(CONCAT('%', :restName, '%')) + """) + fun searchByMenuAndRestaurantName( + @Param("menuName") menuName: String, + @Param("restName") restName: String + ): List + + fun findByNameContainingIgnoreCase(name: String) + : List + + @Query(""" + SELECT m FROM Menu m + WHERE LOWER(m.restaurant.name) LIKE LOWER(CONCAT('%', :restName, '%')) + """) + + fun findByRestaurantNameContainingIgnoreCase( + @Param("restName") restName: String + ): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt index 186a8dd..92fc612 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt @@ -1,10 +1,18 @@ package com.coded.spring.ordering.repositories import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @Repository interface RestaurantRepository: JpaRepository { +// @Query("SELECT r FROM Restaurant r") +// fun allRestaurants(): List + fun findByName(name: String): Restaurant? + + @Query("SELECT r FROM Restaurant r") + fun details(): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt index 8e6b1a9..e55223b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt @@ -1,14 +1,18 @@ package com.coded.spring.ordering.services +import com.coded.spring.ordering.domain.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection interface MenuService { fun findAll(): List fun create(menuItem: Menu): Menu - fun findById(id: Long): Menu? + fun findById(id: Long): MenuDetailResponse? fun findAllIn(items: List): List fun findByRestaurantId(restaurantId: Long): List fun getMenusInRequestOrder(menuIds: List): List + fun searchMenus(menuName: String?=null, restName: String?) + : List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt index 42061e0..0e0a5ba 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt @@ -1,16 +1,27 @@ package com.coded.spring.ordering.services +import com.coded.spring.ordering.domain.dtos.MenuDetailResponse +import com.coded.spring.ordering.domain.dtos.toResponse import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.repositories.MenuRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @Service -class MenuServiceImpl(private val menuRepository: MenuRepository) : MenuService { +class MenuServiceImpl( + private val menuRepository: MenuRepository +) : MenuService { override fun findAll(): List = menuRepository.findAll() - override fun create(menuItem: Menu): Menu = menuRepository.save(menuItem) - override fun findById(id: Long): Menu? = menuRepository.findByIdOrNull(id) + + override fun create(menuItem: Menu): Menu { + val menu = menuRepository.save(menuItem) + return menu + } + + override fun findById(id: Long): MenuDetailResponse? = menuRepository.findByIdOrNull(id)?.toResponse() + override fun findAllIn(items: List): List { return menuRepository.findAllByIdIn(items) } @@ -21,4 +32,19 @@ class MenuServiceImpl(private val menuRepository: MenuRepository) : MenuService override fun getMenusInRequestOrder(menuIds: List): List { return menuRepository.findAllByIdIn(menuIds) } + + override fun searchMenus(menuName: String?, restName: String?): List { + return when { + !menuName.isNullOrBlank() && !restName.isNullOrBlank() -> + menuRepository.searchByMenuAndRestaurantName( + menuName=menuName, + restName=restName + ) + !menuName.isNullOrBlank() -> menuRepository.findByNameContainingIgnoreCase(menuName) + !restName.isNullOrBlank() -> menuRepository.findByRestaurantNameContainingIgnoreCase(restName) + else -> emptyList() + } + } + + } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt index afb10c9..3171da6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt @@ -6,7 +6,7 @@ import com.coded.spring.ordering.domain.projections.OrderInfoProjection import com.coded.spring.ordering.domain.projections.OrderInfoResponse interface OrderService { - fun findAll(): List + fun findAll(): List fun create(newOrder: OrderCreateDto) fun findById(id: Long): Order? fun getAllOrders(): List diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt index 39817f9..3066f03 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt @@ -20,7 +20,7 @@ class OrderServiceImpl( private val orderItemRepository: OrderItemRepository, private val menuRepository: MenuRepository, ) : OrderService { - override fun findAll(): List = orderRepository.findAll() + override fun findAll(): List = orderRepository.findAllProjectedBy() @Transactional override fun create( diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt index 2f20fde..1e3a3fd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt @@ -1,9 +1,11 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection interface RestaurantService { fun findAll(): List + fun getInto(): List fun create(restaurant: Restaurant): Restaurant fun findById(id: Long): Restaurant? fun findByName(name: String): Restaurant? diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt index 8d56db7..8bc01d1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt @@ -1,6 +1,7 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection import com.coded.spring.ordering.repositories.RestaurantRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -8,6 +9,10 @@ import org.springframework.stereotype.Service @Service class RestaurantServiceImpl(private val restaurantRepository: RestaurantRepository) : RestaurantService { override fun findAll(): List = restaurantRepository.findAll() + override fun getInto(): List { + return restaurantRepository.details() + } + override fun create(restaurant: Restaurant): Restaurant = restaurantRepository.save(restaurant) override fun findById(id: Long): Restaurant? = restaurantRepository.findByIdOrNull(id) override fun findByName(name: String): Restaurant? = restaurantRepository.findByName(name) diff --git a/src/main/resources/sample.sql b/src/main/resources/V1_sample.sql similarity index 100% rename from src/main/resources/sample.sql rename to src/main/resources/V1_sample.sql diff --git a/src/main/resources/V2_sample.sql b/src/main/resources/V2_sample.sql new file mode 100644 index 0000000..20ce918 --- /dev/null +++ b/src/main/resources/V2_sample.sql @@ -0,0 +1,12 @@ + +-- Requires V1 data and migrations upto V2 +-- Updates users up to ID 100 with new data for email and password +DO $FN$ + BEGIN + FOR counter IN 1..100 LOOP + UPDATE public.users + SET email= 'someEmail' || counter || '@exmaple.com', password='secretPassword123' + WHERE id = counter; + END LOOP; + END; +$FN$ diff --git a/src/main/resources/db/migration/V1__init_db.sql b/src/main/resources/db/migration/V1__init_db.sql index b885324..8afef00 100644 --- a/src/main/resources/db/migration/V1__init_db.sql +++ b/src/main/resources/db/migration/V1__init_db.sql @@ -8,7 +8,7 @@ CREATE TABLE "users" ( DROP TABLE IF EXISTS "restaurants"; CREATE TABLE "restaurants" ( "id" SERIAL PRIMARY KEY , - "name" VARCHAR(200) + "name" VARCHAR(200) UNIQUE ); DROP TABLE IF EXISTS "menus"; diff --git a/src/main/resources/db/migration/V2__menu_column_change.sql b/src/main/resources/db/migration/V2__menu_column_change.sql index 9ab41dc..9620f30 100644 --- a/src/main/resources/db/migration/V2__menu_column_change.sql +++ b/src/main/resources/db/migration/V2__menu_column_change.sql @@ -8,14 +8,3 @@ ALTER TABLE public.users DEFAULT NULL; SELECT * FROM public.users; - - -DO $FN$ - BEGIN - FOR counter IN 1..100 LOOP - UPDATE public.users - SET email= 'someEmail' || counter || '@exmaple.com', password='secretPassword123' - WHERE id = counter; - END LOOP; - END; -$FN$ From b590c341d3ce4f178d26c50b3fad0f969083ae8d Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 13 Apr 2025 17:05:33 +0300 Subject: [PATCH 07/29] added validation - needs testing --- .../domain/requests/MenuItemCreateRequestDto.kt | 10 ++++++++++ .../ordering/domain/requests/OrderCreateRequestDto.kt | 6 ++++++ .../domain/requests/RestaurantCreateRequestDto.kt | 2 +- .../ordering/domain/requests/UserCreateRequestDto.kt | 1 - 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt index 8976196..c8fd32b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -2,10 +2,20 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.Menu import com.coded.spring.ordering.domain.entities.Restaurant +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Positive +import org.hibernate.validator.constraints.Length +import org.jetbrains.annotations.NotNull import java.math.BigDecimal data class MenuCreateRequestDto( + @field:NotBlank(message = "Menu Name is required") + @field:NotNull + @field:Length(min = 3, message = "Menu Name must be between 3 and 6") val name: String, + @field:NotBlank(message = "Restaurant ID is required") + @field:NotNull + @field:Positive(message = "Restaurant ID be positive") val restaurantId: Long, val price: BigDecimal, ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 60dab9e..3ab0b40 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -10,7 +10,13 @@ import org.hibernate.validator.constraints.Length import org.jetbrains.annotations.NotNull data class OrderItemCreateRequestDto ( + @field:NotBlank(message = "Item ID is required") + @field:NotNull + @field:Positive(message = "Item ID must be positive") val itemId: Long, + @field:NotBlank(message = "Amount is required") + @field:NotNull + @field:Positive(message = "Amount must be positive") val quantity: Int, ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index f3ce6ba..d246b6b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull data class RestaurantCreateRequestDto( @field:NotBlank(message = "Name is required") @field:NotNull - @field:Length(max = 3, message = "Name is too short") + @field:Length(min = 3, message = "Name is too short") val name: String ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 084056d..2763cbd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -2,7 +2,6 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.User import jakarta.validation.constraints.Email -import jakarta.validation.constraints.Min import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Pattern From 3f6c7a157c7c49198611d26b42ffdad082262c20 Mon Sep 17 00:00:00 2001 From: Yousef Date: Wed, 16 Apr 2025 08:57:43 +0300 Subject: [PATCH 08/29] removed unused comments --- .../com/coded/spring/ordering/domain/dtos/OrderResponse.kt | 3 --- .../ordering/domain/projections/MenuBasicInfoProjection.kt | 1 - .../spring/ordering/domain/requests/OrderCreateRequestDto.kt | 2 -- .../com/coded/spring/ordering/repositories/MenuRepository.kt | 4 ---- .../spring/ordering/repositories/RestaurantRepository.kt | 2 -- .../kotlin/com/coded/spring/ordering/services/OrderService.kt | 1 - .../com/coded/spring/ordering/services/OrderServiceImpl.kt | 2 -- 7 files changed, 15 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt index 847d705..1624201 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt @@ -1,8 +1,5 @@ package com.coded.spring.ordering.domain.dtos -import com.coded.spring.ordering.domain.entities.Order -import org.springframework.data.jpa.repository.JpaRepository - interface OrderResponseSummary { val id: Long diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt index 4e63482..9a7c3ad 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt @@ -9,7 +9,6 @@ interface MenuBasicInfoProjection { } interface MenuInfoSearchProjection : MenuBasicInfoProjection { -// val restaurant: RestaurantInfoProjection val restaurant:RestaurantInfoProjection interface RestaurantInfoProjection { diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 3ab0b40..400546d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -5,8 +5,6 @@ import com.coded.spring.ordering.domain.dtos.OrderItemCreateDto import com.coded.spring.ordering.domain.entities.* import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Positive -import jakarta.validation.constraints.PositiveOrZero -import org.hibernate.validator.constraints.Length import org.jetbrains.annotations.NotNull data class OrderItemCreateRequestDto ( diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt index 2cf5105..edd3470 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt @@ -11,10 +11,6 @@ import org.springframework.stereotype.Repository @Repository interface MenuRepository: JpaRepository { -// -// @Query("SELECT m FROM Menu m") -// fun getMenuById(id: Long): Menu? - fun findByRestaurant_Id(restaurantId: Long): List fun findAllByIdIn(menuIds: List): List diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt index 92fc612..3eb4db5 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt @@ -8,8 +8,6 @@ import org.springframework.stereotype.Repository @Repository interface RestaurantRepository: JpaRepository { -// @Query("SELECT r FROM Restaurant r") -// fun allRestaurants(): List fun findByName(name: String): Restaurant? diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt index 3171da6..ce47449 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt @@ -3,7 +3,6 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.OrderCreateDto import com.coded.spring.ordering.domain.entities.Order import com.coded.spring.ordering.domain.projections.OrderInfoProjection -import com.coded.spring.ordering.domain.projections.OrderInfoResponse interface OrderService { fun findAll(): List diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt index 3066f03..a89de9a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt @@ -5,8 +5,6 @@ import com.coded.spring.ordering.domain.dtos.toEntity import com.coded.spring.ordering.domain.entities.Order import com.coded.spring.ordering.domain.entities.OrderItem import com.coded.spring.ordering.domain.projections.OrderInfoProjection -import com.coded.spring.ordering.domain.projections.OrderInfoResponse -import com.coded.spring.ordering.domain.projections.OrderItemResponse import com.coded.spring.ordering.repositories.MenuRepository import com.coded.spring.ordering.repositories.OrderItemRepository import com.coded.spring.ordering.repositories.OrderRepository From 163fd8fab6c57af454514ac800e679b5e732741a Mon Sep 17 00:00:00 2001 From: Yousef Date: Wed, 16 Apr 2025 20:41:53 +0300 Subject: [PATCH 09/29] refactored and added spring security --- pom.xml | 4 +++ .../spring/ordering/config/SecurityConfig.kt | 34 +++++++++++++++++++ .../ordering/controllers/MenuApiController.kt | 10 +++--- .../domain/dtos/MenuDetailResponse.kt | 4 +-- .../ordering/domain/dtos/OrderCreateDto.kt | 10 +++--- .../domain/dtos/RestaurantInfoResponse.kt | 4 +-- .../ordering/domain/dtos/UserResponseDto.kt | 4 +-- .../entities/{Menu.kt => MenuEntity.kt} | 4 +-- .../entities/{Order.kt => OrderEntity.kt} | 8 ++--- .../{OrderItem.kt => OrderItemEntity.kt} | 6 ++-- .../{Restaurant.kt => RestaurantEntity.kt} | 4 +-- .../entities/{User.kt => UserEntity.kt} | 2 +- .../domain/projections/OrderInfoProjection.kt | 10 +++--- .../requests/MenuItemCreateRequestDto.kt | 7 ++-- .../domain/requests/OrderCreateRequestDto.kt | 4 +-- .../requests/RestaurantCreateRequestDto.kt | 4 +-- .../domain/requests/UserCreateRequestDto.kt | 4 +-- .../ordering/repositories/MenuRepository.kt | 10 +++--- .../repositories/OrderItemRepository.kt | 4 +-- .../ordering/repositories/OrderRepository.kt | 8 ++--- .../repositories/RestaurantRepository.kt | 8 ++--- .../ordering/repositories/UserRepository.kt | 5 +-- .../services/CustomerDetailsService.kt | 23 +++++++++++++ .../spring/ordering/services/MenuService.kt | 10 +++--- .../ordering/services/MenuServiceImpl.kt | 10 +++--- .../spring/ordering/services/OrderService.kt | 4 +-- .../ordering/services/OrderServiceImpl.kt | 10 +++--- .../ordering/services/RestaurantService.kt | 10 +++--- .../services/RestaurantServiceImpl.kt | 10 +++--- .../spring/ordering/services/UserService.kt | 8 ++--- .../ordering/services/UserServiceImpl.kt | 8 ++--- src/main/resources/V2_sample.sql | 7 ++-- .../resources/db/migration/V1__init_db.sql | 2 +- .../db/migration/V2__menu_column_change.sql | 8 ++--- 34 files changed, 164 insertions(+), 104 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{Menu.kt => MenuEntity.kt} (90%) rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{Order.kt => OrderEntity.kt} (81%) rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{OrderItem.kt => OrderItemEntity.kt} (84%) rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{Restaurant.kt => RestaurantEntity.kt} (88%) rename src/main/kotlin/com/coded/spring/ordering/domain/entities/{User.kt => UserEntity.kt} (96%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt diff --git a/pom.xml b/pom.xml index e2e61b8..24f3fff 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + com.fasterxml.jackson.module jackson-module-kotlin diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt new file mode 100644 index 0000000..abe3498 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -0,0 +1,34 @@ +package com.coded.spring.ordering.config + +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("/api/v1/menus") + .permitAll() + .anyRequest() + .authenticated() + } + .formLogin { it.defaultSuccessUrl("/login", true) } + .userDetailsService(userDetailsService) + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt index 177f79d..56df623 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -1,8 +1,8 @@ package com.coded.spring.ordering.controllers import com.coded.spring.ordering.domain.dtos.MenuDetailResponse -import com.coded.spring.ordering.domain.entities.Menu -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.MenuEntity +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.domain.requests.MenuCreateRequestDto import com.coded.spring.ordering.domain.requests.toEntity @@ -19,13 +19,13 @@ class MenuApiController( private val restaurantService: RestaurantService ) { @GetMapping - fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) + fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) @PostMapping fun createMenu( @RequestBody menuCreateRequestDto: MenuCreateRequestDto - ): ResponseEntity { - val restaurant: Restaurant = restaurantService.findById(menuCreateRequestDto.restaurantId) + ): ResponseEntity { + val restaurant: RestaurantEntity = restaurantService.findById(menuCreateRequestDto.restaurantId) ?: return ResponseEntity.badRequest().build() val newMenu = menuService.create(menuCreateRequestDto.toEntity(restaurant)) return ResponseEntity(newMenu, HttpStatus.CREATED) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt index 0a8c799..53463e8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.domain.dtos -import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.MenuEntity import java.math.BigDecimal data class MenuDetailResponse ( @@ -10,7 +10,7 @@ data class MenuDetailResponse ( val restaurant: RestaurantInfoResponse, ) -fun Menu.toResponse() = MenuDetailResponse( +fun MenuEntity.toResponse() = MenuDetailResponse( id = id!!, name = name, price = price, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt index 3887cab..1aa2e3a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt @@ -1,16 +1,14 @@ package com.coded.spring.ordering.domain.dtos -import com.coded.spring.ordering.domain.entities.Order -import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.* data class OrderCreateDto( - val user: User, - val restaurant: Restaurant, + val user: UserEntity, + val restaurant: RestaurantEntity, val items: List ) -fun OrderCreateDto.toEntity(): Order = Order( +fun OrderCreateDto.toEntity(): OrderEntity = OrderEntity( user = user, restaurant = restaurant ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt index 13d97a4..cc3866b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt @@ -1,13 +1,13 @@ package com.coded.spring.ordering.domain.dtos -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.RestaurantEntity data class RestaurantInfoResponse( val id: Long, val name: String ) -fun Restaurant.toResponse() = RestaurantInfoResponse( +fun RestaurantEntity.toResponse() = RestaurantInfoResponse( id=id!!, name=name ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt index 2fe6f28..aa423c1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.domain.dtos -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.UserEntity data class UserResponseDto( @@ -10,7 +10,7 @@ data class UserResponseDto( val name: String ) -fun User.toDto() = UserResponseDto( +fun UserEntity.toDto() = UserResponseDto( id = id!!, email = email, username = username, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt similarity index 90% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt index a7260d7..a1b4b40 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Menu.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt @@ -6,7 +6,7 @@ import java.math.BigDecimal @Entity @Table(name = "menus") -data class Menu( +data class MenuEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") @@ -18,7 +18,7 @@ data class Menu( @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name="restaurant_id") @JsonBackReference - val restaurant: Restaurant? = null, + val restaurant: RestaurantEntity? = null, @Column(name="price", precision = 10, scale = 2, nullable = false) val price: BigDecimal = BigDecimal(0) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt similarity index 81% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt index 3ff1941..3665c32 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Order.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt @@ -4,7 +4,7 @@ import jakarta.persistence.* @Entity @Table(name = "orders") -class Order( +class OrderEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") @@ -12,15 +12,15 @@ class Order( @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name="user_id") - val user: User?, + val user: UserEntity?, @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name="restaurant_id") - val restaurant: Restaurant?, + val restaurant: RestaurantEntity?, @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) - val orderItems: List? = null + val orderItems: List? = null ) { constructor(): this(null, null, null) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt similarity index 84% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt index e47d58e..53a13bd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItem.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt @@ -4,18 +4,18 @@ import jakarta.persistence.* @Entity @Table(name = "orders_items") -class OrderItem( +class OrderItemEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) @JoinColumn(name = "menu_id") - val item: Menu? = null, + val item: MenuEntity? = null, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") - val order: Order? = null, + val order: OrderEntity? = null, @Column(name = "quantity") val quantity: Int? = null diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt similarity index 88% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt index 13277a4..e385cb8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/Restaurant.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt @@ -5,7 +5,7 @@ import jakarta.persistence.* @Entity @Table(name = "restaurants") -class Restaurant( +class RestaurantEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") @@ -16,7 +16,7 @@ class Restaurant( @OneToMany(mappedBy = "restaurant", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) @JsonManagedReference - val menus: List? = emptyList() + val menus: List? = emptyList() ) { constructor(): this(null, "", emptyList()) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt similarity index 96% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt rename to src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt index e8a59e9..b5e50fe 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/User.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt @@ -4,7 +4,7 @@ import jakarta.persistence.* @Entity @Table(name = "users") -class User( +class UserEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt index 3735f67..19ab1f0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt @@ -1,7 +1,7 @@ package com.coded.spring.ordering.domain.projections -import com.coded.spring.ordering.domain.entities.Restaurant -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.spring.ordering.domain.entities.UserEntity import java.math.BigDecimal data class ItemResponse( @@ -16,15 +16,15 @@ data class OrderItemResponse( data class OrderInfoResponse( val id: Long, - val user: User, - val restaurant: Restaurant, + val user: UserEntity, + val restaurant: RestaurantEntity, val items: List ) interface OrderInfoProjection { val id: Long val user: UserInfo - val restaurant: Restaurant + val restaurant: RestaurantEntity val orderItems: List interface UserInfo { diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt index c8fd32b..5dfd363 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -1,7 +1,8 @@ package com.coded.spring.ordering.domain.requests -import com.coded.spring.ordering.domain.entities.Menu -import com.coded.spring.ordering.domain.entities.Restaurant + +import com.coded.spring.ordering.domain.entities.MenuEntity +import com.coded.spring.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Positive import org.hibernate.validator.constraints.Length @@ -20,7 +21,7 @@ data class MenuCreateRequestDto( val price: BigDecimal, ) -fun MenuCreateRequestDto.toEntity(restaurant: Restaurant): Menu = Menu( +fun MenuCreateRequestDto.toEntity(restaurant: RestaurantEntity): MenuEntity = MenuEntity( name=name, restaurant=restaurant, price=price diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 400546d..705db35 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -34,8 +34,8 @@ data class OrderCreateRequestDto( ) fun OrderCreateRequestDto.toCreateDto( - user: User, - restaurant: Restaurant, + user: UserEntity, + restaurant: RestaurantEntity, items: List, ) = OrderCreateDto(user=user, restaurant=restaurant, items=items) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index d246b6b..1113840 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.domain.requests -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank import org.hibernate.validator.constraints.Length import org.jetbrains.annotations.NotNull @@ -13,4 +13,4 @@ data class RestaurantCreateRequestDto( val name: String ) -fun RestaurantCreateRequestDto.toEntity() = Restaurant(name = name) \ No newline at end of file +fun RestaurantCreateRequestDto.toEntity() = RestaurantEntity(name = name) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 2763cbd..bba1ee0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.domain.requests -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.UserEntity import jakarta.validation.constraints.Email import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull @@ -29,7 +29,7 @@ data class UserCreateRequestDto( val password: String ) -fun UserCreateRequestDto.toEntity() = User( +fun UserCreateRequestDto.toEntity() = UserEntity( name = name, username = username, email = email, diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt index edd3470..5522ff1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.repositories -import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import org.springframework.data.jpa.repository.JpaRepository @@ -9,13 +9,13 @@ import org.springframework.data.repository.query.Param import org.springframework.stereotype.Repository @Repository -interface MenuRepository: JpaRepository { +interface MenuRepository: JpaRepository { fun findByRestaurant_Id(restaurantId: Long): List - fun findAllByIdIn(menuIds: List): List + fun findAllByIdIn(menuIds: List): List @Query(""" - SELECT m FROM Menu m + SELECT m FROM MenuEntity m WHERE LOWER(m.name) LIKE LOWER(CONCAT('%', :menuName, '%')) AND LOWER(m.restaurant.name) LIKE LOWER(CONCAT('%', :restName, '%')) """) @@ -28,7 +28,7 @@ interface MenuRepository: JpaRepository { : List @Query(""" - SELECT m FROM Menu m + SELECT m FROM MenuEntity m WHERE LOWER(m.restaurant.name) LIKE LOWER(CONCAT('%', :restName, '%')) """) diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt index 44cb51c..11a1aa0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt @@ -1,10 +1,10 @@ package com.coded.spring.ordering.repositories -import com.coded.spring.ordering.domain.entities.OrderItem +import com.coded.spring.ordering.domain.entities.OrderItemEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface OrderItemRepository: JpaRepository { +interface OrderItemRepository: JpaRepository { } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt index 6330f70..eecc503 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.repositories -import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query @@ -9,10 +9,10 @@ import org.springframework.stereotype.Repository @Repository -interface OrderRepository: JpaRepository { - @Query("SELECT o FROM Order o") +interface OrderRepository: JpaRepository { + @Query("SELECT o FROM OrderEntity o") fun findAllProjectedBy(): List - @Query("SELECT o FROM Order o WHERE o.id = :id") + @Query("SELECT o FROM OrderEntity o WHERE o.id = :id") fun findProjectedById(@Param("id") id: Long): OrderInfoProjection? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt index 3eb4db5..bf33e9d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt @@ -1,16 +1,16 @@ package com.coded.spring.ordering.repositories -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @Repository -interface RestaurantRepository: JpaRepository { +interface RestaurantRepository: JpaRepository { - fun findByName(name: String): Restaurant? + fun findByName(name: String): RestaurantEntity? - @Query("SELECT r FROM Restaurant r") + @Query("SELECT r FROM RestaurantEntity r") fun details(): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt index a352af7..c6c7878 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt @@ -1,9 +1,10 @@ package com.coded.spring.ordering.repositories -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.UserEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface UserRepository: JpaRepository { +interface UserRepository: JpaRepository { + fun findByUsername(username: String): UserEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt b/src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt new file mode 100644 index 0000000..1687289 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt @@ -0,0 +1,23 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.repositories.UserRepository +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UsernameNotFoundException +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/ordering/services/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt index e55223b..d4b3e2f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt @@ -1,18 +1,18 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.MenuDetailResponse -import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection interface MenuService { - fun findAll(): List - fun create(menuItem: Menu): Menu + fun findAll(): List + fun create(menuItem: MenuEntity): MenuEntity fun findById(id: Long): MenuDetailResponse? - fun findAllIn(items: List): List + fun findAllIn(items: List): List fun findByRestaurantId(restaurantId: Long): List - fun getMenusInRequestOrder(menuIds: List): List + fun getMenusInRequestOrder(menuIds: List): List fun searchMenus(menuName: String?=null, restName: String?) : List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt index 0e0a5ba..fcf41ba 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt @@ -2,7 +2,7 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.dtos.toResponse -import com.coded.spring.ordering.domain.entities.Menu +import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.repositories.MenuRepository @@ -13,23 +13,23 @@ import org.springframework.stereotype.Service class MenuServiceImpl( private val menuRepository: MenuRepository ) : MenuService { - override fun findAll(): List = menuRepository.findAll() + override fun findAll(): List = menuRepository.findAll() - override fun create(menuItem: Menu): Menu { + override fun create(menuItem: MenuEntity): MenuEntity { val menu = menuRepository.save(menuItem) return menu } override fun findById(id: Long): MenuDetailResponse? = menuRepository.findByIdOrNull(id)?.toResponse() - override fun findAllIn(items: List): List { + override fun findAllIn(items: List): List { return menuRepository.findAllByIdIn(items) } override fun findByRestaurantId(restaurantId: Long) : List = menuRepository.findByRestaurant_Id(restaurantId) - override fun getMenusInRequestOrder(menuIds: List): List { + override fun getMenusInRequestOrder(menuIds: List): List { return menuRepository.findAllByIdIn(menuIds) } diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt index ce47449..77fb743 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt @@ -1,12 +1,12 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.OrderCreateDto -import com.coded.spring.ordering.domain.entities.Order +import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection interface OrderService { fun findAll(): List fun create(newOrder: OrderCreateDto) - fun findById(id: Long): Order? + fun findById(id: Long): OrderEntity? fun getAllOrders(): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt index a89de9a..c461b41 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt @@ -2,8 +2,8 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.OrderCreateDto import com.coded.spring.ordering.domain.dtos.toEntity -import com.coded.spring.ordering.domain.entities.Order -import com.coded.spring.ordering.domain.entities.OrderItem +import com.coded.spring.ordering.domain.entities.OrderEntity +import com.coded.spring.ordering.domain.entities.OrderItemEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection import com.coded.spring.ordering.repositories.MenuRepository import com.coded.spring.ordering.repositories.OrderItemRepository @@ -33,11 +33,11 @@ class OrderServiceImpl( throw IllegalStateException("Menus not found: $missingIds") } - val order: Order = orderRepository.save(newOrder.toEntity()) + val order: OrderEntity = orderRepository.save(newOrder.toEntity()) val orderItems = newOrder.items.map { itemDto -> val menu = foundMenus.find { menu -> menu.id == itemDto.itemId } ?: throw IllegalStateException("Menu not found for id: ${itemDto.itemId}") - OrderItem( + OrderItemEntity( item = menu, order = order, quantity = itemDto.quantity @@ -46,6 +46,6 @@ class OrderServiceImpl( orderItemRepository.saveAll(orderItems) } - override fun findById(id: Long): Order? = orderRepository.findByIdOrNull(id) + override fun findById(id: Long): OrderEntity? = orderRepository.findByIdOrNull(id) override fun getAllOrders(): List = orderRepository.findAllProjectedBy() } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt index 1e3a3fd..bf4f557 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt @@ -1,12 +1,12 @@ package com.coded.spring.ordering.services -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection interface RestaurantService { - fun findAll(): List + fun findAll(): List fun getInto(): List - fun create(restaurant: Restaurant): Restaurant - fun findById(id: Long): Restaurant? - fun findByName(name: String): Restaurant? + fun create(restaurant: RestaurantEntity): RestaurantEntity + fun findById(id: Long): RestaurantEntity? + fun findByName(name: String): RestaurantEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt index 8bc01d1..0f0a8a0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.services -import com.coded.spring.ordering.domain.entities.Restaurant +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection import com.coded.spring.ordering.repositories.RestaurantRepository import org.springframework.data.repository.findByIdOrNull @@ -8,12 +8,12 @@ import org.springframework.stereotype.Service @Service class RestaurantServiceImpl(private val restaurantRepository: RestaurantRepository) : RestaurantService { - override fun findAll(): List = restaurantRepository.findAll() + override fun findAll(): List = restaurantRepository.findAll() override fun getInto(): List { return restaurantRepository.details() } - override fun create(restaurant: Restaurant): Restaurant = restaurantRepository.save(restaurant) - override fun findById(id: Long): Restaurant? = restaurantRepository.findByIdOrNull(id) - override fun findByName(name: String): Restaurant? = restaurantRepository.findByName(name) + override fun create(restaurant: RestaurantEntity): RestaurantEntity = restaurantRepository.save(restaurant) + override fun findById(id: Long): RestaurantEntity? = restaurantRepository.findByIdOrNull(id) + override fun findByName(name: String): RestaurantEntity? = restaurantRepository.findByName(name) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt index 28a682a..db757db 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt @@ -1,10 +1,10 @@ package com.coded.spring.ordering.services -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.UserEntity interface UserService { - fun findAll(): List - fun createUser(user: User): User - fun findById(id: Long): User? + fun findAll(): List + fun createUser(user: UserEntity): UserEntity + fun findById(id: Long): UserEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt index c4e9f9d..1ae8f07 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt @@ -1,6 +1,6 @@ package com.coded.spring.ordering.services -import com.coded.spring.ordering.domain.entities.User +import com.coded.spring.ordering.domain.entities.UserEntity import com.coded.spring.ordering.repositories.UserRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service @Service class UserServiceImpl (private val userRepository: UserRepository): UserService { - override fun findAll(): List = userRepository.findAll() - override fun createUser(user: User): User = userRepository.save(user) - override fun findById(id: Long): User? = userRepository.findByIdOrNull(id) + override fun findAll(): List = userRepository.findAll() + override fun createUser(user: UserEntity): UserEntity = userRepository.save(user) + override fun findById(id: Long): UserEntity? = userRepository.findByIdOrNull(id) } \ No newline at end of file diff --git a/src/main/resources/V2_sample.sql b/src/main/resources/V2_sample.sql index 20ce918..a169268 100644 --- a/src/main/resources/V2_sample.sql +++ b/src/main/resources/V2_sample.sql @@ -1,12 +1,11 @@ - -- Requires V1 data and migrations upto V2 -- Updates users up to ID 100 with new data for email and password DO $FN$ BEGIN FOR counter IN 1..100 LOOP - UPDATE public.users + UPDATE public.users SET email= 'someEmail' || counter || '@exmaple.com', password='secretPassword123' WHERE id = counter; - END LOOP; + END LOOP; END; -$FN$ +$FN$ \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__init_db.sql b/src/main/resources/db/migration/V1__init_db.sql index 8afef00..6ead5f9 100644 --- a/src/main/resources/db/migration/V1__init_db.sql +++ b/src/main/resources/db/migration/V1__init_db.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS users; CREATE TABLE "users" ( "id" SERIAL PRIMARY KEY , - "username" VARCHAR(200), + "username" VARCHAR(200) NOT NULL UNIQUE, "name" VARCHAR(200) ); diff --git a/src/main/resources/db/migration/V2__menu_column_change.sql b/src/main/resources/db/migration/V2__menu_column_change.sql index 9620f30..85d9295 100644 --- a/src/main/resources/db/migration/V2__menu_column_change.sql +++ b/src/main/resources/db/migration/V2__menu_column_change.sql @@ -1,10 +1,10 @@ ALTER TABLE public.users ADD email VARCHAR(255) - CONSTRAINT unique_user_email UNIQUE - DEFAULT NULL; + CONSTRAINT unique_user_email UNIQUE + DEFAULT NULL; ALTER TABLE public.users ADD password VARCHAR(255) - DEFAULT NULL; + DEFAULT NULL; -SELECT * FROM public.users; +SELECT * FROM public.users; \ No newline at end of file From 18aa76a25115c4725c86acdda3e27e147bde01ce Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 17 Apr 2025 16:53:43 +0300 Subject: [PATCH 10/29] User Authentication Task - Excluding authentication --- .../coded/spring/ordering/InitUserRunner.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt new file mode 100644 index 0000000..b32d243 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt @@ -0,0 +1,34 @@ +package com.coded.spring.ordering + +import com.coded.spring.ordering.domain.entities.UserEntity +import com.coded.spring.ordering.repositories.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 = "admin user", + username = "adminUser", + password = passwordEncoder.encode("password123"), + email = "adminUser@ordering.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() +//} + +// COMMENT to avoid multiple main function reference during compilation \ No newline at end of file From 781e809c250528999c33a79a9c6a2f4880b55e22 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 17 Apr 2025 18:45:17 +0300 Subject: [PATCH 11/29] profiles --- .../spring/ordering/config/SecurityConfig.kt | 2 +- .../controllers/OrderApiController.kt | 3 +- .../controllers/ProfileApiController.kt | 37 +++++++++++++++++++ .../ordering/domain/dtos/OrderCreateDto.kt | 2 +- .../domain/dtos/ProfileResponseDto.kt | 20 ++++++++++ .../ordering/domain/entities/ProfileEntity.kt | 28 ++++++++++++++ .../requests/ProfileCreateRequestDto.kt | 34 +++++++++++++++++ .../repositories/ProfileRepository.kt | 10 +++++ .../ordering/services/OrderServiceImpl.kt | 4 +- .../ordering/services/ProfileService.kt | 9 +++++ .../ordering/services/ProfileServiceImpl.kt | 32 ++++++++++++++++ .../db/migration/V3__profile_table_create.sql | 8 ++++ 12 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt create mode 100644 src/main/resources/db/migration/V3__profile_table_create.sql diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt index abe3498..d063d6f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -22,7 +22,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/menus") + it.requestMatchers("/api/v1/profiles") .permitAll() .anyRequest() .authenticated() diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt index ba19878..85ef67e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -5,6 +5,7 @@ import com.coded.spring.ordering.domain.requests.toCreateDto import com.coded.spring.ordering.services.OrderService import com.coded.spring.ordering.services.RestaurantService import com.coded.spring.ordering.services.UserService +import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @@ -21,7 +22,7 @@ class OrderApiController( fun getAllOrders() = ResponseEntity.ok(orderService.getAllOrders()) @PostMapping - fun createOrder(@RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { + fun createOrder(@Valid @RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { println(newOrderDto) val user = userService.findById(newOrderDto.userId) ?: return ResponseEntity(HttpStatus.BAD_REQUEST) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt new file mode 100644 index 0000000..f154bd4 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt @@ -0,0 +1,37 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto +import com.coded.spring.ordering.domain.dtos.toResponseDto +import com.coded.spring.ordering.services.ProfileService +import jakarta.validation.Valid +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.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/profiles") +class ProfileApiController( + private val profileService: ProfileService, +) { + + @GetMapping + fun getProfiles() = profileService.findAll() + + @PostMapping + fun createProfile(@Valid @RequestBody profileCreateDto: ProfileCreateRequestDto) + : ResponseEntity { + return try { + val profile = profileService.createProfile(profileCreateDto) + ResponseEntity(profile.toResponseDto(), HttpStatus.CREATED) + } catch (e: IllegalArgumentException) { + ResponseEntity(e.message, HttpStatus.BAD_REQUEST) + } catch (e: Exception) { + ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt index 1aa2e3a..85aa987 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt @@ -8,7 +8,7 @@ data class OrderCreateDto( val items: List ) -fun OrderCreateDto.toEntity(): OrderEntity = OrderEntity( +fun OrderCreateDto.toOrderEntity(): OrderEntity = OrderEntity( user = user, restaurant = restaurant ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt new file mode 100644 index 0000000..22822cb --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering.domain.dtos + +import com.coded.spring.ordering.domain.entities.ProfileEntity + +data class ProfileResponseDto( + val id: Long, + val userId: Long, + val firstName: String, + val lastName: String, + val phoneNumber: String, +) + + +fun ProfileEntity.toResponseDto() = ProfileResponseDto( + id=id!!, + userId=user?.id!!, + firstName=firstName!!, + lastName=lastName!!, + phoneNumber=phoneNumber!! +) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt new file mode 100644 index 0000000..c6cc349 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt @@ -0,0 +1,28 @@ +package com.coded.spring.ordering.domain.entities + +import jakarta.persistence.* + +@Entity +@Table(name = "profiles") +data class ProfileEntity ( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long? = null, + + @OneToOne + @JoinColumn(name="user_id") + val user: UserEntity? = null, + + @Column(name="first_name") + val firstName: String? = null, + + @Column(name="last_name") + val lastName: String? = null, + + @Column(name="phone_number") + val phoneNumber: String? = null, + +) { + constructor(): this(null, null, "", "", "") +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt new file mode 100644 index 0000000..d0bf9e5 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt @@ -0,0 +1,34 @@ +package com.coded.spring.ordering.domain.requests + +import com.coded.spring.ordering.domain.entities.ProfileEntity +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size + +data class ProfileCreateRequestDto( + @field:NotBlank + @field:Size(min = 3, max = 100) + @field:Pattern(regexp = """(?=.*[A-Za-z]).*""", message = "Name should contain only letters") + val firstName: String, + + @field:NotBlank + @field:Size(min = 3, max = 100) + @field:Pattern(regexp = """(?=.*[A-Za-z]).*""", message = "Name should contain only letters") + val lastName: String, + + @field:NotBlank + @field:Size(min = 7, max = 12) + @field:Pattern(regexp = """(?=.*\d).*""", message = "Phone number should contain only letters") + val phoneNumber: String, + + @field:NotNull + val userId: Long +) + + +fun ProfileCreateRequestDto.toEntity() = ProfileEntity( + firstName = firstName, + lastName = lastName, + phoneNumber = phoneNumber, +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt new file mode 100644 index 0000000..cb65ffa --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.repositories + +import com.coded.spring.ordering.domain.entities.ProfileEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ProfileRepository: JpaRepository { + fun findByUserId(userId: Long): ProfileEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt index c461b41..61d4ee0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt @@ -1,7 +1,7 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.dtos.OrderCreateDto -import com.coded.spring.ordering.domain.dtos.toEntity +import com.coded.spring.ordering.domain.dtos.toOrderEntity import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.entities.OrderItemEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection @@ -33,7 +33,7 @@ class OrderServiceImpl( throw IllegalStateException("Menus not found: $missingIds") } - val order: OrderEntity = orderRepository.save(newOrder.toEntity()) + val order: OrderEntity = orderRepository.save(newOrder.toOrderEntity()) val orderItems = newOrder.items.map { itemDto -> val menu = foundMenus.find { menu -> menu.id == itemDto.itemId } ?: throw IllegalStateException("Menu not found for id: ${itemDto.itemId}") diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt new file mode 100644 index 0000000..c5ebd53 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt @@ -0,0 +1,9 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto +import com.coded.spring.ordering.domain.entities.ProfileEntity + +interface ProfileService { + fun findAll(): List + fun createProfile(profile: ProfileCreateRequestDto): ProfileEntity +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt new file mode 100644 index 0000000..d8c5d0a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt @@ -0,0 +1,32 @@ +package com.coded.spring.ordering.services + +import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto +import com.coded.spring.ordering.domain.requests.toEntity +import com.coded.spring.ordering.domain.entities.ProfileEntity +import com.coded.spring.ordering.repositories.ProfileRepository +import com.coded.spring.ordering.repositories.UserRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class ProfileServiceImpl( + private val profileRepository: ProfileRepository, + private val userRepository: UserRepository +): ProfileService { + override fun findAll(): List { + return profileRepository.findAll() + } + + override fun createProfile(profile: ProfileCreateRequestDto): ProfileEntity { + val user = userRepository.findByIdOrNull(profile.userId) + ?: throw IllegalArgumentException("User with id ${profile.userId} doesn't exist") + + val profileExists = profileRepository.findByUserId(profile.userId) + + if (profileExists != null) { + throw IllegalArgumentException("User already has a profile") + } + + return profileRepository.save(profile.toEntity().copy(user = user)) + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V3__profile_table_create.sql b/src/main/resources/db/migration/V3__profile_table_create.sql new file mode 100644 index 0000000..5029e6e --- /dev/null +++ b/src/main/resources/db/migration/V3__profile_table_create.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS "profiles"; +CREATE TABLE "profiles" ( + "id" SERIAL PRIMARY KEY , + "user_id" INT REFERENCES public.users (id), + "first_name" VARCHAR(255) NOT NULL, + "last_name" VARCHAR(255) NOT NULL, + "phone_number" VARCHAR(255) NOT NULL +); \ No newline at end of file From f44c905ea4e593a8e1140d6880bbde67235d0e00 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 17 Apr 2025 19:34:43 +0300 Subject: [PATCH 12/29] edited requests and the validations --- .../ordering/domain/requests/MenuItemCreateRequestDto.kt | 4 ++-- .../ordering/domain/requests/OrderCreateRequestDto.kt | 4 ---- .../ordering/domain/requests/ProfileCreateRequestDto.kt | 6 ++---- .../ordering/domain/requests/RestaurantCreateRequestDto.kt | 1 - .../spring/ordering/domain/requests/UserCreateRequestDto.kt | 4 ---- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt index 5dfd363..ad45e6e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt @@ -11,13 +11,13 @@ import java.math.BigDecimal data class MenuCreateRequestDto( @field:NotBlank(message = "Menu Name is required") - @field:NotNull @field:Length(min = 3, message = "Menu Name must be between 3 and 6") val name: String, - @field:NotBlank(message = "Restaurant ID is required") + @field:NotNull @field:Positive(message = "Restaurant ID be positive") val restaurantId: Long, + val price: BigDecimal, ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 705db35..287cb28 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -8,23 +8,19 @@ import jakarta.validation.constraints.Positive import org.jetbrains.annotations.NotNull data class OrderItemCreateRequestDto ( - @field:NotBlank(message = "Item ID is required") @field:NotNull @field:Positive(message = "Item ID must be positive") val itemId: Long, - @field:NotBlank(message = "Amount is required") @field:NotNull @field:Positive(message = "Amount must be positive") val quantity: Int, ) data class OrderCreateRequestDto( - @field:NotBlank(message = "User Id is required") @field:NotNull @field:Positive(message = "User Id is must be positive") val userId: Long, - @field:NotBlank(message = "Restaurant Id is required") @field:NotNull @field:Positive(message = "Restaurant Id is must be positive") val restaurantId: Long, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt index d0bf9e5..d6b768a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt @@ -1,10 +1,7 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.ProfileEntity -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull -import jakarta.validation.constraints.Pattern -import jakarta.validation.constraints.Size +import jakarta.validation.constraints.* data class ProfileCreateRequestDto( @field:NotBlank @@ -23,6 +20,7 @@ data class ProfileCreateRequestDto( val phoneNumber: String, @field:NotNull + @field:Positive(message = "User ID must be positive") val userId: Long ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index 1113840..62955e4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -8,7 +8,6 @@ import org.jetbrains.annotations.NotNull data class RestaurantCreateRequestDto( @field:NotBlank(message = "Name is required") - @field:NotNull @field:Length(min = 3, message = "Name is too short") val name: String ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index bba1ee0..7a6a02d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -10,20 +10,16 @@ import org.hibernate.validator.constraints.Length data class UserCreateRequestDto( @field:NotBlank(message = "Name is required") - @field:NotNull val name: String, @field:NotBlank(message = "Email is required") - @field:NotNull val username: String, @field:NotBlank(message = "Password is required") @field:Email(message = "Email is too short") - @field:NotBlank(message = "Password is too short") val email: String, @field:NotBlank(message = "Password is required") - @field:NotNull(message = "Password is required") @field:Length(min = 6, message = "Password is too short") @field:Pattern(regexp = """(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*""", message = "Password is too simple") val password: String From 4da57c617d0493a41c7a7d994359c6b6e64ec551 Mon Sep 17 00:00:00 2001 From: Yousef Date: Fri, 18 Apr 2025 12:04:11 +0300 Subject: [PATCH 13/29] reworked regex for profile create dto validation --- pom.xml | 5 ----- .../com/coded/spring/ordering/config/SecurityConfig.kt | 2 +- .../ordering/domain/requests/OrderCreateRequestDto.kt | 2 +- .../ordering/domain/requests/ProfileCreateRequestDto.kt | 6 +++--- .../ordering/domain/requests/RestaurantCreateRequestDto.kt | 3 ++- .../spring/ordering/domain/requests/UserCreateRequestDto.kt | 5 ++--- src/main/resources/application.properties | 2 +- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 24f3fff..ba17a0f 100644 --- a/pom.xml +++ b/pom.xml @@ -63,11 +63,6 @@ org.springframework.boot spring-boot-starter-validation - - jakarta.validation - jakarta.validation-api - 3.1.0 - com.h2database h2 diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt index d063d6f..abe3498 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -22,7 +22,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/profiles") + it.requestMatchers("/api/v1/menus") .permitAll() .anyRequest() .authenticated() diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 287cb28..597302d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -3,7 +3,6 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.dtos.OrderCreateDto import com.coded.spring.ordering.domain.dtos.OrderItemCreateDto import com.coded.spring.ordering.domain.entities.* -import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Positive import org.jetbrains.annotations.NotNull @@ -11,6 +10,7 @@ data class OrderItemCreateRequestDto ( @field:NotNull @field:Positive(message = "Item ID must be positive") val itemId: Long, + @field:NotNull @field:Positive(message = "Amount must be positive") val quantity: Int, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt index d6b768a..579724a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt @@ -6,17 +6,17 @@ import jakarta.validation.constraints.* data class ProfileCreateRequestDto( @field:NotBlank @field:Size(min = 3, max = 100) - @field:Pattern(regexp = """(?=.*[A-Za-z]).*""", message = "Name should contain only letters") + @field:Pattern(regexp = "^[a-zA-Z]+$", message = "Name should contain only letters") val firstName: String, @field:NotBlank @field:Size(min = 3, max = 100) - @field:Pattern(regexp = """(?=.*[A-Za-z]).*""", message = "Name should contain only letters") + @field:Pattern(regexp = "^[a-zA-Z]+$", message = "Name should contain only letters") val lastName: String, @field:NotBlank @field:Size(min = 7, max = 12) - @field:Pattern(regexp = """(?=.*\d).*""", message = "Phone number should contain only letters") + @field:Pattern(regexp = "^\\d{7,12}$", message = "Phone number must be digits only and 7-12 characters long") val phoneNumber: String, @field:NotNull diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt index 62955e4..ca260eb 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt @@ -2,13 +2,14 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size import org.hibernate.validator.constraints.Length import org.jetbrains.annotations.NotNull data class RestaurantCreateRequestDto( @field:NotBlank(message = "Name is required") - @field:Length(min = 3, message = "Name is too short") + @field:Size(min = 3, message = "Name is too short") val name: String ) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt index 7a6a02d..3a523a3 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt @@ -3,9 +3,8 @@ package com.coded.spring.ordering.domain.requests import com.coded.spring.ordering.domain.entities.UserEntity import jakarta.validation.constraints.Email import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Pattern -import org.hibernate.validator.constraints.Length +import jakarta.validation.constraints.Size data class UserCreateRequestDto( @@ -20,7 +19,7 @@ data class UserCreateRequestDto( val email: String, @field:NotBlank(message = "Password is required") - @field:Length(min = 6, message = "Password is too short") + @field:Size(min = 6, message = "Password is too short") @field:Pattern(regexp = """(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*""", message = "Password is too simple") val password: String ) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f33fd58..3ae1e81 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,4 +7,4 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/shopping spring.datasource.username=postgres spring.datasource.password=changemelater spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.show-sql=true From 543fffc2c32143cabc7730bd3a975e9053cd0340 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 17:18:32 +0300 Subject: [PATCH 14/29] auth - jwt at login --- pom.xml | 17 +++++++ .../ordering/auth/JwtAuthenticationFilter.kt | 46 +++++++++++++++++ .../spring/ordering/config/SecurityConfig.kt | 36 +++++++++++--- .../ordering/controllers/AuthApiController.kt | 49 +++++++++++++++++++ .../ordering/domain/dtos/JwtResponseDto.kt | 5 ++ .../domain/requests/LoginRequestDto.kt | 14 ++++++ .../spring/ordering/services/JwtService.kt | 42 ++++++++++++++++ 7 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt diff --git a/pom.xml b/pom.xml index ba17a0f..b71f4ca 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,23 @@ org.springframework.boot spring-boot-starter-security + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + com.fasterxml.jackson.module jackson-module-kotlin diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..9be8288 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt @@ -0,0 +1,46 @@ +package com.coded.spring.ordering.auth + +import com.coded.spring.ordering.services.JwtService +import jakarta.servlet.FilterChain +import jakarta.servlet.http.* +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter + +@Component +class JwtAuthenticationFilter( + private val jwtService: JwtService, + private val userDetailsService: UserDetailsService +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val authHeader = request.getHeader("Authorization") + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response) + return + } + + val token = authHeader.substring(7) + val username = jwtService.extractUsername(token) + + if (SecurityContextHolder.getContext().authentication == null) { + if (jwtService.isTokenValid(token, username)) { + val userDetails = userDetailsService.loadUserByUsername(username) + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + } + } + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt index abe3498..9210ec0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -1,18 +1,26 @@ package com.coded.spring.ordering.config +import com.coded.spring.ordering.auth.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 userDetailsService: UserDetailsService + private val userDetailsService: UserDetailsService, + private val jwtAuthFilter: JwtAuthenticationFilter, ) { @Bean @@ -22,13 +30,27 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/menus") - .permitAll() - .anyRequest() - .authenticated() + it.requestMatchers("/api/v1/auth/**").permitAll() + .anyRequest().authenticated() } - .formLogin { it.defaultSuccessUrl("/login", true) } - .userDetailsService(userDetailsService) + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + return http.build(); } + + @Bean + fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager = + config.authenticationManager + + @Bean + fun authenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(userDetailsService) + provider.setPasswordEncoder(passwordEncoder()) + return provider + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt new file mode 100644 index 0000000..e4a10b3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt @@ -0,0 +1,49 @@ +package com.coded.spring.ordering.controllers + +import com.coded.spring.ordering.domain.dtos.JwtResponseDto +import com.coded.spring.ordering.domain.requests.LoginRequestDto +import com.coded.spring.ordering.services.JwtService +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/auth") +class AuthApiController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService, +) { + @PostMapping(path=["/login"]) + fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> { + println(loginRequestDto) + println("here ------------------") + val authToken = UsernamePasswordAuthenticationToken(loginRequestDto.username, loginRequestDto.password) + val authenticated = authenticationManager.authenticate(authToken) + + if (authenticated.isAuthenticated.not()) { + throw UsernameNotFoundException("Invalid credentials") + } + + val userDetails = userDetailsService.loadUserByUsername(loginRequestDto.username) + val token = jwtService.generateToken(userDetails.username) + return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) + } + + @PostMapping(path=["/register"]) + fun register() { + + } + + + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt new file mode 100644 index 0000000..e74f70b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering.domain.dtos + +data class JwtResponseDto( + val token: String, +) diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt new file mode 100644 index 0000000..8ad031c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering.domain.requests + +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + + +data class LoginRequestDto( + @field:NotBlank + @field:Size(min = 1, max = 50) + val username: String, + @field:NotBlank + @field:Size(min = 6, max = 50) + val password: String +) diff --git a/src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt b/src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt new file mode 100644 index 0000000..ebab485 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt @@ -0,0 +1,42 @@ +package com.coded.spring.ordering.services + +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 From 2c6852517eb7f5d27c6adff7b4ee488d7f5d6498 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 17:33:35 +0300 Subject: [PATCH 15/29] menus endpoint open to annonymous users --- .../kotlin/com/coded/spring/ordering/config/SecurityConfig.kt | 2 +- .../com/coded/spring/ordering/controllers/MenuApiController.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt index 9210ec0..14adf01 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -30,7 +30,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/auth/**").permitAll() + it.requestMatchers("/api/v1/auth/**", "/api/v1/menus").permitAll() .anyRequest().authenticated() } .sessionManagement { diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt index 56df623..80c9ca7 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -10,6 +10,7 @@ import com.coded.spring.ordering.services.MenuService import com.coded.spring.ordering.services.RestaurantService import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* @RestController @@ -21,7 +22,7 @@ class MenuApiController( @GetMapping fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) - @PostMapping + @PostMapping(path=["create"]) fun createMenu( @RequestBody menuCreateRequestDto: MenuCreateRequestDto ): ResponseEntity { From b1da16623cc53ca2ef091ec4d50cbed6149763dd Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 17:47:56 +0300 Subject: [PATCH 16/29] fixed some endpoints urls --- .../spring/ordering/controllers/AuthApiController.kt | 11 ----------- .../spring/ordering/controllers/MenuApiController.kt | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt index e4a10b3..9fb36b3 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt @@ -24,8 +24,6 @@ class AuthApiController( ) { @PostMapping(path=["/login"]) fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> { - println(loginRequestDto) - println("here ------------------") val authToken = UsernamePasswordAuthenticationToken(loginRequestDto.username, loginRequestDto.password) val authenticated = authenticationManager.authenticate(authToken) @@ -37,13 +35,4 @@ class AuthApiController( val token = jwtService.generateToken(userDetails.username) return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } - - @PostMapping(path=["/register"]) - fun register() { - - } - - - - } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt index 80c9ca7..c3071b8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt @@ -22,7 +22,7 @@ class MenuApiController( @GetMapping fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) - @PostMapping(path=["create"]) + @PostMapping(path=["/create"]) fun createMenu( @RequestBody menuCreateRequestDto: MenuCreateRequestDto ): ResponseEntity { @@ -32,7 +32,7 @@ class MenuApiController( return ResponseEntity(newMenu, HttpStatus.CREATED) } - @GetMapping(path=["details/{menuId}"]) + @GetMapping(path=["/details/{menuId}"]) fun getMenu(@PathVariable("menuId") menuId: Long): ResponseEntity { val foundMenu = menuService.findById(menuId) ?: return ResponseEntity.notFound().build() From 05878c5a83e4c740cf8d775f2aeeac87ee2e5b03 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 19:07:46 +0300 Subject: [PATCH 17/29] create profile for auth use --- .../ordering/controllers/ProfileApiController.kt | 15 +++++++++++---- .../domain/requests/ProfileCreateRequestDto.kt | 4 ---- .../spring/ordering/services/ProfileService.kt | 3 ++- .../ordering/services/ProfileServiceImpl.kt | 11 +++++++---- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt index f154bd4..f9f59a4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt @@ -4,8 +4,12 @@ import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto import com.coded.spring.ordering.domain.dtos.toResponseDto import com.coded.spring.ordering.services.ProfileService import jakarta.validation.Valid +import org.springframework.boot.autoconfigure.security.SecurityProperties import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.Authentication +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.core.userdetails.UserDetails import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -19,13 +23,17 @@ class ProfileApiController( ) { @GetMapping - fun getProfiles() = profileService.findAll() + fun getProfiles() = profileService.findAll().map { it.toResponseDto() } @PostMapping - fun createProfile(@Valid @RequestBody profileCreateDto: ProfileCreateRequestDto) + fun createProfile( + @Valid @RequestBody profileCreateDto: ProfileCreateRequestDto, + authentication: Authentication, + ) : ResponseEntity { return try { - val profile = profileService.createProfile(profileCreateDto) + val userDetails = authentication.principal as UserDetails + val profile = profileService.createProfile(profileCreateDto, userDetails) ResponseEntity(profile.toResponseDto(), HttpStatus.CREATED) } catch (e: IllegalArgumentException) { ResponseEntity(e.message, HttpStatus.BAD_REQUEST) @@ -33,5 +41,4 @@ class ProfileApiController( ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR) } } - } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt index 579724a..6298884 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt @@ -18,10 +18,6 @@ data class ProfileCreateRequestDto( @field:Size(min = 7, max = 12) @field:Pattern(regexp = "^\\d{7,12}$", message = "Phone number must be digits only and 7-12 characters long") val phoneNumber: String, - - @field:NotNull - @field:Positive(message = "User ID must be positive") - val userId: Long ) diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt index c5ebd53..3bd0839 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt @@ -2,8 +2,9 @@ package com.coded.spring.ordering.services import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto import com.coded.spring.ordering.domain.entities.ProfileEntity +import org.springframework.security.core.userdetails.UserDetails interface ProfileService { fun findAll(): List - fun createProfile(profile: ProfileCreateRequestDto): ProfileEntity + fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt index d8c5d0a..72045a3 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt @@ -6,6 +6,7 @@ import com.coded.spring.ordering.domain.entities.ProfileEntity import com.coded.spring.ordering.repositories.ProfileRepository import com.coded.spring.ordering.repositories.UserRepository import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.core.userdetails.UserDetails import org.springframework.stereotype.Service @Service @@ -17,11 +18,13 @@ class ProfileServiceImpl( return profileRepository.findAll() } - override fun createProfile(profile: ProfileCreateRequestDto): ProfileEntity { - val user = userRepository.findByIdOrNull(profile.userId) - ?: throw IllegalArgumentException("User with id ${profile.userId} doesn't exist") + override fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity { + val user = userRepository.findByUsername(userDetails.username) - val profileExists = profileRepository.findByUserId(profile.userId) + require(user != null) { "User with ${userDetails.username} doesn't exist" } + require(user.id != null) { "User with does not have id" } + + val profileExists = profileRepository.findByUserId(user.id) if (profileExists != null) { throw IllegalArgumentException("User already has a profile") From d3bd78e82c1e3285698168584be7c596cf2962ca Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 19:22:24 +0300 Subject: [PATCH 18/29] create order by auth jwt user --- .../ordering/controllers/OrderApiController.kt | 14 +++++++++++--- .../domain/requests/OrderCreateRequestDto.kt | 9 ++------- .../coded/spring/ordering/services/UserService.kt | 1 + .../spring/ordering/services/UserServiceImpl.kt | 1 + 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt index 85ef67e..9bb68d4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt @@ -8,6 +8,8 @@ import com.coded.spring.ordering.services.UserService import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.Authentication +import org.springframework.security.core.userdetails.UserDetails import org.springframework.web.bind.annotation.* @@ -22,12 +24,18 @@ class OrderApiController( fun getAllOrders() = ResponseEntity.ok(orderService.getAllOrders()) @PostMapping - fun createOrder(@Valid @RequestBody newOrderDto: OrderCreateRequestDto): ResponseEntity { - println(newOrderDto) - val user = userService.findById(newOrderDto.userId) + fun createOrder( + @Valid @RequestBody newOrderDto: OrderCreateRequestDto, + authentication: Authentication + ): ResponseEntity { + val userDetails = authentication.principal as UserDetails + + val user = userService.findByUserName(userDetails.username) ?: return ResponseEntity(HttpStatus.BAD_REQUEST) + val restaurant = restaurantService.findById(newOrderDto.restaurantId) ?: return ResponseEntity(HttpStatus.NOT_FOUND) + orderService.create( newOrderDto.toCreateDto( user, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt index 597302d..582a75d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt @@ -17,15 +17,10 @@ data class OrderItemCreateRequestDto ( ) data class OrderCreateRequestDto( - @field:NotNull - @field:Positive(message = "User Id is must be positive") - val userId: Long, - @field:NotNull @field:Positive(message = "Restaurant Id is must be positive") val restaurantId: Long, - val items: List, ) @@ -35,5 +30,5 @@ fun OrderCreateRequestDto.toCreateDto( items: List, ) = OrderCreateDto(user=user, restaurant=restaurant, items=items) -fun OrderItemCreateRequestDto.toCreateDto( -) = OrderItemCreateDto(itemId=itemId, quantity=quantity) \ No newline at end of file +fun OrderItemCreateRequestDto.toCreateDto() + = OrderItemCreateDto(itemId=itemId, quantity=quantity) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt index db757db..62e9f50 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt @@ -7,4 +7,5 @@ interface UserService { fun findAll(): List fun createUser(user: UserEntity): UserEntity fun findById(id: Long): UserEntity? + fun findByUserName(userName: String): UserEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt index 1ae8f07..e365484 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt @@ -11,4 +11,5 @@ class UserServiceImpl (private val userRepository: UserRepository): UserService override fun findAll(): List = userRepository.findAll() override fun createUser(user: UserEntity): UserEntity = userRepository.save(user) override fun findById(id: Long): UserEntity? = userRepository.findByIdOrNull(id) + override fun findByUserName(userName: String): UserEntity? = userRepository.findByUsername(userName) } \ No newline at end of file From 851407114e2415341c4f1f845368f08baffe5301 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 20 Apr 2025 20:24:45 +0300 Subject: [PATCH 19/29] refactor --- .../{controllers => auth}/AuthApiController.kt | 7 +++---- .../{services => auth}/CustomerDetailsService.kt | 2 +- .../spring/ordering/auth/JwtAuthenticationFilter.kt | 1 - .../spring/ordering/{services => auth}/JwtService.kt | 2 +- .../ordering/{domain => auth}/dtos/JwtResponseDto.kt | 2 +- .../requests => auth/dtos}/LoginRequestDto.kt | 2 +- .../HelloWorldApiController.kt | 3 +-- .../{controllers => menus}/MenuApiController.kt | 12 +++++------- .../ordering/{services => menus}/MenuService.kt | 4 ++-- .../ordering/{services => menus}/MenuServiceImpl.kt | 6 +++--- .../{domain => menus}/dtos/MenuDetailResponse.kt | 4 +++- .../dtos}/MenuItemCreateRequestDto.kt | 3 +-- .../{controllers => orders}/OrderApiController.kt | 11 +++++------ .../ordering/{services => orders}/OrderService.kt | 4 ++-- .../{services => orders}/OrderServiceImpl.kt | 6 +++--- .../{domain => orders}/dtos/OrderCreateDto.kt | 2 +- .../dtos}/OrderCreateRequestDto.kt | 4 +--- .../{domain => orders}/dtos/OrderItemCreate.kt | 2 +- .../{domain => orders}/dtos/OrderResponse.kt | 2 +- .../ProfileApiController.kt | 9 +++------ .../coded/spring/ordering/profiles/ProfileService.kt | 10 ++++++++++ .../{services => profiles}/ProfileServiceImpl.kt | 7 +++---- .../dtos}/ProfileCreateRequestDto.kt | 2 +- .../{domain => profiles}/dtos/ProfileResponseDto.kt | 2 +- .../RestaurantApiController.kt | 9 ++++----- .../{services => restaurants}/RestaurantService.kt | 2 +- .../RestaurantServiceImpl.kt | 2 +- .../dtos}/RestaurantCreateRequestDto.kt | 4 +--- .../dtos/RestaurantInfoResponse.kt | 2 +- .../coded/spring/ordering/services/ProfileService.kt | 10 ---------- .../{controllers => users}/UserApiController.kt | 11 +++++------ .../ordering/{services => users}/UserService.kt | 2 +- .../ordering/{services => users}/UserServiceImpl.kt | 2 +- .../requests => users/dtos}/UserCreateRequestDto.kt | 2 +- .../{domain => users}/dtos/UserResponseDto.kt | 3 +-- 35 files changed, 71 insertions(+), 87 deletions(-) rename src/main/kotlin/com/coded/spring/ordering/{controllers => auth}/AuthApiController.kt (87%) rename src/main/kotlin/com/coded/spring/ordering/{services => auth}/CustomerDetailsService.kt (95%) rename src/main/kotlin/com/coded/spring/ordering/{services => auth}/JwtService.kt (96%) rename src/main/kotlin/com/coded/spring/ordering/{domain => auth}/dtos/JwtResponseDto.kt (53%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => auth/dtos}/LoginRequestDto.kt (85%) rename src/main/kotlin/com/coded/spring/ordering/{controllers => helloWorld}/HelloWorldApiController.kt (87%) rename src/main/kotlin/com/coded/spring/ordering/{controllers => menus}/MenuApiController.kt (80%) rename src/main/kotlin/com/coded/spring/ordering/{services => menus}/MenuService.kt (86%) rename src/main/kotlin/com/coded/spring/ordering/{services => menus}/MenuServiceImpl.kt (91%) rename src/main/kotlin/com/coded/spring/ordering/{domain => menus}/dtos/MenuDetailResponse.kt (68%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => menus/dtos}/MenuItemCreateRequestDto.kt (94%) rename src/main/kotlin/com/coded/spring/ordering/{controllers => orders}/OrderApiController.kt (79%) rename src/main/kotlin/com/coded/spring/ordering/{services => orders}/OrderService.kt (76%) rename src/main/kotlin/com/coded/spring/ordering/{services => orders}/OrderServiceImpl.kt (92%) rename src/main/kotlin/com/coded/spring/ordering/{domain => orders}/dtos/OrderCreateDto.kt (86%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => orders/dtos}/OrderCreateRequestDto.kt (83%) rename src/main/kotlin/com/coded/spring/ordering/{domain => orders}/dtos/OrderItemCreate.kt (62%) rename src/main/kotlin/com/coded/spring/ordering/{domain => orders}/dtos/OrderResponse.kt (74%) rename src/main/kotlin/com/coded/spring/ordering/{controllers => profiles}/ProfileApiController.kt (79%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt rename src/main/kotlin/com/coded/spring/ordering/{services => profiles}/ProfileServiceImpl.kt (83%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => profiles/dtos}/ProfileCreateRequestDto.kt (94%) rename src/main/kotlin/com/coded/spring/ordering/{domain => profiles}/dtos/ProfileResponseDto.kt (89%) rename src/main/kotlin/com/coded/spring/ordering/{controllers => restaurants}/RestaurantApiController.kt (81%) rename src/main/kotlin/com/coded/spring/ordering/{services => restaurants}/RestaurantService.kt (90%) rename src/main/kotlin/com/coded/spring/ordering/{services => restaurants}/RestaurantServiceImpl.kt (95%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => restaurants/dtos}/RestaurantCreateRequestDto.kt (73%) rename src/main/kotlin/com/coded/spring/ordering/{domain => restaurants}/dtos/RestaurantInfoResponse.kt (82%) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt rename src/main/kotlin/com/coded/spring/ordering/{controllers => users}/UserApiController.kt (70%) rename src/main/kotlin/com/coded/spring/ordering/{services => users}/UserService.kt (86%) rename src/main/kotlin/com/coded/spring/ordering/{services => users}/UserServiceImpl.kt (93%) rename src/main/kotlin/com/coded/spring/ordering/{domain/requests => users/dtos}/UserCreateRequestDto.kt (94%) rename src/main/kotlin/com/coded/spring/ordering/{domain => users}/dtos/UserResponseDto.kt (86%) diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt index 9fb36b3..827a8fc 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/AuthApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt @@ -1,8 +1,7 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.auth -import com.coded.spring.ordering.domain.dtos.JwtResponseDto -import com.coded.spring.ordering.domain.requests.LoginRequestDto -import com.coded.spring.ordering.services.JwtService +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import com.coded.spring.ordering.auth.dtos.LoginRequestDto import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt b/src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt similarity index 95% rename from src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt rename to src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt index 1687289..e257f62 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/CustomerDetailsService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.auth import com.coded.spring.ordering.repositories.UserRepository import org.springframework.security.core.userdetails.UserDetails diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt index 9be8288..62cbec9 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt @@ -1,6 +1,5 @@ package com.coded.spring.ordering.auth -import com.coded.spring.ordering.services.JwtService import jakarta.servlet.FilterChain import jakarta.servlet.http.* import org.springframework.security.authentication.UsernamePasswordAuthenticationToken diff --git a/src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt b/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt similarity index 96% rename from src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt rename to src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt index ebab485..428aa87 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/JwtService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.auth import io.jsonwebtoken.* import io.jsonwebtoken.security.Keys diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt similarity index 53% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt rename to src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt index e74f70b..7788bc1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/JwtResponseDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.auth.dtos data class JwtResponseDto( val token: String, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt similarity index 85% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt index 8ad031c..19965ad 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/LoginRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.requests +package com.coded.spring.ordering.auth.dtos import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt b/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt index 7058d14..bb82d1e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/HelloWorldApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt @@ -1,10 +1,9 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.helloWorld import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController - @RestController @RequestMapping("/api/v1/hello-world") class HelloWorldApiController { diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt similarity index 80% rename from src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt index c3071b8..5eaba8f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt @@ -1,16 +1,14 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.menus -import com.coded.spring.ordering.domain.dtos.MenuDetailResponse +import com.coded.spring.ordering.menus.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection -import com.coded.spring.ordering.domain.requests.MenuCreateRequestDto -import com.coded.spring.ordering.domain.requests.toEntity -import com.coded.spring.ordering.services.MenuService -import com.coded.spring.ordering.services.RestaurantService +import com.coded.spring.ordering.menus.dtos.MenuCreateRequestDto +import com.coded.spring.ordering.menus.dtos.toEntity +import com.coded.spring.ordering.restaurants.RestaurantService import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* @RestController diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt rename to src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt index d4b3e2f..3a1c211 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.menus -import com.coded.spring.ordering.domain.dtos.MenuDetailResponse +import com.coded.spring.ordering.menus.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt rename to src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt index fcf41ba..b63056c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/MenuServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.menus -import com.coded.spring.ordering.domain.dtos.MenuDetailResponse -import com.coded.spring.ordering.domain.dtos.toResponse +import com.coded.spring.ordering.menus.dtos.MenuDetailResponse +import com.coded.spring.ordering.menus.dtos.toResponse import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt b/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt similarity index 68% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt rename to src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt index 53463e8..cb7ee60 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/MenuDetailResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt @@ -1,5 +1,7 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.menus.dtos +import com.coded.spring.ordering.restaurants.dtos.RestaurantInfoResponse +import com.coded.spring.ordering.restaurants.dtos.toResponse import com.coded.spring.ordering.domain.entities.MenuEntity import java.math.BigDecimal diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt similarity index 94% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt index ad45e6e..117ed25 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/MenuItemCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt @@ -1,5 +1,4 @@ -package com.coded.spring.ordering.domain.requests - +package com.coded.spring.ordering.menus.dtos import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.entities.RestaurantEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt similarity index 79% rename from src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt index 9bb68d4..2f30f52 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt @@ -1,10 +1,9 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.orders -import com.coded.spring.ordering.domain.requests.OrderCreateRequestDto -import com.coded.spring.ordering.domain.requests.toCreateDto -import com.coded.spring.ordering.services.OrderService -import com.coded.spring.ordering.services.RestaurantService -import com.coded.spring.ordering.services.UserService +import com.coded.spring.ordering.orders.dtos.OrderCreateRequestDto +import com.coded.spring.ordering.orders.dtos.toCreateDto +import com.coded.spring.ordering.restaurants.RestaurantService +import com.coded.spring.ordering.users.UserService import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt similarity index 76% rename from src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt index 77fb743..dcf2832 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.orders -import com.coded.spring.ordering.domain.dtos.OrderCreateDto +import com.coded.spring.ordering.orders.dtos.OrderCreateDto import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt similarity index 92% rename from src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt index 61d4ee0..db7773f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.orders -import com.coded.spring.ordering.domain.dtos.OrderCreateDto -import com.coded.spring.ordering.domain.dtos.toOrderEntity +import com.coded.spring.ordering.orders.dtos.OrderCreateDto +import com.coded.spring.ordering.orders.dtos.toOrderEntity import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.entities.OrderItemEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt index 85aa987..4b7f375 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderCreateDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.orders.dtos import com.coded.spring.ordering.domain.entities.* diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt similarity index 83% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt index 582a75d..049139f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/OrderCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt @@ -1,7 +1,5 @@ -package com.coded.spring.ordering.domain.requests +package com.coded.spring.ordering.orders.dtos -import com.coded.spring.ordering.domain.dtos.OrderCreateDto -import com.coded.spring.ordering.domain.dtos.OrderItemCreateDto import com.coded.spring.ordering.domain.entities.* import jakarta.validation.constraints.Positive import org.jetbrains.annotations.NotNull diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt similarity index 62% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt index 8c968b8..28e15d0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderItemCreate.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.orders.dtos data class OrderItemCreateDto( val itemId: Long, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt similarity index 74% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt rename to src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt index 1624201..2f57f87 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/OrderResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.orders.dtos interface OrderResponseSummary { diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt similarity index 79% rename from src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt index f9f59a4..8dc6727 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/ProfileApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt @@ -1,14 +1,11 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.profiles -import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto -import com.coded.spring.ordering.domain.dtos.toResponseDto -import com.coded.spring.ordering.services.ProfileService +import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.spring.ordering.profiles.dtos.toResponseDto import jakarta.validation.Valid -import org.springframework.boot.autoconfigure.security.SecurityProperties import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.userdetails.UserDetails import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt new file mode 100644 index 0000000..445f9ba --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.profiles + +import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.spring.ordering.domain.entities.ProfileEntity +import org.springframework.security.core.userdetails.UserDetails + +interface ProfileService { + fun findAll(): List + fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt similarity index 83% rename from src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt rename to src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt index 72045a3..179cc15 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/ProfileServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt @@ -1,11 +1,10 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.profiles -import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto -import com.coded.spring.ordering.domain.requests.toEntity +import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto import com.coded.spring.ordering.domain.entities.ProfileEntity +import com.coded.spring.ordering.profiles.dtos.toEntity import com.coded.spring.ordering.repositories.ProfileRepository import com.coded.spring.ordering.repositories.UserRepository -import org.springframework.data.repository.findByIdOrNull import org.springframework.security.core.userdetails.UserDetails import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt similarity index 94% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt index 6298884..72a7fb8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/ProfileCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.requests +package com.coded.spring.ordering.profiles.dtos import com.coded.spring.ordering.domain.entities.ProfileEntity import jakarta.validation.constraints.* diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt similarity index 89% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt rename to src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt index 22822cb..bc326c8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/ProfileResponseDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.profiles.dtos import com.coded.spring.ordering.domain.entities.ProfileEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt similarity index 81% rename from src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt index e4907d4..b1f6b65 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/RestaurantApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt @@ -1,10 +1,9 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.restaurants import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.domain.requests.RestaurantCreateRequestDto -import com.coded.spring.ordering.domain.requests.toEntity -import com.coded.spring.ordering.services.MenuService -import com.coded.spring.ordering.services.RestaurantService +import com.coded.spring.ordering.menus.MenuService +import com.coded.spring.ordering.restaurants.dtos.RestaurantCreateRequestDto +import com.coded.spring.ordering.restaurants.dtos.toEntity import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt similarity index 90% rename from src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt rename to src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt index bf4f557..8eb6b50 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.restaurants import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt similarity index 95% rename from src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt rename to src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt index 0f0a8a0..a117b8a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/RestaurantServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.restaurants import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt similarity index 73% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt index ca260eb..e52b607 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/RestaurantCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt @@ -1,10 +1,8 @@ -package com.coded.spring.ordering.domain.requests +package com.coded.spring.ordering.restaurants.dtos import com.coded.spring.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size -import org.hibernate.validator.constraints.Length -import org.jetbrains.annotations.NotNull data class RestaurantCreateRequestDto( diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt similarity index 82% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt rename to src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt index cc3866b..5463178 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/RestaurantInfoResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.restaurants.dtos import com.coded.spring.ordering.domain.entities.RestaurantEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt deleted file mode 100644 index 3bd0839..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/services/ProfileService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.coded.spring.ordering.services - -import com.coded.spring.ordering.domain.requests.ProfileCreateRequestDto -import com.coded.spring.ordering.domain.entities.ProfileEntity -import org.springframework.security.core.userdetails.UserDetails - -interface ProfileService { - fun findAll(): List - fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt similarity index 70% rename from src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt rename to src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt index a439870..fd641c3 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controllers/UserApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt @@ -1,10 +1,9 @@ -package com.coded.spring.ordering.controllers +package com.coded.spring.ordering.users -import com.coded.spring.ordering.domain.dtos.UserResponseDto -import com.coded.spring.ordering.domain.dtos.toDto -import com.coded.spring.ordering.domain.requests.UserCreateRequestDto -import com.coded.spring.ordering.domain.requests.toEntity -import com.coded.spring.ordering.services.UserService +import com.coded.spring.ordering.users.dtos.UserResponseDto +import com.coded.spring.ordering.users.dtos.toDto +import com.coded.spring.ordering.users.dtos.UserCreateRequestDto +import com.coded.spring.ordering.users.dtos.toEntity import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/services/UserService.kt rename to src/main/kotlin/com/coded/spring/ordering/users/UserService.kt index 62e9f50..3890226 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.users import com.coded.spring.ordering.domain.entities.UserEntity diff --git a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt similarity index 93% rename from src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt rename to src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt index e365484..034b25c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/services/UserServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.services +package com.coded.spring.ordering.users import com.coded.spring.ordering.domain.entities.UserEntity import com.coded.spring.ordering.repositories.UserRepository diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt b/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt similarity index 94% rename from src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt rename to src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt index 3a523a3..ab0314b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/requests/UserCreateRequestDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.requests +package com.coded.spring.ordering.users.dtos import com.coded.spring.ordering.domain.entities.UserEntity import jakarta.validation.constraints.Email diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt b/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt rename to src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt index aa423c1..ac069e8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/dtos/UserResponseDto.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt @@ -1,8 +1,7 @@ -package com.coded.spring.ordering.domain.dtos +package com.coded.spring.ordering.users.dtos import com.coded.spring.ordering.domain.entities.UserEntity - data class UserResponseDto( val id: Long, val email: String, From 633a39adef5edf52c10e069099be5cdc47d6e5fa Mon Sep 17 00:00:00 2001 From: Yousef Date: Mon, 21 Apr 2025 21:52:04 +0300 Subject: [PATCH 20/29] added tests for hello world with token --- pom.xml | 4 ++ .../spring/ordering => }/InitUserRunner.kt | 3 - .../spring/ordering/auth/AuthApiController.kt | 24 +++++++ .../spring/ordering/users/UserServiceImpl.kt | 1 + .../coded/spring/ordering/ApplicationTests.kt | 67 ++++++++++++++++++- src/test/resources/application.properties | 4 ++ 6 files changed, 97 insertions(+), 6 deletions(-) rename src/main/kotlin/{com/coded/spring/ordering => }/InitUserRunner.kt (93%) create mode 100644 src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index b71f4ca..a40e34e 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,10 @@ postgresql + + com.fasterxml.jackson.module + jackson-module-kotlin + org.springframework.boot spring-boot-starter-test diff --git a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt b/src/main/kotlin/InitUserRunner.kt similarity index 93% rename from src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt rename to src/main/kotlin/InitUserRunner.kt index b32d243..3ad9ca0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt +++ b/src/main/kotlin/InitUserRunner.kt @@ -1,10 +1,7 @@ -package com.coded.spring.ordering - import com.coded.spring.ordering.domain.entities.UserEntity import com.coded.spring.ordering.repositories.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 diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt index 827a8fc..9480b95 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt @@ -2,17 +2,24 @@ package com.coded.spring.ordering.auth import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.auth.dtos.LoginRequestDto +import com.coded.spring.ordering.users.UserService +import com.coded.spring.ordering.users.dtos.UserCreateRequestDto +import com.coded.spring.ordering.users.dtos.toDto +import com.coded.spring.ordering.users.dtos.toEntity import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +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.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import org.springframework.security.crypto.password.PasswordEncoder + @RestController @RequestMapping("/api/v1/auth") @@ -20,6 +27,8 @@ class AuthApiController( private val authenticationManager: AuthenticationManager, private val userDetailsService: UserDetailsService, private val jwtService: JwtService, + private val userService: UserService, + private val passwordEncoder: PasswordEncoder, ) { @PostMapping(path=["/login"]) fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> { @@ -34,4 +43,19 @@ class AuthApiController( val token = jwtService.generateToken(userDetails.username) return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } + + @PostMapping(path=["/register"]) + fun createUser( + @Valid @RequestBody user: UserCreateRequestDto + ): ResponseEntity { + val userEntity = userService.createUser( + user.copy( + password = passwordEncoder.encode( + user.password + ) + ).toEntity() + ) + val token = jwtService.generateToken(userEntity.username) + return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt index 034b25c..d031dcc 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt @@ -6,6 +6,7 @@ import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service + @Service class UserServiceImpl (private val userRepository: UserRepository): UserService { override fun findAll(): List = userRepository.findAll() diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index b2e2320..03b4448 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -1,13 +1,74 @@ package com.coded.spring.ordering +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import com.coded.spring.ordering.auth.dtos.LoginRequestDto +import com.coded.spring.ordering.users.dtos.UserCreateRequestDto +import org.junit.jupiter.api.Assertions.assertNotNull 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 kotlin.test.assertEquals -@SpringBootTest -class ApplicationTests { +const val HELLO_WORLD_API = "/api/v1/hello-world" +const val AUTH_LOGIN_API = "/api/v1/auth/login" +const val AUTH_REGISTER_API = "/api/v1/auth/register" + +val USER_LOGIN_CREDENTIALS = LoginRequestDto("admin", "passwordTest123") +val USER_REGISTER_REQUEST = UserCreateRequestDto("admin", "admin", "admin@example.com", "passwordTest123") + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ApplicationTests + @Autowired constructor( + var testRestTemplate: TestRestTemplate + ) +{ + + + @Test + fun `init user`() { + val result = testRestTemplate.postForEntity( + AUTH_REGISTER_API, + USER_REGISTER_REQUEST, + JwtResponseDto::class.java, + ) + + println(result.body?.token) + } @Test - fun contextLoads() { + fun `test hello world returned`() { + val loginToken = login() + + assertNotNull(loginToken) + + val expected = "Hello World!" + + val headers = HttpHeaders() + headers.set("Authorization", "Bearer $loginToken") + val httpEntity = HttpEntity(headers) + + + val response = testRestTemplate.exchange( + HELLO_WORLD_API, + HttpMethod.GET, + httpEntity, + String::class.java + ) + + assertEquals(expected, response.body) + assertEquals(200, response.statusCode.value()) } + fun login(): String? { + val result = testRestTemplate.postForEntity( + AUTH_LOGIN_API, + USER_LOGIN_CREDENTIALS, + JwtResponseDto::class.java, + ) + return result.body?.token + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..72f27bf --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,4 @@ +spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH +spring.datasource.username=sa +spring.datasource.password=password +spring.datasource.driver-class-name=org.h2.Driver \ No newline at end of file From 76b701b511debd625c0e0f6951058f9ec0c0f30c Mon Sep 17 00:00:00 2001 From: Yousef Date: Tue, 22 Apr 2025 18:02:29 +0300 Subject: [PATCH 21/29] tests create order and hello world --- pom.xml | 10 +- .../domain/projections/OrderInfoProjection.kt | 15 -- .../ordering/orders/OrderApiController.kt | 4 +- .../spring/ordering/orders/OrderService.kt | 3 +- .../ordering/orders/OrderServiceImpl.kt | 41 +++-- .../ordering/orders/dtos/OrderResponse.kt | 30 +++- .../coded/spring/ordering/ApplicationTests.kt | 106 ++++++------ .../coded/spring/ordering/OrderApiTests.kt | 155 ++++++++++++++++++ .../com/coded/spring/ordering/extensions.kt | 16 ++ .../com/coded/spring/ordering/helpers.kt | 15 ++ 10 files changed, 306 insertions(+), 89 deletions(-) create mode 100644 src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt create mode 100644 src/test/kotlin/com/coded/spring/ordering/extensions.kt create mode 100644 src/test/kotlin/com/coded/spring/ordering/helpers.kt diff --git a/pom.xml b/pom.xml index a40e34e..eb01ad3 100644 --- a/pom.xml +++ b/pom.xml @@ -103,11 +103,11 @@ kotlin-test-junit5 test - - org.springframework.boot - spring-boot-devtools - runtime - + + + + + diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt index 19ab1f0..83eb248 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt +++ b/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt @@ -4,22 +4,7 @@ import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.entities.UserEntity import java.math.BigDecimal -data class ItemResponse( - val id: Long, - val name: String, -) -data class OrderItemResponse( - val item: ItemResponse, - val quantity: Int, -) - -data class OrderInfoResponse( - val id: Long, - val user: UserEntity, - val restaurant: RestaurantEntity, - val items: List -) interface OrderInfoProjection { val id: Long diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt index 2f30f52..5cd8d8a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt @@ -35,13 +35,13 @@ class OrderApiController( val restaurant = restaurantService.findById(newOrderDto.restaurantId) ?: return ResponseEntity(HttpStatus.NOT_FOUND) - orderService.create( + val order = orderService.create( newOrderDto.toCreateDto( user, restaurant, newOrderDto.items.map { it.toCreateDto() } ) ) - return ResponseEntity(HttpStatus.OK) + return ResponseEntity(order, HttpStatus.OK) } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt index dcf2832..1cfe23d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt @@ -3,10 +3,11 @@ package com.coded.spring.ordering.orders import com.coded.spring.ordering.orders.dtos.OrderCreateDto import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import com.coded.spring.ordering.orders.dtos.OrderCreateResponse interface OrderService { fun findAll(): List - fun create(newOrder: OrderCreateDto) + fun create(newOrder: OrderCreateDto): OrderCreateResponse fun findById(id: Long): OrderEntity? fun getAllOrders(): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt index db7773f..c5670cd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt @@ -1,10 +1,9 @@ package com.coded.spring.ordering.orders -import com.coded.spring.ordering.orders.dtos.OrderCreateDto -import com.coded.spring.ordering.orders.dtos.toOrderEntity import com.coded.spring.ordering.domain.entities.OrderEntity import com.coded.spring.ordering.domain.entities.OrderItemEntity import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import com.coded.spring.ordering.orders.dtos.* import com.coded.spring.ordering.repositories.MenuRepository import com.coded.spring.ordering.repositories.OrderItemRepository import com.coded.spring.ordering.repositories.OrderRepository @@ -23,7 +22,7 @@ class OrderServiceImpl( @Transactional override fun create( newOrder: OrderCreateDto - ) { + ): OrderCreateResponse { val menuIds = newOrder.items.map { it.itemId } val foundMenus = menuRepository.findAllByIdIn(menuIds) @@ -34,16 +33,32 @@ class OrderServiceImpl( } val order: OrderEntity = orderRepository.save(newOrder.toOrderEntity()) - val orderItems = newOrder.items.map { itemDto -> - val menu = foundMenus.find { menu -> menu.id == itemDto.itemId } - ?: throw IllegalStateException("Menu not found for id: ${itemDto.itemId}") - OrderItemEntity( - item = menu, - order = order, - quantity = itemDto.quantity - ) - } - orderItemRepository.saveAll(orderItems) + val orderItems = orderItemRepository.saveAll( + newOrder.items.map { itemDto -> + val menu = foundMenus.find { menu -> menu.id == itemDto.itemId } + ?: throw IllegalStateException("Menu not found for id: ${itemDto.itemId}") + OrderItemEntity( + item = menu, + order = order, + quantity = itemDto.quantity + ) + } + ) + + return OrderCreateResponse( + id = order.id!!, + userId = newOrder.user.id!!, + restaurantId = newOrder.restaurant.id!!, + items = orderItems.map { it -> + OrderItemResponse( + id = it.id!!, + item = foundMenus.find { menu -> it.item?.id == menu.id }!!.toItemResponse(), + quantity = it.quantity!! + ) + } + ) + + } override fun findById(id: Long): OrderEntity? = orderRepository.findByIdOrNull(id) diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt index 2f57f87..cb6a4e6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt @@ -1,8 +1,28 @@ package com.coded.spring.ordering.orders.dtos +import com.coded.spring.ordering.domain.entities.MenuEntity -interface OrderResponseSummary { - val id: Long - fun getUserName(): String - fun getOrderItemsMenuItemsName(): List -} + +data class ItemResponse ( + val id: Long, + val name: String, +) + +data class OrderItemResponse ( + val id : Long, + val item: ItemResponse, + val quantity: Int, +) + +data class OrderCreateResponse ( + val id: Long, + val userId: Long, + val restaurantId: Long, + val items: List, +) + + +fun MenuEntity.toItemResponse() = ItemResponse( + id = id!!, + name = name +) diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt index 03b4448..1fd21d2 100644 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -2,73 +2,83 @@ package com.coded.spring.ordering import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.auth.dtos.LoginRequestDto -import com.coded.spring.ordering.users.dtos.UserCreateRequestDto +import com.coded.spring.ordering.repositories.UserRepository +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach 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 kotlin.test.assertEquals +import kotlin.test.assertNull const val HELLO_WORLD_API = "/api/v1/hello-world" const val AUTH_LOGIN_API = "/api/v1/auth/login" -const val AUTH_REGISTER_API = "/api/v1/auth/register" - val USER_LOGIN_CREDENTIALS = LoginRequestDto("admin", "passwordTest123") -val USER_REGISTER_REQUEST = UserCreateRequestDto("admin", "admin", "admin@example.com", "passwordTest123") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApplicationTests - @Autowired constructor( - var testRestTemplate: TestRestTemplate - ) -{ - - - @Test - fun `init user`() { - val result = testRestTemplate.postForEntity( - AUTH_REGISTER_API, - USER_REGISTER_REQUEST, - JwtResponseDto::class.java, - ) - - println(result.body?.token) - } - - @Test - fun `test hello world returned`() { - val loginToken = login() +@Autowired constructor( + var testRestTemplate: TestRestTemplate +) { + var loginToken: String? = null - assertNotNull(loginToken) + @BeforeEach + fun `login user`() { + val response = testRestTemplate.postForEntity( + AUTH_LOGIN_API, + USER_LOGIN_CREDENTIALS, + JwtResponseDto::class.java, + ) + assertEquals(200, response.statusCode.value()) + assertNotNull(response.body) + loginToken = response.body?.token + } - val expected = "Hello World!" + @Test + fun `test hello world returned and HTTP Status 200`() { + val expected = "Hello World!" + val httpEntity = createHttpRequest(loginToken as String) - val headers = HttpHeaders() - headers.set("Authorization", "Bearer $loginToken") - val httpEntity = HttpEntity(headers) + val response = testRestTemplate.exchange( + HELLO_WORLD_API, + HttpMethod.GET, + httpEntity, + String::class.java + ) + assertEquals(expected, response.body) + assertEquals(200, response.statusCode.value()) + } - val response = testRestTemplate.exchange( - HELLO_WORLD_API, - HttpMethod.GET, - httpEntity, - String::class.java - ) + @Test + fun `test hello world returns null and HTTP Status 403`() { + val result = testRestTemplate.getForEntity(HELLO_WORLD_API, String::class.java) + assertEquals(HttpStatus.FORBIDDEN.value(), result.statusCode.value()) + assertNull(result.body) + } - assertEquals(expected, response.body) - assertEquals(200, response.statusCode.value()) - } + companion object { + @JvmStatic + @BeforeAll + fun setUp( + @Autowired userRepository: UserRepository, + @Autowired passwordEncoder: PasswordEncoder + ) { + userRepository.createUser(passwordEncoder) + } - fun login(): String? { - val result = testRestTemplate.postForEntity( - AUTH_LOGIN_API, - USER_LOGIN_CREDENTIALS, - JwtResponseDto::class.java, - ) - return result.body?.token - } + @JvmStatic + @AfterAll + fun tearDown( + @Autowired userRepository: UserRepository + ) { + userRepository.deleteAll() + } + } } diff --git a/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt new file mode 100644 index 0000000..0276da8 --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt @@ -0,0 +1,155 @@ +package com.coded.spring.ordering + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import com.coded.spring.ordering.domain.entities.MenuEntity +import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.spring.ordering.orders.dtos.OrderCreateRequestDto +import com.coded.spring.ordering.orders.dtos.OrderCreateResponse +import com.coded.spring.ordering.orders.dtos.OrderItemCreateRequestDto +import com.coded.spring.ordering.repositories.* +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.http.HttpMethod +import org.springframework.security.crypto.password.PasswordEncoder +import java.math.BigDecimal +import kotlin.test.assertEquals + +const val BASE_ORDER_API = "/api/v1/orders" + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class OrderApiTests +@Autowired constructor( + var testRestTemplate: TestRestTemplate +) { + var loginToken: String? = null + + @BeforeEach + fun `login user`() { + val response = testRestTemplate.postForEntity( + AUTH_LOGIN_API, + USER_LOGIN_CREDENTIALS, + JwtResponseDto::class.java, + ) + assertEquals(200, response.statusCode.value()) + assertNotNull(response.body) + loginToken = response.body?.token + } + + @Test + fun `test create order returned and HTTP Status 200`() { + val expectedOrder = OrderCreateRequestDto( + restaurantId = 1, + items = listOf( + OrderItemCreateRequestDto( + itemId = 1, + quantity = 2 + ), + OrderItemCreateRequestDto( + itemId = 2, + quantity = 3 + ), + OrderItemCreateRequestDto( + itemId = 3, + quantity = 1 + ) + ) + ) + val httpEntity = createHttpRequest(loginToken as String, expectedOrder) + + val orderPostResponse = testRestTemplate.exchange( + BASE_ORDER_API, + HttpMethod.POST, + httpEntity, + OrderCreateResponse::class.java + ) + + val newOrder = orderPostResponse.body + val itemsCount = newOrder?.items?.size + assertNotNull(itemsCount) + assertNotNull(orderPostResponse.body) + assertEquals(200, orderPostResponse.statusCode.value()) + + } + + + companion object { + @JvmStatic + @BeforeAll + fun setUp( + @Autowired userRepository: UserRepository, + @Autowired restaurantsRepository: RestaurantRepository, + @Autowired menuRepository: MenuRepository, + @Autowired passwordEncoder: PasswordEncoder + ) { + userRepository.createUser(passwordEncoder) + val restaurants = restaurantsRepository.saveAll( + listOf( + RestaurantEntity( + name="Five guys" + ), + RestaurantEntity( + name="pick" + ) + ) + ) + + menuRepository.saveAll( + listOf( + MenuEntity( + restaurant = restaurants[0], + name = "Cheese Burger", + price = BigDecimal(4.00) + ), + MenuEntity( + restaurant = restaurants[0], + name = "Milkshake", + price = BigDecimal(2.50) + ), + MenuEntity( + restaurant = restaurants[0], + name = "Fries", + price = BigDecimal(2.00) + ), + MenuEntity( + restaurant = restaurants[1], + name = "Pasta", + price = BigDecimal(3.00) + ), + MenuEntity( + restaurant = restaurants[1], + name = "Latte", + price = BigDecimal(1.5) + ), + MenuEntity( + restaurant = restaurants[1], + name = "Frozen yogurt", + price = BigDecimal(3.50) + ) + ) + ) + + } + + @JvmStatic + @AfterAll + fun tearDown( + @Autowired userRepository: UserRepository, + @Autowired orderRepository: OrderRepository, + @Autowired restaurantsRepository: RestaurantRepository, + @Autowired menuRepository: MenuRepository, + @Autowired orderItemRepository: OrderItemRepository + ) { + orderRepository.deleteAll() + orderItemRepository.deleteAll() + menuRepository.deleteAll() + restaurantsRepository.deleteAll() + userRepository.deleteAll() + } + } +} diff --git a/src/test/kotlin/com/coded/spring/ordering/extensions.kt b/src/test/kotlin/com/coded/spring/ordering/extensions.kt new file mode 100644 index 0000000..5d56910 --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/extensions.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering + +import com.coded.spring.ordering.domain.entities.UserEntity +import com.coded.spring.ordering.repositories.UserRepository +import org.springframework.security.crypto.password.PasswordEncoder + +fun UserRepository.createUser(passwordEncoder: PasswordEncoder?) { + this.save( + UserEntity( + name = "admin user", + username = "admin", + password = passwordEncoder?.encode("passwordTest123") ?: "passwordTest123", + email = "admin@example.com", + ) + ) +} diff --git a/src/test/kotlin/com/coded/spring/ordering/helpers.kt b/src/test/kotlin/com/coded/spring/ordering/helpers.kt new file mode 100644 index 0000000..f12e43e --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/helpers.kt @@ -0,0 +1,15 @@ +package com.coded.spring.ordering + +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders + +fun createHttpRequest(token: String, requestBody: T? = null): HttpEntity { + val headers = HttpHeaders() + headers.set("Authorization", "Bearer $token") + + if (requestBody != null) { + headers.set("Content-Type", "application/json") + } + + return HttpEntity(requestBody, headers) +} From e557714c532e837487751c412efd5293bbcdf66d Mon Sep 17 00:00:00 2001 From: Yousef Date: Tue, 22 Apr 2025 18:12:18 +0300 Subject: [PATCH 22/29] added more asserts to create order test --- src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt index 0276da8..c85ce95 100644 --- a/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt @@ -71,10 +71,17 @@ class OrderApiTests val newOrder = orderPostResponse.body val itemsCount = newOrder?.items?.size + assertNotNull(itemsCount) assertNotNull(orderPostResponse.body) assertEquals(200, orderPostResponse.statusCode.value()) + + assertEquals(1, newOrder?.id) + assertEquals(expectedOrder.restaurantId, newOrder?.restaurantId) + assertEquals(expectedOrder.items.size, itemsCount) + assertEquals(expectedOrder.items[0].quantity, newOrder?.items?.get(0)?.quantity) + } From 43930cf87c84b9dd1b346d0b977f3247eaf99be2 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 27 Apr 2025 18:11:40 +0300 Subject: [PATCH 23/29] not sure where I left of but I remember having ide issues with versions and cucumber --- pom.xml | 23 ++--- .../spring/ordering/auth/AuthApiController.kt | 23 ++--- .../coded/spring/ordering/ApplicationTests.kt | 84 ------------------- .../spring/ordering/test/ApplicationTests.kt | 10 +++ .../ordering/test/HelloWorldApiSteps.kt | 62 ++++++++++++++ .../ordering/test/HelloWorldApiTests.kt | 25 ++++++ .../ordering/{ => test}/OrderApiTests.kt | 2 +- .../spring/ordering/{ => test}/extensions.kt | 2 +- .../spring/ordering/{ => test}/helpers.kt | 2 +- .../resources/features/hello_world.feature | 6 ++ 10 files changed, 130 insertions(+), 109 deletions(-) delete mode 100644 src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt create mode 100644 src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt create mode 100644 src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt create mode 100644 src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt rename src/test/kotlin/com/coded/spring/ordering/{ => test}/OrderApiTests.kt (99%) rename src/test/kotlin/com/coded/spring/ordering/{ => test}/extensions.kt (92%) rename src/test/kotlin/com/coded/spring/ordering/{ => test}/helpers.kt (90%) create mode 100644 src/test/resources/features/hello_world.feature diff --git a/pom.xml b/pom.xml index eb01ad3..bff9bee 100644 --- a/pom.xml +++ b/pom.xml @@ -89,10 +89,6 @@ postgresql - - com.fasterxml.jackson.module - jackson-module-kotlin - org.springframework.boot spring-boot-starter-test @@ -103,6 +99,18 @@ kotlin-test-junit5 test + + io.cucumber + cucumber-java + 7.21.0 + test + + + io.cucumber + cucumber-spring + 7.14.0 + test + @@ -114,13 +122,6 @@ ${project.basedir}/src/main/kotlin ${project.basedir}/src/test/kotlin - - org.apache.maven.plugins - maven-surefire-plugin - - true - - org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt index 9480b95..6592860 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt @@ -4,14 +4,12 @@ import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.auth.dtos.LoginRequestDto import com.coded.spring.ordering.users.UserService import com.coded.spring.ordering.users.dtos.UserCreateRequestDto -import com.coded.spring.ordering.users.dtos.toDto import com.coded.spring.ordering.users.dtos.toEntity import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -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.PostMapping @@ -30,9 +28,12 @@ class AuthApiController( private val userService: UserService, private val passwordEncoder: PasswordEncoder, ) { - @PostMapping(path=["/login"]) + @PostMapping(path = ["/login"]) fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> { - val authToken = UsernamePasswordAuthenticationToken(loginRequestDto.username, loginRequestDto.password) + val authToken = UsernamePasswordAuthenticationToken( + loginRequestDto.username, + loginRequestDto.password + ) val authenticated = authenticationManager.authenticate(authToken) if (authenticated.isAuthenticated.not()) { @@ -44,17 +45,17 @@ class AuthApiController( return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } - @PostMapping(path=["/register"]) + @PostMapping(path = ["/register"]) fun createUser( @Valid @RequestBody user: UserCreateRequestDto ): ResponseEntity { val userEntity = userService.createUser( - user.copy( - password = passwordEncoder.encode( - user.password - ) - ).toEntity() - ) + user.copy( + password = passwordEncoder.encode( + user.password + ) + ).toEntity() + ) val token = jwtService.generateToken(userEntity.username) return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } diff --git a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt deleted file mode 100644 index 1fd21d2..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.coded.spring.ordering - -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.auth.dtos.LoginRequestDto -import com.coded.spring.ordering.repositories.UserRepository -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -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.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.security.crypto.password.PasswordEncoder -import kotlin.test.assertEquals -import kotlin.test.assertNull - -const val HELLO_WORLD_API = "/api/v1/hello-world" -const val AUTH_LOGIN_API = "/api/v1/auth/login" -val USER_LOGIN_CREDENTIALS = LoginRequestDto("admin", "passwordTest123") - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class ApplicationTests -@Autowired constructor( - var testRestTemplate: TestRestTemplate -) { - var loginToken: String? = null - - @BeforeEach - fun `login user`() { - val response = testRestTemplate.postForEntity( - AUTH_LOGIN_API, - USER_LOGIN_CREDENTIALS, - JwtResponseDto::class.java, - ) - assertEquals(200, response.statusCode.value()) - assertNotNull(response.body) - loginToken = response.body?.token - } - - @Test - fun `test hello world returned and HTTP Status 200`() { - val expected = "Hello World!" - val httpEntity = createHttpRequest(loginToken as String) - - val response = testRestTemplate.exchange( - HELLO_WORLD_API, - HttpMethod.GET, - httpEntity, - String::class.java - ) - - assertEquals(expected, response.body) - assertEquals(200, response.statusCode.value()) - } - - @Test - fun `test hello world returns null and HTTP Status 403`() { - val result = testRestTemplate.getForEntity(HELLO_WORLD_API, String::class.java) - assertEquals(HttpStatus.FORBIDDEN.value(), result.statusCode.value()) - assertNull(result.body) - } - - companion object { - @JvmStatic - @BeforeAll - fun setUp( - @Autowired userRepository: UserRepository, - @Autowired passwordEncoder: PasswordEncoder - ) { - userRepository.createUser(passwordEncoder) - } - - @JvmStatic - @AfterAll - fun tearDown( - @Autowired userRepository: UserRepository - ) { - userRepository.deleteAll() - } - } -} diff --git a/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt new file mode 100644 index 0000000..bfb048d --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.test + + + +import io.cucumber.spring.CucumberContextConfiguration +import org.springframework.boot.test.context.SpringBootTest + +@CucumberContextConfiguration +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ApplicationTests diff --git a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt new file mode 100644 index 0000000..0b0d2ff --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt @@ -0,0 +1,62 @@ +package com.coded.spring.ordering.test + +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import com.coded.spring.ordering.repositories.UserRepository +import io.cucumber.java.en.Then +import io.cucumber.java.en.When +import jakarta.inject.Inject +import org.junit.jupiter.api.Assertions.assertNotNull +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.security.crypto.password.PasswordEncoder +import kotlin.test.assertEquals + +class HelloWorldApiSteps { + + @Inject + lateinit var testRestTemplate: TestRestTemplate + @Inject + lateinit var userRepository: UserRepository + @Inject + lateinit var passwordEncoder: PasswordEncoder + + private var response: ResponseEntity? = null + private var loginToken: String? = null + + @When("I make a GET request to {string}") + fun makeRequestToHelloWorld(uri: String) { + + val user = userRepository.createUser(passwordEncoder) + val jwtResponse = testRestTemplate.postForEntity( + AUTH_LOGIN_API, + user, + JwtResponseDto::class.java, + ) + + assertEquals(200, jwtResponse.statusCode.value()) + assertNotNull(jwtResponse.body) + loginToken = jwtResponse.body?.token + + val httpEntity = createHttpRequest(loginToken as String) + + response = testRestTemplate.exchange( + uri, + HttpMethod.GET, + httpEntity, + String::class.java + ) + } +// + @Then("I get the Http status code should be {int}") + fun getStatusCode200(statusCode: Int) { + assertEquals(HttpStatus.OK, response?.statusCode) + } +// +// @And("the response body should be {string}") +// fun getHelloWorldResponse(body: String) { +// assertEquals("Hello World!", response?.body) +// } +} \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt new file mode 100644 index 0000000..715b7a8 --- /dev/null +++ b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering.test + +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import com.coded.spring.ordering.auth.dtos.LoginRequestDto +import com.coded.spring.ordering.repositories.UserRepository +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +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.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.security.crypto.password.PasswordEncoder +import kotlin.test.assertEquals +import kotlin.test.assertNull + +const val HELLO_WORLD_API = "/api/v1/hello-world" +const val AUTH_LOGIN_API = "/api/v1/auth/login" +val USER_LOGIN_CREDENTIALS = LoginRequestDto("admin", "passwordTest123") + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class HelloWorldApiTests \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt similarity index 99% rename from src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt rename to src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt index c85ce95..fcbe447 100644 --- a/src/test/kotlin/com/coded/spring/ordering/OrderApiTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering +package com.coded.spring.ordering.test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest diff --git a/src/test/kotlin/com/coded/spring/ordering/extensions.kt b/src/test/kotlin/com/coded/spring/ordering/test/extensions.kt similarity index 92% rename from src/test/kotlin/com/coded/spring/ordering/extensions.kt rename to src/test/kotlin/com/coded/spring/ordering/test/extensions.kt index 5d56910..f6bef05 100644 --- a/src/test/kotlin/com/coded/spring/ordering/extensions.kt +++ b/src/test/kotlin/com/coded/spring/ordering/test/extensions.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering +package com.coded.spring.ordering.test import com.coded.spring.ordering.domain.entities.UserEntity import com.coded.spring.ordering.repositories.UserRepository diff --git a/src/test/kotlin/com/coded/spring/ordering/helpers.kt b/src/test/kotlin/com/coded/spring/ordering/test/helpers.kt similarity index 90% rename from src/test/kotlin/com/coded/spring/ordering/helpers.kt rename to src/test/kotlin/com/coded/spring/ordering/test/helpers.kt index f12e43e..ac23016 100644 --- a/src/test/kotlin/com/coded/spring/ordering/helpers.kt +++ b/src/test/kotlin/com/coded/spring/ordering/test/helpers.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering +package com.coded.spring.ordering.test import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders diff --git a/src/test/resources/features/hello_world.feature b/src/test/resources/features/hello_world.feature new file mode 100644 index 0000000..0ef7b1d --- /dev/null +++ b/src/test/resources/features/hello_world.feature @@ -0,0 +1,6 @@ + +Feature: Hello World Api + Scenario: Basic Test for the Hello world API + When I make a GET request to "/api/v1/hello-world" + Then I get the Http status code should be 300 + And the response body should be "Hello World!" From b93d69bb0f2a555174946e3f863f721daca6a91c Mon Sep 17 00:00:00 2001 From: Yousef Date: Sun, 27 Apr 2025 19:09:15 +0300 Subject: [PATCH 24/29] added logging --- .../spring/ordering/config/LoggingFilter.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt b/src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt new file mode 100644 index 0000000..882e3b3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt @@ -0,0 +1,41 @@ +package com.coded.spring.ordering.config + +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.HIGHEST_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) + + cachedResponse.copyBodyToResponse() + + logRequest(cachedRequest) + logResponse(cachedResponse) + } + + private fun logRequest(request: ContentCachingRequestWrapper) { + val requestBody = String(request.contentAsByteArray) + 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") + } +} \ No newline at end of file From bd2b35bb23889c1b1f0b6cfb3a30aede13e393cc Mon Sep 17 00:00:00 2001 From: Yousef Date: Tue, 29 Apr 2025 18:19:11 +0300 Subject: [PATCH 25/29] swagger task --- pom.xml | 5 + .../spring/ordering/auth/AuthApiController.kt | 43 + .../coded/spring/ordering/auth/JwtService.kt | 1 + .../spring/ordering/config/SecurityConfig.kt | 2 +- .../helloWorld/HelloWorldApiController.kt | 26 + .../ordering/menus/MenuApiController.kt | 64 ++ .../spring/ordering/menus/MenuServiceImpl.kt | 1 + .../ordering/orders/OrderApiController.kt | 47 +- .../ordering/orders/OrderServiceImpl.kt | 2 +- .../ordering/profiles/ProfileApiController.kt | 44 + .../restaurants/RestaurantApiController.kt | 66 ++ .../ordering/users/UserApiController.kt | 57 +- src/main/resources/application.properties | 2 + .../ordering/test/HelloWorldApiSteps.kt | 7 +- .../spring/ordering/test/OrderApiTests.kt | 2 +- swagger.json | 925 ++++++++++++++++++ 16 files changed, 1276 insertions(+), 18 deletions(-) create mode 100644 swagger.json diff --git a/pom.xml b/pom.xml index bff9bee..306fef6 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,11 @@ 7.14.0 test + + org.springdoc + springdoc-openapi-starter-webmvc-api + 2.6.0 + diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt index 6592860..93ed2d5 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt @@ -5,6 +5,12 @@ import com.coded.spring.ordering.auth.dtos.LoginRequestDto import com.coded.spring.ordering.users.UserService import com.coded.spring.ordering.users.dtos.UserCreateRequestDto import com.coded.spring.ordering.users.dtos.toEntity +import io.swagger.v3.oas.annotations.tags.Tag +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -17,8 +23,10 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.web.ErrorResponse +@Tag(name="Auth Ppi") @RestController @RequestMapping("/api/v1/auth") class AuthApiController( @@ -28,6 +36,23 @@ class AuthApiController( private val userService: UserService, private val passwordEncoder: PasswordEncoder, ) { + @Operation(summary = "User login endpoint to receive JWT token") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Successful login", + content = [ + Content( + schema = Schema(implementation = JwtResponseDto::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping(path = ["/login"]) fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> { val authToken = UsernamePasswordAuthenticationToken( @@ -45,6 +70,24 @@ class AuthApiController( return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } + + @Operation(summary = "Create a new user and receive a JWT token") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Successful registration", + content = [ + Content( + schema = Schema(implementation = JwtResponseDto::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping(path = ["/register"]) fun createUser( @Valid @RequestBody user: UserCreateRequestDto diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt b/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt index 428aa87..a5f975d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt @@ -9,6 +9,7 @@ import javax.crypto.SecretKey @Component class JwtService { + // use env vars for jwt token generation private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) private val expirationMs: Long = 1000 * 60 * 60 diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt index 14adf01..b2229f3 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt @@ -30,7 +30,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/auth/**", "/api/v1/menus").permitAll() + it.requestMatchers("/api/v1/auth/**", "/api/v1/menus", "/api-docs").permitAll() .anyRequest().authenticated() } .sessionManagement { diff --git a/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt b/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt index bb82d1e..a6f2b46 100644 --- a/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt @@ -1,12 +1,38 @@ package com.coded.spring.ordering.helloWorld +import com.coded.spring.ordering.auth.dtos.JwtResponseDto +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +@Tag(name="Hello world") @RestController @RequestMapping("/api/v1/hello-world") class HelloWorldApiController { + + @Operation(summary = "Test endpoint that requires JWT token") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Generic hello world endpoint for authenticated users", + content = [ + Content( + schema = Schema(implementation = String::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "401", + description = "Forbidden", + content = [ + Content(mediaType = "application/json") + ]), + ) @GetMapping fun helloWorld(): String = "Hello World!" diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt index 5eaba8f..09930bb 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.menus +import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.menus.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.entities.RestaurantEntity @@ -7,19 +8,55 @@ import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.menus.dtos.MenuCreateRequestDto import com.coded.spring.ordering.menus.dtos.toEntity import com.coded.spring.ordering.restaurants.RestaurantService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +@Tag(name = "Menu API") @RestController @RequestMapping("/api/v1/menus") class MenuApiController( private val menuService: MenuService, private val restaurantService: RestaurantService ) { + + @Operation(summary = "Receive a list of all menu items available") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Return a list of menu items", + content = [ + Content( + mediaType = "application/json") + ]) + ) @GetMapping fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) + + @Operation(summary = "Create a new menu item") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Successfully created a new menu for authenticated users", + content = [ + Content( + schema = Schema(implementation = MenuCreateRequestDto::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping(path=["/create"]) fun createMenu( @RequestBody menuCreateRequestDto: MenuCreateRequestDto @@ -30,6 +67,23 @@ class MenuApiController( return ResponseEntity(newMenu, HttpStatus.CREATED) } + @Operation(summary = "Get a menu item by id") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Successfully returns a menu by id for authenticated users", + content = [ + Content( + schema = Schema(implementation = MenuDetailResponse::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "404", + description = "Not request", + content = [ + Content(mediaType = "application/json") + ]), + ) @GetMapping(path=["/details/{menuId}"]) fun getMenu(@PathVariable("menuId") menuId: Long): ResponseEntity { val foundMenu = menuService.findById(menuId) @@ -37,6 +91,16 @@ class MenuApiController( return ResponseEntity(foundMenu, HttpStatus.OK) } + @Operation(summary = "Search all restaurants for menu items") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Returns a menu list by item name or restaurant name for authenticated users", + content = [ + Content( + mediaType = "application/json") + ]), + ) @GetMapping(path=["/search"]) fun search( @RequestParam("restName") restName: String?=null, diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt index b63056c..3b16220 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt @@ -8,6 +8,7 @@ import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.repositories.MenuRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +// cache the menu with hazzlecast @Service class MenuServiceImpl( diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt index 5cd8d8a..c15a9f4 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt @@ -1,9 +1,16 @@ package com.coded.spring.ordering.orders +import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.orders.dtos.OrderCreateRequestDto import com.coded.spring.ordering.orders.dtos.toCreateDto import com.coded.spring.ordering.restaurants.RestaurantService import com.coded.spring.ordering.users.UserService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -11,7 +18,7 @@ import org.springframework.security.core.Authentication import org.springframework.security.core.userdetails.UserDetails import org.springframework.web.bind.annotation.* - +@Tag(name = "Order API") @RestController @RequestMapping("/api/v1/orders") class OrderApiController( @@ -19,9 +26,45 @@ class OrderApiController( private val userService: UserService, private val restaurantService: RestaurantService, ){ + + @Operation(summary = "User get a list of all orders for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Returns a list of all orders", + content = [ + Content( + mediaType = "application/json") + ]), + ) @GetMapping fun getAllOrders() = ResponseEntity.ok(orderService.getAllOrders()) + // TODO: return orders based on current auth user + + @Operation(summary = "Create a new order for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "201", + description = "Successful Created a new order", + content = [ + Content( + schema = Schema(implementation = JwtResponseDto::class), + mediaType = "application/json") + ]), + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json") + ]), + ApiResponse( + responseCode = "404", + description = "Not Found - Restaurant not found", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping fun createOrder( @Valid @RequestBody newOrderDto: OrderCreateRequestDto, @@ -42,6 +85,6 @@ class OrderApiController( newOrderDto.items.map { it.toCreateDto() } ) ) - return ResponseEntity(order, HttpStatus.OK) + return ResponseEntity(order, HttpStatus.CREATED) } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt index c5670cd..fa8ac0c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt @@ -10,7 +10,7 @@ import com.coded.spring.ordering.repositories.OrderRepository import jakarta.transaction.Transactional import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service - +// feature switch on for discount on items based on env config @Service class OrderServiceImpl( private val orderRepository: OrderRepository, diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt index 8dc6727..bee9641 100644 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt @@ -1,7 +1,14 @@ package com.coded.spring.ordering.profiles import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.spring.ordering.profiles.dtos.ProfileResponseDto import com.coded.spring.ordering.profiles.dtos.toResponseDto +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -13,15 +20,52 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +@Tag(name = "Profile API") @RestController @RequestMapping("/api/v1/profiles") class ProfileApiController( private val profileService: ProfileService, ) { + @Operation(summary = "Returns a list of all profiles for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Returns a list of all profiles", + content = [ + Content( + mediaType = "application/json") + ]), + ) @GetMapping fun getProfiles() = profileService.findAll().map { it.toResponseDto() } + + @Operation(summary = "Authenticated users can create/update a profile") + @ApiResponses( + ApiResponse( + responseCode = "201", + description = "Users can create or edits their profile", + content = [ + Content( + mediaType = "application/json", + schema = Schema(implementation = ProfileResponseDto::class) + ) + ]), + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json", + ) + ]), + ApiResponse( + responseCode = "500", + description = "Internal server error", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping fun createProfile( @Valid @RequestBody profileCreateDto: ProfileCreateRequestDto, diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt index b1f6b65..ea1e9da 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt @@ -1,9 +1,18 @@ package com.coded.spring.ordering.restaurants +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.menus.MenuService +import com.coded.spring.ordering.profiles.dtos.ProfileResponseDto import com.coded.spring.ordering.restaurants.dtos.RestaurantCreateRequestDto import com.coded.spring.ordering.restaurants.dtos.toEntity +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -12,19 +21,76 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController + +@Tag(name = "Restaurants Api") @RestController @RequestMapping("/api/v1/restaurants") class RestaurantApiController( private val restaurantService: RestaurantService, private val menuService: MenuService, ) { + + @Operation(summary = "Get all restaurants for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Return all restaurants", + content = [ + Content( + mediaType = "application/json", + ) + ]), + ) @GetMapping fun getRestaurants() = restaurantService.findAll() + @Operation(summary = "Create a new restaurant for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "201", + description = "Create a new restaurant", + content = [ + Content( + mediaType = "application/json", + schema = Schema(implementation = RestaurantEntity::class) + ) + ]), + + ApiResponse( + responseCode = "400", + description = "Bad request", + content = [ + Content(mediaType = "application/json") + ]), + ApiResponse( + responseCode = "403", + description = "Forbidden", + content = [ + Content(mediaType = "application/json") + ]), + ) @PostMapping fun createRestaurant(@RequestBody restaurant: RestaurantCreateRequestDto) = restaurantService.create(restaurant.toEntity()) + @Operation(summary = "Get menu items for restaurant by id for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Get restaurant's menu items", + content = [ + Content( + mediaType = "application/json", + ) + ]), + + ApiResponse( + responseCode = "404", + description = "Not Found", + content = [ + Content(mediaType = "application/json") + ]), + ) @GetMapping(path = ["/{id}/menu"]) fun getRestaurantMenu(@PathVariable("id") id: Long): ResponseEntity> { val restaurant = restaurantService.findById(id) diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt index fd641c3..3c1fb6a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt @@ -1,32 +1,75 @@ package com.coded.spring.ordering.users +import com.coded.spring.ordering.domain.entities.RestaurantEntity import com.coded.spring.ordering.users.dtos.UserResponseDto import com.coded.spring.ordering.users.dtos.toDto import com.coded.spring.ordering.users.dtos.UserCreateRequestDto import com.coded.spring.ordering.users.dtos.toEntity +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* - +@Tag(name="User API") @RestController @RequestMapping("/api/v1/users") class UserApiController (private val userService: UserService) { + + @Operation(summary = "Get a list of all users for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "List all users", + content = [ + Content( + mediaType = "application/json", + ) + ]), + + ApiResponse( + responseCode = "403", + description = "Forbidden", + content = [ + Content(mediaType = "application/json") + ]), + ) @GetMapping fun getUsers() = ResponseEntity.ok( userService.findAll() .map { it.toDto() } ) - @PostMapping - fun createUser( - @Valid @RequestBody user: UserCreateRequestDto - ) = ResponseEntity.ok( - userService.createUser(user.toEntity()).toDto() + @Operation(summary = "Get user details by id for authenticated users") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Return user details", + content = [ + Content( + mediaType = "application/json", + schema = Schema(implementation = UserResponseDto::class) + ) + ]), + ApiResponse( + responseCode = "404", + description = "Not Found", + content = [ + Content(mediaType = "application/json") + ]), + ApiResponse( + responseCode = "403", + description = "Forbidden", + content = [ + Content(mediaType = "application/json") + ]), ) - @GetMapping(path=["/{id}"]) fun getUser(@PathVariable("id") id: Long): ResponseEntity { val user = userService.findById(id) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3ae1e81..ac528d9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,3 +8,5 @@ spring.datasource.username=postgres spring.datasource.password=changemelater spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=true + +springdoc.api-docs.path=/api-docs diff --git a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt index 0b0d2ff..077e75f 100644 --- a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt +++ b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt @@ -52,11 +52,6 @@ class HelloWorldApiSteps { // @Then("I get the Http status code should be {int}") fun getStatusCode200(statusCode: Int) { - assertEquals(HttpStatus.OK, response?.statusCode) + assertEquals(statusCode, response?.statusCode?.value()) } -// -// @And("the response body should be {string}") -// fun getHelloWorldResponse(body: String) { -// assertEquals("Hello World!", response?.body) -// } } \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt index fcbe447..81c5d17 100644 --- a/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt +++ b/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt @@ -74,7 +74,7 @@ class OrderApiTests assertNotNull(itemsCount) assertNotNull(orderPostResponse.body) - assertEquals(200, orderPostResponse.statusCode.value()) + assertEquals(201, orderPostResponse.statusCode.value()) assertEquals(1, newOrder?.id) diff --git a/swagger.json b/swagger.json new file mode 100644 index 0000000..4b22d24 --- /dev/null +++ b/swagger.json @@ -0,0 +1,925 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/api/v1/users": { + "get": { + "tags": [ + "user-api-controller" + ], + "operationId": "getUsers", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "user-api-controller" + ], + "operationId": "createUser", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + } + } + }, + "/api/v1/restaurants": { + "get": { + "tags": [ + "restaurant-api-controller" + ], + "operationId": "getRestaurants", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RestaurantEntity" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "restaurant-api-controller" + ], + "operationId": "createRestaurant", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestaurantCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/RestaurantEntity" + } + } + } + } + } + } + }, + "/api/v1/profiles": { + "get": { + "tags": [ + "profile-api-controller" + ], + "operationId": "getProfiles", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileResponseDto" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "profile-api-controller" + ], + "operationId": "createProfile", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/v1/orders": { + "get": { + "tags": [ + "order-api-controller" + ], + "operationId": "getAllOrders", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderInfoProjection" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "order-api-controller" + ], + "operationId": "createOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrderCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/v1/menus/create": { + "post": { + "tags": [ + "menu-api-controller" + ], + "operationId": "createMenu", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MenuCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/MenuEntity" + } + } + } + } + } + } + }, + "/api/v1/auth/register": { + "post": { + "tags": [ + "auth-api-controller" + ], + "operationId": "createUser_1", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserCreateRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/JwtResponseDto" + } + } + } + } + } + } + }, + "/api/v1/auth/login": { + "post": { + "tags": [ + "auth-api-controller" + ], + "operationId": "login", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequestDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/v1/users/{id}": { + "get": { + "tags": [ + "user-api-controller" + ], + "operationId": "getUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + } + } + }, + "/api/v1/restaurants/{id}/menu": { + "get": { + "tags": [ + "restaurant-api-controller" + ], + "operationId": "getRestaurantMenu", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MenuBasicInfoProjection" + } + } + } + } + } + } + } + }, + "/api/v1/menus": { + "get": { + "tags": [ + "menu-api-controller" + ], + "operationId": "getAll", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MenuEntity" + } + } + } + } + } + } + } + }, + "/api/v1/menus/search": { + "get": { + "tags": [ + "menu-api-controller" + ], + "operationId": "search", + "parameters": [ + { + "name": "restName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "menuName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MenuInfoSearchProjection" + } + } + } + } + } + } + } + }, + "/api/v1/menus/details/{menuId}": { + "get": { + "tags": [ + "menu-api-controller" + ], + "operationId": "getMenu", + "parameters": [ + { + "name": "menuId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/MenuDetailResponse" + } + } + } + } + } + } + }, + "/api/v1/hello-world": { + "get": { + "tags": [ + "hello-world-api-controller" + ], + "operationId": "helloWorld", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "UserCreateRequestDto": { + "required": [ + "email", + "name", + "password", + "username" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "maxLength": 2147483647, + "minLength": 6, + "pattern": "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).*", + "type": "string" + } + } + }, + "UserResponseDto": { + "required": [ + "email", + "id", + "name", + "username" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "email": { + "type": "string" + }, + "username": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "RestaurantCreateRequestDto": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "maxLength": 2147483647, + "minLength": 3, + "type": "string" + } + } + }, + "MenuEntity": { + "required": [ + "name", + "price" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "restaurant": { + "$ref": "#/components/schemas/RestaurantEntity" + }, + "price": { + "type": "number" + } + } + }, + "RestaurantEntity": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "menus": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MenuEntity" + } + } + } + }, + "ProfileCreateRequestDto": { + "required": [ + "firstName", + "lastName", + "phoneNumber" + ], + "type": "object", + "properties": { + "firstName": { + "maxLength": 100, + "minLength": 3, + "pattern": "^[a-zA-Z]+$", + "type": "string" + }, + "lastName": { + "maxLength": 100, + "minLength": 3, + "pattern": "^[a-zA-Z]+$", + "type": "string" + }, + "phoneNumber": { + "maxLength": 12, + "minLength": 7, + "pattern": "^\\d{7,12}$", + "type": "string" + } + } + }, + "OrderCreateRequestDto": { + "required": [ + "items", + "restaurantId" + ], + "type": "object", + "properties": { + "restaurantId": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderItemCreateRequestDto" + } + } + } + }, + "OrderItemCreateRequestDto": { + "required": [ + "itemId", + "quantity" + ], + "type": "object", + "properties": { + "itemId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + } + } + }, + "MenuCreateRequestDto": { + "required": [ + "name", + "price", + "restaurantId" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "restaurantId": { + "type": "integer", + "format": "int64" + }, + "price": { + "type": "number" + } + } + }, + "JwtResponseDto": { + "required": [ + "token" + ], + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, + "LoginRequestDto": { + "required": [ + "password", + "username" + ], + "type": "object", + "properties": { + "username": { + "maxLength": 50, + "minLength": 1, + "type": "string" + }, + "password": { + "maxLength": 50, + "minLength": 6, + "type": "string" + } + } + }, + "MenuBasicInfoProjection": { + "required": [ + "id", + "name", + "price" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "integer", + "format": "int64" + }, + "price": { + "type": "number" + } + } + }, + "ProfileResponseDto": { + "required": [ + "firstName", + "id", + "lastName", + "phoneNumber", + "userId" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + } + } + }, + "MenuInfo": { + "required": [ + "id", + "name", + "price" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "integer", + "format": "int64" + }, + "price": { + "type": "number" + } + } + }, + "OrderInfoProjection": { + "required": [ + "id", + "orderItems", + "restaurant", + "user" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "user": { + "$ref": "#/components/schemas/UserInfo" + }, + "restaurant": { + "$ref": "#/components/schemas/RestaurantEntity" + }, + "orderItems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderItemInfo" + } + } + } + }, + "OrderItemInfo": { + "required": [ + "item", + "quantity" + ], + "type": "object", + "properties": { + "quantity": { + "type": "integer", + "format": "int32" + }, + "item": { + "$ref": "#/components/schemas/MenuInfo" + } + } + }, + "UserInfo": { + "required": [ + "email", + "id", + "username" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "MenuInfoSearchProjection": { + "required": [ + "id", + "name", + "price", + "restaurant" + ], + "type": "object", + "properties": { + "restaurant": { + "$ref": "#/components/schemas/RestaurantInfoProjection" + }, + "name": { + "type": "string" + }, + "id": { + "type": "integer", + "format": "int64" + }, + "price": { + "type": "number" + } + } + }, + "RestaurantInfoProjection": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "number", + "format": "float" + } + } + }, + "MenuDetailResponse": { + "required": [ + "id", + "name", + "price", + "restaurant" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "restaurant": { + "$ref": "#/components/schemas/RestaurantInfoResponse" + } + } + }, + "RestaurantInfoResponse": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file From bf943d67d9ca97a5f263dea23cf897208d8f86db Mon Sep 17 00:00:00 2001 From: Yousef Date: Tue, 29 Apr 2025 19:21:00 +0300 Subject: [PATCH 26/29] caching menu items --- pom.xml | 5 + .../com/coded/spring/ordering/Application.kt | 8 + .../ordering/menus/MenuApiController.kt | 3 +- .../spring/ordering/menus/MenuService.kt | 2 +- .../spring/ordering/menus/MenuServiceImpl.kt | 20 +- .../ordering/repositories/MenuRepository.kt | 1 - swagger.json | 608 ++++++++---------- 7 files changed, 301 insertions(+), 346 deletions(-) diff --git a/pom.xml b/pom.xml index 306fef6..82956ac 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,11 @@ org.postgresql postgresql + + com.hazelcast + hazelcast + 5.3.8 + org.springframework.boot diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/ordering/Application.kt index 8554e49..1bce6d8 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/ordering/Application.kt @@ -2,10 +2,18 @@ package com.coded.spring.ordering import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast +import com.hazelcast.core.HazelcastInstance @SpringBootApplication class Application fun main(args: Array) { runApplication(*args) + menuItemsConfig.getMapConfig("menuItems").setTimeToLiveSeconds(5) + } + +val menuItemsConfig = Config("menuItems") +val serverCache: HazelcastInstance = Hazelcast.newHazelcastInstance(menuItemsConfig) diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt index 09930bb..ca28b26 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt @@ -4,6 +4,7 @@ import com.coded.spring.ordering.auth.dtos.JwtResponseDto import com.coded.spring.ordering.menus.dtos.MenuDetailResponse import com.coded.spring.ordering.domain.entities.MenuEntity import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.menus.dtos.MenuCreateRequestDto import com.coded.spring.ordering.menus.dtos.toEntity @@ -37,7 +38,7 @@ class MenuApiController( ]) ) @GetMapping - fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) + fun getAll(): ResponseEntity> = ResponseEntity.ok(menuService.findAll()) @Operation(summary = "Create a new menu item") diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt index 3a1c211..ccfc04a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt @@ -7,7 +7,7 @@ import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection interface MenuService { - fun findAll(): List + fun findAll(): List fun create(menuItem: MenuEntity): MenuEntity fun findById(id: Long): MenuDetailResponse? fun findAllIn(items: List): List diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt index 3b16220..39d35e5 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt +++ b/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt @@ -1,5 +1,6 @@ package com.coded.spring.ordering.menus +import com.coded.spring.ordering.serverCache import com.coded.spring.ordering.menus.dtos.MenuDetailResponse import com.coded.spring.ordering.menus.dtos.toResponse import com.coded.spring.ordering.domain.entities.MenuEntity @@ -8,13 +9,24 @@ import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection import com.coded.spring.ordering.repositories.MenuRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service -// cache the menu with hazzlecast @Service class MenuServiceImpl( private val menuRepository: MenuRepository ) : MenuService { - override fun findAll(): List = menuRepository.findAll() + override fun findAll(): List { + val cachedMenuItems = menuItemsCache["menuItems"] + + return if (cachedMenuItems?.size == 0 || cachedMenuItems == null) { + println("caching menu items") + val menuItems = menuRepository.findAll().map { it.toResponse() } + menuItemsCache.put("menuItems", menuItems) + menuItems + } else { + println("retrieving ${cachedMenuItems.size} menu items") + menuItemsCache["menuItems"] ?: listOf() + } + } override fun create(menuItem: MenuEntity): MenuEntity { val menu = menuRepository.save(menuItem) @@ -46,6 +58,6 @@ class MenuServiceImpl( else -> emptyList() } } +} - -} \ No newline at end of file +val menuItemsCache = serverCache.getMap>("menuItems") \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt index 5522ff1..3b99e1c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt @@ -10,7 +10,6 @@ import org.springframework.stereotype.Repository @Repository interface MenuRepository: JpaRepository { - fun findByRestaurant_Id(restaurantId: Long): List fun findAllByIdIn(menuIds: List): List diff --git a/swagger.json b/swagger.json index 4b22d24..33d55c5 100644 --- a/swagger.json +++ b/swagger.json @@ -11,23 +11,19 @@ } ], "paths": { - "/api/v1/users": { + "/api/v1/restaurants": { "get": { "tags": [ - "user-api-controller" + "Restaurants Api" ], - "operationId": "getUsers", + "summary": "Get all restaurants for authenticated users", + "operationId": "getRestaurants", "responses": { "200": { - "description": "OK", + "description": "Return all restaurants", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserResponseDto" - } - } + "application/json": { + } } } @@ -35,75 +31,41 @@ }, "post": { "tags": [ - "user-api-controller" + "Restaurants Api" ], - "operationId": "createUser", + "summary": "Create a new restaurant for authenticated users", + "operationId": "createRestaurant", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserCreateRequestDto" + "$ref": "#/components/schemas/RestaurantCreateRequestDto" } } }, "required": true }, "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad request", "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/UserResponseDto" - } + "application/json": { + } } - } - } - } - }, - "/api/v1/restaurants": { - "get": { - "tags": [ - "restaurant-api-controller" - ], - "operationId": "getRestaurants", - "responses": { - "200": { - "description": "OK", + }, + "403": { + "description": "Forbidden", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RestaurantEntity" - } - } - } - } - } - } - }, - "post": { - "tags": [ - "restaurant-api-controller" - ], - "operationId": "createRestaurant", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RestaurantCreateRequestDto" + "application/json": { + } } }, - "required": true - }, - "responses": { - "200": { - "description": "OK", + "201": { + "description": "Create a new restaurant", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/RestaurantEntity" } @@ -116,20 +78,16 @@ "/api/v1/profiles": { "get": { "tags": [ - "profile-api-controller" + "Profile API" ], + "summary": "Returns a list of all profiles for authenticated users", "operationId": "getProfiles", "responses": { "200": { - "description": "OK", + "description": "Returns a list of all profiles", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProfileResponseDto" - } - } + "application/json": { + } } } @@ -137,8 +95,9 @@ }, "post": { "tags": [ - "profile-api-controller" + "Profile API" ], + "summary": "Authenticated users can create/update a profile", "operationId": "createProfile", "requestBody": { "content": { @@ -151,12 +110,28 @@ "required": true }, "responses": { - "200": { - "description": "OK", + "400": { + "description": "Bad request", + "content": { + "application/json": { + + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + + } + } + }, + "201": { + "description": "Users can create or edits their profile", "content": { - "*/*": { + "application/json": { "schema": { - "type": "object" + "$ref": "#/components/schemas/ProfileResponseDto" } } } @@ -167,20 +142,16 @@ "/api/v1/orders": { "get": { "tags": [ - "order-api-controller" + "Order API" ], + "summary": "User get a list of all orders for authenticated users", "operationId": "getAllOrders", "responses": { "200": { - "description": "OK", + "description": "Returns a list of all orders", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderInfoProjection" - } - } + "application/json": { + } } } @@ -188,8 +159,9 @@ }, "post": { "tags": [ - "order-api-controller" + "Order API" ], + "summary": "Create a new order for authenticated users", "operationId": "createOrder", "requestBody": { "content": { @@ -202,12 +174,28 @@ "required": true }, "responses": { - "200": { - "description": "OK", + "404": { + "description": "Not Found - Restaurant not found", + "content": { + "application/json": { + + } + } + }, + "400": { + "description": "Bad request", "content": { - "*/*": { + "application/json": { + + } + } + }, + "201": { + "description": "Successful Created a new order", + "content": { + "application/json": { "schema": { - "type": "object" + "$ref": "#/components/schemas/JwtResponseDto" } } } @@ -218,8 +206,9 @@ "/api/v1/menus/create": { "post": { "tags": [ - "menu-api-controller" + "Menu API" ], + "summary": "Create a new menu item", "operationId": "createMenu", "requestBody": { "content": { @@ -233,14 +222,22 @@ }, "responses": { "200": { - "description": "OK", + "description": "Successfully created a new menu for authenticated users", "content": { - "*/*": { + "application/json": { "schema": { - "$ref": "#/components/schemas/MenuEntity" + "$ref": "#/components/schemas/MenuCreateRequestDto" } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + + } + } } } } @@ -248,9 +245,10 @@ "/api/v1/auth/register": { "post": { "tags": [ - "auth-api-controller" + "Auth Ppi" ], - "operationId": "createUser_1", + "summary": "Create a new user and receive a JWT token", + "operationId": "createUser", "requestBody": { "content": { "application/json": { @@ -262,10 +260,18 @@ "required": true }, "responses": { + "400": { + "description": "Bad request", + "content": { + "application/json": { + + } + } + }, "200": { - "description": "OK", + "description": "Successful registration", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/JwtResponseDto" } @@ -278,8 +284,9 @@ "/api/v1/auth/login": { "post": { "tags": [ - "auth-api-controller" + "Auth Ppi" ], + "summary": "User login endpoint to receive JWT token", "operationId": "login", "requestBody": { "content": { @@ -292,12 +299,20 @@ "required": true }, "responses": { + "400": { + "description": "Bad request", + "content": { + "application/json": { + + } + } + }, "200": { - "description": "OK", + "description": "Successful login", "content": { - "*/*": { + "application/json": { "schema": { - "type": "object" + "$ref": "#/components/schemas/JwtResponseDto" } } } @@ -305,11 +320,39 @@ } } }, + "/api/v1/users": { + "get": { + "tags": [ + "User API" + ], + "summary": "Get a list of all users for authenticated users", + "operationId": "getUsers", + "responses": { + "200": { + "description": "List all users", + "content": { + "application/json": { + + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + + } + } + } + } + } + }, "/api/v1/users/{id}": { "get": { "tags": [ - "user-api-controller" + "User API" ], + "summary": "Get user details by id for authenticated users", "operationId": "getUser", "parameters": [ { @@ -324,14 +367,30 @@ ], "responses": { "200": { - "description": "OK", + "description": "Return user details", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/UserResponseDto" } } } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + + } + } } } } @@ -339,8 +398,9 @@ "/api/v1/restaurants/{id}/menu": { "get": { "tags": [ - "restaurant-api-controller" + "Restaurants Api" ], + "summary": "Get menu items for restaurant by id for authenticated users", "operationId": "getRestaurantMenu", "parameters": [ { @@ -354,16 +414,19 @@ } ], "responses": { + "404": { + "description": "Not Found", + "content": { + "application/json": { + + } + } + }, "200": { - "description": "OK", + "description": "Get restaurant's menu items", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MenuBasicInfoProjection" - } - } + "application/json": { + } } } @@ -373,20 +436,16 @@ "/api/v1/menus": { "get": { "tags": [ - "menu-api-controller" + "Menu API" ], + "summary": "Receive a list of all menu items available", "operationId": "getAll", "responses": { "200": { - "description": "OK", + "description": "Return a list of menu items", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MenuEntity" - } - } + "application/json": { + } } } @@ -396,8 +455,9 @@ "/api/v1/menus/search": { "get": { "tags": [ - "menu-api-controller" + "Menu API" ], + "summary": "Search all restaurants for menu items", "operationId": "search", "parameters": [ { @@ -419,15 +479,10 @@ ], "responses": { "200": { - "description": "OK", + "description": "Returns a menu list by item name or restaurant name for authenticated users", "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MenuInfoSearchProjection" - } - } + "application/json": { + } } } @@ -437,8 +492,9 @@ "/api/v1/menus/details/{menuId}": { "get": { "tags": [ - "menu-api-controller" + "Menu API" ], + "summary": "Get a menu item by id", "operationId": "getMenu", "parameters": [ { @@ -453,14 +509,22 @@ ], "responses": { "200": { - "description": "OK", + "description": "Successfully returns a menu by id for authenticated users", "content": { - "*/*": { + "application/json": { "schema": { "$ref": "#/components/schemas/MenuDetailResponse" } } } + }, + "404": { + "description": "Not request", + "content": { + "application/json": { + + } + } } } } @@ -468,19 +532,28 @@ "/api/v1/hello-world": { "get": { "tags": [ - "hello-world-api-controller" + "Hello world" ], + "summary": "Test endpoint that requires JWT token", "operationId": "helloWorld", "responses": { "200": { - "description": "OK", + "description": "Generic hello world endpoint for authenticated users", "content": { - "*/*": { + "application/json": { "schema": { "type": "string" } } } + }, + "401": { + "description": "Forbidden", + "content": { + "application/json": { + + } + } } } } @@ -488,56 +561,6 @@ }, "components": { "schemas": { - "UserCreateRequestDto": { - "required": [ - "email", - "name", - "password", - "username" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "username": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "maxLength": 2147483647, - "minLength": 6, - "pattern": "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).*", - "type": "string" - } - } - }, - "UserResponseDto": { - "required": [ - "email", - "id", - "name", - "username" - ], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "email": { - "type": "string" - }, - "username": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, "RestaurantCreateRequestDto": { "required": [ "name" @@ -622,6 +645,35 @@ } } }, + "ProfileResponseDto": { + "required": [ + "firstName", + "id", + "lastName", + "phoneNumber", + "userId" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + } + } + }, "OrderCreateRequestDto": { "required": [ "items", @@ -658,26 +710,6 @@ } } }, - "MenuCreateRequestDto": { - "required": [ - "name", - "price", - "restaurantId" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "restaurantId": { - "type": "integer", - "format": "int64" - }, - "price": { - "type": "number" - } - } - }, "JwtResponseDto": { "required": [ "token" @@ -689,37 +721,18 @@ } } }, - "LoginRequestDto": { - "required": [ - "password", - "username" - ], - "type": "object", - "properties": { - "username": { - "maxLength": 50, - "minLength": 1, - "type": "string" - }, - "password": { - "maxLength": 50, - "minLength": 6, - "type": "string" - } - } - }, - "MenuBasicInfoProjection": { + "MenuCreateRequestDto": { "required": [ - "id", "name", - "price" + "price", + "restaurantId" ], "type": "object", "properties": { "name": { "type": "string" }, - "id": { + "restaurantId": { "type": "integer", "format": "int64" }, @@ -728,102 +741,56 @@ } } }, - "ProfileResponseDto": { + "UserCreateRequestDto": { "required": [ - "firstName", - "id", - "lastName", - "phoneNumber", - "userId" + "email", + "name", + "password", + "username" ], "type": "object", "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "userId": { - "type": "integer", - "format": "int64" + "name": { + "type": "string" }, - "firstName": { + "username": { "type": "string" }, - "lastName": { + "email": { "type": "string" }, - "phoneNumber": { + "password": { + "maxLength": 2147483647, + "minLength": 6, + "pattern": "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).*", "type": "string" } } }, - "MenuInfo": { + "LoginRequestDto": { "required": [ - "id", - "name", - "price" + "password", + "username" ], "type": "object", "properties": { - "name": { + "username": { + "maxLength": 50, + "minLength": 1, "type": "string" }, - "id": { - "type": "integer", - "format": "int64" - }, - "price": { - "type": "number" - } - } - }, - "OrderInfoProjection": { - "required": [ - "id", - "orderItems", - "restaurant", - "user" - ], - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "user": { - "$ref": "#/components/schemas/UserInfo" - }, - "restaurant": { - "$ref": "#/components/schemas/RestaurantEntity" - }, - "orderItems": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderItemInfo" - } - } - } - }, - "OrderItemInfo": { - "required": [ - "item", - "quantity" - ], - "type": "object", - "properties": { - "quantity": { - "type": "integer", - "format": "int32" - }, - "item": { - "$ref": "#/components/schemas/MenuInfo" + "password": { + "maxLength": 50, + "minLength": 6, + "type": "string" } } }, - "UserInfo": { + "UserResponseDto": { "required": [ "email", "id", + "name", "username" ], "type": "object", @@ -832,51 +799,14 @@ "type": "integer", "format": "int64" }, - "username": { - "type": "string" - }, "email": { "type": "string" - } - } - }, - "MenuInfoSearchProjection": { - "required": [ - "id", - "name", - "price", - "restaurant" - ], - "type": "object", - "properties": { - "restaurant": { - "$ref": "#/components/schemas/RestaurantInfoProjection" }, - "name": { + "username": { "type": "string" }, - "id": { - "type": "integer", - "format": "int64" - }, - "price": { - "type": "number" - } - } - }, - "RestaurantInfoProjection": { - "required": [ - "id", - "name" - ], - "type": "object", - "properties": { "name": { "type": "string" - }, - "id": { - "type": "number", - "format": "float" } } }, From ad0c6900671037e4a2d33285eaa2fa9d35c36202 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 1 May 2025 17:28:29 +0300 Subject: [PATCH 27/29] micro services commit push --- authentication/pom.xml | 58 +++++++ .../coded/authentication/AuthApplication.kt | 11 ++ .../coded/authentication}/InitUserRunner.kt | 10 +- .../authentication}/auth/AuthApiController.kt | 40 +++-- .../auth/CustomerDetailsService.kt | 6 +- .../auth/JwtAuthenticationFilter.kt | 59 +++++++ .../coded/authentication}/auth/JwtService.kt | 11 +- .../auth/dtos/JwtResponseDto.kt | 2 +- .../auth/dtos/LoginRequestDto.kt | 2 +- .../auth/dtos/ValidateTokenResponseDto.kt | 5 + .../authentication/config/LoggingFilter.kt | 41 +++++ .../authentication}/config/SecurityConfig.kt | 6 +- .../coded/authentication/users}/UserEntity.kt | 2 +- .../authentication/users/UserRepository.kt | 9 + .../coded/authentication/users/UserService.kt | 10 ++ .../authentication/users/UserServiceImpl.kt | 13 ++ .../users/dtos/UserCreateRequestDto.kt | 6 +- .../users/dtos/UserResponseDto.kt | 7 +- .../main/resources/application.properties | 2 +- .../test/kotlin/auth/AuthApplicationTests.kt | 13 ++ ordering/pom.xml | 57 ++++++ .../com/coded/ordering/OrderingApplication.kt | 8 +- .../coded}/ordering/config/LoggingFilter.kt | 0 .../config/RemoteAuthenticationFilter.kt | 55 ++++++ .../coded/ordering/config/SecurityConfig.kt | 29 ++++ .../ordering/domain/entities/MenuEntity.kt | 2 +- .../ordering/domain/entities/OrderEntity.kt | 26 +++ .../domain/entities/OrderItemEntity.kt | 2 +- .../ordering/domain/entities/ProfileEntity.kt | 7 +- .../domain/entities/RestaurantEntity.kt | 2 +- .../projections/MenuBasicInfoProjection.kt | 2 +- .../domain/projections/OrderInfoProjection.kt | 13 +- .../projections/RestaurantInfoProjection.kt | 2 +- .../helloWorld/HelloWorldApiController.kt | 10 +- .../ordering/menus/MenuApiController.kt | 18 +- .../com/coded}/ordering/menus/MenuService.kt | 10 +- .../coded}/ordering/menus/MenuServiceImpl.kt | 18 +- .../ordering/menus/dtos/MenuDetailResponse.kt | 8 +- .../menus/dtos/MenuItemCreateRequestDto.kt | 6 +- .../ordering/orders/OrderApiController.kt | 29 ++-- .../com/coded/ordering/orders/OrderService.kt | 13 ++ .../ordering/orders/OrderServiceImpl.kt | 26 +-- .../ordering/orders/dtos/OrderCreateDto.kt | 8 +- .../orders/dtos/OrderCreateRequestDto.kt | 6 +- .../ordering/orders/dtos/OrderItemCreate.kt | 2 +- .../ordering/orders/dtos/OrderResponse.kt | 4 +- .../ordering/profiles/ProfileApiController.kt | 19 +- .../coded/ordering/profiles/ProfileService.kt | 9 + .../ordering/profiles/ProfileServiceImpl.kt | 27 +++ .../profiles/dtos/ProfileCreateRequestDto.kt | 4 +- .../profiles/dtos/ProfileResponseDto.kt | 6 +- .../ordering/providers/JwtAuthProvider.kt | 33 ++++ .../ordering/repositories/MenuRepository.kt | 8 +- .../repositories/OrderItemRepository.kt | 4 +- .../ordering/repositories/OrderRepository.kt | 10 +- .../repositories/ProfileRepository.kt | 6 +- .../repositories/RestaurantRepository.kt | 6 +- .../restaurants/RestaurantApiController.kt | 14 +- .../ordering/restaurants/RestaurantService.kt | 6 +- .../restaurants/RestaurantServiceImpl.kt | 8 +- .../dtos/RestaurantCreateRequestDto.kt | 4 +- .../dtos/RestaurantInfoResponse.kt | 4 +- .../src}/main/resources/V1_sample.sql | 0 .../src}/main/resources/V2_sample.sql | 0 .../src/main/resources/application.properties | 14 ++ .../resources/db/migration/V1__init_db.sql | 0 .../db/migration/V2__menu_column_change.sql | 0 .../db/migration/V3__profile_table_create.sql | 0 .../ordering/OrderingApplicationTests.kt | 13 ++ pom.xml | 13 +- .../ordering/auth/JwtAuthenticationFilter.kt | 45 ----- .../ordering/domain/entities/OrderEntity.kt | 27 --- .../spring/ordering/orders/OrderService.kt | 13 -- .../ordering/profiles/ProfileService.kt | 10 -- .../ordering/profiles/ProfileServiceImpl.kt | 34 ---- .../ordering/repositories/UserRepository.kt | 10 -- .../ordering/users/UserApiController.kt | 79 --------- .../spring/ordering/users/UserService.kt | 11 -- .../spring/ordering/users/UserServiceImpl.kt | 16 -- .../spring/ordering/test/ApplicationTests.kt | 10 -- .../ordering/test/HelloWorldApiSteps.kt | 57 ------ .../ordering/test/HelloWorldApiTests.kt | 25 --- .../spring/ordering/test/OrderApiTests.kt | 162 ------------------ .../coded/spring/ordering/test/extensions.kt | 16 -- .../com/coded/spring/ordering/test/helpers.kt | 15 -- src/test/resources/application.properties | 4 - .../resources/features/hello_world.feature | 6 - 87 files changed, 691 insertions(+), 733 deletions(-) create mode 100644 authentication/pom.xml create mode 100644 authentication/src/main/kotlin/com/coded/authentication/AuthApplication.kt rename {src/main/kotlin => authentication/src/main/kotlin/com/coded/authentication}/InitUserRunner.kt (72%) rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/auth/AuthApiController.kt (78%) rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/auth/CustomerDetailsService.kt (80%) create mode 100644 authentication/src/main/kotlin/com/coded/authentication/auth/JwtAuthenticationFilter.kt rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/auth/JwtService.kt (77%) rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/auth/dtos/JwtResponseDto.kt (54%) rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/auth/dtos/LoginRequestDto.kt (86%) create mode 100644 authentication/src/main/kotlin/com/coded/authentication/auth/dtos/ValidateTokenResponseDto.kt create mode 100644 authentication/src/main/kotlin/com/coded/authentication/config/LoggingFilter.kt rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/config/SecurityConfig.kt (91%) rename {src/main/kotlin/com/coded/spring/ordering/domain/entities => authentication/src/main/kotlin/com/coded/authentication/users}/UserEntity.kt (91%) create mode 100644 authentication/src/main/kotlin/com/coded/authentication/users/UserRepository.kt create mode 100644 authentication/src/main/kotlin/com/coded/authentication/users/UserService.kt create mode 100644 authentication/src/main/kotlin/com/coded/authentication/users/UserServiceImpl.kt rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/users/dtos/UserCreateRequestDto.kt (82%) rename {src/main/kotlin/com/coded/spring/ordering => authentication/src/main/kotlin/com/coded/authentication}/users/dtos/UserResponseDto.kt (54%) rename {src => authentication/src}/main/resources/application.properties (96%) create mode 100644 authentication/src/test/kotlin/auth/AuthApplicationTests.kt create mode 100644 ordering/pom.xml rename src/main/kotlin/com/coded/spring/ordering/Application.kt => ordering/src/main/kotlin/com/coded/ordering/OrderingApplication.kt (71%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/config/LoggingFilter.kt (100%) create mode 100644 ordering/src/main/kotlin/com/coded/ordering/config/RemoteAuthenticationFilter.kt create mode 100644 ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/entities/MenuEntity.kt (93%) create mode 100644 ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderEntity.kt rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/entities/OrderItemEntity.kt (91%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/entities/ProfileEntity.kt (78%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/entities/RestaurantEntity.kt (91%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/projections/MenuBasicInfoProjection.kt (86%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/projections/OrderInfoProjection.kt (53%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/domain/projections/RestaurantInfoProjection.kt (69%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/helloWorld/HelloWorldApiController.kt (82%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/menus/MenuApiController.kt (85%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/menus/MenuService.kt (60%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/menus/MenuServiceImpl.kt (81%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/menus/dtos/MenuDetailResponse.kt (57%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/menus/dtos/MenuItemCreateRequestDto.kt (80%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/OrderApiController.kt (72%) create mode 100644 ordering/src/main/kotlin/com/coded/ordering/orders/OrderService.kt rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/OrderServiceImpl.kt (74%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/dtos/OrderCreateDto.kt (59%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/dtos/OrderCreateRequestDto.kt (87%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/dtos/OrderItemCreate.kt (62%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/orders/dtos/OrderResponse.kt (79%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/profiles/ProfileApiController.kt (79%) create mode 100644 ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileService.kt create mode 100644 ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileServiceImpl.kt rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/profiles/dtos/ProfileCreateRequestDto.kt (87%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/profiles/dtos/ProfileResponseDto.kt (69%) create mode 100644 ordering/src/main/kotlin/com/coded/ordering/providers/JwtAuthProvider.kt rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/repositories/MenuRepository.kt (81%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/repositories/OrderItemRepository.kt (63%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/repositories/OrderRepository.kt (56%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/repositories/ProfileRepository.kt (54%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/repositories/RestaurantRepository.kt (67%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/restaurants/RestaurantApiController.kt (88%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/restaurants/RestaurantService.kt (60%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/restaurants/RestaurantServiceImpl.kt (74%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt (74%) rename {src/main/kotlin/com/coded/spring => ordering/src/main/kotlin/com/coded}/ordering/restaurants/dtos/RestaurantInfoResponse.kt (58%) rename {src => ordering/src}/main/resources/V1_sample.sql (100%) rename {src => ordering/src}/main/resources/V2_sample.sql (100%) create mode 100644 ordering/src/main/resources/application.properties rename {src => ordering/src}/main/resources/db/migration/V1__init_db.sql (100%) rename {src => ordering/src}/main/resources/db/migration/V2__menu_column_change.sql (100%) rename {src => ordering/src}/main/resources/db/migration/V3__profile_table_create.sql (100%) create mode 100644 ordering/src/test/kotlin/com/coded/ordering/OrderingApplicationTests.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserService.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/extensions.kt delete mode 100644 src/test/kotlin/com/coded/spring/ordering/test/helpers.kt delete mode 100644 src/test/resources/application.properties delete mode 100644 src/test/resources/features/hello_world.feature diff --git a/authentication/pom.xml b/authentication/pom.xml new file mode 100644 index 0000000..17d0b87 --- /dev/null +++ b/authentication/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.coded.spring + YousefTech + 0.0.1-SNAPSHOT + + + authentication + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + + \ No newline at end of file diff --git a/authentication/src/main/kotlin/com/coded/authentication/AuthApplication.kt b/authentication/src/main/kotlin/com/coded/authentication/AuthApplication.kt new file mode 100644 index 0000000..30f1c05 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/AuthApplication.kt @@ -0,0 +1,11 @@ +package com.coded.authentication + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class AuthApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/main/kotlin/InitUserRunner.kt b/authentication/src/main/kotlin/com/coded/authentication/InitUserRunner.kt similarity index 72% rename from src/main/kotlin/InitUserRunner.kt rename to authentication/src/main/kotlin/com/coded/authentication/InitUserRunner.kt index 3ad9ca0..51eafe4 100644 --- a/src/main/kotlin/InitUserRunner.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/InitUserRunner.kt @@ -1,5 +1,7 @@ -import com.coded.spring.ordering.domain.entities.UserEntity -import com.coded.spring.ordering.repositories.UserRepository +package com.coded.authentication + +import com.coded.authentication.users.UserEntity +import com.coded.authentication.users.UserRepository import org.springframework.boot.CommandLineRunner import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean @@ -8,8 +10,8 @@ import org.springframework.security.crypto.password.PasswordEncoder @SpringBootApplication class InitUserRunner { @Bean - fun initUsers(userRepository: UserRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { - val user = UserEntity( + fun initUsers(userRepository: com.coded.authentication.users.UserRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { + val user = com.coded.authentication.users.UserEntity( name = "admin user", username = "adminUser", password = passwordEncoder.encode("password123"), diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/AuthApiController.kt similarity index 78% rename from src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt rename to authentication/src/main/kotlin/com/coded/authentication/auth/AuthApiController.kt index 93ed2d5..0ff4846 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/AuthApiController.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/AuthApiController.kt @@ -1,10 +1,12 @@ -package com.coded.spring.ordering.auth +package com.coded.authentication.auth -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.auth.dtos.LoginRequestDto -import com.coded.spring.ordering.users.UserService -import com.coded.spring.ordering.users.dtos.UserCreateRequestDto -import com.coded.spring.ordering.users.dtos.toEntity + +import com.coded.authentication.auth.dtos.JwtResponseDto +import com.coded.authentication.auth.dtos.LoginRequestDto +import com.coded.authentication.auth.dtos.ValidateTokenResponseDto +import com.coded.authentication.users.UserService +import com.coded.authentication.users.dtos.UserCreateRequestDto +import com.coded.authentication.users.dtos.toEntity import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content @@ -18,22 +20,19 @@ import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController import org.springframework.security.crypto.password.PasswordEncoder -import org.springframework.web.ErrorResponse +import org.springframework.web.bind.annotation.* +import java.security.Principal -@Tag(name="Auth Ppi") +@Tag(name="Auth Api") @RestController @RequestMapping("/api/v1/auth") class AuthApiController( private val authenticationManager: AuthenticationManager, private val userDetailsService: UserDetailsService, - private val jwtService: JwtService, - private val userService: UserService, + private val jwtService: com.coded.authentication.auth.JwtService, + private val userService: com.coded.authentication.users.UserService, private val passwordEncoder: PasswordEncoder, ) { @Operation(summary = "User login endpoint to receive JWT token") @@ -102,4 +101,15 @@ class AuthApiController( val token = jwtService.generateToken(userEntity.username) return ResponseEntity(JwtResponseDto(token), HttpStatus.OK) } -} \ No newline at end of file + + + @PostMapping("/check-token") + fun checkToken(principal: Principal + ): ValidateTokenResponseDto { + val user = userService.findByUserName(principal.name) + ?: throw UsernameNotFoundException("User not found") + return ValidateTokenResponseDto(user.id!!) + } + +} + diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/CustomerDetailsService.kt similarity index 80% rename from src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt rename to authentication/src/main/kotlin/com/coded/authentication/auth/CustomerDetailsService.kt index e257f62..9958360 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/CustomerDetailsService.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/CustomerDetailsService.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.auth +package com.coded.authentication.auth -import com.coded.spring.ordering.repositories.UserRepository +import com.coded.authentication.users.UserRepository import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.User @@ -9,7 +9,7 @@ import org.springframework.stereotype.Service @Service class CustomUserDetailsService( - private val usersRepository: UserRepository, + private val usersRepository: com.coded.authentication.users.UserRepository, ) : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { val user = usersRepository.findByUsername(username) diff --git a/authentication/src/main/kotlin/com/coded/authentication/auth/JwtAuthenticationFilter.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..cc36a07 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/JwtAuthenticationFilter.kt @@ -0,0 +1,59 @@ +package com.coded.authentication.auth + +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 ")) { + logger.warn("Missing or malformed Authorization header") + filterChain.doFilter(request, response) + return + } + + val token = authHeader.removePrefix("Bearer ").trim() + logger.info("Received JWT: $token") + + try { + val username = jwtService.extractUsername(token) + logger.info("Extracted username from token: $username") + + if (SecurityContextHolder.getContext().authentication == null) { + if (jwtService.isTokenValid(token, username)) { + logger.info("Token is valid for username: $username") + + val userDetails = userDetailsService.loadUserByUsername(username) + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + logger.info("SecurityContext set for user: $username") + } else { + logger.warn("Token is NOT valid for username: $username") + } + } + } catch (e: Exception) { + logger.error("JWT validation failed: ${e.message}", e) + } + + filterChain.doFilter(request, response) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/JwtService.kt similarity index 77% rename from src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt rename to authentication/src/main/kotlin/com/coded/authentication/auth/JwtService.kt index a5f975d..aef3ea1 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/JwtService.kt @@ -1,16 +1,19 @@ -package com.coded.spring.ordering.auth +package com.coded.authentication.auth import io.jsonwebtoken.* import io.jsonwebtoken.security.Keys +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import java.util.* import javax.crypto.SecretKey @Component -class JwtService { +class JwtService ( + @Value("\${jwt-secret}") + private val secretKeyString: String +){ - // use env vars for jwt token generation - private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private val secretKey: SecretKey = Keys.hmacShaKeyFor(secretKeyString.encodeToByteArray()) private val expirationMs: Long = 1000 * 60 * 60 fun generateToken(username: String): String { diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/JwtResponseDto.kt similarity index 54% rename from src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt rename to authentication/src/main/kotlin/com/coded/authentication/auth/dtos/JwtResponseDto.kt index 7788bc1..079384c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/dtos/JwtResponseDto.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/JwtResponseDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.auth.dtos +package com.coded.authentication.auth.dtos data class JwtResponseDto( val token: String, diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/LoginRequestDto.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt rename to authentication/src/main/kotlin/com/coded/authentication/auth/dtos/LoginRequestDto.kt index 19965ad..9f2e9df 100644 --- a/src/main/kotlin/com/coded/spring/ordering/auth/dtos/LoginRequestDto.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/LoginRequestDto.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.auth.dtos +package com.coded.authentication.auth.dtos import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size diff --git a/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/ValidateTokenResponseDto.kt b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/ValidateTokenResponseDto.kt new file mode 100644 index 0000000..ebcfbb0 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/auth/dtos/ValidateTokenResponseDto.kt @@ -0,0 +1,5 @@ +package com.coded.authentication.auth.dtos + +data class ValidateTokenResponseDto ( + val userId: Long +) \ No newline at end of file diff --git a/authentication/src/main/kotlin/com/coded/authentication/config/LoggingFilter.kt b/authentication/src/main/kotlin/com/coded/authentication/config/LoggingFilter.kt new file mode 100644 index 0000000..de9e307 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/config/LoggingFilter.kt @@ -0,0 +1,41 @@ +package com.coded.authentication.config + +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.HIGHEST_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) + + cachedResponse.copyBodyToResponse() + + logRequest(cachedRequest) + logResponse(cachedResponse) + } + + private fun logRequest(request: ContentCachingRequestWrapper) { + val requestBody = String(request.contentAsByteArray) + 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") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt b/authentication/src/main/kotlin/com/coded/authentication/config/SecurityConfig.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt rename to authentication/src/main/kotlin/com/coded/authentication/config/SecurityConfig.kt index b2229f3..4706264 100644 --- a/src/main/kotlin/com/coded/spring/ordering/config/SecurityConfig.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/config/SecurityConfig.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.config +package com.coded.authentication.config -import com.coded.spring.ordering.auth.JwtAuthenticationFilter +import com.coded.authentication.auth.JwtAuthenticationFilter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationManager @@ -30,7 +30,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/auth/**", "/api/v1/menus", "/api-docs").permitAll() + it.requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll() .anyRequest().authenticated() } .sessionManagement { diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt b/authentication/src/main/kotlin/com/coded/authentication/users/UserEntity.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt rename to authentication/src/main/kotlin/com/coded/authentication/users/UserEntity.kt index b5e50fe..763b91b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/UserEntity.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/users/UserEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.entities +package com.coded.authentication.users import jakarta.persistence.* diff --git a/authentication/src/main/kotlin/com/coded/authentication/users/UserRepository.kt b/authentication/src/main/kotlin/com/coded/authentication/users/UserRepository.kt new file mode 100644 index 0000000..2f92bb0 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/users/UserRepository.kt @@ -0,0 +1,9 @@ +package com.coded.authentication.users + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UserRepository: JpaRepository { + fun findByUsername(username: String): com.coded.authentication.users.UserEntity? +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/com/coded/authentication/users/UserService.kt b/authentication/src/main/kotlin/com/coded/authentication/users/UserService.kt new file mode 100644 index 0000000..7ecd490 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/users/UserService.kt @@ -0,0 +1,10 @@ +package com.coded.authentication.users + +import com.coded.authentication.users.UserEntity + +interface UserService { + fun findAll(): List + fun createUser(user: com.coded.authentication.users.UserEntity): com.coded.authentication.users.UserEntity + fun findById(id: Long): com.coded.authentication.users.UserEntity? + fun findByUserName(userName: String): com.coded.authentication.users.UserEntity? +} \ No newline at end of file diff --git a/authentication/src/main/kotlin/com/coded/authentication/users/UserServiceImpl.kt b/authentication/src/main/kotlin/com/coded/authentication/users/UserServiceImpl.kt new file mode 100644 index 0000000..ad9d9d3 --- /dev/null +++ b/authentication/src/main/kotlin/com/coded/authentication/users/UserServiceImpl.kt @@ -0,0 +1,13 @@ +package com.coded.authentication.users + +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class UserServiceImpl (private val userRepository: com.coded.authentication.users.UserRepository): + com.coded.authentication.users.UserService { + override fun findAll(): List = userRepository.findAll() + override fun createUser(user: com.coded.authentication.users.UserEntity): com.coded.authentication.users.UserEntity = userRepository.save(user) + override fun findById(id: Long): com.coded.authentication.users.UserEntity? = userRepository.findByIdOrNull(id) + override fun findByUserName(userName: String): com.coded.authentication.users.UserEntity? = userRepository.findByUsername(userName) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt b/authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserCreateRequestDto.kt similarity index 82% rename from src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt rename to authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserCreateRequestDto.kt index ab0314b..edb9f46 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserCreateRequestDto.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserCreateRequestDto.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.users.dtos +package com.coded.authentication.users.dtos -import com.coded.spring.ordering.domain.entities.UserEntity +import com.coded.authentication.users.UserEntity import jakarta.validation.constraints.Email import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Pattern @@ -24,7 +24,7 @@ data class UserCreateRequestDto( val password: String ) -fun UserCreateRequestDto.toEntity() = UserEntity( +fun UserCreateRequestDto.toEntity() = com.coded.authentication.users.UserEntity( name = name, username = username, email = email, diff --git a/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt b/authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserResponseDto.kt similarity index 54% rename from src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt rename to authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserResponseDto.kt index ac069e8..6a466d5 100644 --- a/src/main/kotlin/com/coded/spring/ordering/users/dtos/UserResponseDto.kt +++ b/authentication/src/main/kotlin/com/coded/authentication/users/dtos/UserResponseDto.kt @@ -1,7 +1,6 @@ -package com.coded.spring.ordering.users.dtos - -import com.coded.spring.ordering.domain.entities.UserEntity +package com.coded.authentication.users.dtos +import com.coded.authentication.users.UserEntity data class UserResponseDto( val id: Long, val email: String, @@ -9,7 +8,7 @@ data class UserResponseDto( val name: String ) -fun UserEntity.toDto() = UserResponseDto( +fun com.coded.authentication.users.UserEntity.toDto() = UserResponseDto( id = id!!, email = email, username = username, diff --git a/src/main/resources/application.properties b/authentication/src/main/resources/application.properties similarity index 96% rename from src/main/resources/application.properties rename to authentication/src/main/resources/application.properties index ac528d9..d6606b3 100644 --- a/src/main/resources/application.properties +++ b/authentication/src/main/resources/application.properties @@ -8,5 +8,5 @@ spring.datasource.username=postgres spring.datasource.password=changemelater spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.show-sql=true - +server.port=8081 springdoc.api-docs.path=/api-docs diff --git a/authentication/src/test/kotlin/auth/AuthApplicationTests.kt b/authentication/src/test/kotlin/auth/AuthApplicationTests.kt new file mode 100644 index 0000000..fa9836b --- /dev/null +++ b/authentication/src/test/kotlin/auth/AuthApplicationTests.kt @@ -0,0 +1,13 @@ +package auth + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class AuthApplicationTests { + +// @Test +// fun contextLoads() { +// } + +} diff --git a/ordering/pom.xml b/ordering/pom.xml new file mode 100644 index 0000000..3094b7e --- /dev/null +++ b/ordering/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.coded.spring + YousefTech + 0.0.1-SNAPSHOT + + + + ordering + 0.0.1-SNAPSHOT + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + jpa + all-open + + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + + + diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/ordering/src/main/kotlin/com/coded/ordering/OrderingApplication.kt similarity index 71% rename from src/main/kotlin/com/coded/spring/ordering/Application.kt rename to ordering/src/main/kotlin/com/coded/ordering/OrderingApplication.kt index 1bce6d8..f39d132 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/OrderingApplication.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering +package com.coded.ordering import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @@ -7,11 +7,11 @@ import com.hazelcast.core.Hazelcast import com.hazelcast.core.HazelcastInstance @SpringBootApplication -class Application +class OrderingApplication fun main(args: Array) { - runApplication(*args) - menuItemsConfig.getMapConfig("menuItems").setTimeToLiveSeconds(5) + runApplication(*args) + menuItemsConfig.getMapConfig("menuItems").setTimeToLiveSeconds(5) } diff --git a/src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt b/ordering/src/main/kotlin/com/coded/ordering/config/LoggingFilter.kt similarity index 100% rename from src/main/kotlin/com/coded/spring/ordering/config/LoggingFilter.kt rename to ordering/src/main/kotlin/com/coded/ordering/config/LoggingFilter.kt diff --git a/ordering/src/main/kotlin/com/coded/ordering/config/RemoteAuthenticationFilter.kt b/ordering/src/main/kotlin/com/coded/ordering/config/RemoteAuthenticationFilter.kt new file mode 100644 index 0000000..f9550f5 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/config/RemoteAuthenticationFilter.kt @@ -0,0 +1,55 @@ +package com.coded.ordering.config + +import com.coded.ordering.providers.JwtAuthProvider +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.HttpStatus +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import org.springframework.web.filter.OncePerRequestFilter + +@Component +class RemoteAuthenticationFilter( + private val jwtAuthProvider: JwtAuthProvider +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val bearerToken: String? = request.getHeader("Authorization") + logger.info("Token received in filter = $bearerToken") + + if (bearerToken.isNullOrBlank() || !bearerToken.startsWith("Bearer ")) { + logger.warn("Missing or malformed Authorization header.") + response.sendError(HttpStatus.UNAUTHORIZED.value(), "Authorization header is missing or invalid") + return + } + + try { + val token = bearerToken.substring(7) + val result = jwtAuthProvider.authenticateToken(token) + logger.info("userId: ${result.userId}") + + request.setAttribute("userId", result.userId) + + val authentication = UsernamePasswordAuthenticationToken( + result.userId, + null, + listOf(SimpleGrantedAuthority("ROLE_USER")) + ) + SecurityContextHolder.getContext().authentication = authentication + + logger.info("Authentication set in SecurityContext for userId = ${result.userId}") + filterChain.doFilter(request, response) + + } catch (ex: Exception) { + logger.error("Token validation failed", ex) + response.sendError(HttpStatus.FORBIDDEN.value(), "Invalid token") + } + } +} diff --git a/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt b/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt new file mode 100644 index 0000000..4c01479 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt @@ -0,0 +1,29 @@ +package com.coded.ordering.config + +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 remoteAuthenticationFilter: RemoteAuthenticationFilter, +) { + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.requestMatchers("/api/v1/menus", "/api-docs").permitAll() + .anyRequest().authenticated() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .addFilterBefore(remoteAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java) + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/MenuEntity.kt similarity index 93% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/entities/MenuEntity.kt index a1b4b40..e794401 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/MenuEntity.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/MenuEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.entities +package com.coded.ordering.domain.entities import com.fasterxml.jackson.annotation.JsonBackReference import jakarta.persistence.* diff --git a/ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderEntity.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderEntity.kt new file mode 100644 index 0000000..2460734 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderEntity.kt @@ -0,0 +1,26 @@ + package com.coded.ordering.domain.entities + + import jakarta.persistence.* + + @Entity + @Table(name = "orders") + class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id") + val id: Long? = null, + + @JoinColumn(name="user_id") + val userId: Long?, + + @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) + @JoinColumn(name="restaurant_id") + val restaurant: RestaurantEntity?, + + + @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) + val orderItems: List? = null + ) { + constructor(): this(null, null, null) + + } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderItemEntity.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderItemEntity.kt index 53a13bd..9d2cc7b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderItemEntity.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/OrderItemEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.entities +package com.coded.ordering.domain.entities import jakarta.persistence.* diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/ProfileEntity.kt similarity index 78% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/entities/ProfileEntity.kt index c6cc349..5384924 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/ProfileEntity.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/ProfileEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.entities +package com.coded.ordering.domain.entities import jakarta.persistence.* @@ -10,9 +10,8 @@ data class ProfileEntity ( @Column(name = "id") val id: Long? = null, - @OneToOne - @JoinColumn(name="user_id") - val user: UserEntity? = null, + @JoinColumn(name="user_id", unique = true) + val user: Long? = null, @Column(name="first_name") val firstName: String? = null, diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/RestaurantEntity.kt similarity index 91% rename from src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/entities/RestaurantEntity.kt index e385cb8..48bd913 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/RestaurantEntity.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/entities/RestaurantEntity.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.entities +package com.coded.ordering.domain.entities import com.fasterxml.jackson.annotation.JsonManagedReference import jakarta.persistence.* diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/MenuBasicInfoProjection.kt similarity index 86% rename from src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/projections/MenuBasicInfoProjection.kt index 9a7c3ad..35e191d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/MenuBasicInfoProjection.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/MenuBasicInfoProjection.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.projections +package com.coded.ordering.domain.projections import java.math.BigDecimal diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/OrderInfoProjection.kt similarity index 53% rename from src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/projections/OrderInfoProjection.kt index 83eb248..e77a8d6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/OrderInfoProjection.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/OrderInfoProjection.kt @@ -1,23 +1,16 @@ -package com.coded.spring.ordering.domain.projections +package com.coded.ordering.domain.projections -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.entities.UserEntity +import com.coded.ordering.domain.entities.RestaurantEntity import java.math.BigDecimal interface OrderInfoProjection { val id: Long - val user: UserInfo + val userId: Long val restaurant: RestaurantEntity val orderItems: List - interface UserInfo { - val username: String - val email: String - val id: Long - } - interface OrderItemInfo { val item: MenuInfo val quantity: Int diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/RestaurantInfoProjection.kt similarity index 69% rename from src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt rename to ordering/src/main/kotlin/com/coded/ordering/domain/projections/RestaurantInfoProjection.kt index 51c59c6..1d50c9a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/domain/projections/RestaurantInfoProjection.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/domain/projections/RestaurantInfoProjection.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.domain.projections +package com.coded.ordering.domain.projections interface RestaurantInfoProjection { val id: Float diff --git a/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt similarity index 82% rename from src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt rename to ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt index a6f2b46..ae1756f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/helloWorld/HelloWorldApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt @@ -1,13 +1,14 @@ -package com.coded.spring.ordering.helloWorld +package com.coded.ordering.helloWorld -import com.coded.spring.ordering.auth.dtos.JwtResponseDto import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestAttribute import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -34,6 +35,9 @@ class HelloWorldApiController { ]), ) @GetMapping - fun helloWorld(): String = "Hello World!" + fun helloWorld(@RequestAttribute("userId") userId: Long, + + + ): String = "Hello World! $userId" } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuApiController.kt similarity index 85% rename from src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt rename to ordering/src/main/kotlin/com/coded/ordering/menus/MenuApiController.kt index ca28b26..c710132 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuApiController.kt @@ -1,14 +1,12 @@ -package com.coded.spring.ordering.menus +package com.coded.ordering.menus -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.menus.dtos.MenuDetailResponse -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection -import com.coded.spring.ordering.menus.dtos.MenuCreateRequestDto -import com.coded.spring.ordering.menus.dtos.toEntity -import com.coded.spring.ordering.restaurants.RestaurantService +import com.coded.ordering.menus.dtos.MenuDetailResponse +import com.coded.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.projections.MenuInfoSearchProjection +import com.coded.ordering.menus.dtos.MenuCreateRequestDto +import com.coded.ordering.menus.dtos.toEntity +import com.coded.ordering.restaurants.RestaurantService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuService.kt similarity index 60% rename from src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt rename to ordering/src/main/kotlin/com/coded/ordering/menus/MenuService.kt index ccfc04a..482a40d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuService.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuService.kt @@ -1,9 +1,9 @@ -package com.coded.spring.ordering.menus +package com.coded.ordering.menus -import com.coded.spring.ordering.menus.dtos.MenuDetailResponse -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection +import com.coded.ordering.menus.dtos.MenuDetailResponse +import com.coded.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.ordering.domain.projections.MenuInfoSearchProjection interface MenuService { diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt similarity index 81% rename from src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt rename to ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt index 39d35e5..677618e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/MenuServiceImpl.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt @@ -1,12 +1,12 @@ -package com.coded.spring.ordering.menus - -import com.coded.spring.ordering.serverCache -import com.coded.spring.ordering.menus.dtos.MenuDetailResponse -import com.coded.spring.ordering.menus.dtos.toResponse -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection -import com.coded.spring.ordering.repositories.MenuRepository +package com.coded.ordering.menus + +import com.coded.ordering.serverCache +import com.coded.ordering.menus.dtos.MenuDetailResponse +import com.coded.ordering.menus.dtos.toResponse +import com.coded.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.ordering.domain.projections.MenuInfoSearchProjection +import com.coded.ordering.repositories.MenuRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuDetailResponse.kt similarity index 57% rename from src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt rename to ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuDetailResponse.kt index cb7ee60..c67d642 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuDetailResponse.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuDetailResponse.kt @@ -1,8 +1,8 @@ -package com.coded.spring.ordering.menus.dtos +package com.coded.ordering.menus.dtos -import com.coded.spring.ordering.restaurants.dtos.RestaurantInfoResponse -import com.coded.spring.ordering.restaurants.dtos.toResponse -import com.coded.spring.ordering.domain.entities.MenuEntity +import com.coded.ordering.restaurants.dtos.RestaurantInfoResponse +import com.coded.ordering.restaurants.dtos.toResponse +import com.coded.ordering.domain.entities.MenuEntity import java.math.BigDecimal data class MenuDetailResponse ( diff --git a/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuItemCreateRequestDto.kt similarity index 80% rename from src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuItemCreateRequestDto.kt index 117ed25..bc842e6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/menus/dtos/MenuItemCreateRequestDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/dtos/MenuItemCreateRequestDto.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.menus.dtos +package com.coded.ordering.menus.dtos -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Positive import org.hibernate.validator.constraints.Length diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderApiController.kt similarity index 72% rename from src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/OrderApiController.kt index c15a9f4..c5e363f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderApiController.kt @@ -1,13 +1,11 @@ -package com.coded.spring.ordering.orders +package com.coded.ordering.orders -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.orders.dtos.OrderCreateRequestDto -import com.coded.spring.ordering.orders.dtos.toCreateDto -import com.coded.spring.ordering.restaurants.RestaurantService -import com.coded.spring.ordering.users.UserService +import com.coded.ordering.domain.projections.OrderInfoProjection +import com.coded.ordering.orders.dtos.OrderCreateRequestDto +import com.coded.ordering.orders.dtos.toCreateDto +import com.coded.ordering.restaurants.RestaurantService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content -import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag @@ -23,7 +21,6 @@ import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/orders") class OrderApiController( private val orderService: OrderService, - private val userService: UserService, private val restaurantService: RestaurantService, ){ @@ -38,8 +35,10 @@ class OrderApiController( ]), ) @GetMapping - fun getAllOrders() = ResponseEntity.ok(orderService.getAllOrders()) - // TODO: return orders based on current auth user + fun getAllOrders(@RequestAttribute("userId") userId: Long, + ): ResponseEntity> { + return ResponseEntity.ok(orderService.getAllOrdersByUserId(userId)) + } @Operation(summary = "Create a new order for authenticated users") @@ -49,7 +48,6 @@ class OrderApiController( description = "Successful Created a new order", content = [ Content( - schema = Schema(implementation = JwtResponseDto::class), mediaType = "application/json") ]), ApiResponse( @@ -68,19 +66,14 @@ class OrderApiController( @PostMapping fun createOrder( @Valid @RequestBody newOrderDto: OrderCreateRequestDto, - authentication: Authentication + @RequestAttribute("userId") userId: Long, ): ResponseEntity { - val userDetails = authentication.principal as UserDetails - - val user = userService.findByUserName(userDetails.username) - ?: return ResponseEntity(HttpStatus.BAD_REQUEST) - val restaurant = restaurantService.findById(newOrderDto.restaurantId) ?: return ResponseEntity(HttpStatus.NOT_FOUND) val order = orderService.create( newOrderDto.toCreateDto( - user, + userId, restaurant, newOrderDto.items.map { it.toCreateDto() } ) diff --git a/ordering/src/main/kotlin/com/coded/ordering/orders/OrderService.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderService.kt new file mode 100644 index 0000000..7ff02c5 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderService.kt @@ -0,0 +1,13 @@ +package com.coded.ordering.orders + +import com.coded.ordering.orders.dtos.OrderCreateDto +import com.coded.ordering.domain.entities.OrderEntity +import com.coded.ordering.domain.projections.OrderInfoProjection +import com.coded.ordering.orders.dtos.OrderCreateResponse + +interface OrderService { + fun findAll(): List + fun create(newOrder: OrderCreateDto): OrderCreateResponse + fun findById(id: Long): OrderEntity? + fun getAllOrdersByUserId(userId: Long): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderServiceImpl.kt similarity index 74% rename from src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/OrderServiceImpl.kt index fa8ac0c..2fb38ac 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderServiceImpl.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/OrderServiceImpl.kt @@ -1,16 +1,17 @@ -package com.coded.spring.ordering.orders +package com.coded.ordering.orders -import com.coded.spring.ordering.domain.entities.OrderEntity -import com.coded.spring.ordering.domain.entities.OrderItemEntity -import com.coded.spring.ordering.domain.projections.OrderInfoProjection -import com.coded.spring.ordering.orders.dtos.* -import com.coded.spring.ordering.repositories.MenuRepository -import com.coded.spring.ordering.repositories.OrderItemRepository -import com.coded.spring.ordering.repositories.OrderRepository +import com.coded.ordering.domain.entities.OrderEntity +import com.coded.ordering.domain.entities.OrderItemEntity +import com.coded.ordering.domain.projections.OrderInfoProjection +import com.coded.ordering.orders.dtos.* +import com.coded.ordering.repositories.MenuRepository +import com.coded.ordering.repositories.OrderItemRepository +import com.coded.ordering.repositories.OrderRepository import jakarta.transaction.Transactional import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service -// feature switch on for discount on items based on env config + +// TODO: feature switch on for discount on items based on env config @Service class OrderServiceImpl( private val orderRepository: OrderRepository, @@ -23,6 +24,7 @@ class OrderServiceImpl( override fun create( newOrder: OrderCreateDto ): OrderCreateResponse { + val menuIds = newOrder.items.map { it.itemId } val foundMenus = menuRepository.findAllByIdIn(menuIds) @@ -47,7 +49,7 @@ class OrderServiceImpl( return OrderCreateResponse( id = order.id!!, - userId = newOrder.user.id!!, + userId = newOrder.user, restaurantId = newOrder.restaurant.id!!, items = orderItems.map { it -> OrderItemResponse( @@ -57,10 +59,8 @@ class OrderServiceImpl( ) } ) - - } override fun findById(id: Long): OrderEntity? = orderRepository.findByIdOrNull(id) - override fun getAllOrders(): List = orderRepository.findAllProjectedBy() + override fun getAllOrdersByUserId(userId: Long): List = orderRepository.findAllByUserId(userId) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateDto.kt similarity index 59% rename from src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateDto.kt index 4b7f375..2678907 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateDto.kt @@ -1,14 +1,14 @@ -package com.coded.spring.ordering.orders.dtos +package com.coded.ordering.orders.dtos -import com.coded.spring.ordering.domain.entities.* +import com.coded.ordering.domain.entities.* data class OrderCreateDto( - val user: UserEntity, + val user: Long, val restaurant: RestaurantEntity, val items: List ) fun OrderCreateDto.toOrderEntity(): OrderEntity = OrderEntity( - user = user, + userId = user, restaurant = restaurant ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateRequestDto.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateRequestDto.kt index 049139f..3b87246 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderCreateRequestDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderCreateRequestDto.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.orders.dtos +package com.coded.ordering.orders.dtos -import com.coded.spring.ordering.domain.entities.* +import com.coded.ordering.domain.entities.* import jakarta.validation.constraints.Positive import org.jetbrains.annotations.NotNull @@ -23,7 +23,7 @@ data class OrderCreateRequestDto( ) fun OrderCreateRequestDto.toCreateDto( - user: UserEntity, + user: Long, restaurant: RestaurantEntity, items: List, ) = OrderCreateDto(user=user, restaurant=restaurant, items=items) diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderItemCreate.kt similarity index 62% rename from src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderItemCreate.kt index 28e15d0..0695f63 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderItemCreate.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderItemCreate.kt @@ -1,4 +1,4 @@ -package com.coded.spring.ordering.orders.dtos +package com.coded.ordering.orders.dtos data class OrderItemCreateDto( val itemId: Long, diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderResponse.kt similarity index 79% rename from src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt rename to ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderResponse.kt index cb6a4e6..0d6f88e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/orders/dtos/OrderResponse.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/orders/dtos/OrderResponse.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.orders.dtos +package com.coded.ordering.orders.dtos -import com.coded.spring.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.entities.MenuEntity data class ItemResponse ( diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileApiController.kt similarity index 79% rename from src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt rename to ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileApiController.kt index bee9641..c890fcd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileApiController.kt @@ -1,8 +1,8 @@ -package com.coded.spring.ordering.profiles +package com.coded.ordering.profiles -import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto -import com.coded.spring.ordering.profiles.dtos.ProfileResponseDto -import com.coded.spring.ordering.profiles.dtos.toResponseDto +import com.coded.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.ordering.profiles.dtos.ProfileResponseDto +import com.coded.ordering.profiles.dtos.toResponseDto import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -14,11 +14,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication import org.springframework.security.core.userdetails.UserDetails -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.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @Tag(name = "Profile API") @RestController @@ -69,12 +65,11 @@ class ProfileApiController( @PostMapping fun createProfile( @Valid @RequestBody profileCreateDto: ProfileCreateRequestDto, - authentication: Authentication, + @RequestAttribute("authUserId") userId: Long, ) : ResponseEntity { return try { - val userDetails = authentication.principal as UserDetails - val profile = profileService.createProfile(profileCreateDto, userDetails) + val profile = profileService.createProfile(profileCreateDto, userId) ResponseEntity(profile.toResponseDto(), HttpStatus.CREATED) } catch (e: IllegalArgumentException) { ResponseEntity(e.message, HttpStatus.BAD_REQUEST) diff --git a/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileService.kt b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileService.kt new file mode 100644 index 0000000..90705c1 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileService.kt @@ -0,0 +1,9 @@ +package com.coded.ordering.profiles + +import com.coded.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.ordering.domain.entities.ProfileEntity + +interface ProfileService { + fun findAll(): List + fun createProfile(profile: ProfileCreateRequestDto, userId: Long): ProfileEntity +} \ No newline at end of file diff --git a/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileServiceImpl.kt b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileServiceImpl.kt new file mode 100644 index 0000000..4af1524 --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/profiles/ProfileServiceImpl.kt @@ -0,0 +1,27 @@ +package com.coded.ordering.profiles + +import com.coded.ordering.profiles.dtos.ProfileCreateRequestDto +import com.coded.ordering.domain.entities.ProfileEntity +import com.coded.ordering.profiles.dtos.toEntity +import com.coded.ordering.repositories.ProfileRepository +import org.springframework.stereotype.Service + +@Service +class ProfileServiceImpl( + private val profileRepository: ProfileRepository, +): ProfileService { + override fun findAll(): List { + return profileRepository.findAll() + } + + override fun createProfile(profile: ProfileCreateRequestDto, userId: Long): ProfileEntity { + + val profileExists = profileRepository.findByUser(userId) + + if (profileExists != null) { + throw IllegalArgumentException("User already has a profile") + } + + return profileRepository.save(profile.toEntity().copy(user = userId)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt b/ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileCreateRequestDto.kt similarity index 87% rename from src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileCreateRequestDto.kt index 72a7fb8..88d5502 100644 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileCreateRequestDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileCreateRequestDto.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.profiles.dtos +package com.coded.ordering.profiles.dtos -import com.coded.spring.ordering.domain.entities.ProfileEntity +import com.coded.ordering.domain.entities.ProfileEntity import jakarta.validation.constraints.* data class ProfileCreateRequestDto( diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt b/ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileResponseDto.kt similarity index 69% rename from src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileResponseDto.kt index bc326c8..a38b0af 100644 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/dtos/ProfileResponseDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/profiles/dtos/ProfileResponseDto.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.profiles.dtos +package com.coded.ordering.profiles.dtos -import com.coded.spring.ordering.domain.entities.ProfileEntity +import com.coded.ordering.domain.entities.ProfileEntity data class ProfileResponseDto( val id: Long, @@ -13,7 +13,7 @@ data class ProfileResponseDto( fun ProfileEntity.toResponseDto() = ProfileResponseDto( id=id!!, - userId=user?.id!!, + userId=user!!, firstName=firstName!!, lastName=lastName!!, phoneNumber=phoneNumber!! diff --git a/ordering/src/main/kotlin/com/coded/ordering/providers/JwtAuthProvider.kt b/ordering/src/main/kotlin/com/coded/ordering/providers/JwtAuthProvider.kt new file mode 100644 index 0000000..eb7dfed --- /dev/null +++ b/ordering/src/main/kotlin/com/coded/ordering/providers/JwtAuthProvider.kt @@ -0,0 +1,33 @@ +package com.coded.ordering.providers + +import jakarta.inject.Named +import org.springframework.beans.factory.annotation.Value +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 JwtAuthProvider ( + @Value("\${authService.url}") + private val authServiceURL: String +){ + fun authenticateToken(token: String): ValidateTokenResponseDto { + val restTemplate = RestTemplate() + val response = restTemplate.exchange( + url = authServiceURL, + 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 ...") + } +} + +data class ValidateTokenResponseDto ( + val userId: Long +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt b/ordering/src/main/kotlin/com/coded/ordering/repositories/MenuRepository.kt similarity index 81% rename from src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt rename to ordering/src/main/kotlin/com/coded/ordering/repositories/MenuRepository.kt index 3b99e1c..a3cd580 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/MenuRepository.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/repositories/MenuRepository.kt @@ -1,8 +1,8 @@ -package com.coded.spring.ordering.repositories +package com.coded.ordering.repositories -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.domain.projections.MenuInfoSearchProjection +import com.coded.ordering.domain.entities.MenuEntity +import com.coded.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.ordering.domain.projections.MenuInfoSearchProjection import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt b/ordering/src/main/kotlin/com/coded/ordering/repositories/OrderItemRepository.kt similarity index 63% rename from src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt rename to ordering/src/main/kotlin/com/coded/ordering/repositories/OrderItemRepository.kt index 11a1aa0..22e5b7f 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderItemRepository.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/repositories/OrderItemRepository.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.repositories +package com.coded.ordering.repositories -import com.coded.spring.ordering.domain.entities.OrderItemEntity +import com.coded.ordering.domain.entities.OrderItemEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt b/ordering/src/main/kotlin/com/coded/ordering/repositories/OrderRepository.kt similarity index 56% rename from src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt rename to ordering/src/main/kotlin/com/coded/ordering/repositories/OrderRepository.kt index eecc503..5a1aa94 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/OrderRepository.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/repositories/OrderRepository.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.repositories +package com.coded.ordering.repositories -import com.coded.spring.ordering.domain.entities.OrderEntity -import com.coded.spring.ordering.domain.projections.OrderInfoProjection +import com.coded.ordering.domain.entities.OrderEntity +import com.coded.ordering.domain.projections.OrderInfoProjection import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param @@ -13,6 +13,6 @@ interface OrderRepository: JpaRepository { @Query("SELECT o FROM OrderEntity o") fun findAllProjectedBy(): List - @Query("SELECT o FROM OrderEntity o WHERE o.id = :id") - fun findProjectedById(@Param("id") id: Long): OrderInfoProjection? + @Query("SELECT o FROM OrderEntity o WHERE o.userId = :userId") + fun findAllByUserId(@Param("userId") userId: Long): List } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt b/ordering/src/main/kotlin/com/coded/ordering/repositories/ProfileRepository.kt similarity index 54% rename from src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt rename to ordering/src/main/kotlin/com/coded/ordering/repositories/ProfileRepository.kt index cb65ffa..ee0f46c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/ProfileRepository.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/repositories/ProfileRepository.kt @@ -1,10 +1,10 @@ -package com.coded.spring.ordering.repositories +package com.coded.ordering.repositories -import com.coded.spring.ordering.domain.entities.ProfileEntity +import com.coded.ordering.domain.entities.ProfileEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository interface ProfileRepository: JpaRepository { - fun findByUserId(userId: Long): ProfileEntity? + fun findByUser(userId: Long): ProfileEntity? } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt b/ordering/src/main/kotlin/com/coded/ordering/repositories/RestaurantRepository.kt similarity index 67% rename from src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt rename to ordering/src/main/kotlin/com/coded/ordering/repositories/RestaurantRepository.kt index bf33e9d..9277fee 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/RestaurantRepository.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/repositories/RestaurantRepository.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.repositories +package com.coded.ordering.repositories -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection +import com.coded.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.projections.RestaurantInfoProjection import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantApiController.kt similarity index 88% rename from src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt rename to ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantApiController.kt index ea1e9da..0fd4aab 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantApiController.kt @@ -1,11 +1,11 @@ -package com.coded.spring.ordering.restaurants +package com.coded.ordering.restaurants -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.projections.MenuBasicInfoProjection -import com.coded.spring.ordering.menus.MenuService -import com.coded.spring.ordering.profiles.dtos.ProfileResponseDto -import com.coded.spring.ordering.restaurants.dtos.RestaurantCreateRequestDto -import com.coded.spring.ordering.restaurants.dtos.toEntity +import com.coded.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.projections.MenuBasicInfoProjection +import com.coded.ordering.menus.MenuService +import com.coded.ordering.profiles.dtos.ProfileResponseDto +import com.coded.ordering.restaurants.dtos.RestaurantCreateRequestDto +import com.coded.ordering.restaurants.dtos.toEntity import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantService.kt similarity index 60% rename from src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt rename to ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantService.kt index 8eb6b50..7537027 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantService.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantService.kt @@ -1,7 +1,7 @@ -package com.coded.spring.ordering.restaurants +package com.coded.ordering.restaurants -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection +import com.coded.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.projections.RestaurantInfoProjection interface RestaurantService { fun findAll(): List diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantServiceImpl.kt similarity index 74% rename from src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt rename to ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantServiceImpl.kt index a117b8a..eb35b4d 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/RestaurantServiceImpl.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/restaurants/RestaurantServiceImpl.kt @@ -1,8 +1,8 @@ -package com.coded.spring.ordering.restaurants +package com.coded.ordering.restaurants -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.domain.projections.RestaurantInfoProjection -import com.coded.spring.ordering.repositories.RestaurantRepository +import com.coded.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.projections.RestaurantInfoProjection +import com.coded.ordering.repositories.RestaurantRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt b/ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt similarity index 74% rename from src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt rename to ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt index e52b607..33c2d5c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantCreateRequestDto.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.restaurants.dtos +package com.coded.ordering.restaurants.dtos -import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.entities.RestaurantEntity import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size diff --git a/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt b/ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantInfoResponse.kt similarity index 58% rename from src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt rename to ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantInfoResponse.kt index 5463178..3278d62 100644 --- a/src/main/kotlin/com/coded/spring/ordering/restaurants/dtos/RestaurantInfoResponse.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/restaurants/dtos/RestaurantInfoResponse.kt @@ -1,6 +1,6 @@ -package com.coded.spring.ordering.restaurants.dtos +package com.coded.ordering.restaurants.dtos -import com.coded.spring.ordering.domain.entities.RestaurantEntity +import com.coded.ordering.domain.entities.RestaurantEntity data class RestaurantInfoResponse( val id: Long, diff --git a/src/main/resources/V1_sample.sql b/ordering/src/main/resources/V1_sample.sql similarity index 100% rename from src/main/resources/V1_sample.sql rename to ordering/src/main/resources/V1_sample.sql diff --git a/src/main/resources/V2_sample.sql b/ordering/src/main/resources/V2_sample.sql similarity index 100% rename from src/main/resources/V2_sample.sql rename to ordering/src/main/resources/V2_sample.sql diff --git a/ordering/src/main/resources/application.properties b/ordering/src/main/resources/application.properties new file mode 100644 index 0000000..bf7d861 --- /dev/null +++ b/ordering/src/main/resources/application.properties @@ -0,0 +1,14 @@ +spring.application.name=Kotlin.SpringbootV2 + +logging.level.org.springframework.web= DEBUG + +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/shopping +spring.datasource.username=postgres +spring.datasource.password=changemelater +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true +server.port=8080 +springdoc.api-docs.path=/api-docs + +authService.url=http://localhost:8081/api/v1/auth/check-token diff --git a/src/main/resources/db/migration/V1__init_db.sql b/ordering/src/main/resources/db/migration/V1__init_db.sql similarity index 100% rename from src/main/resources/db/migration/V1__init_db.sql rename to ordering/src/main/resources/db/migration/V1__init_db.sql diff --git a/src/main/resources/db/migration/V2__menu_column_change.sql b/ordering/src/main/resources/db/migration/V2__menu_column_change.sql similarity index 100% rename from src/main/resources/db/migration/V2__menu_column_change.sql rename to ordering/src/main/resources/db/migration/V2__menu_column_change.sql diff --git a/src/main/resources/db/migration/V3__profile_table_create.sql b/ordering/src/main/resources/db/migration/V3__profile_table_create.sql similarity index 100% rename from src/main/resources/db/migration/V3__profile_table_create.sql rename to ordering/src/main/resources/db/migration/V3__profile_table_create.sql diff --git a/ordering/src/test/kotlin/com/coded/ordering/OrderingApplicationTests.kt b/ordering/src/test/kotlin/com/coded/ordering/OrderingApplicationTests.kt new file mode 100644 index 0000000..7535539 --- /dev/null +++ b/ordering/src/test/kotlin/com/coded/ordering/OrderingApplicationTests.kt @@ -0,0 +1,13 @@ +package com.coded.ordering + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class OrderingApplicationTests { + +// @Test +// fun contextLoads() { +// } + +} diff --git a/pom.xml b/pom.xml index 82956ac..cadd6cb 100644 --- a/pom.xml +++ b/pom.xml @@ -9,10 +9,11 @@ com.coded.spring - Ordering + YousefTech 0.0.1-SNAPSHOT - Kotlin.SpringbootV2 - Kotlin.SpringbootV2 + pom + YousefTech + YousefTech @@ -20,6 +21,12 @@ + + + + authentication + ordering + diff --git a/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt deleted file mode 100644 index 62cbec9..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.coded.spring.ordering.auth - -import jakarta.servlet.FilterChain -import jakarta.servlet.http.* -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource -import org.springframework.stereotype.Component -import org.springframework.web.filter.OncePerRequestFilter - -@Component -class JwtAuthenticationFilter( - private val jwtService: JwtService, - private val userDetailsService: UserDetailsService -) : OncePerRequestFilter() { - - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - val authHeader = request.getHeader("Authorization") - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response) - return - } - - val token = authHeader.substring(7) - val username = jwtService.extractUsername(token) - - if (SecurityContextHolder.getContext().authentication == null) { - if (jwtService.isTokenValid(token, username)) { - val userDetails = userDetailsService.loadUserByUsername(username) - val authToken = UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.authorities - ) - authToken.details = WebAuthenticationDetailsSource().buildDetails(request) - SecurityContextHolder.getContext().authentication = authToken - } - } - - filterChain.doFilter(request, response) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt deleted file mode 100644 index 3665c32..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/domain/entities/OrderEntity.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.coded.spring.ordering.domain.entities - -import jakarta.persistence.* - -@Entity -@Table(name = "orders") -class OrderEntity( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name="id") - val id: Long? = null, - - @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) - @JoinColumn(name="user_id") - val user: UserEntity?, - - @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.DETACH]) - @JoinColumn(name="restaurant_id") - val restaurant: RestaurantEntity?, - - - @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL], fetch = FetchType.EAGER) - val orderItems: List? = null -) { - constructor(): this(null, null, null) - -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt deleted file mode 100644 index 1cfe23d..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/orders/OrderService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.coded.spring.ordering.orders - -import com.coded.spring.ordering.orders.dtos.OrderCreateDto -import com.coded.spring.ordering.domain.entities.OrderEntity -import com.coded.spring.ordering.domain.projections.OrderInfoProjection -import com.coded.spring.ordering.orders.dtos.OrderCreateResponse - -interface OrderService { - fun findAll(): List - fun create(newOrder: OrderCreateDto): OrderCreateResponse - fun findById(id: Long): OrderEntity? - fun getAllOrders(): List -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt deleted file mode 100644 index 445f9ba..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.coded.spring.ordering.profiles - -import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto -import com.coded.spring.ordering.domain.entities.ProfileEntity -import org.springframework.security.core.userdetails.UserDetails - -interface ProfileService { - fun findAll(): List - fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt deleted file mode 100644 index 179cc15..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/profiles/ProfileServiceImpl.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.coded.spring.ordering.profiles - -import com.coded.spring.ordering.profiles.dtos.ProfileCreateRequestDto -import com.coded.spring.ordering.domain.entities.ProfileEntity -import com.coded.spring.ordering.profiles.dtos.toEntity -import com.coded.spring.ordering.repositories.ProfileRepository -import com.coded.spring.ordering.repositories.UserRepository -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.stereotype.Service - -@Service -class ProfileServiceImpl( - private val profileRepository: ProfileRepository, - private val userRepository: UserRepository -): ProfileService { - override fun findAll(): List { - return profileRepository.findAll() - } - - override fun createProfile(profile: ProfileCreateRequestDto, userDetails: UserDetails): ProfileEntity { - val user = userRepository.findByUsername(userDetails.username) - - require(user != null) { "User with ${userDetails.username} doesn't exist" } - require(user.id != null) { "User with does not have id" } - - val profileExists = profileRepository.findByUserId(user.id) - - if (profileExists != null) { - throw IllegalArgumentException("User already has a profile") - } - - return profileRepository.save(profile.toEntity().copy(user = user)) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt deleted file mode 100644 index c6c7878..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/repositories/UserRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.coded.spring.ordering.repositories - -import com.coded.spring.ordering.domain.entities.UserEntity -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - -@Repository -interface UserRepository: JpaRepository { - fun findByUsername(username: String): UserEntity? -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt deleted file mode 100644 index 3c1fb6a..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/users/UserApiController.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.coded.spring.ordering.users - -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.users.dtos.UserResponseDto -import com.coded.spring.ordering.users.dtos.toDto -import com.coded.spring.ordering.users.dtos.UserCreateRequestDto -import com.coded.spring.ordering.users.dtos.toEntity -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.media.Content -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.validation.Valid -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* - -@Tag(name="User API") -@RestController -@RequestMapping("/api/v1/users") -class UserApiController - (private val userService: UserService) -{ - - @Operation(summary = "Get a list of all users for authenticated users") - @ApiResponses( - ApiResponse( - responseCode = "200", - description = "List all users", - content = [ - Content( - mediaType = "application/json", - ) - ]), - - ApiResponse( - responseCode = "403", - description = "Forbidden", - content = [ - Content(mediaType = "application/json") - ]), - ) - @GetMapping - fun getUsers() = ResponseEntity.ok( - userService.findAll() - .map { it.toDto() } - ) - - @Operation(summary = "Get user details by id for authenticated users") - @ApiResponses( - ApiResponse( - responseCode = "200", - description = "Return user details", - content = [ - Content( - mediaType = "application/json", - schema = Schema(implementation = UserResponseDto::class) - ) - ]), - ApiResponse( - responseCode = "404", - description = "Not Found", - content = [ - Content(mediaType = "application/json") - ]), - ApiResponse( - responseCode = "403", - description = "Forbidden", - content = [ - Content(mediaType = "application/json") - ]), - ) - @GetMapping(path=["/{id}"]) - fun getUser(@PathVariable("id") id: Long): ResponseEntity { - val user = userService.findById(id) - ?: return ResponseEntity.badRequest().build() - return ResponseEntity.ok(user.toDto()) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt deleted file mode 100644 index 3890226..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/users/UserService.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.coded.spring.ordering.users - -import com.coded.spring.ordering.domain.entities.UserEntity - - -interface UserService { - fun findAll(): List - fun createUser(user: UserEntity): UserEntity - fun findById(id: Long): UserEntity? - fun findByUserName(userName: String): UserEntity? -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt b/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt deleted file mode 100644 index d031dcc..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/users/UserServiceImpl.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.coded.spring.ordering.users - -import com.coded.spring.ordering.domain.entities.UserEntity -import com.coded.spring.ordering.repositories.UserRepository -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Service - - - -@Service -class UserServiceImpl (private val userRepository: UserRepository): UserService { - override fun findAll(): List = userRepository.findAll() - override fun createUser(user: UserEntity): UserEntity = userRepository.save(user) - override fun findById(id: Long): UserEntity? = userRepository.findByIdOrNull(id) - override fun findByUserName(userName: String): UserEntity? = userRepository.findByUsername(userName) -} \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt deleted file mode 100644 index bfb048d..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/ApplicationTests.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.coded.spring.ordering.test - - - -import io.cucumber.spring.CucumberContextConfiguration -import org.springframework.boot.test.context.SpringBootTest - -@CucumberContextConfiguration -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class ApplicationTests diff --git a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt deleted file mode 100644 index 077e75f..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiSteps.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.coded.spring.ordering.test - -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.repositories.UserRepository -import io.cucumber.java.en.Then -import io.cucumber.java.en.When -import jakarta.inject.Inject -import org.junit.jupiter.api.Assertions.assertNotNull -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.web.client.TestRestTemplate -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.security.crypto.password.PasswordEncoder -import kotlin.test.assertEquals - -class HelloWorldApiSteps { - - @Inject - lateinit var testRestTemplate: TestRestTemplate - @Inject - lateinit var userRepository: UserRepository - @Inject - lateinit var passwordEncoder: PasswordEncoder - - private var response: ResponseEntity? = null - private var loginToken: String? = null - - @When("I make a GET request to {string}") - fun makeRequestToHelloWorld(uri: String) { - - val user = userRepository.createUser(passwordEncoder) - val jwtResponse = testRestTemplate.postForEntity( - AUTH_LOGIN_API, - user, - JwtResponseDto::class.java, - ) - - assertEquals(200, jwtResponse.statusCode.value()) - assertNotNull(jwtResponse.body) - loginToken = jwtResponse.body?.token - - val httpEntity = createHttpRequest(loginToken as String) - - response = testRestTemplate.exchange( - uri, - HttpMethod.GET, - httpEntity, - String::class.java - ) - } -// - @Then("I get the Http status code should be {int}") - fun getStatusCode200(statusCode: Int) { - assertEquals(statusCode, response?.statusCode?.value()) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt deleted file mode 100644 index 715b7a8..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/HelloWorldApiTests.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.coded.spring.ordering.test - -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.auth.dtos.LoginRequestDto -import com.coded.spring.ordering.repositories.UserRepository -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -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.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.security.crypto.password.PasswordEncoder -import kotlin.test.assertEquals -import kotlin.test.assertNull - -const val HELLO_WORLD_API = "/api/v1/hello-world" -const val AUTH_LOGIN_API = "/api/v1/auth/login" -val USER_LOGIN_CREDENTIALS = LoginRequestDto("admin", "passwordTest123") - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class HelloWorldApiTests \ No newline at end of file diff --git a/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt b/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt deleted file mode 100644 index 81c5d17..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/OrderApiTests.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.coded.spring.ordering.test - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.client.TestRestTemplate -import com.coded.spring.ordering.auth.dtos.JwtResponseDto -import com.coded.spring.ordering.domain.entities.MenuEntity -import com.coded.spring.ordering.domain.entities.RestaurantEntity -import com.coded.spring.ordering.orders.dtos.OrderCreateRequestDto -import com.coded.spring.ordering.orders.dtos.OrderCreateResponse -import com.coded.spring.ordering.orders.dtos.OrderItemCreateRequestDto -import com.coded.spring.ordering.repositories.* -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.http.HttpMethod -import org.springframework.security.crypto.password.PasswordEncoder -import java.math.BigDecimal -import kotlin.test.assertEquals - -const val BASE_ORDER_API = "/api/v1/orders" - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class OrderApiTests -@Autowired constructor( - var testRestTemplate: TestRestTemplate -) { - var loginToken: String? = null - - @BeforeEach - fun `login user`() { - val response = testRestTemplate.postForEntity( - AUTH_LOGIN_API, - USER_LOGIN_CREDENTIALS, - JwtResponseDto::class.java, - ) - assertEquals(200, response.statusCode.value()) - assertNotNull(response.body) - loginToken = response.body?.token - } - - @Test - fun `test create order returned and HTTP Status 200`() { - val expectedOrder = OrderCreateRequestDto( - restaurantId = 1, - items = listOf( - OrderItemCreateRequestDto( - itemId = 1, - quantity = 2 - ), - OrderItemCreateRequestDto( - itemId = 2, - quantity = 3 - ), - OrderItemCreateRequestDto( - itemId = 3, - quantity = 1 - ) - ) - ) - val httpEntity = createHttpRequest(loginToken as String, expectedOrder) - - val orderPostResponse = testRestTemplate.exchange( - BASE_ORDER_API, - HttpMethod.POST, - httpEntity, - OrderCreateResponse::class.java - ) - - val newOrder = orderPostResponse.body - val itemsCount = newOrder?.items?.size - - assertNotNull(itemsCount) - assertNotNull(orderPostResponse.body) - assertEquals(201, orderPostResponse.statusCode.value()) - - - assertEquals(1, newOrder?.id) - assertEquals(expectedOrder.restaurantId, newOrder?.restaurantId) - assertEquals(expectedOrder.items.size, itemsCount) - assertEquals(expectedOrder.items[0].quantity, newOrder?.items?.get(0)?.quantity) - - } - - - companion object { - @JvmStatic - @BeforeAll - fun setUp( - @Autowired userRepository: UserRepository, - @Autowired restaurantsRepository: RestaurantRepository, - @Autowired menuRepository: MenuRepository, - @Autowired passwordEncoder: PasswordEncoder - ) { - userRepository.createUser(passwordEncoder) - val restaurants = restaurantsRepository.saveAll( - listOf( - RestaurantEntity( - name="Five guys" - ), - RestaurantEntity( - name="pick" - ) - ) - ) - - menuRepository.saveAll( - listOf( - MenuEntity( - restaurant = restaurants[0], - name = "Cheese Burger", - price = BigDecimal(4.00) - ), - MenuEntity( - restaurant = restaurants[0], - name = "Milkshake", - price = BigDecimal(2.50) - ), - MenuEntity( - restaurant = restaurants[0], - name = "Fries", - price = BigDecimal(2.00) - ), - MenuEntity( - restaurant = restaurants[1], - name = "Pasta", - price = BigDecimal(3.00) - ), - MenuEntity( - restaurant = restaurants[1], - name = "Latte", - price = BigDecimal(1.5) - ), - MenuEntity( - restaurant = restaurants[1], - name = "Frozen yogurt", - price = BigDecimal(3.50) - ) - ) - ) - - } - - @JvmStatic - @AfterAll - fun tearDown( - @Autowired userRepository: UserRepository, - @Autowired orderRepository: OrderRepository, - @Autowired restaurantsRepository: RestaurantRepository, - @Autowired menuRepository: MenuRepository, - @Autowired orderItemRepository: OrderItemRepository - ) { - orderRepository.deleteAll() - orderItemRepository.deleteAll() - menuRepository.deleteAll() - restaurantsRepository.deleteAll() - userRepository.deleteAll() - } - } -} diff --git a/src/test/kotlin/com/coded/spring/ordering/test/extensions.kt b/src/test/kotlin/com/coded/spring/ordering/test/extensions.kt deleted file mode 100644 index f6bef05..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/extensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.coded.spring.ordering.test - -import com.coded.spring.ordering.domain.entities.UserEntity -import com.coded.spring.ordering.repositories.UserRepository -import org.springframework.security.crypto.password.PasswordEncoder - -fun UserRepository.createUser(passwordEncoder: PasswordEncoder?) { - this.save( - UserEntity( - name = "admin user", - username = "admin", - password = passwordEncoder?.encode("passwordTest123") ?: "passwordTest123", - email = "admin@example.com", - ) - ) -} diff --git a/src/test/kotlin/com/coded/spring/ordering/test/helpers.kt b/src/test/kotlin/com/coded/spring/ordering/test/helpers.kt deleted file mode 100644 index ac23016..0000000 --- a/src/test/kotlin/com/coded/spring/ordering/test/helpers.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.coded.spring.ordering.test - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders - -fun createHttpRequest(token: String, requestBody: T? = null): HttpEntity { - val headers = HttpHeaders() - headers.set("Authorization", "Bearer $token") - - if (requestBody != null) { - headers.set("Content-Type", "application/json") - } - - return HttpEntity(requestBody, headers) -} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties deleted file mode 100644 index 72f27bf..0000000 --- a/src/test/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH -spring.datasource.username=sa -spring.datasource.password=password -spring.datasource.driver-class-name=org.h2.Driver \ No newline at end of file diff --git a/src/test/resources/features/hello_world.feature b/src/test/resources/features/hello_world.feature deleted file mode 100644 index 0ef7b1d..0000000 --- a/src/test/resources/features/hello_world.feature +++ /dev/null @@ -1,6 +0,0 @@ - -Feature: Hello World Api - Scenario: Basic Test for the Hello world API - When I make a GET request to "/api/v1/hello-world" - Then I get the Http status code should be 300 - And the response body should be "Hello World!" From 638758472470d6fac61f75b09a2b78d202a6afb4 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 1 May 2025 20:55:17 +0300 Subject: [PATCH 28/29] Configuration --- .../coded/ordering/config/SecurityConfig.kt | 2 +- .../helloWorld/HelloWorldApiController.kt | 54 +++++++++++++++---- .../coded/ordering/menus/MenuServiceImpl.kt | 14 ++++- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt b/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt index 4c01479..2e194f2 100644 --- a/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/config/SecurityConfig.kt @@ -17,7 +17,7 @@ class SecurityConfig( fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { - it.requestMatchers("/api/v1/menus", "/api-docs").permitAll() + it.requestMatchers("/api/v1/menus", "/api-docs", "/api/v1/welcome").permitAll() .anyRequest().authenticated() } .sessionManagement { diff --git a/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt index ae1756f..7fd5616 100644 --- a/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt @@ -6,15 +6,12 @@ import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestAttribute -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -@Tag(name="Hello world") +@Tag(name = "Hello world") @RestController -@RequestMapping("/api/v1/hello-world") class HelloWorldApiController { @Operation(summary = "Test endpoint that requires JWT token") @@ -25,19 +22,56 @@ class HelloWorldApiController { content = [ Content( schema = Schema(implementation = String::class), - mediaType = "application/json") - ]), + mediaType = "application/json" + ) + ] + ), ApiResponse( responseCode = "401", description = "Forbidden", content = [ Content(mediaType = "application/json") - ]), + ] + ), ) - @GetMapping - fun helloWorld(@RequestAttribute("userId") userId: Long, + @GetMapping("/api/v1/hello-world") + fun helloWorld( + @RequestAttribute("userId") userId: Long): String = "Hello World! $userId" - ): String = "Hello World! $userId" + @Operation(summary = "Test endpoint that requires JWT token") + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "Generic hello world endpoint for authenticated users", + content = [ + Content( + schema = Schema(implementation = String::class), + mediaType = "application/json" + ) + ] + ), + ApiResponse( + responseCode = "401", + description = "Forbidden", + content = [ + Content(mediaType = "application/json") + ] + ), + ) + @GetMapping("/api/v1/welcome") + fun welcomeMessage(): String { + val stringBuilder = StringBuilder() + stringBuilder.append("Welcome to Online Ordering") + + System.getenv("companyName")?.let { + stringBuilder.append(" by $it!") + } + val festivity = System.getenv("festivity") + return when { + !festivity.isNullOrBlank() -> festivity + else -> stringBuilder.toString() + } + } } \ No newline at end of file diff --git a/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt index 677618e..fb33688 100644 --- a/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/menus/MenuServiceImpl.kt @@ -9,6 +9,7 @@ import com.coded.ordering.domain.projections.MenuInfoSearchProjection import com.coded.ordering.repositories.MenuRepository import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import java.math.BigDecimal @Service class MenuServiceImpl( @@ -16,10 +17,19 @@ class MenuServiceImpl( ) : MenuService { override fun findAll(): List { val cachedMenuItems = menuItemsCache["menuItems"] - + val discountActive = (System.getenv("discountActive") ?: "false") == "true" return if (cachedMenuItems?.size == 0 || cachedMenuItems == null) { println("caching menu items") - val menuItems = menuRepository.findAll().map { it.toResponse() } + val menuItems = menuRepository.findAll().map { + if (discountActive) { + it.copy( + price = it.price.subtract( + it.price.multiply(BigDecimal.valueOf(0.2)) + )).toResponse() + } else { + it.toResponse() + } + } menuItemsCache.put("menuItems", menuItems) menuItems } else { From 86777018d8800212d6af0ae612c761f4bfa6b4f2 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 1 May 2025 23:51:17 +0300 Subject: [PATCH 29/29] updated swagger for welcome message endpoint --- .../com/coded/ordering/helloWorld/HelloWorldApiController.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt index 7fd5616..0cddb65 100644 --- a/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt +++ b/ordering/src/main/kotlin/com/coded/ordering/helloWorld/HelloWorldApiController.kt @@ -39,11 +39,11 @@ class HelloWorldApiController { @RequestAttribute("userId") userId: Long): String = "Hello World! $userId" - @Operation(summary = "Test endpoint that requires JWT token") + @Operation(summary = "Returns a welcome message or festive greeting") @ApiResponses( ApiResponse( responseCode = "200", - description = "Generic hello world endpoint for authenticated users", + description = "Returns a welcome message or greeting", content = [ Content( schema = Schema(implementation = String::class),