Invitations accept: use updated typed OpenAPI; keep router guard tests green

This commit is contained in:
jableader 2025-11-01 19:21:12 +11:00
parent 6ec15debef
commit ddad070eb7
3 changed files with 27 additions and 30 deletions

View file

@ -153,7 +153,7 @@ Progress Log (Nov 1, 2025)
- Added auth API tests driving a minimal multitenant-ready surface. - Added auth API tests driving a minimal multitenant-ready surface.
- Implemented `loginWithPassword`, `logout`, and stubs in `src/api/auth.ts` to satisfy tests. - 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. - 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 `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. - 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. - 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: - Backend updated OpenAPI and codegen has been run:
- Many endpoints are now path-scoped with `{householdSlug}` (recipes, meals, shopping, invitations (create), whoami). - 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 }`. - 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).
--- ---

View file

@ -1,33 +1,16 @@
import { api, fetchApi } from '@/api/client' import { api } from '@/api/client'
export type Household = { id: number; name: string; slug: string } export type Household = { id: number; name: string; slug: string }
function hasKey<T extends object, K extends PropertyKey>(obj: T, key: K): obj is T & Record<K, unknown> {
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<string, unknown>
return (
typeof v.id === 'number' &&
typeof v.name === 'string' &&
typeof v.slug === 'string'
)
}
export async function acceptInvitation(token: string): Promise<Household> { export async function acceptInvitation(token: string): Promise<Household> {
// OpenAPI requestBody type currently rejects fields; use raw fetch until spec is corrected. const { data, response, error } = await api.POST('/api/v1/invitations/accept', { body: { token } })
const resp = await fetchApi('/api/v1/invitations/accept', { if (!response.ok) {
method: 'POST', throw new Error(`${response.status} ${response.statusText || 'HTTP error'}${error ? `: ${String(error)}` : ''}`)
headers: { 'content-type': 'application/json' }, }
body: JSON.stringify({ token }), const h = data?.household
}) if (!h || typeof h.id !== 'number' || typeof h.name !== 'string' || typeof h.slug !== 'string') {
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText || 'HTTP error'}`) throw new Error('Invalid invitation accept response')
const obj: unknown = await resp.json().then((x: unknown) => x) }
const wrapped = obj && typeof obj === 'object' ? (obj as Record<string, unknown>) : null
const h = wrapped && hasKey(wrapped, 'household') ? wrapped.household : undefined
if (!isHousehold(h)) throw new Error('Invalid invitation accept response')
return { id: h.id, name: h.name, slug: h.slug } return { id: h.id, name: h.name, slug: h.slug }
} }

View file

@ -403,6 +403,20 @@ export interface paths {
export type webhooks = Record<string, never>; export type webhooks = Record<string, never>;
export interface components { export interface components {
schemas: { schemas: {
/** AcceptInvitationBody */
AcceptInvitationBody: {
/** Token */
token: string;
};
/** AcceptInvitationResponse */
AcceptInvitationResponse: {
/**
* Status
* @default accepted
*/
status: string;
household: components["schemas"]["HouseholdResponse"];
};
/** CreateHouseholdBody */ /** CreateHouseholdBody */
CreateHouseholdBody: { CreateHouseholdBody: {
/** Name */ /** Name */
@ -1187,7 +1201,7 @@ export interface operations {
}; };
requestBody: { requestBody: {
content: { content: {
"application/json": Record<string, never>; "application/json": components["schemas"]["AcceptInvitationBody"];
}; };
}; };
responses: { responses: {
@ -1197,7 +1211,7 @@ export interface operations {
[name: string]: unknown; [name: string]: unknown;
}; };
content: { content: {
"application/json": unknown; "application/json": components["schemas"]["AcceptInvitationResponse"];
}; };
}; };
/** @description Validation Error */ /** @description Validation Error */