feat(shopping): add GET /v1/shopping-lists endpoint and planner-only guard
New week-based lookup endpoint with optional weekStart param (defaults to current week). Generate endpoint now enforced with @RequiresHouseholdRole. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
package com.recipeapp.shopping;
|
package com.recipeapp.shopping;
|
||||||
|
|
||||||
|
import com.recipeapp.common.RequiresHouseholdRole;
|
||||||
|
import com.recipeapp.common.ResourceNotFoundException;
|
||||||
import com.recipeapp.recipe.HouseholdResolver;
|
import com.recipeapp.recipe.HouseholdResolver;
|
||||||
import com.recipeapp.shopping.dto.*;
|
import com.recipeapp.shopping.dto.*;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -19,8 +22,21 @@ public class ShoppingListController {
|
|||||||
this.householdResolver = householdResolver;
|
this.householdResolver = householdResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/v1/shopping-lists")
|
||||||
|
public ShoppingListResponse getByWeekStart(
|
||||||
|
@RequestParam(required = false) LocalDate weekStart,
|
||||||
|
Principal principal) {
|
||||||
|
UUID householdId = householdResolver.resolve(principal.getName());
|
||||||
|
ShoppingListResponse response = shoppingService.getByWeekStart(householdId, weekStart);
|
||||||
|
if (response == null) {
|
||||||
|
throw new ResourceNotFoundException("No shopping list for this week");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/v1/week-plans/{id}/shopping-list")
|
@PostMapping("/v1/week-plans/{id}/shopping-list")
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
@RequiresHouseholdRole("planner")
|
||||||
public ShoppingListResponse generateFromPlan(@PathVariable UUID id, Principal principal) {
|
public ShoppingListResponse generateFromPlan(@PathVariable UUID id, Principal principal) {
|
||||||
UUID householdId = householdResolver.resolve(principal.getName());
|
UUID householdId = householdResolver.resolve(principal.getName());
|
||||||
return shoppingService.generateFromPlan(householdId, id);
|
return shoppingService.generateFromPlan(householdId, id);
|
||||||
|
|||||||
@@ -49,6 +49,32 @@ class ShoppingListControllerTest {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByWeekStartShouldReturn200() throws Exception {
|
||||||
|
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 3, List.of());
|
||||||
|
|
||||||
|
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
|
||||||
|
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(response);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/v1/shopping-lists")
|
||||||
|
.param("weekStart", "2026-04-06")
|
||||||
|
.principal(() -> "sarah@example.com"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.id").value(LIST_ID.toString()))
|
||||||
|
.andExpect(jsonPath("$.filteredStaplesCount").value(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByWeekStartShouldReturn404WhenNoListExists() throws Exception {
|
||||||
|
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
|
||||||
|
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(null);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/v1/shopping-lists")
|
||||||
|
.param("weekStart", "2026-04-06")
|
||||||
|
.principal(() -> "sarah@example.com"))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generateFromPlanShouldReturn201() throws Exception {
|
void generateFromPlanShouldReturn201() throws Exception {
|
||||||
var recipeId = UUID.randomUUID();
|
var recipeId = UUID.randomUUID();
|
||||||
|
|||||||
Reference in New Issue
Block a user