From e316d99453fcb67336657f14e687f2ae567463d9 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 1 Mar 2026 16:23:15 +0100 Subject: [PATCH] Reformat code --- .../AllowancePlannerApplication.java | 10 +- .../controller/ApiController.java | 768 +++--- .../controller/WebController.java | 258 +- .../dto/AddAllowanceAmountRequest.java | 3 +- .../allowanceplanner/dto/AllowanceDto.java | 3 +- .../dto/BulkUpdateAllowanceRequest.java | 3 +- .../dto/CreateAllowanceRequest.java | 3 +- .../dto/CreateTaskRequest.java | 11 +- .../allowanceplanner/dto/ErrorResponse.java | 3 +- .../allowanceplanner/dto/HistoryDto.java | 3 +- .../allowanceplanner/dto/IdResponse.java | 3 +- .../allowanceplanner/dto/MessageResponse.java | 3 +- .../allowanceplanner/dto/MigrationDto.java | 27 +- .../dto/PostHistoryRequest.java | 3 +- .../allowanceplanner/dto/TaskDto.java | 3 +- .../allowanceplanner/dto/TransferRequest.java | 3 +- .../dto/UpdateAllowanceRequest.java | 3 +- .../allowanceplanner/dto/UserDto.java | 3 +- .../dto/UserWithAllowanceDto.java | 3 +- .../allowanceplanner/entity/Allowance.java | 129 +- .../allowanceplanner/entity/History.java | 93 +- .../allowanceplanner/entity/Task.java | 125 +- .../allowanceplanner/entity/User.java | 77 +- .../repository/AllowanceRepository.java | 19 +- .../repository/HistoryRepository.java | 5 +- .../repository/TaskRepository.java | 7 +- .../repository/UserRepository.java | 7 +- .../service/AllowanceService.java | 456 ++-- .../service/MigrationService.java | 161 +- .../allowanceplanner/service/TaskService.java | 190 +- .../service/TransferService.java | 132 +- .../allowanceplanner/service/UserService.java | 51 +- .../allowanceplanner/util/ColourUtil.java | 65 +- .../src/main/resources/application.properties | 4 - .../src/main/resources/templates/index.html | 18 +- .../seeseepuff/allowanceplanner/ApiTest.java | 2373 ++++++++--------- .../allowanceplanner/ColourUtilTest.java | 39 +- 37 files changed, 2348 insertions(+), 2719 deletions(-) diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/AllowancePlannerApplication.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/AllowancePlannerApplication.java index dbc9452..b3e3c22 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/AllowancePlannerApplication.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/AllowancePlannerApplication.java @@ -4,10 +4,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class AllowancePlannerApplication -{ - public static void main(String[] args) - { - SpringApplication.run(AllowancePlannerApplication.class, args); - } +public class AllowancePlannerApplication { + static void main(String[] args) { + SpringApplication.run(AllowancePlannerApplication.class, args); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/ApiController.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/ApiController.java index 6f8e126..3c8d971 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/ApiController.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/ApiController.java @@ -1,11 +1,7 @@ package be.seeseepuff.allowanceplanner.controller; import be.seeseepuff.allowanceplanner.dto.*; -import be.seeseepuff.allowanceplanner.service.AllowanceService; -import be.seeseepuff.allowanceplanner.service.MigrationService; -import be.seeseepuff.allowanceplanner.service.TaskService; -import be.seeseepuff.allowanceplanner.service.TransferService; -import be.seeseepuff.allowanceplanner.service.UserService; +import be.seeseepuff.allowanceplanner.service.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,504 +12,394 @@ import java.util.Optional; @RestController @RequestMapping("/api") @CrossOrigin(origins = "*") -public class ApiController -{ - private final UserService userService; - private final AllowanceService allowanceService; - private final TaskService taskService; - private final TransferService transferService; - private final MigrationService migrationService; +public class ApiController { + private final UserService userService; + private final AllowanceService allowanceService; + private final TaskService taskService; + private final TransferService transferService; + private final MigrationService migrationService; - public ApiController(UserService userService, - AllowanceService allowanceService, - TaskService taskService, - TransferService transferService, - MigrationService migrationService) - { - this.userService = userService; - this.allowanceService = allowanceService; - this.taskService = taskService; - this.transferService = transferService; - this.migrationService = migrationService; - } + public ApiController(UserService userService, + AllowanceService allowanceService, + TaskService taskService, + TransferService transferService, + MigrationService migrationService) { + this.userService = userService; + this.allowanceService = allowanceService; + this.taskService = taskService; + this.transferService = transferService; + this.migrationService = migrationService; + } - // ---- Users ---- + // ---- Users ---- - @GetMapping("/users") - public List getUsers() - { - return userService.getUsers(); - } + @GetMapping("/users") + public List getUsers() { + return userService.getUsers(); + } - @GetMapping("/user/{userId}") - public ResponseEntity getUser(@PathVariable String userId) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @GetMapping("/user/{userId}") + public ResponseEntity getUser(@PathVariable String userId) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - Optional user = userService.getUser(id); - if (user.isEmpty()) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } - return ResponseEntity.ok(user.get()); - } + Optional user = userService.getUser(id); + if (user.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } + return ResponseEntity.ok(user.get()); + } - // ---- History ---- + // ---- History ---- - @PostMapping("/user/{userId}/history") - public ResponseEntity postHistory(@PathVariable String userId, @RequestBody PostHistoryRequest request) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PostMapping("/user/{userId}/history") + public ResponseEntity postHistory(@PathVariable String userId, @RequestBody PostHistoryRequest request) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - if (request.description() == null || request.description().isEmpty()) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Description cannot be empty")); - } + if (request.description() == null || request.description().isEmpty()) { + return ResponseEntity.badRequest().body(new ErrorResponse("Description cannot be empty")); + } - if (!userService.userExists(id)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(id)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - allowanceService.addHistory(id, request); - return ResponseEntity.ok(new MessageResponse("History updated successfully")); - } + allowanceService.addHistory(id, request); + return ResponseEntity.ok(new MessageResponse("History updated successfully")); + } - @GetMapping("/user/{userId}/history") - public ResponseEntity getHistory(@PathVariable String userId) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @GetMapping("/user/{userId}/history") + public ResponseEntity getHistory(@PathVariable String userId) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - List history = allowanceService.getHistory(id); - return ResponseEntity.ok(history); - } + List history = allowanceService.getHistory(id); + return ResponseEntity.ok(history); + } - // ---- Allowances ---- + // ---- Allowances ---- - @GetMapping("/user/{userId}/allowance") - public ResponseEntity getUserAllowance(@PathVariable String userId) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @GetMapping("/user/{userId}/allowance") + public ResponseEntity getUserAllowance(@PathVariable String userId) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - if (!userService.userExists(id)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(id)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - return ResponseEntity.ok(allowanceService.getUserAllowances(id)); - } + return ResponseEntity.ok(allowanceService.getUserAllowances(id)); + } - @PostMapping("/user/{userId}/allowance") - public ResponseEntity createUserAllowance(@PathVariable String userId, - @RequestBody CreateAllowanceRequest request) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PostMapping("/user/{userId}/allowance") + public ResponseEntity createUserAllowance(@PathVariable String userId, + @RequestBody CreateAllowanceRequest request) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - if (request.name() == null || request.name().isEmpty()) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Allowance name cannot be empty")); - } + if (request.name() == null || request.name().isEmpty()) { + return ResponseEntity.badRequest().body(new ErrorResponse("Allowance name cannot be empty")); + } - if (!userService.userExists(id)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(id)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - int allowanceId = allowanceService.createAllowance(id, request); - return ResponseEntity.status(HttpStatus.CREATED).body(new IdResponse(allowanceId)); - } + int allowanceId = allowanceService.createAllowance(id, request); + return ResponseEntity.status(HttpStatus.CREATED).body(new IdResponse(allowanceId)); + } - @PutMapping("/user/{userId}/allowance") - public ResponseEntity bulkPutUserAllowance(@PathVariable String userId, - @RequestBody List requests) - { - int id; - try - { - id = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PutMapping("/user/{userId}/allowance") + public ResponseEntity bulkPutUserAllowance(@PathVariable String userId, + @RequestBody List requests) { + int id; + try { + id = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - if (!userService.userExists(id)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(id)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - allowanceService.bulkUpdateAllowance(id, requests); - return ResponseEntity.ok(new MessageResponse("Allowance updated successfully")); - } + allowanceService.bulkUpdateAllowance(id, requests); + return ResponseEntity.ok(new MessageResponse("Allowance updated successfully")); + } - @GetMapping("/user/{userId}/allowance/{allowanceId}") - public ResponseEntity getUserAllowanceById(@PathVariable String userId, @PathVariable String allowanceId) - { - int uid; - try - { - uid = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @GetMapping("/user/{userId}/allowance/{allowanceId}") + public ResponseEntity getUserAllowanceById(@PathVariable String userId, @PathVariable String allowanceId) { + int uid; + try { + uid = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - int aid; - try - { - aid = Integer.parseInt(allowanceId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); - } + int aid; + try { + aid = Integer.parseInt(allowanceId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); + } - if (!userService.userExists(uid)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(uid)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - Optional allowance = allowanceService.getUserAllowanceById(uid, aid); - if (allowance.isEmpty()) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); - } - return ResponseEntity.ok(allowance.get()); - } + Optional allowance = allowanceService.getUserAllowanceById(uid, aid); + if (allowance.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); + } + return ResponseEntity.ok(allowance.get()); + } - @DeleteMapping("/user/{userId}/allowance/{allowanceId}") - public ResponseEntity deleteUserAllowance(@PathVariable String userId, @PathVariable String allowanceId) - { - int uid; - try - { - uid = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @DeleteMapping("/user/{userId}/allowance/{allowanceId}") + public ResponseEntity deleteUserAllowance(@PathVariable String userId, @PathVariable String allowanceId) { + int uid; + try { + uid = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - int aid; - try - { - aid = Integer.parseInt(allowanceId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); - } + int aid; + try { + aid = Integer.parseInt(allowanceId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); + } - if (aid == 0) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Allowance id zero cannot be deleted")); - } + if (aid == 0) { + return ResponseEntity.badRequest().body(new ErrorResponse("Allowance id zero cannot be deleted")); + } - if (!userService.userExists(uid)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(uid)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - boolean deleted = allowanceService.deleteAllowance(uid, aid); - if (!deleted) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("History not found")); - } - return ResponseEntity.ok(new MessageResponse("History deleted successfully")); - } + boolean deleted = allowanceService.deleteAllowance(uid, aid); + if (!deleted) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("History not found")); + } + return ResponseEntity.ok(new MessageResponse("History deleted successfully")); + } - @PutMapping("/user/{userId}/allowance/{allowanceId}") - public ResponseEntity putUserAllowance(@PathVariable String userId, @PathVariable String allowanceId, - @RequestBody UpdateAllowanceRequest request) - { - int uid; - try - { - uid = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PutMapping("/user/{userId}/allowance/{allowanceId}") + public ResponseEntity putUserAllowance(@PathVariable String userId, @PathVariable String allowanceId, + @RequestBody UpdateAllowanceRequest request) { + int uid; + try { + uid = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - int aid; - try - { - aid = Integer.parseInt(allowanceId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); - } + int aid; + try { + aid = Integer.parseInt(allowanceId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); + } - if (!userService.userExists(uid)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(uid)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - boolean updated = allowanceService.updateAllowance(uid, aid, request); - if (!updated) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); - } - return ResponseEntity.ok(new MessageResponse("Allowance updated successfully")); - } + boolean updated = allowanceService.updateAllowance(uid, aid, request); + if (!updated) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); + } + return ResponseEntity.ok(new MessageResponse("Allowance updated successfully")); + } - @PostMapping("/user/{userId}/allowance/{allowanceId}/complete") - public ResponseEntity completeAllowance(@PathVariable String userId, @PathVariable String allowanceId) - { - int uid; - try - { - uid = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PostMapping("/user/{userId}/allowance/{allowanceId}/complete") + public ResponseEntity completeAllowance(@PathVariable String userId, @PathVariable String allowanceId) { + int uid; + try { + uid = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - int aid; - try - { - aid = Integer.parseInt(allowanceId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); - } + int aid; + try { + aid = Integer.parseInt(allowanceId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); + } - if (!userService.userExists(uid)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(uid)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - boolean completed = allowanceService.completeAllowance(uid, aid); - if (!completed) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); - } - return ResponseEntity.ok(new MessageResponse("Allowance completed successfully")); - } + boolean completed = allowanceService.completeAllowance(uid, aid); + if (!completed) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); + } + return ResponseEntity.ok(new MessageResponse("Allowance completed successfully")); + } - @PostMapping("/user/{userId}/allowance/{allowanceId}/add") - public ResponseEntity addToAllowance(@PathVariable String userId, @PathVariable String allowanceId, - @RequestBody AddAllowanceAmountRequest request) - { - int uid; - try - { - uid = Integer.parseInt(userId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); - } + @PostMapping("/user/{userId}/allowance/{allowanceId}/add") + public ResponseEntity addToAllowance(@PathVariable String userId, @PathVariable String allowanceId, + @RequestBody AddAllowanceAmountRequest request) { + int uid; + try { + uid = Integer.parseInt(userId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid user ID")); + } - int aid; - try - { - aid = Integer.parseInt(allowanceId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); - } + int aid; + try { + aid = Integer.parseInt(allowanceId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid allowance ID")); + } - if (!userService.userExists(uid)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } + if (!userService.userExists(uid)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } - boolean result = allowanceService.addAllowanceAmount(uid, aid, request); - if (!result) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); - } - return ResponseEntity.ok(new MessageResponse("Allowance completed successfully")); - } + boolean result = allowanceService.addAllowanceAmount(uid, aid, request); + if (!result) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Allowance not found")); + } + return ResponseEntity.ok(new MessageResponse("Allowance completed successfully")); + } - // ---- Tasks ---- + // ---- Tasks ---- - @PostMapping("/tasks") - public ResponseEntity createTask(@RequestBody CreateTaskRequest request) - { - if (request.name() == null || request.name().isEmpty()) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Task name cannot be empty")); - } + @PostMapping("/tasks") + public ResponseEntity createTask(@RequestBody CreateTaskRequest request) { + if (request.name() == null || request.name().isEmpty()) { + return ResponseEntity.badRequest().body(new ErrorResponse("Task name cannot be empty")); + } - if (request.schedule() != null) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Schedules are not yet supported")); - } + if (request.schedule() != null) { + return ResponseEntity.badRequest().body(new ErrorResponse("Schedules are not yet supported")); + } - if (request.assigned() != null) - { - if (!userService.userExists(request.assigned())) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); - } - } + if (request.assigned() != null) { + if (!userService.userExists(request.assigned())) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("User not found")); + } + } - int taskId = taskService.createTask(request); - return ResponseEntity.status(HttpStatus.CREATED).body(new IdResponse(taskId)); - } + int taskId = taskService.createTask(request); + return ResponseEntity.status(HttpStatus.CREATED).body(new IdResponse(taskId)); + } - @GetMapping("/tasks") - public List getTasks() - { - return taskService.getTasks(); - } + @GetMapping("/tasks") + public List getTasks() { + return taskService.getTasks(); + } - @GetMapping("/task/{taskId}") - public ResponseEntity getTask(@PathVariable String taskId) - { - int id; - try - { - id = Integer.parseInt(taskId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); - } + @GetMapping("/task/{taskId}") + public ResponseEntity getTask(@PathVariable String taskId) { + int id; + try { + id = Integer.parseInt(taskId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); + } - Optional task = taskService.getTask(id); - if (task.isEmpty()) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); - } - return ResponseEntity.ok(task.get()); - } + Optional task = taskService.getTask(id); + if (task.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); + } + return ResponseEntity.ok(task.get()); + } - @PutMapping("/task/{taskId}") - public ResponseEntity putTask(@PathVariable String taskId, @RequestBody CreateTaskRequest request) - { - int id; - try - { - id = Integer.parseInt(taskId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); - } + @PutMapping("/task/{taskId}") + public ResponseEntity putTask(@PathVariable String taskId, @RequestBody CreateTaskRequest request) { + int id; + try { + id = Integer.parseInt(taskId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); + } - Optional existing = taskService.getTask(id); - if (existing.isEmpty()) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); - } + Optional existing = taskService.getTask(id); + if (existing.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); + } - taskService.updateTask(id, request); - return ResponseEntity.ok(new MessageResponse("Task updated successfully")); - } + taskService.updateTask(id, request); + return ResponseEntity.ok(new MessageResponse("Task updated successfully")); + } - @DeleteMapping("/task/{taskId}") - public ResponseEntity deleteTask(@PathVariable String taskId) - { - int id; - try - { - id = Integer.parseInt(taskId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); - } + @DeleteMapping("/task/{taskId}") + public ResponseEntity deleteTask(@PathVariable String taskId) { + int id; + try { + id = Integer.parseInt(taskId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); + } - if (!taskService.hasTask(id)) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); - } + if (!taskService.hasTask(id)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); + } - taskService.deleteTask(id); - return ResponseEntity.ok(new MessageResponse("Task deleted successfully")); - } + taskService.deleteTask(id); + return ResponseEntity.ok(new MessageResponse("Task deleted successfully")); + } - @PostMapping("/task/{taskId}/complete") - public ResponseEntity completeTask(@PathVariable String taskId) - { - int id; - try - { - id = Integer.parseInt(taskId); - } - catch (NumberFormatException e) - { - return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); - } + @PostMapping("/task/{taskId}/complete") + public ResponseEntity completeTask(@PathVariable String taskId) { + int id; + try { + id = Integer.parseInt(taskId); + } catch (NumberFormatException e) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid task ID")); + } - boolean completed = taskService.completeTask(id); - if (!completed) - { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); - } - return ResponseEntity.ok(new MessageResponse("Task completed successfully")); - } + boolean completed = taskService.completeTask(id); + if (!completed) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse("Task not found")); + } + return ResponseEntity.ok(new MessageResponse("Task completed successfully")); + } - // ---- Transfer ---- + // ---- Transfer ---- - @PostMapping("/transfer") - public ResponseEntity transfer(@RequestBody TransferRequest request) - { - TransferService.TransferResult result = transferService.transfer(request); - return switch (result.status()) - { - case SUCCESS -> ResponseEntity.ok(new MessageResponse(result.message())); - case BAD_REQUEST -> ResponseEntity.badRequest().body(new ErrorResponse(result.message())); - case NOT_FOUND -> - ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(result.message())); - }; - } + @PostMapping("/transfer") + public ResponseEntity transfer(@RequestBody TransferRequest request) { + TransferService.TransferResult result = transferService.transfer(request); + return switch (result.status()) { + case SUCCESS -> ResponseEntity.ok(new MessageResponse(result.message())); + case BAD_REQUEST -> ResponseEntity.badRequest().body(new ErrorResponse(result.message())); + case NOT_FOUND -> ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(result.message())); + }; + } - // ---- Migration ---- + // ---- Migration ---- - @PostMapping("/import") - public ResponseEntity importData(@RequestBody MigrationDto data) - { - migrationService.importData(data); - return ResponseEntity.ok(new MessageResponse("Import successful")); - } + @PostMapping("/import") + public ResponseEntity importData(@RequestBody MigrationDto data) { + migrationService.importData(data); + return ResponseEntity.ok(new MessageResponse("Import successful")); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/WebController.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/WebController.java index d290984..9e4bc78 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/WebController.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/controller/WebController.java @@ -1,6 +1,7 @@ package be.seeseepuff.allowanceplanner.controller; -import be.seeseepuff.allowanceplanner.dto.*; +import be.seeseepuff.allowanceplanner.dto.CreateAllowanceRequest; +import be.seeseepuff.allowanceplanner.dto.CreateTaskRequest; import be.seeseepuff.allowanceplanner.service.AllowanceService; import be.seeseepuff.allowanceplanner.service.TaskService; import be.seeseepuff.allowanceplanner.service.UserService; @@ -14,160 +15,135 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.Arrays; -import java.util.List; @Controller -public class WebController -{ - private final UserService userService; - private final AllowanceService allowanceService; - private final TaskService taskService; +public class WebController { + private final UserService userService; + private final AllowanceService allowanceService; + private final TaskService taskService; - public WebController(UserService userService, AllowanceService allowanceService, TaskService taskService) - { - this.userService = userService; - this.allowanceService = allowanceService; - this.taskService = taskService; - } + public WebController(UserService userService, AllowanceService allowanceService, TaskService taskService) { + this.userService = userService; + this.allowanceService = allowanceService; + this.taskService = taskService; + } - @GetMapping("/") - public String index(HttpServletRequest request, HttpServletResponse response, Model model) - { - Integer currentUser = getCurrentUser(request, response); - if (currentUser == null) - { - model.addAttribute("users", userService.getUsers()); - return "index"; - } - return renderWithUser(model, currentUser); - } + @GetMapping("/") + public String index(HttpServletRequest request, HttpServletResponse response, Model model) { + Integer currentUser = getCurrentUser(request, response); + if (currentUser == null) { + model.addAttribute("users", userService.getUsers()); + return "index"; + } + return renderWithUser(model, currentUser); + } - @GetMapping("/login") - public String login(@RequestParam(required = false) String user, HttpServletResponse response) - { - if (user != null && !user.isEmpty()) - { - Cookie cookie = new Cookie("user", user); - cookie.setMaxAge(3600); - cookie.setHttpOnly(true); - response.addCookie(cookie); - } - return "redirect:/"; - } + @GetMapping("/login") + public String login(@RequestParam(required = false) String user, HttpServletResponse response) { + if (user != null && !user.isEmpty()) { + Cookie cookie = new Cookie("user", user); + cookie.setMaxAge(3600); + cookie.setHttpOnly(true); + response.addCookie(cookie); + } + return "redirect:/"; + } - @PostMapping("/createTask") - public String createTask(@RequestParam String name, @RequestParam double reward, - @RequestParam(required = false) String schedule, - HttpServletRequest request, HttpServletResponse response, Model model) - { - Integer currentUser = getCurrentUser(request, response); - if (currentUser == null) - { - return "redirect:/"; - } + @PostMapping("/createTask") + public String createTask(@RequestParam String name, @RequestParam double reward, + @RequestParam(required = false) String schedule, + HttpServletRequest request, HttpServletResponse response, Model model) { + Integer currentUser = getCurrentUser(request, response); + if (currentUser == null) { + return "redirect:/"; + } - if (name.isEmpty() || reward <= 0) - { - model.addAttribute("error", "Invalid input"); - return "index"; - } + if (name.isEmpty() || reward <= 0) { + model.addAttribute("error", "Invalid input"); + return "index"; + } - CreateTaskRequest taskRequest = new CreateTaskRequest(name, reward, null, - (schedule != null && !schedule.isEmpty()) ? schedule : null); - taskService.createTask(taskRequest); - return "redirect:/"; - } + CreateTaskRequest taskRequest = new CreateTaskRequest(name, reward, null, + (schedule != null && !schedule.isEmpty()) ? schedule : null); + taskService.createTask(taskRequest); + return "redirect:/"; + } - @GetMapping("/completeTask") - public String completeTask(@RequestParam("task") int taskId) - { - taskService.completeTask(taskId); - return "redirect:/"; - } + @GetMapping("/completeTask") + public String completeTask(@RequestParam("task") int taskId) { + taskService.completeTask(taskId); + return "redirect:/"; + } - @PostMapping("/createAllowance") - public String createAllowance(@RequestParam String name, @RequestParam double target, - @RequestParam double weight, - HttpServletRequest request, HttpServletResponse response, Model model) - { - Integer currentUser = getCurrentUser(request, response); - if (currentUser == null) - { - return "redirect:/"; - } + @PostMapping("/createAllowance") + public String createAllowance(@RequestParam String name, @RequestParam double target, + @RequestParam double weight, + HttpServletRequest request, HttpServletResponse response, Model model) { + Integer currentUser = getCurrentUser(request, response); + if (currentUser == null) { + return "redirect:/"; + } - if (name.isEmpty() || target <= 0 || weight <= 0) - { - model.addAttribute("error", "Invalid input"); - return "index"; - } + if (name.isEmpty() || target <= 0 || weight <= 0) { + model.addAttribute("error", "Invalid input"); + return "index"; + } - allowanceService.createAllowance(currentUser, new CreateAllowanceRequest(name, target, weight, "")); - return "redirect:/"; - } + allowanceService.createAllowance(currentUser, new CreateAllowanceRequest(name, target, weight, "")); + return "redirect:/"; + } - @GetMapping("/completeAllowance") - public String completeAllowance(@RequestParam("allowance") int allowanceId, - HttpServletRequest request, HttpServletResponse response) - { - Integer currentUser = getCurrentUser(request, response); - if (currentUser == null) - { - return "redirect:/"; - } - allowanceService.completeAllowance(currentUser, allowanceId); - return "redirect:/"; - } + @GetMapping("/completeAllowance") + public String completeAllowance(@RequestParam("allowance") int allowanceId, + HttpServletRequest request, HttpServletResponse response) { + Integer currentUser = getCurrentUser(request, response); + if (currentUser == null) { + return "redirect:/"; + } + allowanceService.completeAllowance(currentUser, allowanceId); + return "redirect:/"; + } - private Integer getCurrentUser(HttpServletRequest request, HttpServletResponse response) - { - Cookie[] cookies = request.getCookies(); - if (cookies == null) - { - return null; - } - String userStr = Arrays.stream(cookies) - .filter(c -> "user".equals(c.getName())) - .map(Cookie::getValue) - .findFirst() - .orElse(null); - if (userStr == null) - { - return null; - } - try - { - int userId = Integer.parseInt(userStr); - if (!userService.userExists(userId)) - { - unsetUserCookie(response); - return null; - } - return userId; - } - catch (NumberFormatException e) - { - unsetUserCookie(response); - return null; - } - } + private Integer getCurrentUser(HttpServletRequest request, HttpServletResponse response) { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + return null; + } + String userStr = Arrays.stream(cookies) + .filter(c -> "user".equals(c.getName())) + .map(Cookie::getValue) + .findFirst() + .orElse(null); + if (userStr == null) { + return null; + } + try { + int userId = Integer.parseInt(userStr); + if (!userService.userExists(userId)) { + unsetUserCookie(response); + return null; + } + return userId; + } catch (NumberFormatException e) { + unsetUserCookie(response); + return null; + } + } - private void unsetUserCookie(HttpServletResponse response) - { - Cookie cookie = new Cookie("user", ""); - cookie.setMaxAge(0); - cookie.setPath("/"); - cookie.setHttpOnly(true); - response.addCookie(cookie); - } + private void unsetUserCookie(HttpServletResponse response) { + Cookie cookie = new Cookie("user", ""); + cookie.setMaxAge(0); + cookie.setPath("/"); + cookie.setHttpOnly(true); + response.addCookie(cookie); + } - private String renderWithUser(Model model, int currentUser) - { - model.addAttribute("users", userService.getUsers()); - model.addAttribute("currentUser", currentUser); - model.addAttribute("allowances", allowanceService.getUserAllowances(currentUser)); - model.addAttribute("tasks", taskService.getTasks()); - model.addAttribute("history", allowanceService.getHistory(currentUser)); - return "index"; - } + private String renderWithUser(Model model, int currentUser) { + model.addAttribute("users", userService.getUsers()); + model.addAttribute("currentUser", currentUser); + model.addAttribute("allowances", allowanceService.getUserAllowances(currentUser)); + model.addAttribute("tasks", taskService.getTasks()); + model.addAttribute("history", allowanceService.getHistory(currentUser)); + return "index"; + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AddAllowanceAmountRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AddAllowanceAmountRequest.java index f1c467d..a8c393e 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AddAllowanceAmountRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AddAllowanceAmountRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record AddAllowanceAmountRequest(double amount, String description) -{ +public record AddAllowanceAmountRequest(double amount, String description) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AllowanceDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AllowanceDto.java index fc7b2e7..9f4171d 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AllowanceDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/AllowanceDto.java @@ -3,6 +3,5 @@ package be.seeseepuff.allowanceplanner.dto; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.ALWAYS) -public record AllowanceDto(int id, String name, double target, double progress, double weight, String colour) -{ +public record AllowanceDto(int id, String name, double target, double progress, double weight, String colour) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/BulkUpdateAllowanceRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/BulkUpdateAllowanceRequest.java index 124a08c..7da0594 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/BulkUpdateAllowanceRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/BulkUpdateAllowanceRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record BulkUpdateAllowanceRequest(int id, double weight) -{ +public record BulkUpdateAllowanceRequest(int id, double weight) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateAllowanceRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateAllowanceRequest.java index ac4e8aa..5af6e17 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateAllowanceRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateAllowanceRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record CreateAllowanceRequest(String name, double target, double weight, String colour) -{ +public record CreateAllowanceRequest(String name, double target, double weight, String colour) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateTaskRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateTaskRequest.java index e430354..69e4146 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateTaskRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/CreateTaskRequest.java @@ -1,11 +1,8 @@ package be.seeseepuff.allowanceplanner.dto; -import com.fasterxml.jackson.annotation.JsonProperty; - public record CreateTaskRequest( - String name, - Double reward, - Integer assigned, - String schedule) -{ + String name, + Double reward, + Integer assigned, + String schedule) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/ErrorResponse.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/ErrorResponse.java index c5763de..ed75413 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/ErrorResponse.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/ErrorResponse.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record ErrorResponse(String error) -{ +public record ErrorResponse(String error) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/HistoryDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/HistoryDto.java index 7e404bd..63aa97f 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/HistoryDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/HistoryDto.java @@ -2,6 +2,5 @@ package be.seeseepuff.allowanceplanner.dto; import java.time.Instant; -public record HistoryDto(double allowance, Instant timestamp, String description) -{ +public record HistoryDto(double allowance, Instant timestamp, String description) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/IdResponse.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/IdResponse.java index 54b2bea..7bdbde3 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/IdResponse.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/IdResponse.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record IdResponse(int id) -{ +public record IdResponse(int id) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MessageResponse.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MessageResponse.java index 2fef1f9..91dc296 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MessageResponse.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MessageResponse.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record MessageResponse(String message) -{ +public record MessageResponse(String message) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MigrationDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MigrationDto.java index cda8f22..5a48819 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MigrationDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/MigrationDto.java @@ -3,19 +3,22 @@ package be.seeseepuff.allowanceplanner.dto; import java.util.List; public record MigrationDto( - List users, - List allowances, - List history, - List tasks -) -{ - public record MigrationUserDto(int id, String name, long balance, double weight) {} + List users, + List allowances, + List history, + List tasks +) { + public record MigrationUserDto(int id, String name, long balance, double weight) { + } - public record MigrationAllowanceDto(int id, int userId, String name, long target, long balance, double weight, - Integer colour) {} + public record MigrationAllowanceDto(int id, int userId, String name, long target, long balance, double weight, + Integer colour) { + } - public record MigrationHistoryDto(int id, int userId, long timestamp, long amount, String description) {} + public record MigrationHistoryDto(int id, int userId, long timestamp, long amount, String description) { + } - public record MigrationTaskDto(int id, String name, long reward, Integer assigned, String schedule, - Long completed, Long nextRun) {} + public record MigrationTaskDto(int id, String name, long reward, Integer assigned, String schedule, + Long completed, Long nextRun) { + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/PostHistoryRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/PostHistoryRequest.java index abff1a5..fffc9a7 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/PostHistoryRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/PostHistoryRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record PostHistoryRequest(double allowance, String description) -{ +public record PostHistoryRequest(double allowance, String description) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TaskDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TaskDto.java index 168f10c..63d604d 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TaskDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TaskDto.java @@ -3,6 +3,5 @@ package be.seeseepuff.allowanceplanner.dto; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.ALWAYS) -public record TaskDto(int id, String name, double reward, Integer assigned, String schedule) -{ +public record TaskDto(int id, String name, double reward, Integer assigned, String schedule) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TransferRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TransferRequest.java index f631e40..ef4247b 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TransferRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/TransferRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record TransferRequest(int from, int to, double amount) -{ +public record TransferRequest(int from, int to, double amount) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UpdateAllowanceRequest.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UpdateAllowanceRequest.java index 7b1cca8..14ab0a1 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UpdateAllowanceRequest.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UpdateAllowanceRequest.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record UpdateAllowanceRequest(String name, double target, double weight, String colour) -{ +public record UpdateAllowanceRequest(String name, double target, double weight, String colour) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserDto.java index 18a0823..c498b22 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserDto.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record UserDto(int id, String name) -{ +public record UserDto(int id, String name) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserWithAllowanceDto.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserWithAllowanceDto.java index 553ddc0..32e3ad8 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserWithAllowanceDto.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/dto/UserWithAllowanceDto.java @@ -1,5 +1,4 @@ package be.seeseepuff.allowanceplanner.dto; -public record UserWithAllowanceDto(int id, String name, double allowance) -{ +public record UserWithAllowanceDto(int id, String name, double allowance) { } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Allowance.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Allowance.java index f562b25..68e02cb 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Allowance.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Allowance.java @@ -4,96 +4,81 @@ import jakarta.persistence.*; @Entity @Table(name = "allowances") -public class Allowance -{ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; +public class Allowance { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - @Column(name = "user_id", nullable = false) - private int userId; + @Column(name = "user_id", nullable = false) + private int userId; - @Column(nullable = false) - private String name; + @Column(nullable = false) + private String name; - @Column(nullable = false) - private long target; + @Column(nullable = false) + private long target; - @Column(nullable = false) - private long balance = 0; + @Column(nullable = false) + private long balance = 0; - @Column(nullable = false) - private double weight; + @Column(nullable = false) + private double weight; - private Integer colour; + private Integer colour; - public int getId() - { - return id; - } + public int getId() { + return id; + } - public void setId(int id) - { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public int getUserId() - { - return userId; - } + public int getUserId() { + return userId; + } - public void setUserId(int userId) - { - this.userId = userId; - } + public void setUserId(int userId) { + this.userId = userId; + } - public String getName() - { - return name; - } + public String getName() { + return name; + } - public void setName(String name) - { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public long getTarget() - { - return target; - } + public long getTarget() { + return target; + } - public void setTarget(long target) - { - this.target = target; - } + public void setTarget(long target) { + this.target = target; + } - public long getBalance() - { - return balance; - } + public long getBalance() { + return balance; + } - public void setBalance(long balance) - { - this.balance = balance; - } + public void setBalance(long balance) { + this.balance = balance; + } - public double getWeight() - { - return weight; - } + public double getWeight() { + return weight; + } - public void setWeight(double weight) - { - this.weight = weight; - } + public void setWeight(double weight) { + this.weight = weight; + } - public Integer getColour() - { - return colour; - } + public Integer getColour() { + return colour; + } - public void setColour(Integer colour) - { - this.colour = colour; - } + public void setColour(Integer colour) { + this.colour = colour; + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/History.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/History.java index 520422b..6f7340e 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/History.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/History.java @@ -4,70 +4,59 @@ import jakarta.persistence.*; @Entity @Table(name = "history") -public class History -{ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; +public class History { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - @Column(name = "user_id", nullable = false) - private int userId; + @Column(name = "user_id", nullable = false) + private int userId; - @Column(nullable = false) - private long timestamp; + @Column(nullable = false) + private long timestamp; - @Column(nullable = false) - private long amount; + @Column(nullable = false) + private long amount; - private String description; + private String description; - public int getId() - { - return id; - } + public int getId() { + return id; + } - public void setId(int id) - { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public int getUserId() - { - return userId; - } + public int getUserId() { + return userId; + } - public void setUserId(int userId) - { - this.userId = userId; - } + public void setUserId(int userId) { + this.userId = userId; + } - public long getTimestamp() - { - return timestamp; - } + public long getTimestamp() { + return timestamp; + } - public void setTimestamp(long timestamp) - { - this.timestamp = timestamp; - } + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } - public long getAmount() - { - return amount; - } + public long getAmount() { + return amount; + } - public void setAmount(long amount) - { - this.amount = amount; - } + public void setAmount(long amount) { + this.amount = amount; + } - public String getDescription() - { - return description; - } + public String getDescription() { + return description; + } - public void setDescription(String description) - { - this.description = description; - } + public void setDescription(String description) { + this.description = description; + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Task.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Task.java index 474c98e..da662e1 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Task.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/Task.java @@ -4,94 +4,79 @@ import jakarta.persistence.*; @Entity @Table(name = "tasks") -public class Task -{ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; +public class Task { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - @Column(nullable = false) - private String name; + @Column(nullable = false) + private String name; - @Column(nullable = false) - private long reward; + @Column(nullable = false) + private long reward; - private Integer assigned; + private Integer assigned; - private String schedule; + private String schedule; - private Long completed; + private Long completed; - @Column(name = "next_run") - private Long nextRun; + @Column(name = "next_run") + private Long nextRun; - public int getId() - { - return id; - } + public int getId() { + return id; + } - public void setId(int id) - { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public String getName() - { - return name; - } + public String getName() { + return name; + } - public void setName(String name) - { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public long getReward() - { - return reward; - } + public long getReward() { + return reward; + } - public void setReward(long reward) - { - this.reward = reward; - } + public void setReward(long reward) { + this.reward = reward; + } - public Integer getAssigned() - { - return assigned; - } + public Integer getAssigned() { + return assigned; + } - public void setAssigned(Integer assigned) - { - this.assigned = assigned; - } + public void setAssigned(Integer assigned) { + this.assigned = assigned; + } - public String getSchedule() - { - return schedule; - } + public String getSchedule() { + return schedule; + } - public void setSchedule(String schedule) - { - this.schedule = schedule; - } + public void setSchedule(String schedule) { + this.schedule = schedule; + } - public Long getCompleted() - { - return completed; - } + public Long getCompleted() { + return completed; + } - public void setCompleted(Long completed) - { - this.completed = completed; - } + public void setCompleted(Long completed) { + this.completed = completed; + } - public Long getNextRun() - { - return nextRun; - } + public Long getNextRun() { + return nextRun; + } - public void setNextRun(Long nextRun) - { - this.nextRun = nextRun; - } + public void setNextRun(Long nextRun) { + this.nextRun = nextRun; + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/User.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/User.java index 5b1b501..8a74483 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/User.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/entity/User.java @@ -4,58 +4,49 @@ import jakarta.persistence.*; @Entity @Table(name = "users") -public class User -{ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - @Column(nullable = false) - private String name; + @Column(nullable = false) + private String name; - @Column(nullable = false) - private double weight = 10.0; + @Column(nullable = false) + private double weight = 10.0; - @Column(nullable = false) - private long balance = 0; + @Column(nullable = false) + private long balance = 0; - public int getId() - { - return id; - } + public int getId() { + return id; + } - public void setId(int id) - { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public String getName() - { - return name; - } + public String getName() { + return name; + } - public void setName(String name) - { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public double getWeight() - { - return weight; - } + public double getWeight() { + return weight; + } - public void setWeight(double weight) - { - this.weight = weight; - } + public void setWeight(double weight) { + this.weight = weight; + } - public long getBalance() - { - return balance; - } + public long getBalance() { + return balance; + } - public void setBalance(long balance) - { - this.balance = balance; - } + public void setBalance(long balance) { + this.balance = balance; + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/AllowanceRepository.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/AllowanceRepository.java index ba79bfb..33ab473 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/AllowanceRepository.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/AllowanceRepository.java @@ -9,19 +9,18 @@ import java.util.List; import java.util.Optional; @Repository -public interface AllowanceRepository extends JpaRepository -{ - List findByUserIdOrderByIdAsc(int userId); +public interface AllowanceRepository extends JpaRepository { + List findByUserIdOrderByIdAsc(int userId); - Optional findByIdAndUserId(int id, int userId); + Optional findByIdAndUserId(int id, int userId); - int countByIdAndUserId(int id, int userId); + int countByIdAndUserId(int id, int userId); - void deleteByIdAndUserId(int id, int userId); + void deleteByIdAndUserId(int id, int userId); - @Query("SELECT a FROM Allowance a WHERE a.userId = :userId AND a.weight > 0 ORDER BY (a.target - a.balance) ASC") - List findByUserIdWithPositiveWeightOrderByRemainingAsc(int userId); + @Query("SELECT a FROM Allowance a WHERE a.userId = :userId AND a.weight > 0 ORDER BY (a.target - a.balance) ASC") + List findByUserIdWithPositiveWeightOrderByRemainingAsc(int userId); - @Query("SELECT COALESCE(SUM(a.weight), 0) FROM Allowance a WHERE a.userId = :userId AND a.weight > 0") - double sumPositiveWeights(int userId); + @Query("SELECT COALESCE(SUM(a.weight), 0) FROM Allowance a WHERE a.userId = :userId AND a.weight > 0") + double sumPositiveWeights(int userId); } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/HistoryRepository.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/HistoryRepository.java index 67030fe..0d3a849 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/HistoryRepository.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/HistoryRepository.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface HistoryRepository extends JpaRepository -{ - List findByUserIdOrderByIdDesc(int userId); +public interface HistoryRepository extends JpaRepository { + List findByUserIdOrderByIdDesc(int userId); } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/TaskRepository.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/TaskRepository.java index 7e6fac0..9c6154a 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/TaskRepository.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/TaskRepository.java @@ -8,9 +8,8 @@ import java.util.List; import java.util.Optional; @Repository -public interface TaskRepository extends JpaRepository -{ - List findByCompletedIsNull(); +public interface TaskRepository extends JpaRepository { + List findByCompletedIsNull(); - Optional findByIdAndCompletedIsNull(int id); + Optional findByIdAndCompletedIsNull(int id); } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/UserRepository.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/UserRepository.java index edf7723..45c1b9e 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/UserRepository.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/repository/UserRepository.java @@ -6,8 +6,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository -public interface UserRepository extends JpaRepository -{ - @Query("SELECT COALESCE(SUM(h.amount), 0) FROM History h WHERE h.userId = :userId") - long sumHistoryAmount(int userId); +public interface UserRepository extends JpaRepository { + @Query("SELECT COALESCE(SUM(h.amount), 0) FROM History h WHERE h.userId = :userId") + long sumHistoryAmount(int userId); } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/AllowanceService.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/AllowanceService.java index 0b3d6a7..929ff8a 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/AllowanceService.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/AllowanceService.java @@ -17,282 +17,242 @@ import java.util.List; import java.util.Optional; @Service -public class AllowanceService -{ - private final AllowanceRepository allowanceRepository; - private final UserRepository userRepository; - private final HistoryRepository historyRepository; +public class AllowanceService { + private final AllowanceRepository allowanceRepository; + private final UserRepository userRepository; + private final HistoryRepository historyRepository; - public AllowanceService(AllowanceRepository allowanceRepository, - UserRepository userRepository, - HistoryRepository historyRepository) - { - this.allowanceRepository = allowanceRepository; - this.userRepository = userRepository; - this.historyRepository = historyRepository; - } + public AllowanceService(AllowanceRepository allowanceRepository, + UserRepository userRepository, + HistoryRepository historyRepository) { + this.allowanceRepository = allowanceRepository; + this.userRepository = userRepository; + this.historyRepository = historyRepository; + } - public List getUserAllowances(int userId) - { - User user = userRepository.findById(userId).orElseThrow(); - List result = new ArrayList<>(); + public List getUserAllowances(int userId) { + User user = userRepository.findById(userId).orElseThrow(); + List result = new ArrayList<>(); - // Add the "rest" allowance (id=0) - result.add(new AllowanceDto(0, "", 0, user.getBalance() / 100.0, user.getWeight(), "")); + // Add the "rest" allowance (id=0) + result.add(new AllowanceDto(0, "", 0, user.getBalance() / 100.0, user.getWeight(), "")); - // Add named allowances - for (Allowance a : allowanceRepository.findByUserIdOrderByIdAsc(userId)) - { - result.add(toDto(a)); - } - return result; - } + // Add named allowances + for (Allowance a : allowanceRepository.findByUserIdOrderByIdAsc(userId)) { + result.add(toDto(a)); + } + return result; + } - public Optional getUserAllowanceById(int userId, int allowanceId) - { - if (allowanceId == 0) - { - return userRepository.findById(userId) - .map(u -> new AllowanceDto(0, "", 0, u.getBalance() / 100.0, u.getWeight(), "")); - } - return allowanceRepository.findByIdAndUserId(allowanceId, userId) - .map(this::toDto); - } + public Optional getUserAllowanceById(int userId, int allowanceId) { + if (allowanceId == 0) { + return userRepository.findById(userId) + .map(u -> new AllowanceDto(0, "", 0, u.getBalance() / 100.0, u.getWeight(), "")); + } + return allowanceRepository.findByIdAndUserId(allowanceId, userId) + .map(this::toDto); + } - @Transactional - public int createAllowance(int userId, CreateAllowanceRequest request) - { - int colour = ColourUtil.convertStringToColour(request.colour()); - Allowance allowance = new Allowance(); - allowance.setUserId(userId); - allowance.setName(request.name()); - allowance.setTarget(Math.round(request.target() * 100.0)); - allowance.setWeight(request.weight()); - allowance.setColour(colour); - allowance = allowanceRepository.save(allowance); - return allowance.getId(); - } + @Transactional + public int createAllowance(int userId, CreateAllowanceRequest request) { + int colour = ColourUtil.convertStringToColour(request.colour()); + Allowance allowance = new Allowance(); + allowance.setUserId(userId); + allowance.setName(request.name()); + allowance.setTarget(Math.round(request.target() * 100.0)); + allowance.setWeight(request.weight()); + allowance.setColour(colour); + allowance = allowanceRepository.save(allowance); + return allowance.getId(); + } - @Transactional - public boolean deleteAllowance(int userId, int allowanceId) - { - int count = allowanceRepository.countByIdAndUserId(allowanceId, userId); - if (count == 0) - { - return false; - } - allowanceRepository.deleteByIdAndUserId(allowanceId, userId); - return true; - } + @Transactional + public boolean deleteAllowance(int userId, int allowanceId) { + int count = allowanceRepository.countByIdAndUserId(allowanceId, userId); + if (count == 0) { + return false; + } + allowanceRepository.deleteByIdAndUserId(allowanceId, userId); + return true; + } - @Transactional - public boolean updateAllowance(int userId, int allowanceId, UpdateAllowanceRequest request) - { - if (allowanceId == 0) - { - User user = userRepository.findById(userId).orElseThrow(); - user.setWeight(request.weight()); - userRepository.save(user); - return true; - } + @Transactional + public boolean updateAllowance(int userId, int allowanceId, UpdateAllowanceRequest request) { + if (allowanceId == 0) { + User user = userRepository.findById(userId).orElseThrow(); + user.setWeight(request.weight()); + userRepository.save(user); + return true; + } - Optional opt = allowanceRepository.findByIdAndUserId(allowanceId, userId); - if (opt.isEmpty()) - { - return false; - } + Optional opt = allowanceRepository.findByIdAndUserId(allowanceId, userId); + if (opt.isEmpty()) { + return false; + } - int colour = ColourUtil.convertStringToColour(request.colour()); - Allowance allowance = opt.get(); - allowance.setName(request.name()); - allowance.setTarget(Math.round(request.target() * 100.0)); - allowance.setWeight(request.weight()); - allowance.setColour(colour); - allowanceRepository.save(allowance); - return true; - } + int colour = ColourUtil.convertStringToColour(request.colour()); + Allowance allowance = opt.get(); + allowance.setName(request.name()); + allowance.setTarget(Math.round(request.target() * 100.0)); + allowance.setWeight(request.weight()); + allowance.setColour(colour); + allowanceRepository.save(allowance); + return true; + } - @Transactional - public void bulkUpdateAllowance(int userId, List requests) - { - for (BulkUpdateAllowanceRequest req : requests) - { - if (req.id() == 0) - { - User user = userRepository.findById(userId).orElseThrow(); - user.setWeight(req.weight()); - userRepository.save(user); - } - else - { - allowanceRepository.findByIdAndUserId(req.id(), userId).ifPresent(a -> - { - a.setWeight(req.weight()); - allowanceRepository.save(a); - }); - } - } - } + @Transactional + public void bulkUpdateAllowance(int userId, List requests) { + for (BulkUpdateAllowanceRequest req : requests) { + if (req.id() == 0) { + User user = userRepository.findById(userId).orElseThrow(); + user.setWeight(req.weight()); + userRepository.save(user); + } else { + allowanceRepository.findByIdAndUserId(req.id(), userId).ifPresent(a -> + { + a.setWeight(req.weight()); + allowanceRepository.save(a); + }); + } + } + } - @Transactional - public boolean completeAllowance(int userId, int allowanceId) - { - Optional opt = allowanceRepository.findByIdAndUserId(allowanceId, userId); - if (opt.isEmpty()) - { - return false; - } + @Transactional + public boolean completeAllowance(int userId, int allowanceId) { + Optional opt = allowanceRepository.findByIdAndUserId(allowanceId, userId); + if (opt.isEmpty()) { + return false; + } - Allowance allowance = opt.get(); - long cost = allowance.getBalance(); - String allowanceName = allowance.getName(); + Allowance allowance = opt.get(); + long cost = allowance.getBalance(); + String allowanceName = allowance.getName(); - // Delete the allowance - allowanceRepository.delete(allowance); + // Delete the allowance + allowanceRepository.delete(allowance); - // Add a history entry - History history = new History(); - history.setUserId(userId); - history.setTimestamp(Instant.now().getEpochSecond()); - history.setAmount(-cost); - history.setDescription("Allowance completed: " + allowanceName); - historyRepository.save(history); + // Add a history entry + History history = new History(); + history.setUserId(userId); + history.setTimestamp(Instant.now().getEpochSecond()); + history.setAmount(-cost); + history.setDescription("Allowance completed: " + allowanceName); + historyRepository.save(history); - return true; - } + return true; + } - @Transactional - public boolean addAllowanceAmount(int userId, int allowanceId, AddAllowanceAmountRequest request) - { - long remainingAmount = Math.round(request.amount() * 100); + @Transactional + public boolean addAllowanceAmount(int userId, int allowanceId, AddAllowanceAmountRequest request) { + long remainingAmount = Math.round(request.amount() * 100); - // Insert history entry - History history = new History(); - history.setUserId(userId); - history.setTimestamp(Instant.now().getEpochSecond()); - history.setAmount(remainingAmount); - history.setDescription(request.description()); - historyRepository.save(history); + // Insert history entry + History history = new History(); + history.setUserId(userId); + history.setTimestamp(Instant.now().getEpochSecond()); + history.setAmount(remainingAmount); + history.setDescription(request.description()); + historyRepository.save(history); - if (allowanceId == 0) - { - if (remainingAmount < 0) - { - User user = userRepository.findById(userId).orElseThrow(); - if (remainingAmount > user.getBalance()) - { - throw new IllegalArgumentException("cannot remove more than the current balance: " + user.getBalance()); - } - } - User user = userRepository.findById(userId).orElseThrow(); - user.setBalance(user.getBalance() + remainingAmount); - userRepository.save(user); - } - else if (remainingAmount < 0) - { - Allowance allowance = allowanceRepository.findByIdAndUserId(allowanceId, userId).orElse(null); - if (allowance == null) - { - return false; - } - if (remainingAmount > allowance.getBalance()) - { - throw new IllegalArgumentException("cannot remove more than the current allowance balance: " + allowance.getBalance()); - } - allowance.setBalance(allowance.getBalance() + remainingAmount); - allowanceRepository.save(allowance); - } - else - { - Allowance allowance = allowanceRepository.findByIdAndUserId(allowanceId, userId).orElse(null); - if (allowance == null) - { - return false; - } + if (allowanceId == 0) { + if (remainingAmount < 0) { + User user = userRepository.findById(userId).orElseThrow(); + if (remainingAmount > user.getBalance()) { + throw new IllegalArgumentException("cannot remove more than the current balance: " + user.getBalance()); + } + } + User user = userRepository.findById(userId).orElseThrow(); + user.setBalance(user.getBalance() + remainingAmount); + userRepository.save(user); + } else if (remainingAmount < 0) { + Allowance allowance = allowanceRepository.findByIdAndUserId(allowanceId, userId).orElse(null); + if (allowance == null) { + return false; + } + if (remainingAmount > allowance.getBalance()) { + throw new IllegalArgumentException("cannot remove more than the current allowance balance: " + allowance.getBalance()); + } + allowance.setBalance(allowance.getBalance() + remainingAmount); + allowanceRepository.save(allowance); + } else { + Allowance allowance = allowanceRepository.findByIdAndUserId(allowanceId, userId).orElse(null); + if (allowance == null) { + return false; + } - long toAdd = remainingAmount; - if (allowance.getBalance() + toAdd > allowance.getTarget()) - { - toAdd = allowance.getTarget() - allowance.getBalance(); - } - remainingAmount -= toAdd; + long toAdd = remainingAmount; + if (allowance.getBalance() + toAdd > allowance.getTarget()) { + toAdd = allowance.getTarget() - allowance.getBalance(); + } + remainingAmount -= toAdd; - if (toAdd > 0) - { - allowance.setBalance(allowance.getBalance() + toAdd); - allowanceRepository.save(allowance); - } + if (toAdd > 0) { + allowance.setBalance(allowance.getBalance() + toAdd); + allowanceRepository.save(allowance); + } - if (remainingAmount > 0) - { - addDistributedReward(userId, (int) remainingAmount); - } - } - return true; - } + if (remainingAmount > 0) { + addDistributedReward(userId, (int) remainingAmount); + } + } + return true; + } - public void addDistributedReward(int userId, int reward) - { - User user = userRepository.findById(userId).orElseThrow(); - double userWeight = user.getWeight(); + public void addDistributedReward(int userId, int reward) { + User user = userRepository.findById(userId).orElseThrow(); + double userWeight = user.getWeight(); - double sumOfWeights = allowanceRepository.sumPositiveWeights(userId) + userWeight; + double sumOfWeights = allowanceRepository.sumPositiveWeights(userId) + userWeight; - int remainingReward = reward; + int remainingReward = reward; - if (sumOfWeights > 0) - { - List allowances = allowanceRepository.findByUserIdWithPositiveWeightOrderByRemainingAsc(userId); - for (Allowance allowance : allowances) - { - int amount = (int) ((allowance.getWeight() / sumOfWeights) * remainingReward); - if (allowance.getBalance() + amount > allowance.getTarget()) - { - amount = (int) (allowance.getTarget() - allowance.getBalance()); - } - sumOfWeights -= allowance.getWeight(); - allowance.setBalance(allowance.getBalance() + amount); - allowanceRepository.save(allowance); - remainingReward -= amount; - } - } + if (sumOfWeights > 0) { + List allowances = allowanceRepository.findByUserIdWithPositiveWeightOrderByRemainingAsc(userId); + for (Allowance allowance : allowances) { + int amount = (int) ((allowance.getWeight() / sumOfWeights) * remainingReward); + if (allowance.getBalance() + amount > allowance.getTarget()) { + amount = (int) (allowance.getTarget() - allowance.getBalance()); + } + sumOfWeights -= allowance.getWeight(); + allowance.setBalance(allowance.getBalance() + amount); + allowanceRepository.save(allowance); + remainingReward -= amount; + } + } - // Add remaining to user's balance - user = userRepository.findById(userId).orElseThrow(); - user.setBalance(user.getBalance() + remainingReward); - userRepository.save(user); - } + // Add remaining to user's balance + user = userRepository.findById(userId).orElseThrow(); + user.setBalance(user.getBalance() + remainingReward); + userRepository.save(user); + } - public List getHistory(int userId) - { - return historyRepository.findByUserIdOrderByIdDesc(userId).stream() - .map(h -> new HistoryDto( - h.getAmount() / 100.0, - Instant.ofEpochSecond(h.getTimestamp()), - h.getDescription())) - .toList(); - } + public List getHistory(int userId) { + return historyRepository.findByUserIdOrderByIdDesc(userId).stream() + .map(h -> new HistoryDto( + h.getAmount() / 100.0, + Instant.ofEpochSecond(h.getTimestamp()), + h.getDescription())) + .toList(); + } - @Transactional - public void addHistory(int userId, PostHistoryRequest request) - { - long amount = Math.round(request.allowance() * 100.0); - History history = new History(); - history.setUserId(userId); - history.setTimestamp(Instant.now().getEpochSecond()); - history.setAmount(amount); - history.setDescription(request.description()); - historyRepository.save(history); - } + @Transactional + public void addHistory(int userId, PostHistoryRequest request) { + long amount = Math.round(request.allowance() * 100.0); + History history = new History(); + history.setUserId(userId); + history.setTimestamp(Instant.now().getEpochSecond()); + history.setAmount(amount); + history.setDescription(request.description()); + historyRepository.save(history); + } - private AllowanceDto toDto(Allowance a) - { - return new AllowanceDto( - a.getId(), - a.getName(), - a.getTarget() / 100.0, - a.getBalance() / 100.0, - a.getWeight(), - ColourUtil.convertColourToString(a.getColour())); - } + private AllowanceDto toDto(Allowance a) { + return new AllowanceDto( + a.getId(), + a.getName(), + a.getTarget() / 100.0, + a.getBalance() / 100.0, + a.getWeight(), + ColourUtil.convertColourToString(a.getColour())); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/MigrationService.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/MigrationService.java index e717ffb..80f2261 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/MigrationService.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/MigrationService.java @@ -10,95 +10,88 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -public class MigrationService -{ - private final UserRepository userRepository; - private final AllowanceRepository allowanceRepository; - private final HistoryRepository historyRepository; - private final TaskRepository taskRepository; - private final EntityManager entityManager; +public class MigrationService { + private final UserRepository userRepository; + private final AllowanceRepository allowanceRepository; + private final HistoryRepository historyRepository; + private final TaskRepository taskRepository; + private final EntityManager entityManager; - public MigrationService(UserRepository userRepository, - AllowanceRepository allowanceRepository, - HistoryRepository historyRepository, - TaskRepository taskRepository, - EntityManager entityManager) - { - this.userRepository = userRepository; - this.allowanceRepository = allowanceRepository; - this.historyRepository = historyRepository; - this.taskRepository = taskRepository; - this.entityManager = entityManager; - } + public MigrationService(UserRepository userRepository, + AllowanceRepository allowanceRepository, + HistoryRepository historyRepository, + TaskRepository taskRepository, + EntityManager entityManager) { + this.userRepository = userRepository; + this.allowanceRepository = allowanceRepository; + this.historyRepository = historyRepository; + this.taskRepository = taskRepository; + this.entityManager = entityManager; + } - @Transactional - public void importData(MigrationDto data) - { - // Delete in dependency order - taskRepository.deleteAll(); - historyRepository.deleteAll(); - allowanceRepository.deleteAll(); - userRepository.deleteAll(); + @Transactional + public void importData(MigrationDto data) { + // Delete in dependency order + taskRepository.deleteAll(); + historyRepository.deleteAll(); + allowanceRepository.deleteAll(); + userRepository.deleteAll(); - // Insert users with original IDs using native SQL to bypass auto-increment - for (MigrationDto.MigrationUserDto u : data.users()) - { - entityManager.createNativeQuery( - "INSERT INTO users (id, name, balance, weight) VALUES (:id, :name, :balance, :weight)") - .setParameter("id", u.id()) - .setParameter("name", u.name()) - .setParameter("balance", u.balance()) - .setParameter("weight", u.weight()) - .executeUpdate(); - } + // Insert users with original IDs using native SQL to bypass auto-increment + for (MigrationDto.MigrationUserDto u : data.users()) { + entityManager.createNativeQuery( + "INSERT INTO users (id, name, balance, weight) VALUES (:id, :name, :balance, :weight)") + .setParameter("id", u.id()) + .setParameter("name", u.name()) + .setParameter("balance", u.balance()) + .setParameter("weight", u.weight()) + .executeUpdate(); + } - // Insert allowances with original IDs - for (MigrationDto.MigrationAllowanceDto a : data.allowances()) - { - entityManager.createNativeQuery( - "INSERT INTO allowances (id, user_id, name, target, balance, weight, colour) VALUES (:id, :userId, :name, :target, :balance, :weight, :colour)") - .setParameter("id", a.id()) - .setParameter("userId", a.userId()) - .setParameter("name", a.name()) - .setParameter("target", a.target()) - .setParameter("balance", a.balance()) - .setParameter("weight", a.weight()) - .setParameter("colour", a.colour()) - .executeUpdate(); - } + // Insert allowances with original IDs + for (MigrationDto.MigrationAllowanceDto a : data.allowances()) { + entityManager.createNativeQuery( + "INSERT INTO allowances (id, user_id, name, target, balance, weight, colour) VALUES (:id, :userId, :name, :target, :balance, :weight, :colour)") + .setParameter("id", a.id()) + .setParameter("userId", a.userId()) + .setParameter("name", a.name()) + .setParameter("target", a.target()) + .setParameter("balance", a.balance()) + .setParameter("weight", a.weight()) + .setParameter("colour", a.colour()) + .executeUpdate(); + } - // Insert history with original IDs - for (MigrationDto.MigrationHistoryDto h : data.history()) - { - entityManager.createNativeQuery( - "INSERT INTO history (id, user_id, timestamp, amount, description) VALUES (:id, :userId, :timestamp, :amount, :description)") - .setParameter("id", h.id()) - .setParameter("userId", h.userId()) - .setParameter("timestamp", h.timestamp()) - .setParameter("amount", h.amount()) - .setParameter("description", h.description()) - .executeUpdate(); - } + // Insert history with original IDs + for (MigrationDto.MigrationHistoryDto h : data.history()) { + entityManager.createNativeQuery( + "INSERT INTO history (id, user_id, timestamp, amount, description) VALUES (:id, :userId, :timestamp, :amount, :description)") + .setParameter("id", h.id()) + .setParameter("userId", h.userId()) + .setParameter("timestamp", h.timestamp()) + .setParameter("amount", h.amount()) + .setParameter("description", h.description()) + .executeUpdate(); + } - // Insert tasks with original IDs - for (MigrationDto.MigrationTaskDto t : data.tasks()) - { - entityManager.createNativeQuery( - "INSERT INTO tasks (id, name, reward, assigned, schedule, completed, next_run) VALUES (:id, :name, :reward, :assigned, :schedule, :completed, :nextRun)") - .setParameter("id", t.id()) - .setParameter("name", t.name()) - .setParameter("reward", t.reward()) - .setParameter("assigned", t.assigned()) - .setParameter("schedule", t.schedule()) - .setParameter("completed", t.completed()) - .setParameter("nextRun", t.nextRun()) - .executeUpdate(); - } + // Insert tasks with original IDs + for (MigrationDto.MigrationTaskDto t : data.tasks()) { + entityManager.createNativeQuery( + "INSERT INTO tasks (id, name, reward, assigned, schedule, completed, next_run) VALUES (:id, :name, :reward, :assigned, :schedule, :completed, :nextRun)") + .setParameter("id", t.id()) + .setParameter("name", t.name()) + .setParameter("reward", t.reward()) + .setParameter("assigned", t.assigned()) + .setParameter("schedule", t.schedule()) + .setParameter("completed", t.completed()) + .setParameter("nextRun", t.nextRun()) + .executeUpdate(); + } - // Reset sequences so new inserts don't collide with migrated IDs - entityManager.createNativeQuery("SELECT setval('users_id_seq', COALESCE((SELECT MAX(id) FROM users), 0))").getSingleResult(); - entityManager.createNativeQuery("SELECT setval('allowances_id_seq', COALESCE((SELECT MAX(id) FROM allowances), 0))").getSingleResult(); - entityManager.createNativeQuery("SELECT setval('history_id_seq', COALESCE((SELECT MAX(id) FROM history), 0))").getSingleResult(); - entityManager.createNativeQuery("SELECT setval('tasks_id_seq', COALESCE((SELECT MAX(id) FROM tasks), 0))").getSingleResult(); - } + // Reset sequences so new inserts don't collide with migrated IDs + entityManager.createNativeQuery("SELECT setval('users_id_seq', COALESCE((SELECT MAX(id) FROM users), 0))").getSingleResult(); + entityManager.createNativeQuery("SELECT setval('allowances_id_seq', COALESCE((SELECT MAX(id) FROM allowances), 0))").getSingleResult(); + entityManager.createNativeQuery("SELECT setval('history_id_seq', COALESCE((SELECT MAX(id) FROM history), 0))").getSingleResult(); + entityManager.createNativeQuery("SELECT setval('tasks_id_seq', COALESCE((SELECT MAX(id) FROM tasks), 0))").getSingleResult(); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TaskService.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TaskService.java index 47dd254..9841278 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TaskService.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TaskService.java @@ -1,6 +1,7 @@ package be.seeseepuff.allowanceplanner.service; -import be.seeseepuff.allowanceplanner.dto.*; +import be.seeseepuff.allowanceplanner.dto.CreateTaskRequest; +import be.seeseepuff.allowanceplanner.dto.TaskDto; import be.seeseepuff.allowanceplanner.entity.History; import be.seeseepuff.allowanceplanner.entity.Task; import be.seeseepuff.allowanceplanner.entity.User; @@ -15,118 +16,105 @@ import java.util.List; import java.util.Optional; @Service -public class TaskService -{ - private final TaskRepository taskRepository; - private final UserRepository userRepository; - private final HistoryRepository historyRepository; - private final AllowanceService allowanceService; +public class TaskService { + private final TaskRepository taskRepository; + private final UserRepository userRepository; + private final HistoryRepository historyRepository; + private final AllowanceService allowanceService; - public TaskService(TaskRepository taskRepository, - UserRepository userRepository, - HistoryRepository historyRepository, - AllowanceService allowanceService) - { - this.taskRepository = taskRepository; - this.userRepository = userRepository; - this.historyRepository = historyRepository; - this.allowanceService = allowanceService; - } + public TaskService(TaskRepository taskRepository, + UserRepository userRepository, + HistoryRepository historyRepository, + AllowanceService allowanceService) { + this.taskRepository = taskRepository; + this.userRepository = userRepository; + this.historyRepository = historyRepository; + this.allowanceService = allowanceService; + } - @Transactional - public int createTask(CreateTaskRequest request) - { - Task task = new Task(); - task.setName(request.name()); - task.setReward(Math.round((request.reward() != null ? request.reward() : 0.0) * 100.0)); - task.setAssigned(request.assigned()); - task = taskRepository.save(task); - return task.getId(); - } + @Transactional + public int createTask(CreateTaskRequest request) { + Task task = new Task(); + task.setName(request.name()); + task.setReward(Math.round((request.reward() != null ? request.reward() : 0.0) * 100.0)); + task.setAssigned(request.assigned()); + task = taskRepository.save(task); + return task.getId(); + } - public List getTasks() - { - return taskRepository.findByCompletedIsNull().stream() - .map(this::toDto) - .toList(); - } + public List getTasks() { + return taskRepository.findByCompletedIsNull().stream() + .map(this::toDto) + .toList(); + } - public Optional getTask(int taskId) - { - return taskRepository.findByIdAndCompletedIsNull(taskId) - .map(this::toDto); - } + public Optional getTask(int taskId) { + return taskRepository.findByIdAndCompletedIsNull(taskId) + .map(this::toDto); + } - @Transactional - public boolean updateTask(int taskId, CreateTaskRequest request) - { - Optional opt = taskRepository.findByIdAndCompletedIsNull(taskId); - if (opt.isEmpty()) - { - return false; - } - Task task = opt.get(); - task.setName(request.name()); - task.setReward(Math.round((request.reward() != null ? request.reward() : 0.0) * 100.0)); - task.setAssigned(request.assigned()); - taskRepository.save(task); - return true; - } + @Transactional + public boolean updateTask(int taskId, CreateTaskRequest request) { + Optional opt = taskRepository.findByIdAndCompletedIsNull(taskId); + if (opt.isEmpty()) { + return false; + } + Task task = opt.get(); + task.setName(request.name()); + task.setReward(Math.round((request.reward() != null ? request.reward() : 0.0) * 100.0)); + task.setAssigned(request.assigned()); + taskRepository.save(task); + return true; + } - public boolean hasTask(int taskId) - { - return taskRepository.existsById(taskId); - } + public boolean hasTask(int taskId) { + return taskRepository.existsById(taskId); + } - @Transactional - public void deleteTask(int taskId) - { - taskRepository.deleteById(taskId); - } + @Transactional + public void deleteTask(int taskId) { + taskRepository.deleteById(taskId); + } - @Transactional - public boolean completeTask(int taskId) - { - Optional opt = taskRepository.findById(taskId); - if (opt.isEmpty()) - { - return false; - } + @Transactional + public boolean completeTask(int taskId) { + Optional opt = taskRepository.findById(taskId); + if (opt.isEmpty()) { + return false; + } - Task task = opt.get(); - long reward = task.getReward(); - String rewardName = task.getName(); + Task task = opt.get(); + long reward = task.getReward(); + String rewardName = task.getName(); - // Give reward to all users - List users = userRepository.findAll(); - for (User user : users) - { - // Add history entry - History history = new History(); - history.setUserId(user.getId()); - history.setTimestamp(Instant.now().getEpochSecond()); - history.setAmount(reward); - history.setDescription("Task completed: " + rewardName); - historyRepository.save(history); + // Give reward to all users + List users = userRepository.findAll(); + for (User user : users) { + // Add history entry + History history = new History(); + history.setUserId(user.getId()); + history.setTimestamp(Instant.now().getEpochSecond()); + history.setAmount(reward); + history.setDescription("Task completed: " + rewardName); + historyRepository.save(history); - // Distribute reward - allowanceService.addDistributedReward(user.getId(), (int) reward); - } + // Distribute reward + allowanceService.addDistributedReward(user.getId(), (int) reward); + } - // Mark task as completed - task.setCompleted(Instant.now().getEpochSecond()); - taskRepository.save(task); + // Mark task as completed + task.setCompleted(Instant.now().getEpochSecond()); + taskRepository.save(task); - return true; - } + return true; + } - private TaskDto toDto(Task t) - { - return new TaskDto( - t.getId(), - t.getName(), - t.getReward() / 100.0, - t.getAssigned(), - t.getSchedule()); - } + private TaskDto toDto(Task t) { + return new TaskDto( + t.getId(), + t.getName(), + t.getReward() / 100.0, + t.getAssigned(), + t.getSchedule()); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TransferService.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TransferService.java index 7b81ba0..ab2e31a 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TransferService.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/TransferService.java @@ -9,94 +9,78 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Service -public class TransferService -{ - private final AllowanceRepository allowanceRepository; +public class TransferService { + private final AllowanceRepository allowanceRepository; - public TransferService(AllowanceRepository allowanceRepository) - { - this.allowanceRepository = allowanceRepository; - } + public TransferService(AllowanceRepository allowanceRepository) { + this.allowanceRepository = allowanceRepository; + } - @Transactional - public TransferResult transfer(TransferRequest request) - { - if (request.from() == request.to()) - { - return TransferResult.success(); - } + @Transactional + public TransferResult transfer(TransferRequest request) { + if (request.from() == request.to()) { + return TransferResult.success(); + } - int amountCents = (int) Math.round(request.amount() * 100.0); - if (amountCents <= 0) - { - return TransferResult.badRequest("amount must be positive"); - } + int amountCents = (int) Math.round(request.amount() * 100.0); + if (amountCents <= 0) { + return TransferResult.badRequest("amount must be positive"); + } - Optional fromOpt = allowanceRepository.findById(request.from()); - if (fromOpt.isEmpty()) - { - return TransferResult.notFound(); - } + Optional fromOpt = allowanceRepository.findById(request.from()); + if (fromOpt.isEmpty()) { + return TransferResult.notFound(); + } - Optional toOpt = allowanceRepository.findById(request.to()); - if (toOpt.isEmpty()) - { - return TransferResult.notFound(); - } + Optional toOpt = allowanceRepository.findById(request.to()); + if (toOpt.isEmpty()) { + return TransferResult.notFound(); + } - Allowance from = fromOpt.get(); - Allowance to = toOpt.get(); + Allowance from = fromOpt.get(); + Allowance to = toOpt.get(); - if (from.getUserId() != to.getUserId()) - { - return TransferResult.badRequest("Allowances do not belong to the same user"); - } + if (from.getUserId() != to.getUserId()) { + return TransferResult.badRequest("Allowances do not belong to the same user"); + } - long remainingTo = to.getTarget() - to.getBalance(); - if (remainingTo <= 0) - { - return TransferResult.badRequest("target already reached"); - } + long remainingTo = to.getTarget() - to.getBalance(); + if (remainingTo <= 0) { + return TransferResult.badRequest("target already reached"); + } - int transfer = amountCents; - if (transfer > remainingTo) - { - transfer = (int) remainingTo; - } + int transfer = amountCents; + if (transfer > remainingTo) { + transfer = (int) remainingTo; + } - if (from.getBalance() < transfer) - { - return TransferResult.badRequest("Insufficient funds in source allowance"); - } + if (from.getBalance() < transfer) { + return TransferResult.badRequest("Insufficient funds in source allowance"); + } - from.setBalance(from.getBalance() - transfer); - to.setBalance(to.getBalance() + transfer); - allowanceRepository.save(from); - allowanceRepository.save(to); + from.setBalance(from.getBalance() - transfer); + to.setBalance(to.getBalance() + transfer); + allowanceRepository.save(from); + allowanceRepository.save(to); - return TransferResult.success(); - } + return TransferResult.success(); + } - public record TransferResult(Status status, String message) - { - public enum Status - { - SUCCESS, BAD_REQUEST, NOT_FOUND - } + public record TransferResult(Status status, String message) { + public static TransferResult success() { + return new TransferResult(Status.SUCCESS, "Transfer successful"); + } - public static TransferResult success() - { - return new TransferResult(Status.SUCCESS, "Transfer successful"); - } + public static TransferResult badRequest(String message) { + return new TransferResult(Status.BAD_REQUEST, message); + } - public static TransferResult badRequest(String message) - { - return new TransferResult(Status.BAD_REQUEST, message); - } + public static TransferResult notFound() { + return new TransferResult(Status.NOT_FOUND, "Allowance not found"); + } - public static TransferResult notFound() - { - return new TransferResult(Status.NOT_FOUND, "Allowance not found"); - } - } + public enum Status { + SUCCESS, BAD_REQUEST, NOT_FOUND + } + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/UserService.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/UserService.java index ccc74cd..d729d05 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/UserService.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/service/UserService.java @@ -1,7 +1,7 @@ package be.seeseepuff.allowanceplanner.service; -import be.seeseepuff.allowanceplanner.dto.*; -import be.seeseepuff.allowanceplanner.entity.User; +import be.seeseepuff.allowanceplanner.dto.UserDto; +import be.seeseepuff.allowanceplanner.dto.UserWithAllowanceDto; import be.seeseepuff.allowanceplanner.repository.UserRepository; import org.springframework.stereotype.Service; @@ -9,34 +9,29 @@ import java.util.List; import java.util.Optional; @Service -public class UserService -{ - private final UserRepository userRepository; +public class UserService { + private final UserRepository userRepository; - public UserService(UserRepository userRepository) - { - this.userRepository = userRepository; - } + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } - public List getUsers() - { - return userRepository.findAll().stream() - .map(u -> new UserDto(u.getId(), u.getName())) - .toList(); - } + public List getUsers() { + return userRepository.findAll().stream() + .map(u -> new UserDto(u.getId(), u.getName())) + .toList(); + } - public Optional getUser(int userId) - { - return userRepository.findById(userId) - .map(u -> - { - long totalAmount = userRepository.sumHistoryAmount(userId); - return new UserWithAllowanceDto(u.getId(), u.getName(), totalAmount / 100.0); - }); - } + public Optional getUser(int userId) { + return userRepository.findById(userId) + .map(u -> + { + long totalAmount = userRepository.sumHistoryAmount(userId); + return new UserWithAllowanceDto(u.getId(), u.getName(), totalAmount / 100.0); + }); + } - public boolean userExists(int userId) - { - return userRepository.existsById(userId); - } + public boolean userExists(int userId) { + return userRepository.existsById(userId); + } } diff --git a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/util/ColourUtil.java b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/util/ColourUtil.java index 6fda514..eb8770b 100644 --- a/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/util/ColourUtil.java +++ b/backend-spring/src/main/java/be/seeseepuff/allowanceplanner/util/ColourUtil.java @@ -1,42 +1,33 @@ package be.seeseepuff.allowanceplanner.util; -public class ColourUtil -{ - private ColourUtil() - { - } +public class ColourUtil { + private ColourUtil() { + } - public static int convertStringToColour(String colourStr) - { - if (colourStr == null || colourStr.isEmpty()) - { - return 0xFF0000; // Default colour - } - if (colourStr.charAt(0) == '#') - { - colourStr = colourStr.substring(1); - } - if (colourStr.length() != 6 && colourStr.length() != 3) - { - throw new IllegalArgumentException("colour must be a valid hex string"); - } - int colour = Integer.parseInt(colourStr, 16); - if (colourStr.length() == 3) - { - int r = (colour & 0xF00) >> 8; - int g = (colour & 0x0F0) >> 4; - int b = (colour & 0x00F); - colour = (r << 20) | (g << 12) | (b << 4); - } - return colour; - } + public static int convertStringToColour(String colourStr) { + if (colourStr == null || colourStr.isEmpty()) { + return 0xFF0000; // Default colour + } + if (colourStr.charAt(0) == '#') { + colourStr = colourStr.substring(1); + } + if (colourStr.length() != 6 && colourStr.length() != 3) { + throw new IllegalArgumentException("colour must be a valid hex string"); + } + int colour = Integer.parseInt(colourStr, 16); + if (colourStr.length() == 3) { + int r = (colour & 0xF00) >> 8; + int g = (colour & 0x0F0) >> 4; + int b = (colour & 0x00F); + colour = (r << 20) | (g << 12) | (b << 4); + } + return colour; + } - public static String convertColourToString(Integer colour) - { - if (colour == null) - { - return ""; - } - return String.format("#%06X", colour); - } + public static String convertColourToString(Integer colour) { + if (colour == null) { + return ""; + } + return String.format("#%06X", colour); + } } diff --git a/backend-spring/src/main/resources/application.properties b/backend-spring/src/main/resources/application.properties index 35e78e6..e197cb9 100644 --- a/backend-spring/src/main/resources/application.properties +++ b/backend-spring/src/main/resources/application.properties @@ -1,12 +1,8 @@ spring.application.name=allowance-planner - spring.datasource.url=jdbc:postgresql://localhost:5432/allowance_planner spring.datasource.username=postgres spring.datasource.password=postgres - spring.jpa.hibernate.ddl-auto=validate spring.jpa.open-in-view=false - spring.flyway.enabled=true - server.port=8080 diff --git a/backend-spring/src/main/resources/templates/index.html b/backend-spring/src/main/resources/templates/index.html index a199188..b48962b 100644 --- a/backend-spring/src/main/resources/templates/index.html +++ b/backend-spring/src/main/resources/templates/index.html @@ -20,8 +20,8 @@

Users

- +
@@ -39,10 +39,10 @@ - + - - + + @@ -66,7 +66,7 @@

Tasks

-
+ @@ -91,10 +91,10 @@ - + - - + + diff --git a/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ApiTest.java b/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ApiTest.java index 1433976..40cf6da 100644 --- a/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ApiTest.java +++ b/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ApiTest.java @@ -2,18 +2,17 @@ package be.seeseepuff.allowanceplanner; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import org.flywaydb.core.Flyway; 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.server.LocalServerPort; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; - -import org.flywaydb.core.Flyway; -import org.springframework.beans.factory.annotation.Autowired; +import org.testcontainers.postgresql.PostgreSQLContainer; import java.time.Instant; import java.util.List; @@ -24,1219 +23,1155 @@ import static org.hamcrest.Matchers.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers -class ApiTest -{ - private static final String TEST_HISTORY_NAME = "Test History"; - - @Container - static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:17") - .withDatabaseName("allowance_planner_test") - .withUsername("test") - .withPassword("test"); - - @LocalServerPort - int port; - - @Autowired - Flyway flyway; - - @DynamicPropertySource - static void configureProperties(DynamicPropertyRegistry registry) - { - registry.add("spring.datasource.url", postgres::getJdbcUrl); - registry.add("spring.datasource.username", postgres::getUsername); - registry.add("spring.datasource.password", postgres::getPassword); - registry.add("spring.flyway.clean-disabled", () -> "false"); - } - - @BeforeEach - void setUp() - { - RestAssured.port = port; - RestAssured.basePath = "/api"; - RestAssured.config = RestAssured.config() - .jsonConfig(io.restassured.config.JsonConfig.jsonConfig() - .numberReturnType(io.restassured.path.json.config.JsonPathConfig.NumberReturnType.DOUBLE)); - - // Clean and re-migrate the database before each test - flyway.clean(); - flyway.migrate(); - } - - // ---- User Tests ---- - - @Test - void getUsers() - { - given() - .when() - .get("/users") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[0].name", isA(String.class)) - .body("[1].name", isA(String.class)); - } - - @Test - void getUser() - { - given() - .when() - .get("/user/1") - .then() - .statusCode(200) - .body("name", is("Seeseemelk")) - .body("id", is(1)) - .body("allowance", is(0.0d)); - } - - @Test - void getUserUnknown() - { - given() - .when() - .get("/user/999") - .then() - .statusCode(404); - } - - @Test - void getUserBadId() - { - given() - .when() - .get("/user/bad-id") - .then() - .statusCode(400); - } - - // ---- Allowance Tests ---- - - @Test - void getUserAllowanceWhenNoAllowancePresent() - { - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].id", is(0)); - } - - @Test - void getUserAllowance() - { - createAllowance(1, TEST_HISTORY_NAME, 5000, 10); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[1].name", is(TEST_HISTORY_NAME)) - .body("[1].target", is(5000.0d)) - .body("[1].weight", is(10.0d)) - .body("[1].progress", is(0.0d)) - .body("[1]", not(hasKey("user_id"))); - } - - @Test - void getUserAllowanceNoUser() - { - given() - .when() - .get("/user/999/allowance") - .then() - .statusCode(404); - } - - @Test - void getUserAllowanceBadId() - { - given() - .when() - .get("/user/bad-id/allowance") - .then() - .statusCode(400); - } - - @Test - void createUserAllowance() - { - int allowanceId = createAllowance(1, TEST_HISTORY_NAME, 5000, 10); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[1].id", is(allowanceId)) - .body("[1].name", is(TEST_HISTORY_NAME)) - .body("[1].target", is(5000.0d)) - .body("[1].weight", is(10.0d)) - .body("[1].progress", is(0.0d)); - } - - @Test - void createUserAllowanceNoUser() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("name", TEST_HISTORY_NAME, "target", 5000, "weight", 10)) - .when() - .post("/user/999/allowance") - .then() - .statusCode(404); - } - - @Test - void createUserAllowanceInvalidInput() - { - // Empty name - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 5000, "weight", 10)) - .when() - .post("/user/1/allowance") - .then() - .statusCode(400); - - // Missing name - given() - .contentType(ContentType.JSON) - .body(Map.of("target", 5000)) - .when() - .post("/user/1/allowance") - .then() - .statusCode(400); - } - - @Test - void createUserAllowanceBadId() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("name", TEST_HISTORY_NAME, "target", 5000, "weight", 10)) - .when() - .post("/user/bad-id/allowance") - .then() - .statusCode(400); - } - - @Test - void deleteUserAllowance() - { - int allowanceId = createAllowance(1, TEST_HISTORY_NAME, 1000, 5); - - given() - .when() - .delete("/user/1/allowance/" + allowanceId) - .then() - .statusCode(200) - .body("message", is("History deleted successfully")); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(1)); - } - - @Test - void deleteUserRestAllowance() - { - given() - .when() - .delete("/user/1/allowance/0") - .then() - .statusCode(400); - } - - @Test - void deleteUserAllowanceNotFound() - { - given() - .when() - .delete("/user/1/allowance/999") - .then() - .statusCode(404) - .body("error", is("History not found")); - } - - @Test - void deleteUserAllowanceInvalidId() - { - given() - .when() - .delete("/user/1/allowance/invalid-id") - .then() - .statusCode(400) - .body("error", is("Invalid allowance ID")); - } - - // ---- Task Tests ---- - - @Test - void createTask() - { - // Without assigned user - int taskId = given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Test Task", "reward", 100)) - .when() - .post("/tasks") - .then() - .statusCode(201) - .body("id", notNullValue()) - .extract().path("id"); - - given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); - - given() - .when() - .get("/task/" + taskId) - .then() - .statusCode(200) - .body("id", is(taskId)) - .body("name", is("Test Task")) - .body("reward", is(100.0d)) - .body("assigned", nullValue()); - - // With assigned user - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Test Task Assigned", "reward", 200, "assigned", 1)) - .when() - .post("/tasks") - .then() - .statusCode(201) - .body("id", notNullValue()); - } - - @Test - void deleteTask() - { - int taskId = createTestTask(100); - - given().when().delete("/task/" + taskId).then().statusCode(200); - given().when().get("/task/" + taskId).then().statusCode(404); - } - - @Test - void deleteTaskNotFound() - { - given().when().delete("/task/1").then().statusCode(404); - } - - @Test - void createTaskNoName() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("reward", 100)) - .when() - .post("/tasks") - .then() - .statusCode(400); - } - - @Test - void createTaskInvalidAssignedUser() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Test Task Invalid User", "reward", 100, "assigned", 999)) - .when() - .post("/tasks") - .then() - .statusCode(404) - .body("error", is("User not found")); - } - - @Test - void createTaskInvalidRequestBody() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("reward", 5000)) - .when() - .post("/tasks") - .then() - .statusCode(400); - } - - @Test - void getTaskWhenNoTasks() - { - given() - .when() - .get("/tasks") - .then() - .statusCode(200) - .body("$.size()", is(0)); - } - - @Test - void getTasksWhenTasks() - { - createTestTask(100); - - given() - .when() - .get("/tasks") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].name", is("Test Task")) - .body("[0].reward", is(100.0d)) - .body("[0].assigned", nullValue()); - } - - @Test - void getTask() - { - int taskId = createTestTask(100); - - given() - .when() - .get("/task/" + taskId) - .then() - .statusCode(200) - .body("id", is(taskId)) - .body("name", is("Test Task")) - .body("reward", is(100.0d)) - .body("assigned", nullValue()); - } - - @Test - void getTaskInvalidId() - { - createTestTask(100); - // Task ID won't be found since we use auto-increment and there's only one - given().when().get("/task/99999").then().statusCode(404); - } - - @Test - void getTaskBadId() - { - createTestTask(100); - given().when().get("/task/invalid").then().statusCode(400); - } - - @Test - void putTaskModifiesTask() - { - int taskId = createTestTask(100); - - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Updated Task", "reward", 100)) - .when() - .put("/task/" + taskId) - .then() - .statusCode(200); - - given() - .when() - .get("/task/" + taskId) - .then() - .statusCode(200) - .body("id", is(taskId)) - .body("name", is("Updated Task")) - .body("reward", is(100.0d)); - } - - @Test - void putTaskInvalidTaskId() - { - createTestTask(100); - - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Updated Task")) - .when() - .put("/task/999") - .then() - .statusCode(404); - } - - // ---- History Tests ---- - - @Test - void postHistory() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 100, "description", "Add a 100")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 20, "description", "Lolol")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", -10, "description", "Subtracting")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1") - .then() - .statusCode(200) - .body("allowance", is(110.0d)); - } - - @Test - void postHistoryInvalidUserId() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 100, "description", "Good")) - .when() - .post("/user/999/history") - .then() - .statusCode(404); - } - - @Test - void postHistoryInvalidDescription() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 100)) - .when() - .post("/user/1/history") - .then() - .statusCode(400); - } - - @Test - void getHistory() - { - Instant before = Instant.now().minusSeconds(2); - Instant after = Instant.now().plusSeconds(2); - - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 100, "description", "Add 100")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", 20, "description", "Add 20")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - given() - .contentType(ContentType.JSON) - .body(Map.of("allowance", -10, "description", "Subtract 10")) - .when() - .post("/user/1/history") - .then() - .statusCode(200); - - // History is returned newest first (by ID desc) - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(3)) - .body("[0].allowance", is(-10.0d)) - .body("[0].description", is("Subtract 10")) - .body("[1].allowance", is(20.0d)) - .body("[1].description", is("Add 20")) - .body("[2].allowance", is(100.0d)) - .body("[2].description", is("Add 100")); - } - - // ---- Allowance By ID Tests ---- - - @Test - void getUserAllowanceById() - { - int allowanceId = createAllowanceWithColour(1, TEST_HISTORY_NAME, 5000, 10, "#FF5733"); - - given() - .when() - .get("/user/1/allowance/" + allowanceId) - .then() - .statusCode(200) - .body("id", is(allowanceId)) - .body("name", is(TEST_HISTORY_NAME)) - .body("target", is(5000.0d)) - .body("weight", is(10.0d)) - .body("progress", is(0.0d)) - .body("colour", is("#FF5733")); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[1].id", is(allowanceId)) - .body("[1].colour", is("#FF5733")); - } - - @Test - void getUserByAllowanceIdInvalidAllowance() - { - given().when().get("/user/1/allowance/9999").then().statusCode(404); - } - - @Test - void getUserByAllowanceByIdInvalidUserId() - { - given().when().get("/user/999/allowance/1").then().statusCode(404); - } - - @Test - void getUserByAllowanceByIdBadUserId() - { - given().when().get("/user/bad/allowance/1").then().statusCode(400); - } - - @Test - void getUserByAllowanceByIdBadAllowanceId() - { - given().when().get("/user/1/allowance/bad").then().statusCode(400); - } - - @Test - void putAllowanceById() - { - int allowanceId = createAllowanceWithColour(1, TEST_HISTORY_NAME, 5000, 10, "#FF5733"); - - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Updated Allowance", "target", 6000, "weight", 15, "colour", "#3357FF")) - .when() - .put("/user/1/allowance/" + allowanceId) - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance/" + allowanceId) - .then() - .statusCode(200) - .body("id", is(allowanceId)) - .body("name", is("Updated Allowance")) - .body("target", is(6000.0d)) - .body("weight", is(15.0d)) - .body("colour", is("#3357FF")); - } - - // ---- Complete Task Tests ---- - - @Test - void completeTask() - { - int taskId = createTestTask(101); - - given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); - - // Update rest allowance weight - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 0, "weight", 25, "colour", "")) - .when() - .put("/user/1/allowance/0") - .then() - .statusCode(200); - - // Create two allowance goals - createAllowance(1, "Test Allowance 1", 100, 50); - createAllowance(1, "Test Allowance 1", 10, 25); - - // Complete the task - given().when().post("/task/" + taskId + "/complete").then().statusCode(200); - - // Verify task is completed - given().when().get("/task/" + taskId).then().statusCode(404); - - // Verify allowances for user 1 - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(3)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(30.34, 0.01)) - .body("[1].progress", closeTo(60.66, 0.01)) - .body("[2].progress", closeTo(10.0, 0.01)); - - // Verify allowances for user 2 - given() - .when() - .get("/user/2/allowance") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(101.0, 0.01)); - - // Verify history for both users - for (int userId = 1; userId <= 2; userId++) - { - given() - .when() - .get("/user/" + userId + "/history") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].allowance", closeTo(101.0, 0.01)); - } - } - - @Test - void completeTaskWithNoWeights() - { - int taskId = createTestTask(101); - - given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); - - // Ensure main allowance has no weight - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) - .when() - .put("/user/1/allowance/0") - .then() - .statusCode(200); - - // Complete the task - given().when().post("/task/" + taskId + "/complete").then().statusCode(200); - - // Verify task is completed - given().when().get("/task/" + taskId).then().statusCode(404); - - // Verify allowances for user 1 - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(101.0, 0.01)); - - // Verify allowances for user 2 - given() - .when() - .get("/user/2/allowance") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(101.0, 0.01)); - } - - @Test - void completeTaskAllowanceWeightsSumTo0() - { - int taskId = createTestTask(101); - - given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); - - // Update rest allowance to 0 weight - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) - .when() - .put("/user/1/allowance/0") - .then() - .statusCode(200); - - // Create allowance with 0 weight - createAllowance(1, "Test Allowance 1", 1000, 0); - - // Complete the task - given().when().post("/task/" + taskId + "/complete").then().statusCode(200); - - // Verify allowances for user 1 - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(101.0, 0.01)) - .body("[1].progress", closeTo(0.0, 0.01)); - } - - @Test - void completeTaskInvalidId() - { - given().when().post("/task/999/complete").then().statusCode(404); - } - - // ---- Complete Allowance Tests ---- - - @Test - void completeAllowance() - { - createTestTask(100); - createAllowance(1, "Test Allowance 1", 100, 50); - - // Update base allowance to 0 weight - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) - .when() - .put("/user/1/allowance/0") - .then() - .statusCode(200); - - // Complete the task - given().when().post("/task/1/complete").then().statusCode(200); - - // Get the allowance ID (first named allowance) - int allowanceId = given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .extract() - .path("[1].id"); - - // Complete allowance - given().when().post("/user/1/allowance/" + allowanceId + "/complete").then().statusCode(200); - - // Verify allowance no longer exists - given().when().get("/user/1/allowance/" + allowanceId).then().statusCode(404); - - // Verify history (newest first) - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[0].allowance", closeTo(-100.0, 0.01)) - .body("[0].description", is("Allowance completed: Test Allowance 1")) - .body("[1].allowance", closeTo(100.0, 0.01)) - .body("[1].description", is("Task completed: Test Task")); - } - - @Test - void completeAllowanceInvalidUserId() - { - given().when().post("/user/999/allowance/1/complete").then().statusCode(404); - } - - @Test - void completeAllowanceInvalidAllowanceId() - { - given().when().post("/user/1/allowance/999/complete").then().statusCode(404); - } - - // ---- Bulk Update Tests ---- - - @Test - void putBulkAllowance() - { - int id1 = createAllowance(1, "Test Allowance 1", 1000, 1); - int id2 = createAllowance(1, "Test Allowance 2", 1000, 2); - - given() - .contentType(ContentType.JSON) - .body(List.of( - Map.of("id", id1, "weight", 5), - Map.of("id", 0, "weight", 99), - Map.of("id", id2, "weight", 10))) - .when() - .put("/user/1/allowance") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("$.size()", is(3)) - .body("[0].id", is(0)) - .body("[0].weight", closeTo(99.0, 0.01)) - .body("[1].id", is(id1)) - .body("[1].weight", closeTo(5.0, 0.01)) - .body("[2].id", is(id2)) - .body("[2].weight", closeTo(10.0, 0.01)); - } - - // ---- Add Allowance Amount Tests ---- - - @Test - void addAllowanceSimple() - { - int allowanceId = createAllowance(1, "Test Allowance 1", 1000, 1); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/" + allowanceId + "/add") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[1].id", is(allowanceId)) - .body("[1].progress", closeTo(10.0, 0.01)); - - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].allowance", closeTo(10.0, 0.01)) - .body("[0].description", is("Added to allowance 1")); - } - - @Test - void addAllowanceWithSpillage() - { - int id1 = createAllowance(1, "Test Allowance 1", 5, 1); - int id2 = createAllowance(1, "Test Allowance 2", 5, 1); - given() - .contentType(ContentType.JSON) - .body(Map.of("name", "", "target", 0, "weight", 1, "colour", "")) - .when() - .put("/user/1/allowance/0") - .then() - .statusCode(200); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/" + id1 + "/add") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[1].id", is(id1)) - .body("[1].progress", closeTo(5.0, 0.01)) - .body("[2].id", is(id2)) - .body("[2].progress", closeTo(2.5, 0.01)) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(2.5, 0.01)); - - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].allowance", closeTo(10.0, 0.01)) - .body("[0].description", is("Added to allowance 1")); - } - - @Test - void addAllowanceIdZero() - { - createAllowance(1, "Test Allowance 1", 1000, 1); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/0/add") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(10.0, 0.01)); - - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(1)) - .body("[0].allowance", closeTo(10.0, 0.01)) - .body("[0].description", is("Added to allowance 1")); - } - - @Test - void subtractAllowanceSimple() - { - int allowanceId = createAllowance(1, "Test Allowance 1", 1000, 1); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/" + allowanceId + "/add") - .then() - .statusCode(200); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", -2.5, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/" + allowanceId + "/add") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[1].id", is(allowanceId)) - .body("[1].progress", closeTo(7.5, 0.01)); - - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[0].allowance", closeTo(-2.5, 0.01)) - .body("[0].description", is("Added to allowance 1")) - .body("[1].allowance", closeTo(10.0, 0.01)) - .body("[1].description", is("Added to allowance 1")); - } - - @Test - void subtractAllowanceIdZero() - { - createAllowance(1, "Test Allowance 1", 1000, 1); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/0/add") - .then() - .statusCode(200); - - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", -2.5, "description", "Added to allowance 1")) - .when() - .post("/user/1/allowance/0/add") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[0].id", is(0)) - .body("[0].progress", closeTo(7.5, 0.01)); - - given() - .when() - .get("/user/1/history") - .then() - .statusCode(200) - .body("$.size()", is(2)) - .body("[0].allowance", closeTo(-2.5, 0.01)) - .body("[0].description", is("Added to allowance 1")) - .body("[1].allowance", closeTo(10.0, 0.01)) - .body("[1].description", is("Added to allowance 1")); - } - - // ---- Transfer Tests ---- - - @Test - void transferSuccessful() - { - int id1 = createAllowance(1, "From Allowance", 100, 1); - int id2 = createAllowance(1, "To Allowance", 100, 1); - - // Add 30 to allowance 1 - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 30, "description", "funds")) - .when() - .post("/user/1/allowance/" + id1 + "/add") - .then() - .statusCode(200); - - // Transfer 10 from 1 to 2 - given() - .contentType(ContentType.JSON) - .body(Map.of("from", id1, "to", id2, "amount", 10)) - .when() - .post("/transfer") - .then() - .statusCode(200) - .body("message", is("Transfer successful")); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[1].progress", closeTo(20.0, 0.01)) - .body("[2].progress", closeTo(10.0, 0.01)); - } - - @Test - void transferCapsAtTarget() - { - int id1 = createAllowance(1, "From Allowance", 100, 1); - int id2 = createAllowance(1, "To Allowance", 5, 1); - - // Add 10 to allowance 1 - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "funds")) - .when() - .post("/user/1/allowance/" + id1 + "/add") - .then() - .statusCode(200); - - // Transfer 10, but to only needs 5 - given() - .contentType(ContentType.JSON) - .body(Map.of("from", id1, "to", id2, "amount", 10)) - .when() - .post("/transfer") - .then() - .statusCode(200); - - given() - .when() - .get("/user/1/allowance") - .then() - .statusCode(200) - .body("[1].progress", closeTo(5.0, 0.01)) - .body("[2].progress", closeTo(5.0, 0.01)); - } - - @Test - void transferDifferentUsersFails() - { - int id1 = createAllowance(1, "User1 Allowance", 100, 1); - - // Create allowance for user 2 - int id2 = createAllowance(2, "User2 Allowance", 100, 1); - - // Add to user1 allowance - given() - .contentType(ContentType.JSON) - .body(Map.of("amount", 10, "description", "funds")) - .when() - .post("/user/1/allowance/" + id1 + "/add") - .then() - .statusCode(200); - - // Transfer between different users - given() - .contentType(ContentType.JSON) - .body(Map.of("from", id1, "to", id2, "amount", 5)) - .when() - .post("/transfer") - .then() - .statusCode(400); - } - - @Test - void transferInsufficientFunds() - { - int id1 = createAllowance(1, "From Allowance", 100, 1); - int id2 = createAllowance(1, "To Allowance", 100, 1); - - given() - .contentType(ContentType.JSON) - .body(Map.of("from", id1, "to", id2, "amount", 10)) - .when() - .post("/transfer") - .then() - .statusCode(400) - .body("error", containsStringIgnoringCase("insufficient")); - } - - @Test - void transferNotFound() - { - given() - .contentType(ContentType.JSON) - .body(Map.of("from", 999, "to", 1000, "amount", 1)) - .when() - .post("/transfer") - .then() - .statusCode(404); - } - - // ---- Helpers ---- - - private int createTestTask(int reward) - { - return given() - .contentType(ContentType.JSON) - .body(Map.of("name", "Test Task", "reward", reward)) - .when() - .post("/tasks") - .then() - .statusCode(201) - .extract() - .path("id"); - } - - private int createAllowance(int userId, String name, double target, double weight) - { - return given() - .contentType(ContentType.JSON) - .body(Map.of("name", name, "target", target, "weight", weight)) - .when() - .post("/user/" + userId + "/allowance") - .then() - .statusCode(201) - .extract() - .path("id"); - } - - private int createAllowanceWithColour(int userId, String name, double target, double weight, String colour) - { - return given() - .contentType(ContentType.JSON) - .body(Map.of("name", name, "target", target, "weight", weight, "colour", colour)) - .when() - .post("/user/" + userId + "/allowance") - .then() - .statusCode(201) - .extract() - .path("id"); - } +class ApiTest { + private static final String TEST_HISTORY_NAME = "Test History"; + + @Container + static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:18") + .withDatabaseName("allowance_planner_test") + .withUsername("test") + .withPassword("test"); + + @LocalServerPort + int port; + + @Autowired + Flyway flyway; + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgres::getJdbcUrl); + registry.add("spring.datasource.username", postgres::getUsername); + registry.add("spring.datasource.password", postgres::getPassword); + registry.add("spring.flyway.clean-disabled", () -> "false"); + } + + @BeforeEach + void setUp() { + RestAssured.port = port; + RestAssured.basePath = "/api"; + RestAssured.config = RestAssured.config() + .jsonConfig(io.restassured.config.JsonConfig.jsonConfig() + .numberReturnType(io.restassured.path.json.config.JsonPathConfig.NumberReturnType.DOUBLE)); + + // Clean and re-migrate the database before each test + flyway.clean(); + flyway.migrate(); + } + + // ---- User Tests ---- + + @Test + void getUsers() { + given() + .when() + .get("/users") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[0].name", isA(String.class)) + .body("[1].name", isA(String.class)); + } + + @Test + void getUser() { + given() + .when() + .get("/user/1") + .then() + .statusCode(200) + .body("name", is("Seeseemelk")) + .body("id", is(1)) + .body("allowance", is(0.0d)); + } + + @Test + void getUserUnknown() { + given() + .when() + .get("/user/999") + .then() + .statusCode(404); + } + + @Test + void getUserBadId() { + given() + .when() + .get("/user/bad-id") + .then() + .statusCode(400); + } + + // ---- Allowance Tests ---- + + @Test + void getUserAllowanceWhenNoAllowancePresent() { + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].id", is(0)); + } + + @Test + void getUserAllowance() { + createAllowance(1, TEST_HISTORY_NAME, 5000, 10); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[1].name", is(TEST_HISTORY_NAME)) + .body("[1].target", is(5000.0d)) + .body("[1].weight", is(10.0d)) + .body("[1].progress", is(0.0d)) + .body("[1]", not(hasKey("user_id"))); + } + + @Test + void getUserAllowanceNoUser() { + given() + .when() + .get("/user/999/allowance") + .then() + .statusCode(404); + } + + @Test + void getUserAllowanceBadId() { + given() + .when() + .get("/user/bad-id/allowance") + .then() + .statusCode(400); + } + + @Test + void createUserAllowance() { + int allowanceId = createAllowance(1, TEST_HISTORY_NAME, 5000, 10); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[1].id", is(allowanceId)) + .body("[1].name", is(TEST_HISTORY_NAME)) + .body("[1].target", is(5000.0d)) + .body("[1].weight", is(10.0d)) + .body("[1].progress", is(0.0d)); + } + + @Test + void createUserAllowanceNoUser() { + given() + .contentType(ContentType.JSON) + .body(Map.of("name", TEST_HISTORY_NAME, "target", 5000, "weight", 10)) + .when() + .post("/user/999/allowance") + .then() + .statusCode(404); + } + + @Test + void createUserAllowanceInvalidInput() { + // Empty name + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 5000, "weight", 10)) + .when() + .post("/user/1/allowance") + .then() + .statusCode(400); + + // Missing name + given() + .contentType(ContentType.JSON) + .body(Map.of("target", 5000)) + .when() + .post("/user/1/allowance") + .then() + .statusCode(400); + } + + @Test + void createUserAllowanceBadId() { + given() + .contentType(ContentType.JSON) + .body(Map.of("name", TEST_HISTORY_NAME, "target", 5000, "weight", 10)) + .when() + .post("/user/bad-id/allowance") + .then() + .statusCode(400); + } + + @Test + void deleteUserAllowance() { + int allowanceId = createAllowance(1, TEST_HISTORY_NAME, 1000, 5); + + given() + .when() + .delete("/user/1/allowance/" + allowanceId) + .then() + .statusCode(200) + .body("message", is("History deleted successfully")); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(1)); + } + + @Test + void deleteUserRestAllowance() { + given() + .when() + .delete("/user/1/allowance/0") + .then() + .statusCode(400); + } + + @Test + void deleteUserAllowanceNotFound() { + given() + .when() + .delete("/user/1/allowance/999") + .then() + .statusCode(404) + .body("error", is("History not found")); + } + + @Test + void deleteUserAllowanceInvalidId() { + given() + .when() + .delete("/user/1/allowance/invalid-id") + .then() + .statusCode(400) + .body("error", is("Invalid allowance ID")); + } + + // ---- Task Tests ---- + + @Test + void createTask() { + // Without assigned user + int taskId = given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Test Task", "reward", 100)) + .when() + .post("/tasks") + .then() + .statusCode(201) + .body("id", notNullValue()) + .extract().path("id"); + + given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); + + given() + .when() + .get("/task/" + taskId) + .then() + .statusCode(200) + .body("id", is(taskId)) + .body("name", is("Test Task")) + .body("reward", is(100.0d)) + .body("assigned", nullValue()); + + // With assigned user + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Test Task Assigned", "reward", 200, "assigned", 1)) + .when() + .post("/tasks") + .then() + .statusCode(201) + .body("id", notNullValue()); + } + + @Test + void deleteTask() { + int taskId = createTestTask(100); + + given().when().delete("/task/" + taskId).then().statusCode(200); + given().when().get("/task/" + taskId).then().statusCode(404); + } + + @Test + void deleteTaskNotFound() { + given().when().delete("/task/1").then().statusCode(404); + } + + @Test + void createTaskNoName() { + given() + .contentType(ContentType.JSON) + .body(Map.of("reward", 100)) + .when() + .post("/tasks") + .then() + .statusCode(400); + } + + @Test + void createTaskInvalidAssignedUser() { + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Test Task Invalid User", "reward", 100, "assigned", 999)) + .when() + .post("/tasks") + .then() + .statusCode(404) + .body("error", is("User not found")); + } + + @Test + void createTaskInvalidRequestBody() { + given() + .contentType(ContentType.JSON) + .body(Map.of("reward", 5000)) + .when() + .post("/tasks") + .then() + .statusCode(400); + } + + @Test + void getTaskWhenNoTasks() { + given() + .when() + .get("/tasks") + .then() + .statusCode(200) + .body("$.size()", is(0)); + } + + @Test + void getTasksWhenTasks() { + createTestTask(100); + + given() + .when() + .get("/tasks") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].name", is("Test Task")) + .body("[0].reward", is(100.0d)) + .body("[0].assigned", nullValue()); + } + + @Test + void getTask() { + int taskId = createTestTask(100); + + given() + .when() + .get("/task/" + taskId) + .then() + .statusCode(200) + .body("id", is(taskId)) + .body("name", is("Test Task")) + .body("reward", is(100.0d)) + .body("assigned", nullValue()); + } + + @Test + void getTaskInvalidId() { + createTestTask(100); + // Task ID won't be found since we use auto-increment and there's only one + given().when().get("/task/99999").then().statusCode(404); + } + + @Test + void getTaskBadId() { + createTestTask(100); + given().when().get("/task/invalid").then().statusCode(400); + } + + @Test + void putTaskModifiesTask() { + int taskId = createTestTask(100); + + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Updated Task", "reward", 100)) + .when() + .put("/task/" + taskId) + .then() + .statusCode(200); + + given() + .when() + .get("/task/" + taskId) + .then() + .statusCode(200) + .body("id", is(taskId)) + .body("name", is("Updated Task")) + .body("reward", is(100.0d)); + } + + @Test + void putTaskInvalidTaskId() { + createTestTask(100); + + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Updated Task")) + .when() + .put("/task/999") + .then() + .statusCode(404); + } + + // ---- History Tests ---- + + @Test + void postHistory() { + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 100, "description", "Add a 100")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 20, "description", "Lolol")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", -10, "description", "Subtracting")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1") + .then() + .statusCode(200) + .body("allowance", is(110.0d)); + } + + @Test + void postHistoryInvalidUserId() { + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 100, "description", "Good")) + .when() + .post("/user/999/history") + .then() + .statusCode(404); + } + + @Test + void postHistoryInvalidDescription() { + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 100)) + .when() + .post("/user/1/history") + .then() + .statusCode(400); + } + + @Test + void getHistory() { + Instant before = Instant.now().minusSeconds(2); + Instant after = Instant.now().plusSeconds(2); + + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 100, "description", "Add 100")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", 20, "description", "Add 20")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + given() + .contentType(ContentType.JSON) + .body(Map.of("allowance", -10, "description", "Subtract 10")) + .when() + .post("/user/1/history") + .then() + .statusCode(200); + + // History is returned newest first (by ID desc) + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(3)) + .body("[0].allowance", is(-10.0d)) + .body("[0].description", is("Subtract 10")) + .body("[1].allowance", is(20.0d)) + .body("[1].description", is("Add 20")) + .body("[2].allowance", is(100.0d)) + .body("[2].description", is("Add 100")); + } + + // ---- Allowance By ID Tests ---- + + @Test + void getUserAllowanceById() { + int allowanceId = createAllowanceWithColour(1, TEST_HISTORY_NAME, 5000, 10, "#FF5733"); + + given() + .when() + .get("/user/1/allowance/" + allowanceId) + .then() + .statusCode(200) + .body("id", is(allowanceId)) + .body("name", is(TEST_HISTORY_NAME)) + .body("target", is(5000.0d)) + .body("weight", is(10.0d)) + .body("progress", is(0.0d)) + .body("colour", is("#FF5733")); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[1].id", is(allowanceId)) + .body("[1].colour", is("#FF5733")); + } + + @Test + void getUserByAllowanceIdInvalidAllowance() { + given().when().get("/user/1/allowance/9999").then().statusCode(404); + } + + @Test + void getUserByAllowanceByIdInvalidUserId() { + given().when().get("/user/999/allowance/1").then().statusCode(404); + } + + @Test + void getUserByAllowanceByIdBadUserId() { + given().when().get("/user/bad/allowance/1").then().statusCode(400); + } + + @Test + void getUserByAllowanceByIdBadAllowanceId() { + given().when().get("/user/1/allowance/bad").then().statusCode(400); + } + + @Test + void putAllowanceById() { + int allowanceId = createAllowanceWithColour(1, TEST_HISTORY_NAME, 5000, 10, "#FF5733"); + + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Updated Allowance", "target", 6000, "weight", 15, "colour", "#3357FF")) + .when() + .put("/user/1/allowance/" + allowanceId) + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance/" + allowanceId) + .then() + .statusCode(200) + .body("id", is(allowanceId)) + .body("name", is("Updated Allowance")) + .body("target", is(6000.0d)) + .body("weight", is(15.0d)) + .body("colour", is("#3357FF")); + } + + // ---- Complete Task Tests ---- + + @Test + void completeTask() { + int taskId = createTestTask(101); + + given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); + + // Update rest allowance weight + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 0, "weight", 25, "colour", "")) + .when() + .put("/user/1/allowance/0") + .then() + .statusCode(200); + + // Create two allowance goals + createAllowance(1, "Test Allowance 1", 100, 50); + createAllowance(1, "Test Allowance 1", 10, 25); + + // Complete the task + given().when().post("/task/" + taskId + "/complete").then().statusCode(200); + + // Verify task is completed + given().when().get("/task/" + taskId).then().statusCode(404); + + // Verify allowances for user 1 + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(3)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(30.34, 0.01)) + .body("[1].progress", closeTo(60.66, 0.01)) + .body("[2].progress", closeTo(10.0, 0.01)); + + // Verify allowances for user 2 + given() + .when() + .get("/user/2/allowance") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(101.0, 0.01)); + + // Verify history for both users + for (int userId = 1; userId <= 2; userId++) { + given() + .when() + .get("/user/" + userId + "/history") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].allowance", closeTo(101.0, 0.01)); + } + } + + @Test + void completeTaskWithNoWeights() { + int taskId = createTestTask(101); + + given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); + + // Ensure main allowance has no weight + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) + .when() + .put("/user/1/allowance/0") + .then() + .statusCode(200); + + // Complete the task + given().when().post("/task/" + taskId + "/complete").then().statusCode(200); + + // Verify task is completed + given().when().get("/task/" + taskId).then().statusCode(404); + + // Verify allowances for user 1 + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(101.0, 0.01)); + + // Verify allowances for user 2 + given() + .when() + .get("/user/2/allowance") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(101.0, 0.01)); + } + + @Test + void completeTaskAllowanceWeightsSumTo0() { + int taskId = createTestTask(101); + + given().when().get("/tasks").then().statusCode(200).body("$.size()", is(1)); + + // Update rest allowance to 0 weight + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) + .when() + .put("/user/1/allowance/0") + .then() + .statusCode(200); + + // Create allowance with 0 weight + createAllowance(1, "Test Allowance 1", 1000, 0); + + // Complete the task + given().when().post("/task/" + taskId + "/complete").then().statusCode(200); + + // Verify allowances for user 1 + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(101.0, 0.01)) + .body("[1].progress", closeTo(0.0, 0.01)); + } + + @Test + void completeTaskInvalidId() { + given().when().post("/task/999/complete").then().statusCode(404); + } + + // ---- Complete Allowance Tests ---- + + @Test + void completeAllowance() { + createTestTask(100); + createAllowance(1, "Test Allowance 1", 100, 50); + + // Update base allowance to 0 weight + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 0, "weight", 0, "colour", "")) + .when() + .put("/user/1/allowance/0") + .then() + .statusCode(200); + + // Complete the task + given().when().post("/task/1/complete").then().statusCode(200); + + // Get the allowance ID (first named allowance) + int allowanceId = given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .extract() + .path("[1].id"); + + // Complete allowance + given().when().post("/user/1/allowance/" + allowanceId + "/complete").then().statusCode(200); + + // Verify allowance no longer exists + given().when().get("/user/1/allowance/" + allowanceId).then().statusCode(404); + + // Verify history (newest first) + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[0].allowance", closeTo(-100.0, 0.01)) + .body("[0].description", is("Allowance completed: Test Allowance 1")) + .body("[1].allowance", closeTo(100.0, 0.01)) + .body("[1].description", is("Task completed: Test Task")); + } + + @Test + void completeAllowanceInvalidUserId() { + given().when().post("/user/999/allowance/1/complete").then().statusCode(404); + } + + @Test + void completeAllowanceInvalidAllowanceId() { + given().when().post("/user/1/allowance/999/complete").then().statusCode(404); + } + + // ---- Bulk Update Tests ---- + + @Test + void putBulkAllowance() { + int id1 = createAllowance(1, "Test Allowance 1", 1000, 1); + int id2 = createAllowance(1, "Test Allowance 2", 1000, 2); + + given() + .contentType(ContentType.JSON) + .body(List.of( + Map.of("id", id1, "weight", 5), + Map.of("id", 0, "weight", 99), + Map.of("id", id2, "weight", 10))) + .when() + .put("/user/1/allowance") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("$.size()", is(3)) + .body("[0].id", is(0)) + .body("[0].weight", closeTo(99.0, 0.01)) + .body("[1].id", is(id1)) + .body("[1].weight", closeTo(5.0, 0.01)) + .body("[2].id", is(id2)) + .body("[2].weight", closeTo(10.0, 0.01)); + } + + // ---- Add Allowance Amount Tests ---- + + @Test + void addAllowanceSimple() { + int allowanceId = createAllowance(1, "Test Allowance 1", 1000, 1); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/" + allowanceId + "/add") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[1].id", is(allowanceId)) + .body("[1].progress", closeTo(10.0, 0.01)); + + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].allowance", closeTo(10.0, 0.01)) + .body("[0].description", is("Added to allowance 1")); + } + + @Test + void addAllowanceWithSpillage() { + int id1 = createAllowance(1, "Test Allowance 1", 5, 1); + int id2 = createAllowance(1, "Test Allowance 2", 5, 1); + given() + .contentType(ContentType.JSON) + .body(Map.of("name", "", "target", 0, "weight", 1, "colour", "")) + .when() + .put("/user/1/allowance/0") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/" + id1 + "/add") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[1].id", is(id1)) + .body("[1].progress", closeTo(5.0, 0.01)) + .body("[2].id", is(id2)) + .body("[2].progress", closeTo(2.5, 0.01)) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(2.5, 0.01)); + + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].allowance", closeTo(10.0, 0.01)) + .body("[0].description", is("Added to allowance 1")); + } + + @Test + void addAllowanceIdZero() { + createAllowance(1, "Test Allowance 1", 1000, 1); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/0/add") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(10.0, 0.01)); + + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].allowance", closeTo(10.0, 0.01)) + .body("[0].description", is("Added to allowance 1")); + } + + @Test + void subtractAllowanceSimple() { + int allowanceId = createAllowance(1, "Test Allowance 1", 1000, 1); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/" + allowanceId + "/add") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", -2.5, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/" + allowanceId + "/add") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[1].id", is(allowanceId)) + .body("[1].progress", closeTo(7.5, 0.01)); + + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[0].allowance", closeTo(-2.5, 0.01)) + .body("[0].description", is("Added to allowance 1")) + .body("[1].allowance", closeTo(10.0, 0.01)) + .body("[1].description", is("Added to allowance 1")); + } + + @Test + void subtractAllowanceIdZero() { + createAllowance(1, "Test Allowance 1", 1000, 1); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/0/add") + .then() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", -2.5, "description", "Added to allowance 1")) + .when() + .post("/user/1/allowance/0/add") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[0].id", is(0)) + .body("[0].progress", closeTo(7.5, 0.01)); + + given() + .when() + .get("/user/1/history") + .then() + .statusCode(200) + .body("$.size()", is(2)) + .body("[0].allowance", closeTo(-2.5, 0.01)) + .body("[0].description", is("Added to allowance 1")) + .body("[1].allowance", closeTo(10.0, 0.01)) + .body("[1].description", is("Added to allowance 1")); + } + + // ---- Transfer Tests ---- + + @Test + void transferSuccessful() { + int id1 = createAllowance(1, "From Allowance", 100, 1); + int id2 = createAllowance(1, "To Allowance", 100, 1); + + // Add 30 to allowance 1 + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 30, "description", "funds")) + .when() + .post("/user/1/allowance/" + id1 + "/add") + .then() + .statusCode(200); + + // Transfer 10 from 1 to 2 + given() + .contentType(ContentType.JSON) + .body(Map.of("from", id1, "to", id2, "amount", 10)) + .when() + .post("/transfer") + .then() + .statusCode(200) + .body("message", is("Transfer successful")); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[1].progress", closeTo(20.0, 0.01)) + .body("[2].progress", closeTo(10.0, 0.01)); + } + + @Test + void transferCapsAtTarget() { + int id1 = createAllowance(1, "From Allowance", 100, 1); + int id2 = createAllowance(1, "To Allowance", 5, 1); + + // Add 10 to allowance 1 + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "funds")) + .when() + .post("/user/1/allowance/" + id1 + "/add") + .then() + .statusCode(200); + + // Transfer 10, but to only needs 5 + given() + .contentType(ContentType.JSON) + .body(Map.of("from", id1, "to", id2, "amount", 10)) + .when() + .post("/transfer") + .then() + .statusCode(200); + + given() + .when() + .get("/user/1/allowance") + .then() + .statusCode(200) + .body("[1].progress", closeTo(5.0, 0.01)) + .body("[2].progress", closeTo(5.0, 0.01)); + } + + @Test + void transferDifferentUsersFails() { + int id1 = createAllowance(1, "User1 Allowance", 100, 1); + + // Create allowance for user 2 + int id2 = createAllowance(2, "User2 Allowance", 100, 1); + + // Add to user1 allowance + given() + .contentType(ContentType.JSON) + .body(Map.of("amount", 10, "description", "funds")) + .when() + .post("/user/1/allowance/" + id1 + "/add") + .then() + .statusCode(200); + + // Transfer between different users + given() + .contentType(ContentType.JSON) + .body(Map.of("from", id1, "to", id2, "amount", 5)) + .when() + .post("/transfer") + .then() + .statusCode(400); + } + + @Test + void transferInsufficientFunds() { + int id1 = createAllowance(1, "From Allowance", 100, 1); + int id2 = createAllowance(1, "To Allowance", 100, 1); + + given() + .contentType(ContentType.JSON) + .body(Map.of("from", id1, "to", id2, "amount", 10)) + .when() + .post("/transfer") + .then() + .statusCode(400) + .body("error", containsStringIgnoringCase("insufficient")); + } + + @Test + void transferNotFound() { + given() + .contentType(ContentType.JSON) + .body(Map.of("from", 999, "to", 1000, "amount", 1)) + .when() + .post("/transfer") + .then() + .statusCode(404); + } + + // ---- Helpers ---- + + private int createTestTask(int reward) { + return given() + .contentType(ContentType.JSON) + .body(Map.of("name", "Test Task", "reward", reward)) + .when() + .post("/tasks") + .then() + .statusCode(201) + .extract() + .path("id"); + } + + private int createAllowance(int userId, String name, double target, double weight) { + return given() + .contentType(ContentType.JSON) + .body(Map.of("name", name, "target", target, "weight", weight)) + .when() + .post("/user/" + userId + "/allowance") + .then() + .statusCode(201) + .extract() + .path("id"); + } + + private int createAllowanceWithColour(int userId, String name, double target, double weight, String colour) { + return given() + .contentType(ContentType.JSON) + .body(Map.of("name", name, "target", target, "weight", weight, "colour", colour)) + .when() + .post("/user/" + userId + "/allowance") + .then() + .statusCode(201) + .extract() + .path("id"); + } } diff --git a/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ColourUtilTest.java b/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ColourUtilTest.java index 2e7766a..f2252ff 100644 --- a/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ColourUtilTest.java +++ b/backend-spring/src/test/java/be/seeseepuff/allowanceplanner/ColourUtilTest.java @@ -5,29 +5,24 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -class ColourUtilTest -{ - @Test - void convertStringToColourWithSign() - { - assertEquals(0x123456, ColourUtil.convertStringToColour("#123456")); - } +class ColourUtilTest { + @Test + void convertStringToColourWithSign() { + assertEquals(0x123456, ColourUtil.convertStringToColour("#123456")); + } - @Test - void convertStringToColourWithoutSign() - { - assertEquals(0x123456, ColourUtil.convertStringToColour("123456")); - } + @Test + void convertStringToColourWithoutSign() { + assertEquals(0x123456, ColourUtil.convertStringToColour("123456")); + } - @Test - void convertStringToColourWithSignThreeDigits() - { - assertEquals(0xA0B0C0, ColourUtil.convertStringToColour("#ABC")); - } + @Test + void convertStringToColourWithSignThreeDigits() { + assertEquals(0xA0B0C0, ColourUtil.convertStringToColour("#ABC")); + } - @Test - void convertStringToColourWithoutSignThreeDigits() - { - assertEquals(0xA0B0C0, ColourUtil.convertStringToColour("ABC")); - } + @Test + void convertStringToColourWithoutSignThreeDigits() { + assertEquals(0xA0B0C0, ColourUtil.convertStringToColour("ABC")); + } }