import { z } from 'zod'; /** * Runtime mirrors of the TypeScript interfaces in `src/types/index.ts`. * * A TS interface is a compile-time-only promise: nothing stops the Django * side from renaming a field, changing its nullability, or dropping a facet, * and the frontend would only find out at runtime, in production, as a blank * screen or a silently-wrong render. These schemas are validated against the * real API response in `search-response.contract.test.ts`, so that class of * drift fails a test instead. */ export const FacetBucketSchema = z.object({ key: z.string(), count: z.number().int().nonnegative(), }); export const FACET_NAMES = [ 'skills', 'job_title_role', 'seniority', 'country', 'industry', 'company', ] as const; export const SearchHitSchema = z.object({ id: z.number(), score: z.number(), full_name: z.string(), headline: z.string(), job_title: z.string(), job_title_role: z.string(), seniority_levels: z.array(z.string()), company: z.string(), industry: z.string(), location_name: z.string(), location_country: z.string(), years_experience: z.number().nullable(), linkedin_url: z.string(), skills: z.array(z.string()), highlight: z.record(z.string(), z.array(z.string())), }); export const SearchResponseSchema = z.object({ count: z.number().int().nonnegative(), page: z.number().int().positive(), page_size: z.number().int().positive(), total_pages: z.number().int().nonnegative(), took_ms: z.number().int().nonnegative(), backend: z.string(), results: z.array(SearchHitSchema), facets: z.record(z.enum(FACET_NAMES), z.array(FacetBucketSchema)), }); export const UserSchema = z.object({ id: z.number(), email: z.string(), full_name: z.string(), role: z.enum(['admin', 'analyst', 'viewer']), can_view_pii: z.boolean(), date_joined: z.string(), }); export const TokenPairSchema = z.object({ access: z.string(), refresh: z.string(), user: UserSchema, }); export const ApiErrorSchema = z.object({ error: z.object({ code: z.string(), message: z.string(), details: z.unknown().optional(), request_id: z.string(), }), });