From e24a7d51185f373409829c954abcc5ff7e9d7457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20P=C3=BCschel?= Date: Fri, 12 Jun 2026 17:03:08 +0200 Subject: [PATCH 1/2] Added Create and Delete operations --- .../server/GroceryListController.java | 47 ++++++++++++++ .../server/dto/GroceryItemRequestDTO.java | 3 + .../server/dto/GroceryListCreateRequest.java | 6 ++ .../bytebite/server/entity/GroceryItem.java | 9 +++ .../bytebite/server/entity/GroceryList.java | 9 ++- .../server/service/GroceryListService.java | 63 +++++++++++++++++++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryItemRequestDTO.java create mode 100644 server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryListCreateRequest.java diff --git a/server/grocery-service/src/main/java/com/bytebite/server/GroceryListController.java b/server/grocery-service/src/main/java/com/bytebite/server/GroceryListController.java index cfc0d80..de76558 100644 --- a/server/grocery-service/src/main/java/com/bytebite/server/GroceryListController.java +++ b/server/grocery-service/src/main/java/com/bytebite/server/GroceryListController.java @@ -1,5 +1,6 @@ package com.bytebite.server; +import com.bytebite.server.dto.GroceryListCreateRequest; import com.bytebite.server.dto.GroceryListDetailDTO; import com.bytebite.server.dto.GroceryListSummaryDTO; import com.bytebite.server.service.GroceryListService; @@ -11,11 +12,17 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.net.URI; import java.util.List; import java.util.Map; import java.util.UUID; @@ -61,4 +68,44 @@ public GroceryListDetailDTO getById( @PathVariable UUID id) { return service.getById(id); } + + @PostMapping(value = "/history", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = "Create a grocery list", + description = "Persists a new grocery list with its items and returns the created resource.", + security = @SecurityRequirement(name = "bearerAuth"), + responses = { + @ApiResponse(responseCode = "201", description = "Grocery list created"), + @ApiResponse(responseCode = "400", description = "Invalid request body", content = @Content(schema = @Schema(implementation = Map.class))), + @ApiResponse(responseCode = "401", description = "Missing, expired, or invalid JWT", content = @Content) + } + ) + public ResponseEntity create(@RequestBody GroceryListCreateRequest request) { + GroceryListDetailDTO created = service.create(request); + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}") + .buildAndExpand(created.id()) + .toUri(); + return ResponseEntity.created(location).body(created); + } + + @DeleteMapping(value = "/history/{id}") + @Operation( + summary = "Delete a grocery list", + description = "Deletes the grocery list identified by the given id along with its items.", + security = @SecurityRequirement(name = "bearerAuth"), + responses = { + @ApiResponse(responseCode = "204", description = "Grocery list deleted"), + @ApiResponse(responseCode = "401", description = "Missing, expired, or invalid JWT", content = @Content), + @ApiResponse(responseCode = "404", description = "Grocery list not found", content = @Content(schema = @Schema(implementation = Map.class))) + } + ) + public ResponseEntity delete( + @Parameter(description = "Identifier of the grocery list", required = true) + @PathVariable UUID id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } } diff --git a/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryItemRequestDTO.java b/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryItemRequestDTO.java new file mode 100644 index 0000000..a5b20e6 --- /dev/null +++ b/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryItemRequestDTO.java @@ -0,0 +1,3 @@ +package com.bytebite.server.dto; + +public record GroceryItemRequestDTO(String name, double quantity, String unit, String category, boolean purchased) {} diff --git a/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryListCreateRequest.java b/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryListCreateRequest.java new file mode 100644 index 0000000..e6b01d6 --- /dev/null +++ b/server/grocery-service/src/main/java/com/bytebite/server/dto/GroceryListCreateRequest.java @@ -0,0 +1,6 @@ +package com.bytebite.server.dto; + +import java.util.List; +import java.util.UUID; + +public record GroceryListCreateRequest(String name, UUID userId, List items) {} diff --git a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java index 2cff949..a8d1dbf 100644 --- a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java +++ b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java @@ -37,4 +37,13 @@ public class GroceryItem { public String getUnit() { return unit; } public GroceryCategory getCategory() { return category; } public boolean isPurchased() { return purchased; } + public GroceryList getGroceryList() { return groceryList; } + + public void setId(UUID id) { this.id = id; } + public void setName(String name) { this.name = name; } + public void setQuantity(double quantity) { this.quantity = quantity; } + public void setUnit(String unit) { this.unit = unit; } + public void setCategory(GroceryCategory category) { this.category = category; } + public void setPurchased(boolean purchased) { this.purchased = purchased; } + public void setGroceryList(GroceryList groceryList) { this.groceryList = groceryList; } } diff --git a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryList.java b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryList.java index 8311867..a3fece2 100644 --- a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryList.java +++ b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryList.java @@ -25,7 +25,7 @@ public class GroceryList { @Column(name = "created_at", nullable = false, updatable = false) private Instant createdAt; - @OneToMany(mappedBy = "groceryList", fetch = FetchType.LAZY) + @OneToMany(mappedBy = "groceryList", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private List items; public UUID getId() { return id; } @@ -34,4 +34,11 @@ public class GroceryList { public UUID getUserId() { return userId; } public Instant getCreatedAt() { return createdAt; } public List getItems() { return items; } + + public void setId(UUID id) { this.id = id; } + public void setName(String name) { this.name = name; } + public void setOutdated(boolean outdated) { this.outdated = outdated; } + public void setUserId(UUID userId) { this.userId = userId; } + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } + public void setItems(List items) { this.items = items; } } diff --git a/server/grocery-service/src/main/java/com/bytebite/server/service/GroceryListService.java b/server/grocery-service/src/main/java/com/bytebite/server/service/GroceryListService.java index 65ceb17..e33e46a 100644 --- a/server/grocery-service/src/main/java/com/bytebite/server/service/GroceryListService.java +++ b/server/grocery-service/src/main/java/com/bytebite/server/service/GroceryListService.java @@ -1,8 +1,12 @@ package com.bytebite.server.service; +import com.bytebite.server.dto.GroceryItemRequestDTO; import com.bytebite.server.dto.GroceryItemResponseDTO; +import com.bytebite.server.dto.GroceryListCreateRequest; import com.bytebite.server.dto.GroceryListDetailDTO; import com.bytebite.server.dto.GroceryListSummaryDTO; +import com.bytebite.server.entity.GroceryCategory; +import com.bytebite.server.entity.GroceryItem; import com.bytebite.server.entity.GroceryList; import com.bytebite.server.repository.GroceryListRepository; import org.springframework.http.HttpStatus; @@ -10,6 +14,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -34,7 +40,53 @@ public GroceryListDetailDTO getById(UUID id) { GroceryList groceryList = repository.findById(id) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Grocery list not found: " + id)); + return toDetail(groceryList); + } + + @Transactional + public GroceryListDetailDTO create(GroceryListCreateRequest request) { + if (request.name() == null || request.name().isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Grocery list name is required"); + } + if (request.userId() == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "userId is required"); + } + + GroceryList groceryList = new GroceryList(); + groceryList.setId(UUID.randomUUID()); + groceryList.setName(request.name()); + groceryList.setUserId(request.userId()); + groceryList.setOutdated(false); + groceryList.setCreatedAt(Instant.now()); + + List items = new ArrayList<>(); + if (request.items() != null) { + for (GroceryItemRequestDTO itemDto : request.items()) { + GroceryItem item = new GroceryItem(); + item.setId(UUID.randomUUID()); + item.setName(itemDto.name()); + item.setQuantity(itemDto.quantity()); + item.setUnit(itemDto.unit()); + item.setCategory(parseCategory(itemDto.category())); + item.setPurchased(itemDto.purchased()); + item.setGroceryList(groceryList); + items.add(item); + } + } + groceryList.setItems(items); + + return toDetail(repository.save(groceryList)); + } + + @Transactional + public void delete(UUID id) { + if (!repository.existsById(id)) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Grocery list not found: " + id); + } + repository.deleteById(id); + } + private GroceryListDetailDTO toDetail(GroceryList groceryList) { List items = groceryList.getItems().stream() .map(item -> new GroceryItemResponseDTO( item.getId(), @@ -48,4 +100,15 @@ public GroceryListDetailDTO getById(UUID id) { return new GroceryListDetailDTO(groceryList.getId(), groceryList.getName(), groceryList.getCreatedAt(), items); } + + private GroceryCategory parseCategory(String category) { + if (category == null || category.isBlank()) { + return GroceryCategory.OTHER; + } + try { + return GroceryCategory.valueOf(category.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown grocery category: " + category); + } + } } From e81b4ec0532975fc3dee60d14d924851e2cd6c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20P=C3=BCschel?= Date: Fri, 12 Jun 2026 17:20:49 +0200 Subject: [PATCH 2/2] Quickfix --- .../src/main/java/com/bytebite/server/entity/GroceryItem.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java index a8d1dbf..20cf3b8 100644 --- a/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java +++ b/server/grocery-service/src/main/java/com/bytebite/server/entity/GroceryItem.java @@ -1,6 +1,8 @@ package com.bytebite.server.entity; import jakarta.persistence.*; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLEnumJdbcType; import java.util.UUID; @Entity @@ -21,6 +23,7 @@ public class GroceryItem { private String unit; @Enumerated(EnumType.STRING) + @JdbcType(PostgreSQLEnumJdbcType.class) @Column(columnDefinition = "grocery_category", nullable = false) private GroceryCategory category;