feat(shopping): finalize GET /v1/shopping-list endpoint and regenerate OpenAPI types

Renamed endpoint to /v1/shopping-list to avoid Springdoc path conflict.
Added @RequiresHouseholdRole("planner") on generate. Regenerated
frontend OpenAPI schema with all new shopping list endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 18:49:08 +02:00
parent 16b70bd818
commit 92922533ac
6 changed files with 57 additions and 6 deletions

View File

@@ -22,7 +22,7 @@ public class ShoppingListController {
this.householdResolver = householdResolver;
}
@GetMapping("/v1/shopping-lists")
@GetMapping("/v1/shopping-list")
public ShoppingListResponse getByWeekStart(
@RequestParam(required = false) LocalDate weekStart,
Principal principal) {

View File

@@ -0,0 +1,4 @@
spring:
flyway:
locations: classpath:db/migration,classpath:db/seed
out-of-order: true

View File

@@ -1,2 +1,2 @@
ALTER TABLE shopping_list
ADD COLUMN generated_at timestamptz NOT NULL DEFAULT now();
ADD COLUMN IF NOT EXISTS generated_at timestamptz NOT NULL DEFAULT now();

View File

@@ -56,7 +56,7 @@ class ShoppingListControllerTest {
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(response);
mockMvc.perform(get("/v1/shopping-lists")
mockMvc.perform(get("/v1/shopping-list")
.param("weekStart", "2026-04-06")
.principal(() -> "sarah@example.com"))
.andExpect(status().isOk())
@@ -69,7 +69,7 @@ class ShoppingListControllerTest {
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(shoppingService.getByWeekStart(eq(HOUSEHOLD_ID), any())).thenReturn(null);
mockMvc.perform(get("/v1/shopping-lists")
mockMvc.perform(get("/v1/shopping-list")
.param("weekStart", "2026-04-06")
.principal(() -> "sarah@example.com"))
.andExpect(status().isNotFound());

File diff suppressed because one or more lines are too long

View File

@@ -452,6 +452,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/shopping-list": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getByWeekStart"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/ingredients": {
parameters: {
query?: never;
@@ -624,6 +640,11 @@ export interface components {
/** Format: uuid */
recipeId: string;
};
RecipeRef: {
/** Format: uuid */
id?: string;
name?: string;
};
ShoppingListItemResponse: {
/** Format: uuid */
id?: string;
@@ -636,13 +657,17 @@ export interface components {
isChecked?: boolean;
/** Format: uuid */
checkedBy?: string;
sourceRecipes?: string[];
sourceRecipes?: components["schemas"]["RecipeRef"][];
};
ShoppingListResponse: {
/** Format: uuid */
id?: string;
/** Format: uuid */
weekPlanId?: string;
/** Format: date-time */
generatedAt?: string;
/** Format: int32 */
filteredStaplesCount?: number;
items?: components["schemas"]["ShoppingListItemResponse"][];
};
TagCreateRequest: {
@@ -1902,6 +1927,28 @@ export interface operations {
};
};
};
getByWeekStart: {
parameters: {
query?: {
weekStart?: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["ShoppingListResponse"];
};
};
};
};
searchIngredients: {
parameters: {
query?: {