Files
cyberyan_linkedin_search/frontend/contract/schemas.ts
T

80 lines
2.1 KiB
TypeScript
Raw Normal View History

2026-09-04 12:52:21 +03:30
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(),
}),
});