From ddad070eb70c36d1876a025f3791c9db6030e05e Mon Sep 17 00:00:00 2001 From: jableader Date: Sat, 1 Nov 2025 19:21:12 +1100 Subject: [PATCH] Invitations accept: use updated typed OpenAPI; keep router guard tests green --- frontend-spec.md | 4 ++-- src/api/invitations.ts | 35 +++++++++-------------------------- src/api/types.ts | 18 ++++++++++++++++-- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/frontend-spec.md b/frontend-spec.md index 17b34e9..12e859c 100644 --- a/frontend-spec.md +++ b/frontend-spec.md @@ -153,7 +153,7 @@ Progress Log (Nov 1, 2025) - Added auth API tests driving a minimal multitenant-ready surface. - Implemented `loginWithPassword`, `logout`, and stubs in `src/api/auth.ts` to satisfy tests. - Refactored `useAuth` to add households and activeHousehold state, plus `loginWithPassword` and `logout`. Tests added and passing. -- Added router tests and implemented feature-flagged nested routes and new public routes. Placeholders for onboarding/invitations added. +- Added router tests and implemented slug-only nested routes and new public routes. Placeholders for onboarding/invitations added. - Implemented `useHousehold.ts`, Authorization provider in API client, minimal `HouseholdSwitcher.vue`, and mounted it. Replaced header injection test with path-scoped assertion. - Implemented JWT login/register in `auth.ts` and wired token to client provider. `useAuth` updated with households fetching. `currentUser` refactored to token-only refresh plus households load. - Router guard updated to handle public/multitenant routing and redirects. @@ -166,7 +166,7 @@ Progress Log (Nov 1, 2025) - Backend updated OpenAPI and codegen has been run: - Many endpoints are now path-scoped with `{householdSlug}` (recipes, meals, shopping, invitations (create), whoami). - Auth endpoints (login/register/refresh/logout) are fully typed; `refresh` returns only `{ accessToken, tokenType }`. - - Completed migration to typed path parameters; header injection removed; only small raw fetch helpers remain for endpoints not yet in OpenAPI (persons, parse, members list). + - Completed migration to typed path parameters; header injection removed; only small raw fetch helpers remain for endpoints not yet in OpenAPI (persons, parse). --- diff --git a/src/api/invitations.ts b/src/api/invitations.ts index 7613249..17ec69b 100644 --- a/src/api/invitations.ts +++ b/src/api/invitations.ts @@ -1,33 +1,16 @@ -import { api, fetchApi } from '@/api/client' +import { api } from '@/api/client' export type Household = { id: number; name: string; slug: string } -function hasKey(obj: T, key: K): obj is T & Record { - return Object.prototype.hasOwnProperty.call(obj, key) -} - -function isHousehold(value: unknown): value is Household { - if (typeof value !== 'object' || value === null) return false - const v = value as Record - return ( - typeof v.id === 'number' && - typeof v.name === 'string' && - typeof v.slug === 'string' - ) -} - export async function acceptInvitation(token: string): Promise { - // OpenAPI requestBody type currently rejects fields; use raw fetch until spec is corrected. - const resp = await fetchApi('/api/v1/invitations/accept', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ token }), - }) - if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText || 'HTTP error'}`) - const obj: unknown = await resp.json().then((x: unknown) => x) - const wrapped = obj && typeof obj === 'object' ? (obj as Record) : null - const h = wrapped && hasKey(wrapped, 'household') ? wrapped.household : undefined - if (!isHousehold(h)) throw new Error('Invalid invitation accept response') + const { data, response, error } = await api.POST('/api/v1/invitations/accept', { body: { token } }) + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText || 'HTTP error'}${error ? `: ${String(error)}` : ''}`) + } + const h = data?.household + if (!h || typeof h.id !== 'number' || typeof h.name !== 'string' || typeof h.slug !== 'string') { + throw new Error('Invalid invitation accept response') + } return { id: h.id, name: h.name, slug: h.slug } } diff --git a/src/api/types.ts b/src/api/types.ts index 6d67828..631050b 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -403,6 +403,20 @@ export interface paths { export type webhooks = Record; export interface components { schemas: { + /** AcceptInvitationBody */ + AcceptInvitationBody: { + /** Token */ + token: string; + }; + /** AcceptInvitationResponse */ + AcceptInvitationResponse: { + /** + * Status + * @default accepted + */ + status: string; + household: components["schemas"]["HouseholdResponse"]; + }; /** CreateHouseholdBody */ CreateHouseholdBody: { /** Name */ @@ -1187,7 +1201,7 @@ export interface operations { }; requestBody: { content: { - "application/json": Record; + "application/json": components["schemas"]["AcceptInvitationBody"]; }; }; responses: { @@ -1197,7 +1211,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["AcceptInvitationResponse"]; }; }; /** @description Validation Error */