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.
|
- 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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue