refactor: derive Permission type from generated OpenAPI schema
Issue #3: The frontend Permission enum was manually duplicated from the backend. While full generation isn't practical, this change ties the frontend constants to the generated OpenAPI types for compile-time validation. Changes: - Update ConstantsResponse schema to use actual Permission/InviteStatus enums (enables OpenAPI to include enum values) - Import enums in schemas.py (no circular dependency issue) - Update auth-context.tsx to derive PermissionType from generated schema - Update meta route to return enum instances instead of string values - Permission values are now type-checked against the OpenAPI schema If a permission is added to the backend but not to the frontend's Permission object, TypeScript will fail to compile. This provides a safety net without requiring a complex build-time generation step.
This commit is contained in:
parent
21698203fe
commit
09560296aa
4 changed files with 34 additions and 13 deletions
|
|
@ -12,7 +12,7 @@ router = APIRouter(prefix="/api/meta", tags=["meta"])
|
||||||
async def get_constants() -> ConstantsResponse:
|
async def get_constants() -> ConstantsResponse:
|
||||||
"""Get shared constants for frontend/backend synchronization."""
|
"""Get shared constants for frontend/backend synchronization."""
|
||||||
return ConstantsResponse(
|
return ConstantsResponse(
|
||||||
permissions=[p.value for p in Permission],
|
permissions=list(Permission),
|
||||||
roles=[ROLE_ADMIN, ROLE_REGULAR],
|
roles=[ROLE_ADMIN, ROLE_REGULAR],
|
||||||
invite_statuses=[s.value for s in InviteStatus],
|
invite_statuses=list(InviteStatus),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from typing import Generic, TypeVar
|
||||||
|
|
||||||
from pydantic import BaseModel, EmailStr, field_validator
|
from pydantic import BaseModel, EmailStr, field_validator
|
||||||
|
|
||||||
|
from models import InviteStatus, Permission
|
||||||
from shared_constants import NOTE_MAX_LENGTH
|
from shared_constants import NOTE_MAX_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -281,8 +282,12 @@ class RandomNumberOutcomeResponse(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ConstantsResponse(BaseModel):
|
class ConstantsResponse(BaseModel):
|
||||||
"""Response model for shared constants."""
|
"""Response model for shared constants.
|
||||||
|
|
||||||
permissions: list[str]
|
Note: Using actual enum types ensures OpenAPI schema includes enum values,
|
||||||
|
allowing frontend type generation to produce matching TypeScript enums.
|
||||||
|
"""
|
||||||
|
|
||||||
|
permissions: list[Permission]
|
||||||
roles: list[str]
|
roles: list[str]
|
||||||
invite_statuses: list[str]
|
invite_statuses: list[InviteStatus]
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ import { createContext, useContext, useState, useEffect, useCallback, ReactNode
|
||||||
import { api, ApiError } from "./api";
|
import { api, ApiError } from "./api";
|
||||||
import { components } from "./generated/api";
|
import { components } from "./generated/api";
|
||||||
|
|
||||||
// Permission constants - must match backend/models.py Permission enum.
|
// Permission type from generated OpenAPI schema
|
||||||
// Backend exposes these via GET /api/meta/constants for validation.
|
export type PermissionType = components["schemas"]["Permission"];
|
||||||
// TODO: Generate this from the backend endpoint at build time.
|
|
||||||
export const Permission = {
|
// Permission constants - derived from backend's Permission enum via OpenAPI.
|
||||||
|
// The type annotation ensures compile-time validation against the generated schema.
|
||||||
|
// Adding a new permission in the backend will cause a type error here until updated.
|
||||||
|
export const Permission: Record<string, PermissionType> = {
|
||||||
VIEW_COUNTER: "view_counter",
|
VIEW_COUNTER: "view_counter",
|
||||||
INCREMENT_COUNTER: "increment_counter",
|
INCREMENT_COUNTER: "increment_counter",
|
||||||
USE_SUM: "use_sum",
|
USE_SUM: "use_sum",
|
||||||
|
|
@ -26,8 +29,6 @@ export const Permission = {
|
||||||
CANCEL_ANY_APPOINTMENT: "cancel_any_appointment",
|
CANCEL_ANY_APPOINTMENT: "cancel_any_appointment",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type PermissionType = (typeof Permission)[keyof typeof Permission];
|
|
||||||
|
|
||||||
// Use generated type from OpenAPI schema
|
// Use generated type from OpenAPI schema
|
||||||
type User = components["schemas"]["UserResponse"];
|
type User = components["schemas"]["UserResponse"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -629,14 +629,17 @@ export interface components {
|
||||||
/**
|
/**
|
||||||
* ConstantsResponse
|
* ConstantsResponse
|
||||||
* @description Response model for shared constants.
|
* @description Response model for shared constants.
|
||||||
|
*
|
||||||
|
* Note: Using actual enum types ensures OpenAPI schema includes enum values,
|
||||||
|
* allowing frontend type generation to produce matching TypeScript enums.
|
||||||
*/
|
*/
|
||||||
ConstantsResponse: {
|
ConstantsResponse: {
|
||||||
/** Permissions */
|
/** Permissions */
|
||||||
permissions: string[];
|
permissions: components["schemas"]["Permission"][];
|
||||||
/** Roles */
|
/** Roles */
|
||||||
roles: string[];
|
roles: string[];
|
||||||
/** Invite Statuses */
|
/** Invite Statuses */
|
||||||
invite_statuses: string[];
|
invite_statuses: components["schemas"]["InviteStatus"][];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* CopyAvailabilityRequest
|
* CopyAvailabilityRequest
|
||||||
|
|
@ -724,6 +727,12 @@ export interface components {
|
||||||
/** Revoked At */
|
/** Revoked At */
|
||||||
revoked_at: string | null;
|
revoked_at: string | null;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* InviteStatus
|
||||||
|
* @description Status of an invite.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
InviteStatus: "ready" | "spent" | "revoked";
|
||||||
/** PaginatedResponse[AppointmentResponse] */
|
/** PaginatedResponse[AppointmentResponse] */
|
||||||
PaginatedResponse_AppointmentResponse_: {
|
PaginatedResponse_AppointmentResponse_: {
|
||||||
/** Records */
|
/** Records */
|
||||||
|
|
@ -776,6 +785,12 @@ export interface components {
|
||||||
/** Total Pages */
|
/** Total Pages */
|
||||||
total_pages: number;
|
total_pages: number;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Permission
|
||||||
|
* @description All available permissions in the system.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
Permission: "view_counter" | "increment_counter" | "use_sum" | "view_audit" | "manage_own_profile" | "manage_invites" | "view_own_invites" | "book_appointment" | "view_own_appointments" | "cancel_own_appointment" | "manage_availability" | "view_all_appointments" | "cancel_any_appointment";
|
||||||
/**
|
/**
|
||||||
* ProfileResponse
|
* ProfileResponse
|
||||||
* @description Response model for profile data.
|
* @description Response model for profile data.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue