Invitations accept: use updated typed OpenAPI; keep router guard tests green
This commit is contained in:
parent
6ec15debef
commit
ddad070eb7
3 changed files with 27 additions and 30 deletions
|
|
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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<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> {
|
||||
// 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<string, unknown>) : 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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -403,6 +403,20 @@ export interface paths {
|
|||
export type webhooks = Record<string, never>;
|
||||
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<string, never>;
|
||||
"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 */
|
||||
|
|
|
|||
Loading…
Reference in a new issue