Compare commits

2 Commits

Author SHA1 Message Date
0ee56e4e52 Fixed bug in finding ExpenseLists 2024-12-29 00:47:10 +01:00
4df0b36f45 Sharing Lists logic 2024-12-28 01:35:50 +01:00
9 changed files with 135 additions and 14 deletions

View File

@@ -2,8 +2,10 @@ package de.zendric.app.xpensely_server;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication
@EnableScheduling
public class XpenselyServerApplication { public class XpenselyServerApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -3,6 +3,7 @@ package de.zendric.app.xpensely_server.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -41,6 +42,14 @@ public class AppUserController {
return ResponseEntity.status(HttpStatus.CREATED).body(appUser); return ResponseEntity.status(HttpStatus.CREATED).body(appUser);
} }
@PostMapping("/createUser")
public ResponseEntity<?> createUsername(@RequestBody AppUser user, Authentication authentication) {
String googleUserId = authentication.getName();
// Validate and store the username with googleUserId
return ResponseEntity.ok("Username created successfully");
}
@DeleteMapping @DeleteMapping
public String deleteUser(@RequestParam Long id) { public String deleteUser(@RequestParam Long id) {
AppUser user = userService.deleteUserById(id); AppUser user = userService.deleteUserById(id);

View File

@@ -1,5 +1,6 @@
package de.zendric.app.xpensely_server.controller; package de.zendric.app.xpensely_server.controller;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -16,19 +17,24 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.Expense; import de.zendric.app.xpensely_server.model.Expense;
import de.zendric.app.xpensely_server.model.ExpenseList; import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.model.InviteRequest;
import de.zendric.app.xpensely_server.services.ExpenseListService; import de.zendric.app.xpensely_server.services.ExpenseListService;
import de.zendric.app.xpensely_server.services.UserService;
@RestController @RestController
@RequestMapping("/api/expenselist") @RequestMapping("/api/expenselist")
class ExpenseListController { class ExpenseListController {
private ExpenseListService expenseListService; private ExpenseListService expenseListService;
private UserService userService;
@Autowired @Autowired
public ExpenseListController(ExpenseListService expenseListService) { public ExpenseListController(ExpenseListService expenseListService, UserService userService) {
this.expenseListService = expenseListService; this.expenseListService = expenseListService;
this.userService = userService;
} }
@GetMapping("/all") @GetMapping("/all")
@@ -129,4 +135,36 @@ class ExpenseListController {
return new ResponseEntity<>(null, HttpStatus.EXPECTATION_FAILED); return new ResponseEntity<>(null, HttpStatus.EXPECTATION_FAILED);
} }
} }
@PostMapping("/{listId}/invite")
public ResponseEntity<String> generateInvite(@PathVariable Long listId) {
String inviteCode = expenseListService.generateInviteCode(listId);
return ResponseEntity.ok(inviteCode);
}
@PostMapping("/accept-invite")
public ResponseEntity<?> acceptInvite(@RequestBody InviteRequest inviteRequest) {
ExpenseList list = expenseListService.findByInviteCode(inviteRequest.getInviteCode());
if (list == null || list.getInviteCodeExpiration() == null ||
list.getInviteCodeExpiration().isBefore(LocalDateTime.now())) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Invalid or expired invite code");
}
if (list.getSharedWith() != null) {
return ResponseEntity.status(HttpStatus.IM_USED).body("List has already been shared");
}
AppUser user = null;
try {
user = userService.getUser(inviteRequest.getUserId());
} catch (Exception e) {
throw new RuntimeException("User not found");
}
if (user != null) {
list.setSharedWith(user);
expenseListService.save(list);
} else {
throw new RuntimeException("User not found");
}
return ResponseEntity.ok("User added to the list");
}
} }

View File

@@ -1,7 +1,9 @@
package de.zendric.app.xpensely_server.model; package de.zendric.app.xpensely_server.model;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference; import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
@@ -9,7 +11,6 @@ import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -29,11 +30,15 @@ public class ExpenseList {
private String name; private String name;
private String inviteCode;
@JsonIgnore
private LocalDateTime inviteCodeExpiration;
@ManyToOne @ManyToOne
private AppUser owner; private AppUser owner;
@ManyToMany @ManyToOne
private List<AppUser> sharedWith; private AppUser sharedWith;
@OneToMany(mappedBy = "expenseList", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "expenseList", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference @JsonManagedReference

View File

@@ -0,0 +1,13 @@
package de.zendric.app.xpensely_server.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InviteRequest {
private String inviteCode;
private Long userId;
}

View File

@@ -10,4 +10,6 @@ import de.zendric.app.xpensely_server.model.ExpenseList;
@Repository @Repository
public interface ExpenseListRepository extends JpaRepository<ExpenseList, Long> { public interface ExpenseListRepository extends JpaRepository<ExpenseList, Long> {
List<ExpenseList> findByOwnerId(Long ownerId); List<ExpenseList> findByOwnerId(Long ownerId);
ExpenseList findByInviteCode(String inviteCode);
} }

View File

@@ -0,0 +1,34 @@
package de.zendric.app.xpensely_server.services;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
@Service
public class CleanupService {
@Autowired
private ExpenseListRepository expenseListRepository;
@Scheduled(cron = "0 0 0 * * ?") // Runs daily at midnight
public void cleanupExpiredInvites() {
List<ExpenseList> expiredLists = expenseListRepository.findAll().stream()
.filter(list -> list.getInviteCodeExpiration() != null &&
list.getInviteCodeExpiration().isBefore(LocalDateTime.now()))
.collect(Collectors.toList());
for (ExpenseList list : expiredLists) {
list.setInviteCode(null);
list.setInviteCodeExpiration(null);
expenseListRepository.save(list);
}
}
}

View File

@@ -1,9 +1,11 @@
package de.zendric.app.xpensely_server.services; package de.zendric.app.xpensely_server.services;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -64,15 +66,13 @@ public class ExpenseListService {
List<ExpenseList> allLists = repository.findAll(); List<ExpenseList> allLists = repository.findAll();
List<ExpenseList> userSpecificList = new ArrayList<>(); List<ExpenseList> userSpecificList = new ArrayList<>();
for (ExpenseList expenseList : allLists) { for (ExpenseList expenseList : allLists) {
List<AppUser> sharedWith = expenseList.getSharedWith(); AppUser sharedWith = expenseList.getSharedWith();
if (expenseList.getOwner().getId().equals(id)) { if (expenseList.getOwner().getId().equals(id)) {
userSpecificList.add(expenseList); userSpecificList.add(expenseList);
} else { } else {
for (AppUser user : sharedWith) { if (sharedWith != null && sharedWith.getId().equals(id)) {
if (user.getId().equals(id)) { userSpecificList.add(expenseList);
userSpecificList.add(expenseList);
}
} }
} }
} }
@@ -83,19 +83,18 @@ public class ExpenseListService {
List<ExpenseList> allLists = repository.findAll(); List<ExpenseList> allLists = repository.findAll();
List<ExpenseList> userSpecificList = new ArrayList<>(); List<ExpenseList> userSpecificList = new ArrayList<>();
for (ExpenseList expenseList : allLists) { for (ExpenseList expenseList : allLists) {
List<AppUser> sharedWith = expenseList.getSharedWith(); AppUser sharedWith = expenseList.getSharedWith();
if (expenseList.getOwner().getUsername().equals(username)) { if (expenseList.getOwner().getUsername().equals(username)) {
userSpecificList.add(expenseList); userSpecificList.add(expenseList);
} else { } else {
for (AppUser user : sharedWith) { if (sharedWith != null && sharedWith.getUsername().equals(username)) {
if (user.getUsername().equals(username)) { userSpecificList.add(expenseList);
userSpecificList.add(expenseList);
}
} }
} }
} }
return userSpecificList; return userSpecificList;
} }
public Expense addExpenseToList(Long expenseListId, Expense expense) { public Expense addExpenseToList(Long expenseListId, Expense expense) {
@@ -134,4 +133,22 @@ public class ExpenseListService {
} }
repository.save(expenseList); repository.save(expenseList);
} }
public String generateInviteCode(Long listId) {
ExpenseList list = repository.findById(listId)
.orElseThrow(() -> new RuntimeException("List not found"));
String inviteCode = UUID.randomUUID().toString().substring(0, 6).toUpperCase();
LocalDateTime expirationTime = LocalDateTime.now().plusWeeks(1);
list.setInviteCode(inviteCode);
list.setInviteCodeExpiration(expirationTime);
repository.save(list);
return inviteCode;
}
public ExpenseList findByInviteCode(String inviteCode) {
return repository.findByInviteCode(inviteCode);
}
} }

View File

@@ -48,4 +48,5 @@ public class UserService {
} else } else
return null; return null;
} }
} }