MySHopping spec updates

This commit is contained in:
jableader 2025-11-01 18:17:46 +11:00
parent 53f8909924
commit 290ad06f0c
2 changed files with 96 additions and 8 deletions

View file

@ -128,7 +128,7 @@ What exists now
- `src/api/client.ts`: Authorization header provider only; household header injection removed. - `src/api/client.ts`: Authorization header provider only; household header injection removed.
- Domain & UI - Domain & UI
- Member arrays (`chefs`, `consumers`, `cleanup`) normalized to MemberRef `{ id, displayName }` with decoders handling legacy shapes gracefully. - Member arrays (`chefs`, `consumers`, `cleanup`) normalized to MemberRef `{ id, displayName }` with decoders handling legacy shapes gracefully.
- Invitation Accept flow implemented; Household Settings supports sending invitations and listing members (members via temporary raw fetch path-scoped endpoint until typed spec lands). - Invitation Accept flow implemented; Household Settings supports sending invitations and listing members using typed endpoints.
Status of tests and typing Status of tests and typing
- All tests pass: 27 files, 48 tests. - All tests pass: 27 files, 48 tests.
@ -201,13 +201,21 @@ API client
SDK SDK
- `src/api/sdk.ts`: - `src/api/sdk.ts`:
- Recipes, meals, shopping fully path-scoped to households. Persons/parse remain via raw fetch until typed coverage. - Recipes, meals, shopping fully path-scoped to households.
- Newly typed endpoints (post-latest codegen):
- `GET /api/v1/households/{householdSlug}/shopping/current` — current aggregated list.
- `GET /api/v1/households/{householdSlug}/shopping/{list_id}` — purchased list by id.
- `POST /api/v1/households/{householdSlug}/shopping` — purchase items (storeName + items).
- `POST /api/v1/households/{householdSlug}/shopping/current/meals/me` — request a meal.
- `DELETE /api/v1/households/{householdSlug}/shopping/current/meals/{meal_id}` — unrequest a meal.
- `POST /api/v1/households/{householdSlug}/shopping/current/ingredients` — request an ingredient (by id) for shopping.
- Persons and parse endpoints are still raw until they are added to the OpenAPI.
UI UI
- Login Page: Refactored to show email/password form when multitenant flag is enabled; legacy person list retained otherwise. Link to Create Account added. - Login Page: Refactored to show email/password form when multitenant flag is enabled; legacy person list retained otherwise. Link to Create Account added.
- Add `HouseholdSwitcher.vue` to app chrome and wire with router. - Add `HouseholdSwitcher.vue` to app chrome and wire with router.
- Update components that navigate using string paths to use named routes with slug. - Update components that navigate using string paths to use named routes with slug.
- `src/views/HouseholdSettings.vue`: Invite members form wired to `sendInvitation(email)`. Members list rendered from `listMembers()`; guarded for backends that dont yet support the endpoint. - `src/views/HouseholdSettings.vue`: Invite members form wired to `sendInvitation(email)`. Members list rendered from `listMembers()` using the typed endpoint.
- Adjust pages that call SDK/API to pass `{ householdSlug }` path params once client services are migrated. - Adjust pages that call SDK/API to pass `{ householdSlug }` path params once client services are migrated.
Tests Tests
@ -223,9 +231,10 @@ Tests
- Household scoping: use typed path params (`{ params: { path: { householdSlug } } }`); do not mutate path strings. - Household scoping: use typed path params (`{ params: { path: { householdSlug } } }`); do not mutate path strings.
- Auth refresh: returns `{ accessToken, tokenType }`. After refresh, call user/household endpoints to populate app state. Use `whoami` to validate the active routes slug when needed. - Auth refresh: returns `{ accessToken, tokenType }`. After refresh, call user/household endpoints to populate app state. Use `whoami` to validate the active routes slug when needed.
- Cleanup/migration tasks: - Cleanup/migration tasks:
1) Remove X-Household-Slug header injection in `api/client.ts` and refactor services to accept `householdSlug` via typed params. 1) Remove X-Household-Slug header injection in `api/client.ts` and refactor services to accept `householdSlug` via typed params. (Completed)
2) Replace temporary raw fetch for invitations (both accept and create) and members listing with generated typed endpoints. Invitations are now typed; members listing remains pending. 2) Replace temporary raw fetch calls with generated typed endpoints where available: invitations and members listing are now typed; migrate usages. Persons and parse remain raw for now.
3) Legacy identity: continue using `User` as the primary identity. Keep `Person` in meal-related UIs where the backend requires it, but remove Person as the login/identity concept. 3) Integrate the new `POST /shopping/current/ingredients` endpoint into the SDK (`requestIngredient(ingredientId: number)`) and expose via `useShopping`; refactor `MyShoppingPage.vue` accordingly and remove legacy stubs.
4) Legacy identity: continue using `User` as the primary identity. Keep `Person` in meal-related UIs where required by backend, but remove Person as the login/identity concept.
--- ---
@ -252,6 +261,21 @@ Google OAuth is planned next.
- Add a user profile endpoint and load it post-refresh to populate `currentUser()` with real data instead of a placeholder. - Add a user profile endpoint and load it post-refresh to populate `currentUser()` with real data instead of a placeholder.
- Replace temporary raw fetch calls (persons, parse, members listing) with typed endpoints when available. - Replace temporary raw fetch calls (persons, parse, members listing) with typed endpoints when available.
- Implement Google OAuth login and account creation flows. - Implement Google OAuth login and account creation flows.
- Review `MyShoppingPage` usage and remove legacy `getMyShoppingList/saveMyShoppingList` stubs when UI is refactored or removed. - MyShopping ad-hoc item requests (planned):
- The v2 OpenAPI spec is being updated to include endpoints for creating, listing, updating, and deleting ad-hoc requested items on the current shopping list (independent of meals), scoped under `/api/v1/households/{householdSlug}/shopping/current/items`.
- Once available, add typed SDK methods:
- `createRequestedItem(input: { name: string; line?: string; quantity?: number; unit?: Unit })`
- `updateRequestedItem(id: number, patch: { name?: string; line?: string; quantity?: number; unit?: Unit })`
- `deleteRequestedItem(id: number)`
- `listRequestedItems()` (if provided separately; otherwise rely on `getCurrentShoppingList()`)
- Refactor `src/components/shopping/MyShoppingPage.vue` to use the above methods and remove legacy stubs `getMyShoppingList/saveMyShoppingList` from the SDK and `useShopping` composable.
- Update tests to drive TDD:
- MSW handlers for the new endpoints with path-scoped URLs and Authorization.
- Component tests to add, edit, and delete an ad-hoc requested item and verify it appears under outstanding items in `getCurrentShoppingList()`.
- Acceptance criteria:
- Users can add an item without associating it to a meal.
- Users can edit and delete such items.
- All calls use typed path-scoped endpoints; no header injection; Authorization still via provider.
- Note: No temporary measures required; proceed directly once backend ships endpoints.
- Remove the legacy username login shim (`login(username: string)`) and any fallback UI; standardize on email/password (and Google) only. - Remove the legacy username login shim (`login(username: string)`) and any fallback UI; standardize on email/password (and Google) only.
- Rollout flag: default `VUE_APP_MULTITENANT_ENABLED` to true across environments and plan removal of legacy flat routes and related tests once stable. - Rollout flag: default `VUE_APP_MULTITENANT_ENABLED` to true across environments and plan removal of legacy flat routes and related tests once stable.

View file

@ -365,6 +365,23 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/v1/households/{householdSlug}/shopping/current/ingredients": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Request an ingredient for shopping (scoped) */
post: operations["requestIngredientV2"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/healthz": { "/healthz": {
parameters: { parameters: {
query?: never; query?: never;
@ -481,6 +498,11 @@ export interface components {
mealId?: number | null; mealId?: number | null;
product?: components["schemas"]["Product"] | null; product?: components["schemas"]["Product"] | null;
}; };
/** IngredientIdWrapper */
IngredientIdWrapper: {
/** Ingredientid */
ingredientId: number;
};
/** IngredientPurchaseItemIn */ /** IngredientPurchaseItemIn */
IngredientPurchaseItemIn: { IngredientPurchaseItemIn: {
/** Ingredientid */ /** Ingredientid */
@ -822,6 +844,12 @@ export interface components {
imageUrls: string[]; imageUrls: string[];
/** Ingredients */ /** Ingredients */
ingredients: components["schemas"]["Ingredient"][]; ingredients: components["schemas"]["Ingredient"][];
/** Createdbyid */
createdById: number;
createdBy?: components["schemas"]["MemberRef"] | null;
/** Hiddenbyid */
hiddenById?: number | null;
hiddenBy?: components["schemas"]["MemberRef"] | null;
}; };
/** RefreshResponse */ /** RefreshResponse */
RefreshResponse: { RefreshResponse: {
@ -882,7 +910,7 @@ export interface components {
storeName: "woolworths" | "coles" | "home"; storeName: "woolworths" | "coles" | "home";
/** Purchasedbyid */ /** Purchasedbyid */
purchasedById: number; purchasedById: number;
purchasedBy?: components["schemas"]["Person"] | null; purchasedBy?: components["schemas"]["MemberRef"] | null;
/** Items */ /** Items */
items: components["schemas"]["ListIngredientItem"][]; items: components["schemas"]["ListIngredientItem"][];
}; };
@ -1812,6 +1840,42 @@ export interface operations {
}; };
}; };
}; };
requestIngredientV2: {
parameters: {
query?: never;
header?: never;
path: {
householdSlug: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["IngredientIdWrapper"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ListIngredientItem"];
};
};
403: components["responses"]["Problem403"];
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
healthz_healthz_get: { healthz_healthz_get: {
parameters: { parameters: {
query?: never; query?: never;