diff --git a/pom.xml b/pom.xml
index 45528c4..9d421c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,11 +6,11 @@
org.springframework.boot
spring-boot-starter-parent
4.0.6
-
+
de.zendric.app
XpenselyServer
- 1.0.0
+ 1.1.0
XpenselyServer
XpenselyServer used to handle the Xpensely App
@@ -52,10 +52,6 @@
org.springframework.boot
spring-boot-starter-oauth2-resource-server
-
- org.springframework.boot
- spring-boot-starter-oauth2-client
-
org.springframework.boot
spring-boot-starter-web
diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
index 597824b..a3a483b 100644
--- a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
+++ b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
@@ -9,6 +9,7 @@ import org.springframework.web.server.ResponseStatusException;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.AppUserCreateRequest;
+import de.zendric.app.xpensely_server.model.UsernameUpdateRequest;
import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver;
import de.zendric.app.xpensely_server.services.UserService;
@@ -31,11 +32,6 @@ public class AppUserController {
return ResponseEntity.ok(userService.getUser(id));
}
- @GetMapping("/byName")
- public AppUser getUserByName(@RequestParam String username) {
- return userService.getUserByName(username);
- }
-
@GetMapping("/byGoogleId")
public ResponseEntity getUserByGoogleId(@RequestParam String id, Authentication authentication) {
AppUser self = authenticatedUserResolver.resolveCurrentUser(authentication);
@@ -51,6 +47,14 @@ public class AppUserController {
return new ResponseEntity<>(nUser, HttpStatus.CREATED);
}
+ @PutMapping("/username")
+ public ResponseEntity updateUsername(@RequestBody @Valid UsernameUpdateRequest request,
+ Authentication authentication) {
+ AppUser self = authenticatedUserResolver.resolveCurrentUser(authentication);
+ AppUser updated = userService.updateUsername(self.getId(), request.getUsername());
+ return ResponseEntity.ok(updated);
+ }
+
@DeleteMapping
public ResponseEntity deleteUser(@RequestParam Long id, Authentication authentication) {
AppUser self = authenticatedUserResolver.resolveCurrentUser(authentication);
diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
index a2227e3..ae4bb58 100644
--- a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
+++ b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
@@ -17,7 +17,6 @@ import de.zendric.app.xpensely_server.model.*;
import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver;
import de.zendric.app.xpensely_server.services.CategoryService;
import de.zendric.app.xpensely_server.services.ExpenseListService;
-import de.zendric.app.xpensely_server.services.UserService;
@RestController
@RequestMapping("/api/expenselist")
@@ -26,14 +25,12 @@ public class ExpenseListController {
private static final Logger log = LoggerFactory.getLogger(ExpenseListController.class);
private final ExpenseListService expenseListService;
- private final UserService userService;
private final CategoryService categoryService;
private final AuthenticatedUserResolver authenticatedUserResolver;
- public ExpenseListController(ExpenseListService expenseListService, UserService userService,
+ public ExpenseListController(ExpenseListService expenseListService,
CategoryService categoryService, AuthenticatedUserResolver authenticatedUserResolver) {
this.expenseListService = expenseListService;
- this.userService = userService;
this.categoryService = categoryService;
this.authenticatedUserResolver = authenticatedUserResolver;
}
@@ -93,7 +90,7 @@ public class ExpenseListController {
if (listOpt.isEmpty())
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
assertMember(user, listOpt.get());
- AppUser expenseOwner = userService.getUserByName(expenseInput.getOwner());
+ AppUser expenseOwner = resolveListMember(listOpt.get(), expenseInput.getOwner());
Expense expense = expenseInput.convertToExpense(expenseOwner.getId());
Expense addedExpense = expenseListService.addExpenseToList(expenseListId, expense);
return new ResponseEntity<>(addedExpense, HttpStatus.CREATED);
@@ -109,7 +106,7 @@ public class ExpenseListController {
if (expenseListOpt.isEmpty())
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
assertMember(user, expenseListOpt.get());
- AppUser expenseOwner = userService.getUserByName(expenseChangeRequest.getOwnerName());
+ AppUser expenseOwner = resolveListMember(expenseListOpt.get(), expenseChangeRequest.getOwnerName());
Expense expense = expenseChangeRequest.convertToExpense(expenseOwner.getId(), expenseListOpt.get());
Expense updatedExpense = expenseListService.updateExpense(expenseListId, expense);
return new ResponseEntity<>(updatedExpense, HttpStatus.OK);
@@ -156,6 +153,11 @@ public class ExpenseListController {
if (list.getOwner().getId().equals(authenticatedUser.getId())) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("You cannot join your own list");
}
+ if (list.getOwner().getUsername().equals(authenticatedUser.getUsername())) {
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(
+ "You and the list owner both use the username \"" + authenticatedUser.getUsername()
+ + "\". Please change your username before joining this list.");
+ }
list.setSharedWith(authenticatedUser);
expenseListService.save(list);
return ResponseEntity.ok("User added to the list");
@@ -173,4 +175,20 @@ public class ExpenseListController {
if (!isOwner && !isShared)
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
+
+ /**
+ * Resolves an expense owner by username among the list's members. Usernames are
+ * not globally unique, so the owner must be one of this list's (at most two)
+ * members — never a global lookup.
+ */
+ private AppUser resolveListMember(ExpenseList list, String username) {
+ if (list.getOwner() != null && list.getOwner().getUsername().equals(username)) {
+ return list.getOwner();
+ }
+ if (list.getSharedWith() != null && list.getSharedWith().getUsername().equals(username)) {
+ return list.getSharedWith();
+ }
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
+ "Expense owner must be a member of this list");
+ }
}
diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/GlobalExceptionHandler.java b/src/main/java/de/zendric/app/xpensely_server/controller/GlobalExceptionHandler.java
index 5a0c270..2a923f8 100644
--- a/src/main/java/de/zendric/app/xpensely_server/controller/GlobalExceptionHandler.java
+++ b/src/main/java/de/zendric/app/xpensely_server/controller/GlobalExceptionHandler.java
@@ -1,5 +1,6 @@
package de.zendric.app.xpensely_server.controller;
+import de.zendric.app.xpensely_server.model.Exception.GoogleAccountAlreadyRegisteredException;
import de.zendric.app.xpensely_server.model.Exception.ResourceNotFoundException;
import de.zendric.app.xpensely_server.model.Exception.UsernameAlreadyExistsException;
import org.slf4j.Logger;
@@ -47,6 +48,13 @@ public class GlobalExceptionHandler {
.body(Map.of("error", ex.getMessage()));
}
+ @ExceptionHandler(GoogleAccountAlreadyRegisteredException.class)
+ public ResponseEntity