From 04355bab8407350ebe573617b1dddb2a01b74194 Mon Sep 17 00:00:00 2001 From: spdys Date: Wed, 16 Apr 2025 17:35:09 +0300 Subject: [PATCH 01/16] Finished first two exercises. --- .../ordering/OnlineOrderingController.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt new file mode 100644 index 0000000..e26ee68 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt @@ -0,0 +1,37 @@ +package com.example.onlineOrdering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime + +@RestController +class OnlineOrderingController { + + val orderDatabase = mutableListOf() + + @GetMapping("/welcome") + fun welcome() = "Welcome to our Online Ordering Server!" + + + @PostMapping("/order") + fun saveOrder(@RequestBody order: Order): String { + orderDatabase.add(order) + return "Order created." + } + + @GetMapping("/order") + fun getAll(): + List = orderDatabase.sortedBy { + it.createdAt + } + + data class Order( + val user: String, + val restaurant: String, + val items: List, + val createdAt: LocalDateTime = LocalDateTime.now() + ) +} + From 5fb68640de22b27ebaaddca412510d114994371a Mon Sep 17 00:00:00 2001 From: spdys Date: Wed, 16 Apr 2025 18:10:08 +0300 Subject: [PATCH 02/16] Renamed controller --- .../{OnlineOrderingController.kt => OrderingController.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/kotlin/com/coded/spring/ordering/{OnlineOrderingController.kt => OrderingController.kt} (96%) diff --git a/src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/OrderingController.kt similarity index 96% rename from src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt rename to src/main/kotlin/com/coded/spring/ordering/OrderingController.kt index e26ee68..734eb90 100644 --- a/src/main/kotlin/com/coded/spring/ordering/OnlineOrderingController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/OrderingController.kt @@ -1,4 +1,4 @@ -package com.example.onlineOrdering +package com.coded.spring.ordering import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping From 430e31f688de0be1a9f22cb4f638005a68a70aef Mon Sep 17 00:00:00 2001 From: spdys Date: Thu, 17 Apr 2025 18:55:07 +0300 Subject: [PATCH 03/16] finished creating online ordering db, exercise 3. removed createdAt column. --- src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt new file mode 100644 index 0000000..4f0c330 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt @@ -0,0 +1,4 @@ +package com.coded.spring.ordering + +class OrderEntity { +} \ No newline at end of file From b8af62fb58c6ee577f8bb3b04d5cc59dcde01c88 Mon Sep 17 00:00:00 2001 From: spdys Date: Tue, 22 Apr 2025 19:45:02 +0300 Subject: [PATCH 04/16] refactored code to have entities,repos,services,controllers, and dtos,all separate and created own readme --- .../com/coded/spring/ordering/OrderEntity.kt | 4 -- .../spring/ordering/OrderingController.kt | 37 ------------------- .../ordering/controller/OrderingController.kt | 2 + .../coded/spring/ordering/dto/OrderRequest.kt | 4 ++ .../spring/ordering/entity/OrderEntity.kt | 2 + .../ordering/repository/OrderRepository.kt | 26 +++++++++++++ .../spring/ordering/service/OrderService.kt | 4 ++ 7 files changed, 38 insertions(+), 41 deletions(-) delete mode 100644 src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/OrderingController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt deleted file mode 100644 index 4f0c330..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/OrderEntity.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.coded.spring.ordering - -class OrderEntity { -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/OrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/OrderingController.kt deleted file mode 100644 index 734eb90..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/OrderingController.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.coded.spring.ordering - -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RestController -import java.time.LocalDateTime - -@RestController -class OnlineOrderingController { - - val orderDatabase = mutableListOf() - - @GetMapping("/welcome") - fun welcome() = "Welcome to our Online Ordering Server!" - - - @PostMapping("/order") - fun saveOrder(@RequestBody order: Order): String { - orderDatabase.add(order) - return "Order created." - } - - @GetMapping("/order") - fun getAll(): - List = orderDatabase.sortedBy { - it.createdAt - } - - data class Order( - val user: String, - val restaurant: String, - val items: List, - val createdAt: LocalDateTime = LocalDateTime.now() - ) -} - diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt new file mode 100644 index 0000000..02c7fb7 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt @@ -0,0 +1,2 @@ +package com.coded.spring.ordering.controller + diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt new file mode 100644 index 0000000..775ff3a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt @@ -0,0 +1,4 @@ +package com.coded.spring.ordering.dto + +class OrderRequest { +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt new file mode 100644 index 0000000..b49cc79 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt @@ -0,0 +1,2 @@ +package com.coded.spring.ordering.entity + diff --git a/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt new file mode 100644 index 0000000..cbde8da --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt @@ -0,0 +1,26 @@ +package com.coded.spring.ordering + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderRepository: JpaRepository {} + +@Entity +@Table(name = "orders") +data class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + val userId: Long, + val restaurant: String, + val items: String, +) { + constructor(): this( + 0, + 0, + "", + "" + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt new file mode 100644 index 0000000..d7375ef --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -0,0 +1,4 @@ +package com.coded.spring.ordering.service + +class OrderService { +} \ No newline at end of file From 79f81331190b0823167adfff6e34c4645c4fd98f Mon Sep 17 00:00:00 2001 From: spdys Date: Tue, 22 Apr 2025 20:09:39 +0300 Subject: [PATCH 05/16] changed branch name master to main --- pom.xml | 24 +++++ readme.md | 101 ++++++++---------- .../com/coded/spring/ordering/Application.kt | 3 +- .../ordering/controller/OrderingController.kt | 31 +++++- .../coded/spring/ordering/dto/OrderRequest.kt | 8 +- .../spring/ordering/entity/OrderEntity.kt | 25 ++++- .../ordering/repository/OrderRepository.kt | 24 +---- .../spring/ordering/service/OrderService.kt | 3 + src/main/resources/application.properties | 6 ++ 9 files changed, 140 insertions(+), 85 deletions(-) diff --git a/pom.xml b/pom.xml index 163ad53..15cf8c4 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,30 @@ org.jetbrains.kotlin kotlin-stdlib + + jakarta.inject + jakarta.inject-api + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-starter-security + + + com.hazelcast + hazelcast + 5.5.0 + + + org.springframework.boot diff --git a/readme.md b/readme.md index e18b898..3d9adb7 100644 --- a/readme.md +++ b/readme.md @@ -1,59 +1,42 @@ - -# Coded Academy: Student Submission Guidelines - -## Online Ordering Server - -## Getting Started - -### 1. Fork the Repository - -1. Click the "Fork" button at the top right of this repository. -2. This will create a copy of the repository in your personal GitHub account. - -### 2. Clone Your Forked Repository - -### 3. Complete Your Assignment - -### 4. Commit Your Changes (Include Task Name in the comment) - -Example: "Online Ordering - Post an Order" - -### 5. Push Your Branch to Your Fork - - -### 6. Submit a Pull Request (PR) - - -## Best Practices - -- Write clean, readable code -- Include comments explaining your logic -- Test your code thoroughly -- Follow the coding standards provided in class - - - -## Submission Checklist - -- [ ] Forked the repository -- [ ] Completed all assignment requirements -- [ ] Committed changes with descriptive messages -- [ ] Pushed branch to your fork -- [ ] Submitted a pull request - -## Need Help? - -If you encounter any issues or have questions: -- Check the course materials -- Consult the documentation -- Ask your instructor or teaching assistants - -## Code of Conduct - -- Be respectful -- Collaborate professionally -- Maintain academic integrity - ---- - -Happy Coding! 🚀👩‍💻👨‍💻 +### project navi/info + +`src/main/kotlin/com.coded.spring.ordering` is where all the packages are + +`controller` → `service` (→ `dtos`) → `repository` → `entity` + +`controller` = entry point for http requests (receive requests, process data, determine response) +- `GET`: retrieve data from a server +- `POST`: send data to the server to create a new resource +- `PUT`: update or replace an existing resource +- `DELETE`: remove a resource from the server + +`service` = business logic (processes data, makes decisions, eg: save order) + +`repository` = database operations (handles communication with db: save, find, delete) + +`entity` = domain model (defines what your data is in the system: fields, relationships) + +`dto` = data transfer object (cleans request/response json bodies, to avoid returning entities) + +### services +- register user (username & password) +- list accounts +- create new/multiple accounts +- close account +- transfer money to other account +- get user kyc info +- create or update kyc info + +### progress +- #### ultimate goal is to catch up!!!! +- [x] exercise 1: welcome (bonus done) +- [x] exercise 2: endpoint to `POST` order + - [ ] do bonus + - tried and failed because i didn't know about dtos yet +- [X] exercise 3: create + connect db + - [ ] do bonus + - might have to delete and remake dbs? + - im not good at altering tables ;-; +- [ ] exercise 4: user authentication +- [ ] exercise 5: user profiles +- [ ] exercise 6: unit testing \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/ordering/Application.kt index 8554e49..2a5ad0b 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/ordering/Application.kt @@ -8,4 +8,5 @@ class Application fun main(args: Array) { runApplication(*args) -} + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt index 02c7fb7..d3dc44c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt @@ -1,2 +1,31 @@ -package com.coded.spring.ordering.controller +package com.coded.spring.ordering.controller +import com.coded.spring.ordering.dto.OrderRequest +import com.coded.spring.ordering.entity.OrderEntity +import com.coded.spring.ordering.repository.OrderRepository +import org.springframework.web.bind.annotation.* + +@RestController +class OrderingController(val orderRepository: OrderRepository) { + + @GetMapping("/welcome") + fun welcome() = "Welcome to our Online Ordering Server!" + + + @GetMapping("/order") + fun getAll(): List { + return orderRepository.findAll() + } + + @PostMapping("/order") + fun saveOrder(@RequestBody request: OrderRequest): String { + val order = OrderEntity( + request.id, + request.userId, + request.restaurant, + request.items.joinToString(", ") + ) + orderRepository.save(order) + return "Order created." + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt index 775ff3a..4a2a782 100644 --- a/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt +++ b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt @@ -1,4 +1,8 @@ package com.coded.spring.ordering.dto -class OrderRequest { -} \ No newline at end of file +data class OrderRequest( + val id: Long, + val userId: Long, + val restaurant: String, + val items: List +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt index b49cc79..780bde0 100644 --- a/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt +++ b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt @@ -1,2 +1,25 @@ -package com.coded.spring.ordering.entity +package com.coded.spring.ordering.entity +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name = "orders") +data class OrderEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + val userId: Long, + val restaurant: String, + val items: String, +) { + constructor(): this( + 0, + 0, + "", + "" + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt index cbde8da..0212895 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt @@ -1,26 +1,8 @@ -package com.coded.spring.ordering +package com.coded.spring.ordering.repository -import jakarta.persistence.* +import com.coded.spring.ordering.entity.OrderEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface OrderRepository: JpaRepository {} - -@Entity -@Table(name = "orders") -data class OrderEntity( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long, - val userId: Long, - val restaurant: String, - val items: String, -) { - constructor(): this( - 0, - 0, - "", - "" - ) -} \ No newline at end of file +interface OrderRepository: JpaRepository {} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index d7375ef..b80d6a6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -1,4 +1,7 @@ package com.coded.spring.ordering.service class OrderService { + fun doSomething() { + TODO() + } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..d4154e4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ spring.application.name=Kotlin.SpringbootV2 + +spring.datasource.url=jdbc:postgresql://localhost:5432/OrderDatabase +spring.datasource.username=postgres +spring.datasource.password=thepassword +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true From 9401c25a82b92fd7485b60c720229557682e08d4 Mon Sep 17 00:00:00 2001 From: spdys Date: Thu, 24 Apr 2025 17:34:21 +0300 Subject: [PATCH 06/16] removed empty line in application.kt --- src/main/kotlin/com/coded/spring/ordering/Application.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/Application.kt b/src/main/kotlin/com/coded/spring/ordering/Application.kt index 2a5ad0b..fb02c06 100644 --- a/src/main/kotlin/com/coded/spring/ordering/Application.kt +++ b/src/main/kotlin/com/coded/spring/ordering/Application.kt @@ -8,5 +8,4 @@ class Application fun main(args: Array) { runApplication(*args) - } \ No newline at end of file From f35c64d5b0757839f97a2f07dabfb2dec1685c77 Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 25 Apr 2025 04:59:43 +0300 Subject: [PATCH 07/16] normalized names, neutralized security, todo make orderresponse so you dont return list of entities --- .../coded/spring/ordering/SecurityConfig.kt | 23 ++++++++++++++ .../ordering/controller/OrderController.kt | 25 +++++++++++++++ .../ordering/controller/OrderingController.kt | 31 ------------------- .../coded/spring/ordering/dto/OrderRequest.kt | 1 - .../spring/ordering/service/OrderService.kt | 24 ++++++++++++-- 5 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt new file mode 100644 index 0000000..35b9ff7 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt @@ -0,0 +1,23 @@ +package com.coded.spring.ordering + +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.web.SecurityFilterChain + +@Configuration +class SecurityConfig { + + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + http + .csrf { it.disable() } + .authorizeHttpRequests { auth -> + auth.anyRequest().permitAll() + } + .httpBasic { it.disable() } + .formLogin { it.disable() } + + return http.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt new file mode 100644 index 0000000..9547a3b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering.controller + +import com.coded.spring.ordering.dto.OrderRequest +import com.coded.spring.ordering.entity.OrderEntity +import com.coded.spring.ordering.service.OrderService +import org.springframework.web.bind.annotation.* + +@RestController +class OrderController(val orderService: OrderService) { + + @GetMapping("/welcome") + fun welcome() = "Welcome to our Online Ordering Server!" + + + @GetMapping("/order") + fun getAll(): Map> { + val orders = orderService.getAllOrders() + return mapOf("orders" to orders) + } + + @PostMapping("/order") + fun saveOrder(@RequestBody request: OrderRequest): String { + return orderService.createOrder(request) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt deleted file mode 100644 index d3dc44c..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/controller/OrderingController.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.coded.spring.ordering.controller - -import com.coded.spring.ordering.dto.OrderRequest -import com.coded.spring.ordering.entity.OrderEntity -import com.coded.spring.ordering.repository.OrderRepository -import org.springframework.web.bind.annotation.* - -@RestController -class OrderingController(val orderRepository: OrderRepository) { - - @GetMapping("/welcome") - fun welcome() = "Welcome to our Online Ordering Server!" - - - @GetMapping("/order") - fun getAll(): List { - return orderRepository.findAll() - } - - @PostMapping("/order") - fun saveOrder(@RequestBody request: OrderRequest): String { - val order = OrderEntity( - request.id, - request.userId, - request.restaurant, - request.items.joinToString(", ") - ) - orderRepository.save(order) - return "Order created." - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt index 4a2a782..7754bbc 100644 --- a/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt +++ b/src/main/kotlin/com/coded/spring/ordering/dto/OrderRequest.kt @@ -1,7 +1,6 @@ package com.coded.spring.ordering.dto data class OrderRequest( - val id: Long, val userId: Long, val restaurant: String, val items: List diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index b80d6a6..b26de3e 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -1,7 +1,25 @@ package com.coded.spring.ordering.service -class OrderService { - fun doSomething() { - TODO() +import com.coded.spring.ordering.dto.OrderRequest +import com.coded.spring.ordering.entity.OrderEntity +import com.coded.spring.ordering.repository.OrderRepository +import org.springframework.stereotype.Service + +@Service +class OrderService(private val orderRepository: OrderRepository) { + + fun getAllOrders(): List { + return orderRepository.findAll() + } + + fun createOrder(request: OrderRequest): String { + val order = OrderEntity( + 0, + request.userId, + request.restaurant, + request.items.joinToString(", ") + ) + orderRepository.save(order) + return "Order created." } } \ No newline at end of file From 7710f01e4c3dcd98e61e94a00deb26058acf940e Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 25 Apr 2025 05:03:22 +0300 Subject: [PATCH 08/16] fixed readme --- readme.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 3d9adb7..e3b87a8 100644 --- a/readme.md +++ b/readme.md @@ -19,13 +19,9 @@ `dto` = data transfer object (cleans request/response json bodies, to avoid returning entities) ### services -- register user (username & password) -- list accounts -- create new/multiple accounts -- close account -- transfer money to other account -- get user kyc info -- create or update kyc info +- welcome page +- list all orders +- create new order ### progress - #### ultimate goal is to catch up!!!! From 0d6c6d0ee5373694611725efc5fdf6414336f5c7 Mon Sep 17 00:00:00 2001 From: spdys Date: Sat, 26 Apr 2025 20:29:53 +0300 Subject: [PATCH 09/16] finished task 3 completed up to task 3 bonus as well as all previous bonuses --- readme.md | 12 +++++------ .../ordering/controller/OrderController.kt | 5 ++--- .../spring/ordering/entity/ItemEntity.kt | 20 +++++++++++++++++++ .../spring/ordering/entity/OrderEntity.kt | 13 ++++++++++-- .../ordering/repository/ItemRepository.kt | 10 ++++++++++ .../ordering/repository/OrderRepository.kt | 4 +++- .../spring/ordering/service/OrderService.kt | 18 ++++++++++++++--- 7 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/entity/ItemEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repository/ItemRepository.kt diff --git a/readme.md b/readme.md index e3b87a8..d58590d 100644 --- a/readme.md +++ b/readme.md @@ -24,15 +24,13 @@ - create new order ### progress -- #### ultimate goal is to catch up!!!! -- [x] exercise 1: welcome (bonus done) +#### ultimate goal is to catch up!!!! +- [x] exercise 1: welcome + - [x] bonus: checked with `curl` command - [x] exercise 2: endpoint to `POST` order - - [ ] do bonus - - tried and failed because i didn't know about dtos yet + - [x] bonus: add `createdAt` column and sort - [X] exercise 3: create + connect db - - [ ] do bonus - - might have to delete and remake dbs? - - im not good at altering tables ;-; + - [x] bonus: create `items` table and connect it to `orders` - [ ] exercise 4: user authentication - [ ] exercise 5: user profiles - [ ] exercise 6: unit testing \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt index 9547a3b..6947421 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt @@ -13,9 +13,8 @@ class OrderController(val orderService: OrderService) { @GetMapping("/order") - fun getAll(): Map> { - val orders = orderService.getAllOrders() - return mapOf("orders" to orders) + fun getAll(): List { + return orderService.getAllOrders() } @PostMapping("/order") diff --git a/src/main/kotlin/com/coded/spring/ordering/entity/ItemEntity.kt b/src/main/kotlin/com/coded/spring/ordering/entity/ItemEntity.kt new file mode 100644 index 0000000..ba200c1 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/entity/ItemEntity.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering.entity + +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Table + +@Entity +@Table(name = "items") +data class ItemEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + + val name: String +) { + + constructor(): this(0, "") +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt index 780bde0..24d2b78 100644 --- a/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt +++ b/src/main/kotlin/com/coded/spring/ordering/entity/OrderEntity.kt @@ -4,7 +4,10 @@ import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import jakarta.persistence.Table +import java.time.LocalDateTime @Entity @Table(name = "orders") @@ -12,14 +15,20 @@ data class OrderEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long, + val userId: Long, + val createdAt: LocalDateTime, val restaurant: String, - val items: String, + + @ManyToOne + @JoinColumn(name = "items_id") + val items: ItemEntity, ) { constructor(): this( 0, 0, + LocalDateTime.now(), "", - "" + ItemEntity() ) } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repository/ItemRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repository/ItemRepository.kt new file mode 100644 index 0000000..662f8a3 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repository/ItemRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.repository + +import com.coded.spring.ordering.entity.ItemEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ItemRepository : JpaRepository { + fun findByName(name: String): ItemEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt index 0212895..5ec78a5 100644 --- a/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt +++ b/src/main/kotlin/com/coded/spring/ordering/repository/OrderRepository.kt @@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface OrderRepository: JpaRepository {} \ No newline at end of file +interface OrderRepository: JpaRepository { + fun findAllByOrderByCreatedAtAsc(): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index b26de3e..b8f376a 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -1,23 +1,35 @@ package com.coded.spring.ordering.service import com.coded.spring.ordering.dto.OrderRequest +import com.coded.spring.ordering.entity.ItemEntity import com.coded.spring.ordering.entity.OrderEntity +import com.coded.spring.ordering.repository.ItemRepository import com.coded.spring.ordering.repository.OrderRepository import org.springframework.stereotype.Service +import java.time.LocalDateTime @Service -class OrderService(private val orderRepository: OrderRepository) { +class OrderService( + private val orderRepository: OrderRepository, + private val itemRepository: ItemRepository +) { fun getAllOrders(): List { - return orderRepository.findAll() + return orderRepository.findAllByOrderByCreatedAtAsc() } fun createOrder(request: OrderRequest): String { + val itemsToString = request.items.joinToString(", ") + val existingItem = itemRepository.findByName(itemsToString) + + val item = existingItem ?: itemRepository.save(ItemEntity(0, itemsToString)) + val order = OrderEntity( 0, request.userId, + LocalDateTime.now(), request.restaurant, - request.items.joinToString(", ") + item ) orderRepository.save(order) return "Order created." From 64e76ca187c160b773bb17cecff6d697929e49f9 Mon Sep 17 00:00:00 2001 From: spdys Date: Sun, 27 Apr 2025 17:13:28 +0300 Subject: [PATCH 10/16] small readme fix --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d58590d..e008eed 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ `src/main/kotlin/com.coded.spring.ordering` is where all the packages are -`controller` → `service` (→ `dtos`) → `repository` → `entity` +`dtos` ←→ `controller` → `service` → `repository` → `entity` `controller` = entry point for http requests (receive requests, process data, determine response) - `GET`: retrieve data from a server From db3d55e1f0f1c3aad54fb96ed1ae422c670eb4dc Mon Sep 17 00:00:00 2001 From: spdys Date: Wed, 30 Apr 2025 19:47:03 +0300 Subject: [PATCH 11/16] finished authentication task i know it took forever but i really labored over every single thing to make sure i got it right. next todo is to add extracting user info from token and not need userId to create order --- pom.xml | 17 ++++++ readme.md | 5 +- .../com/coded/spring/ordering/Exceptions.kt | 5 ++ .../coded/spring/ordering/SecurityConfig.kt | 23 -------- .../AuthenticationController.kt | 38 ++++++++++++ .../authentication/AuthenticationFilter.kt | 47 +++++++++++++++ .../authentication/AuthenticationService.kt | 42 +++++++++++++ .../CustomUserDetailsService.kt | 22 +++++++ .../ordering/authentication/SecurityConfig.kt | 59 +++++++++++++++++++ .../ordering/controller/OrderController.kt | 18 +++++- .../ordering/dto/AuthenticationRequest.kt | 6 ++ .../ordering/dto/AuthenticationResponse.kt | 5 ++ .../spring/ordering/dto/FailureResponse.kt | 5 ++ .../spring/ordering/entity/UserEntity.kt | 21 +++++++ .../ordering/repository/UserRepository.kt | 10 ++++ .../spring/ordering/service/OrderService.kt | 26 +++++++- 16 files changed, 320 insertions(+), 29 deletions(-) create mode 100644 src/main/kotlin/com/coded/spring/ordering/Exceptions.kt delete mode 100644 src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationController.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationFilter.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationRequest.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationResponse.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/dto/FailureResponse.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/entity/UserEntity.kt create mode 100644 src/main/kotlin/com/coded/spring/ordering/repository/UserRepository.kt diff --git a/pom.xml b/pom.xml index 15cf8c4..113864c 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,23 @@ hazelcast 5.5.0 + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + diff --git a/readme.md b/readme.md index e008eed..660a20e 100644 --- a/readme.md +++ b/readme.md @@ -32,5 +32,8 @@ - [X] exercise 3: create + connect db - [x] bonus: create `items` table and connect it to `orders` - [ ] exercise 4: user authentication + - [ ] bonus: - [ ] exercise 5: user profiles -- [ ] exercise 6: unit testing \ No newline at end of file + - [ ] bonus: +- [ ] exercise 6: unit testing + - [ ] bonus: \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/Exceptions.kt b/src/main/kotlin/com/coded/spring/ordering/Exceptions.kt new file mode 100644 index 0000000..9f0e55a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/Exceptions.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering + +open class OrderException(message: String) : RuntimeException(message) + +class UserIdNotFound(userId: Long): OrderException("User ID $userId not found.") diff --git a/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt deleted file mode 100644 index 35b9ff7..0000000 --- a/src/main/kotlin/com/coded/spring/ordering/SecurityConfig.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.coded.spring.ordering - -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.web.SecurityFilterChain - -@Configuration -class SecurityConfig { - - @Bean - fun filterChain(http: HttpSecurity): SecurityFilterChain { - http - .csrf { it.disable() } - .authorizeHttpRequests { auth -> - auth.anyRequest().permitAll() - } - .httpBasic { it.disable() } - .formLogin { it.disable() } - - return http.build() - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationController.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationController.kt new file mode 100644 index 0000000..28765f0 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationController.kt @@ -0,0 +1,38 @@ +package com.coded.spring.ordering.authentication + +import com.coded.spring.ordering.dto.AuthenticationRequest +import com.coded.spring.ordering.dto.AuthenticationResponse +import com.coded.spring.ordering.dto.FailureResponse +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.* +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping() +class AuthenticationController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val authenticationService: AuthenticationService +) { + + @PostMapping("/login") + fun login(@RequestBody authRequest: AuthenticationRequest): ResponseEntity<*> { + return try { + val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password) + val authentication = authenticationManager.authenticate(authToken) + + if (authentication.isAuthenticated) { + val userDetails = userDetailsService.loadUserByUsername(authRequest.username) + val token = authenticationService.generateToken(userDetails.username) + ResponseEntity.ok(AuthenticationResponse(token)) + } else { + throw UsernameNotFoundException("Invalid user request!") + } + } catch (e: BadCredentialsException) { + ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(FailureResponse("Invalid username or password.")) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationFilter.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationFilter.kt new file mode 100644 index 0000000..1a3a561 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationFilter.kt @@ -0,0 +1,47 @@ +package com.coded.spring.ordering.authentication + +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 +import kotlin.text.startsWith +import kotlin.text.substring + +@Component +class AuthenticationFilter( + private val authenticationService: AuthenticationService, + 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 = authenticationService.extractUsername(token) + + if (SecurityContextHolder.getContext().authentication == null) { + val userDetails = userDetailsService.loadUserByUsername(username) + if (authenticationService.isTokenValid(token, userDetails.username)) { + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + } + } + + filterChain.doFilter(request, response) + } +} diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationService.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationService.kt new file mode 100644 index 0000000..0336445 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/AuthenticationService.kt @@ -0,0 +1,42 @@ +package com.coded.spring.ordering.authentication + +import io.jsonwebtoken.* +import io.jsonwebtoken.security.Keys +import org.springframework.stereotype.Component +import java.util.* +import javax.crypto.SecretKey + +@Component +class AuthenticationService { + + private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private val expirationMs: Long = 1000 * 60 * 60 // milliseconds * seconds * minutes = 1 hour + + fun generateToken(username: String): String { + val now = Date() + val expiry = Date(now.time + expirationMs) + + return Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiry) + .signWith(secretKey) + .compact() + } + + fun extractUsername(token: String): String = + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .body + .subject + + fun isTokenValid(token: String, username: String): Boolean { + return try { + extractUsername(token) == username + } catch (e: Exception) { + false + } + } +} diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt new file mode 100644 index 0000000..6ca53dc --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/CustomUserDetailsService.kt @@ -0,0 +1,22 @@ +package com.coded.spring.ordering.authentication + +import com.coded.spring.ordering.repository.UserRepository +import org.springframework.security.core.userdetails.* +import org.springframework.stereotype.Service +import org.springframework.security.core.userdetails.User + +@Service +class CustomUserDetailsService( + private val userRepository: UserRepository): UserDetailsService { + + override fun loadUserByUsername(username: String): UserDetails { + val user = userRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User not found") + + return User.builder() + .username(user.username) + .password(user.password) + .authorities(emptyList()) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt new file mode 100644 index 0000000..d108b0c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/authentication/SecurityConfig.kt @@ -0,0 +1,59 @@ +package com.coded.spring.ordering.authentication + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.* +import org.springframework.security.authentication.dao.DaoAuthenticationProvider +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val jwtAuthFilter: AuthenticationFilter, + private val userDetailsService: UserDetailsService +) { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.requestMatchers("/login", + "/register", + "/welcome", + "/error" + ).permitAll() + + .anyRequest().authenticated() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build() + } + + @Bean + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + + @Bean + fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager = + config.authenticationManager + + @Bean + fun authenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(userDetailsService) + provider.setPasswordEncoder(passwordEncoder()) + return provider + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt index 6947421..9fe4ffd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt @@ -1,8 +1,12 @@ package com.coded.spring.ordering.controller +import com.coded.spring.ordering.OrderException +import com.coded.spring.ordering.dto.AuthenticationRequest +import com.coded.spring.ordering.dto.FailureResponse import com.coded.spring.ordering.dto.OrderRequest import com.coded.spring.ordering.entity.OrderEntity import com.coded.spring.ordering.service.OrderService +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RestController @@ -11,6 +15,10 @@ class OrderController(val orderService: OrderService) { @GetMapping("/welcome") fun welcome() = "Welcome to our Online Ordering Server!" + @PostMapping("/register") + fun registerUser(@RequestBody request: AuthenticationRequest) { + orderService.registerUser(request) + } @GetMapping("/order") fun getAll(): List { @@ -18,7 +26,13 @@ class OrderController(val orderService: OrderService) { } @PostMapping("/order") - fun saveOrder(@RequestBody request: OrderRequest): String { - return orderService.createOrder(request) + fun saveOrder(@RequestBody request: OrderRequest): ResponseEntity<*> { + return try { + ResponseEntity.ok().body(orderService.createOrder(request)) + } catch (e: OrderException) { + ResponseEntity.badRequest().body( + FailureResponse(e.message ?: "Couldn't create order.") + ) + } } } \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationRequest.kt b/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationRequest.kt new file mode 100644 index 0000000..6f9c208 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationRequest.kt @@ -0,0 +1,6 @@ +package com.coded.spring.ordering.dto + +data class AuthenticationRequest ( + val username: String, + val password: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationResponse.kt b/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationResponse.kt new file mode 100644 index 0000000..11e536a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/dto/AuthenticationResponse.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering.dto + +class AuthenticationResponse ( + val token: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/dto/FailureResponse.kt b/src/main/kotlin/com/coded/spring/ordering/dto/FailureResponse.kt new file mode 100644 index 0000000..c9eb6df --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/dto/FailureResponse.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering.dto + +data class FailureResponse( + val error: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/entity/UserEntity.kt b/src/main/kotlin/com/coded/spring/ordering/entity/UserEntity.kt new file mode 100644 index 0000000..c6fd1b6 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/entity/UserEntity.kt @@ -0,0 +1,21 @@ +package com.coded.spring.ordering.entity + +import jakarta.persistence.* + +@Entity +@Table(name = "users") +class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + + var username: String, + var password: String, + + ) { + constructor(): this( + 0, + "", + "", + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/repository/UserRepository.kt b/src/main/kotlin/com/coded/spring/ordering/repository/UserRepository.kt new file mode 100644 index 0000000..ffb2ac9 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/repository/UserRepository.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.repository + +import com.coded.spring.ordering.entity.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/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index b8f376a..d437f57 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -1,37 +1,57 @@ package com.coded.spring.ordering.service +import com.coded.spring.ordering.UserIdNotFound +import com.coded.spring.ordering.dto.AuthenticationRequest import com.coded.spring.ordering.dto.OrderRequest import com.coded.spring.ordering.entity.ItemEntity import com.coded.spring.ordering.entity.OrderEntity +import com.coded.spring.ordering.entity.UserEntity import com.coded.spring.ordering.repository.ItemRepository import com.coded.spring.ordering.repository.OrderRepository +import com.coded.spring.ordering.repository.UserRepository +import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service import java.time.LocalDateTime @Service class OrderService( private val orderRepository: OrderRepository, - private val itemRepository: ItemRepository + private val itemRepository: ItemRepository, + private val userRepository: UserRepository, + private val passwordEncoder: PasswordEncoder ) { + fun registerUser(request: AuthenticationRequest) { + val user = UserEntity( + 0, + request.username, + passwordEncoder.encode(request.password) + ) + userRepository.save(user) + } + fun getAllOrders(): List { return orderRepository.findAllByOrderByCreatedAtAsc() } fun createOrder(request: OrderRequest): String { + val user = userRepository.findById(request.userId) + .orElseThrow { UserIdNotFound(request.userId) } + val itemsToString = request.items.joinToString(", ") val existingItem = itemRepository.findByName(itemsToString) - val item = existingItem ?: itemRepository.save(ItemEntity(0, itemsToString)) val order = OrderEntity( 0, - request.userId, + user.id, LocalDateTime.now(), request.restaurant, item ) + orderRepository.save(order) return "Order created." } + } \ No newline at end of file From 7f71187335de45f3a69d327c77435308ad8e7496 Mon Sep 17 00:00:00 2001 From: spdys Date: Wed, 30 Apr 2025 19:57:12 +0300 Subject: [PATCH 12/16] readme update --- readme.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 660a20e..844a83f 100644 --- a/readme.md +++ b/readme.md @@ -31,9 +31,20 @@ - [x] bonus: add `createdAt` column and sort - [X] exercise 3: create + connect db - [x] bonus: create `items` table and connect it to `orders` -- [ ] exercise 4: user authentication - - [ ] bonus: +- [x] exercise 4: user authentication + - [ ] bonus: password validation so people can't create a weak password + - longer than or equal to 6 letters + - at least 1 capital letter + - at least 1 number - [ ] exercise 5: user profiles - - [ ] bonus: + - [ ] bonus: - [ ] exercise 6: unit testing - - [ ] bonus: \ No newline at end of file + - [ ] bonus: +- [ ] exercise 7: menu endpoint + - [ ] bonus: +- [ ] exercise 8: configuration + - [ ] bonus: +- [ ] exercise 9: setup swagger + - [ ] bonus: +- [ ] exercise 10: refactor to micro services + - [ ] bonus: \ No newline at end of file From 138debedf2c2ecfd9fd50a08a4f494665bcad8d4 Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 2 May 2025 21:01:35 +0300 Subject: [PATCH 13/16] added global exception handler --- .../{Exceptions.kt => CustomExceptions.kt} | 0 .../spring/ordering/GlobalExceptionHandler.kt | 16 ++++++++++++++++ .../ordering/controller/OrderController.kt | 10 ++-------- 3 files changed, 18 insertions(+), 8 deletions(-) rename src/main/kotlin/com/coded/spring/ordering/{Exceptions.kt => CustomExceptions.kt} (100%) create mode 100644 src/main/kotlin/com/coded/spring/ordering/GlobalExceptionHandler.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/Exceptions.kt b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt similarity index 100% rename from src/main/kotlin/com/coded/spring/ordering/Exceptions.kt rename to src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt diff --git a/src/main/kotlin/com/coded/spring/ordering/GlobalExceptionHandler.kt b/src/main/kotlin/com/coded/spring/ordering/GlobalExceptionHandler.kt new file mode 100644 index 0000000..421729a --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/GlobalExceptionHandler.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestControllerAdvice +class GlobalExceptionHandler { + + @ExceptionHandler(OrderException::class) + fun handleOrderingException(ex: OrderException): ResponseEntity> { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(mapOf("error" to ex.message!!)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt index 9fe4ffd..8262205 100644 --- a/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt +++ b/src/main/kotlin/com/coded/spring/ordering/controller/OrderController.kt @@ -26,13 +26,7 @@ class OrderController(val orderService: OrderService) { } @PostMapping("/order") - fun saveOrder(@RequestBody request: OrderRequest): ResponseEntity<*> { - return try { - ResponseEntity.ok().body(orderService.createOrder(request)) - } catch (e: OrderException) { - ResponseEntity.badRequest().body( - FailureResponse(e.message ?: "Couldn't create order.") - ) - } + fun saveOrder(@RequestBody request: OrderRequest): String { + return orderService.createOrder(request) } } \ No newline at end of file From 2a9eb8a5a5d46fb10eedb7d3d1d2fddab6676188 Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 2 May 2025 21:23:43 +0300 Subject: [PATCH 14/16] finished auth bonus also added checking if username already exists --- .../kotlin/com/coded/spring/ordering/CustomExceptions.kt | 1 + .../com/coded/spring/ordering/service/OrderService.kt | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt index 9f0e55a..d4390bd 100644 --- a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt +++ b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt @@ -3,3 +3,4 @@ package com.coded.spring.ordering open class OrderException(message: String) : RuntimeException(message) class UserIdNotFound(userId: Long): OrderException("User ID $userId not found.") +class InvalidPasswordException(reason: String): OrderException("Invalid password: $reason") \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index d437f57..344f975 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -1,6 +1,7 @@ package com.coded.spring.ordering.service import com.coded.spring.ordering.UserIdNotFound +import com.coded.spring.ordering.InvalidPasswordException import com.coded.spring.ordering.dto.AuthenticationRequest import com.coded.spring.ordering.dto.OrderRequest import com.coded.spring.ordering.entity.ItemEntity @@ -22,11 +23,19 @@ class OrderService( ) { fun registerUser(request: AuthenticationRequest) { + if (request.password.length < 6) + throw InvalidPasswordException("Password must be at least 6 characters long.") + if (!request.password.any { it.isUpperCase() }) + throw InvalidPasswordException("Password must contain at least one uppercase letter.") + if (!request.password.any { it.isDigit() }) + throw InvalidPasswordException("Password must contain at least one number.") + val user = UserEntity( 0, request.username, passwordEncoder.encode(request.password) ) + userRepository.save(user) } From cdee5abafac1139f1c491aa45ef95c3071a0ca39 Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 2 May 2025 21:24:40 +0300 Subject: [PATCH 15/16] whoops forgot to git add . --- readme.md | 5 +---- .../kotlin/com/coded/spring/ordering/CustomExceptions.kt | 3 ++- .../kotlin/com/coded/spring/ordering/service/OrderService.kt | 3 +++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 844a83f..3a49ac1 100644 --- a/readme.md +++ b/readme.md @@ -32,10 +32,7 @@ - [X] exercise 3: create + connect db - [x] bonus: create `items` table and connect it to `orders` - [x] exercise 4: user authentication - - [ ] bonus: password validation so people can't create a weak password - - longer than or equal to 6 letters - - at least 1 capital letter - - at least 1 number + - [x] bonus: password validation so people can't create a weak password - [ ] exercise 5: user profiles - [ ] bonus: - [ ] exercise 6: unit testing diff --git a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt index d4390bd..3edbe2c 100644 --- a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt +++ b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt @@ -3,4 +3,5 @@ package com.coded.spring.ordering open class OrderException(message: String) : RuntimeException(message) class UserIdNotFound(userId: Long): OrderException("User ID $userId not found.") -class InvalidPasswordException(reason: String): OrderException("Invalid password: $reason") \ No newline at end of file +class InvalidPasswordException(reason: String): OrderException("Invalid password: $reason") +class UsergnameAlreadyExistsException(): OrderException("Username already exists.") \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt index 344f975..30ce0e6 100644 --- a/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt +++ b/src/main/kotlin/com/coded/spring/ordering/service/OrderService.kt @@ -2,6 +2,7 @@ package com.coded.spring.ordering.service import com.coded.spring.ordering.UserIdNotFound import com.coded.spring.ordering.InvalidPasswordException +import com.coded.spring.ordering.UsernameAlreadyExistsException import com.coded.spring.ordering.dto.AuthenticationRequest import com.coded.spring.ordering.dto.OrderRequest import com.coded.spring.ordering.entity.ItemEntity @@ -23,6 +24,8 @@ class OrderService( ) { fun registerUser(request: AuthenticationRequest) { + if (userRepository.findByUsername(request.username) != null) + throw UsernameAlreadyExistsException() if (request.password.length < 6) throw InvalidPasswordException("Password must be at least 6 characters long.") if (!request.password.any { it.isUpperCase() }) From e4bed48182cb2dd481dd2f77410c73f48b920673 Mon Sep 17 00:00:00 2001 From: spdys Date: Fri, 2 May 2025 21:25:17 +0300 Subject: [PATCH 16/16] oh my god there was a typo i hate my life --- src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt index 3edbe2c..85c87ca 100644 --- a/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt +++ b/src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt @@ -4,4 +4,4 @@ open class OrderException(message: String) : RuntimeException(message) class UserIdNotFound(userId: Long): OrderException("User ID $userId not found.") class InvalidPasswordException(reason: String): OrderException("Invalid password: $reason") -class UsergnameAlreadyExistsException(): OrderException("Username already exists.") \ No newline at end of file +class UsernameAlreadyExistsException(): OrderException("Username already exists.") \ No newline at end of file