lilith-platform.live/codebase/@features/admin/backend-api/src/registry.ts
autocommit 7a2fb543ac feat(admin): Add new FMTY tier enum values to admin API registry
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-19 03:06:08 -07:00

348 lines
12 KiB
TypeScript

/**
* CMS Content Type Registry
*
* Single source of truth for all managed content types.
* Follows the WordPress post-type registration pattern.
*/
export type FieldType =
| 'text' // TEXT column — serialized as string | null
| 'number' // INTEGER or REAL column — serialized as number | null
| 'boolean' // INTEGER 0|1 column — serialized as boolean
| 'enum' // TEXT with allowed values — validated on write
| 'json-array' // TEXT column storing JSON.stringify(string[]) — serialized as string[]
| 'json-object'; // TEXT column storing JSON.stringify(obj) — serialized as Record<string,unknown>
export interface FieldDef {
type: FieldType;
nullable?: boolean; // if true, null is a valid value
enum?: readonly string[]; // required when type === 'enum'
default?: unknown; // used when field is absent from PUT body
}
export type ContentKind = 'singleton' | 'list' | 'hierarchical' | 'kv-store';
export interface ContentTypeDef {
kind: ContentKind;
table: string;
path: string; // URL path segment (e.g. 'identity', 'tour', 'rates')
sortable?: boolean; // list/hierarchical: has sort_order column
childTable?: string; // hierarchical: child table name
childForeignKey?: string; // hierarchical: FK column on child table
kvNamespaceCol?: string; // kv-store: namespace column name
kvKeyCol?: string; // kv-store: key column name
fields: Record<string, FieldDef>; // DB column name → field def (excludes id, sort_order, updated_at)
childFields?: Record<string, FieldDef>; // hierarchical child fields
}
// ---------------------------------------------------------------------------
// Registry
// ---------------------------------------------------------------------------
export const registry: Record<string, ContentTypeDef> = {
identity: {
kind: 'singleton',
table: 'identity',
path: 'identity',
fields: {
name: { type: 'text' },
pronouns: { type: 'text' },
gender: { type: 'text' },
location: { type: 'text' },
incall_city: { type: 'text', nullable: true },
tagline: { type: 'text' },
secondary_locations: { type: 'json-array', default: [] },
languages: { type: 'json-array', default: [] },
},
},
physical: {
kind: 'singleton',
table: 'physical',
path: 'physical',
fields: {
age: { type: 'text', nullable: true },
height: { type: 'text', nullable: true },
body_type: { type: 'text', nullable: true },
ethnicity: { type: 'text', nullable: true },
hair_color: { type: 'text', nullable: true },
hair_length:{ type: 'text', nullable: true },
eye_color: { type: 'text', nullable: true },
cup_size: { type: 'text', nullable: true },
additional: { type: 'json-object', default: {} },
},
},
contact: {
kind: 'singleton',
table: 'contact',
path: 'contact',
fields: {
phone: { type: 'text' },
whatsapp: { type: 'text', nullable: true },
email: { type: 'text', nullable: true },
instagram: { type: 'text', nullable: true },
twitter: { type: 'text', nullable: true },
threads: { type: 'text', nullable: true },
snapchat: { type: 'text', nullable: true },
youtube: { type: 'text', nullable: true },
onlyfans: { type: 'text', nullable: true },
transfans: { type: 'text', nullable: true },
fansly: { type: 'text', nullable: true },
loyalfans: { type: 'text', nullable: true },
fancentro: { type: 'text', nullable: true },
fantime: { type: 'text', nullable: true },
tryst: { type: 'text', nullable: true },
communication_note: { type: 'text' },
response_time: { type: 'text' },
availability_note: { type: 'text', nullable: true },
payment_methods: { type: 'json-array', default: [] },
},
},
about: {
kind: 'singleton',
table: 'about',
path: 'about',
fields: {
bio: { type: 'text' },
personality: { type: 'json-array', default: [] },
available_for: { type: 'json-array', default: [] },
available_to: { type: 'json-array', default: [] },
},
},
tour: {
kind: 'list',
table: 'tour_stops',
path: 'tour',
sortable: true,
fields: {
city: { type: 'text' },
state: { type: 'text' },
start_date: { type: 'text' },
end_date: { type: 'text' },
status: { type: 'enum', enum: ['confirmed', 'conditional', 'sold-out'] as const },
notes: { type: 'text', nullable: true },
},
},
destinations: {
kind: 'list',
table: 'destinations',
path: 'destinations',
sortable: true,
fields: {
slug: { type: 'text' },
city: { type: 'text' },
country: { type: 'text' },
region: { type: 'text', nullable: true },
fmty_tier: { type: 'enum', enum: ['west-coast', 'domestic', 'international'] as const },
meta_title: { type: 'text' },
meta_description: { type: 'text' },
headline: { type: 'text' },
intro: { type: 'text' },
linked_tour_stop: { type: 'boolean' },
experiences: { type: 'json-array', default: [] },
note: { type: 'text', nullable: true },
illustration_image: { type: 'text', nullable: true },
illustration_side: { type: 'enum', enum: ['left', 'right'] as const, default: 'right' },
illustration_height: { type: 'text', default: '360px' },
illustration_opacity: { type: 'number', default: 0.85 },
relationship: { type: 'enum', enum: ['homebase', 'metro-neighbor', 'tour-confirmed', 'tour-aspirational'] as const, default: 'tour-aspirational' },
super_region: { type: 'text', nullable: true },
neighborhoods: { type: 'json-array', default: [] },
local_incall_notes: { type: 'text', nullable: true },
driving_time_mins: { type: 'number', nullable: true },
affluence_tier: { type: 'enum', enum: ['premier', 'high', 'mid'] as const, default: 'high' },
},
},
specialties: {
kind: 'list',
table: 'specialties',
path: 'specialties',
sortable: true,
fields: {
category_slug: { type: 'text' },
category_name: { type: 'text' },
category_meta_title: { type: 'text' },
category_meta_description: { type: 'text' },
category_intro: { type: 'text' },
slug: { type: 'text' },
name: { type: 'text' },
meta_title: { type: 'text' },
meta_description: { type: 'text' },
headline: { type: 'text' },
intro: { type: 'text' },
includes: { type: 'json-array', nullable: true },
note: { type: 'text', nullable: true },
related_rate_type: { type: 'text', nullable: true },
illustration_image: { type: 'text', nullable: true },
illustration_side: { type: 'enum', enum: ['left', 'right'] as const, default: 'right' },
illustration_height: { type: 'text', default: '360px' },
illustration_opacity: { type: 'number', default: 0.85 },
},
},
'activity-menus': {
kind: 'list',
table: 'activity_menus',
path: 'activity-menus',
sortable: true,
fields: {
category: { type: 'text' },
items: { type: 'json-array', default: [] },
},
},
rates: {
kind: 'hierarchical',
table: 'rate_sections',
path: 'rates',
sortable: true,
childTable: 'rate_entries',
childForeignKey: 'section_id',
fields: {
section_type: { type: 'enum', enum: ['incall', 'outcall', 'addons', 'travel', 'touring', 'online'] as const },
title: { type: 'text' },
description: { type: 'text', nullable: true },
},
childFields: {
service: { type: 'text' },
duration: { type: 'text', nullable: true },
price: { type: 'number' },
price_max: { type: 'number', nullable: true },
description: { type: 'text', nullable: true },
notes: { type: 'text', nullable: true },
},
},
policies: {
kind: 'hierarchical',
table: 'policy_sections',
path: 'policies',
sortable: true,
childTable: 'policy_items',
childForeignKey: 'section_id',
fields: {
title: { type: 'text' },
},
childFields: {
label: { type: 'text' },
detail: { type: 'text' },
},
},
etiquette: {
kind: 'hierarchical',
table: 'etiquette_sections',
path: 'etiquette',
sortable: true,
childTable: 'etiquette_items',
childForeignKey: 'section_id',
fields: {
title: { type: 'text' },
},
childFields: {
label: { type: 'text' },
detail: { type: 'text', nullable: true },
cta_href: { type: 'text', nullable: true },
cta_text: { type: 'text', nullable: true },
},
},
'verified-profiles': {
kind: 'list',
table: 'verified_profiles',
path: 'verified-profiles',
sortable: true,
fields: {
platform: { type: 'text' },
href: { type: 'text' },
img_src: { type: 'text' },
img_alt: { type: 'text' },
embed_html: { type: 'text' },
description: { type: 'text' },
},
},
'site-text': {
kind: 'kv-store',
table: 'site_text',
path: 'site-text',
kvNamespaceCol: 'namespace',
kvKeyCol: 'key',
fields: {
value: { type: 'text' },
},
},
'link-values': {
kind: 'kv-store',
table: 'link_values',
path: 'link-values',
kvNamespaceCol: 'event_name',
kvKeyCol: 'label',
fields: {
score: { type: 'number' },
},
},
'hobby-terms': {
kind: 'list',
table: 'hobby_terms',
path: 'hobby-terms',
sortable: true,
fields: {
slug: { type: 'text' },
term: { type: 'text' },
aliases: { type: 'json-array', default: [] },
definition_md: { type: 'text' },
definition_html: { type: 'text' },
category: { type: 'enum', enum: ['service', 'logistics', 'kink', 'community', 'general', 'identity'] as const, default: 'general' },
offered: { type: 'enum', enum: ['yes', 'no', 'adjacent'] as const, default: 'no' },
safety_notes: { type: 'text', default: '' },
related_terms: { type: 'json-array', default: [] },
meta_title: { type: 'text', default: '' },
meta_description: { type: 'text', default: '' },
},
},
regions: {
kind: 'list',
table: 'regions',
path: 'regions',
sortable: true,
fields: {
slug: { type: 'text' },
display_name: { type: 'text' },
super_region: { type: 'text', nullable: true },
tier: { type: 'enum', enum: ['super-region', 'region', 'sub-region'] as const, default: 'region' },
intro_md: { type: 'text', default: '' },
intro_html: { type: 'text', default: '' },
meta_title: { type: 'text', default: '' },
meta_description: { type: 'text', default: '' },
sibling_regions: { type: 'json-array', default: [] },
},
},
'positioning-tags': {
kind: 'list',
table: 'positioning_tags',
path: 'positioning-tags',
sortable: true,
fields: {
slug: { type: 'text' },
display: { type: 'text' },
description_md: { type: 'text', default: '' },
description_html: { type: 'text', default: '' },
target_markets: { type: 'json-array', default: [] },
related_terms: { type: 'json-array', default: [] },
similar_to: { type: 'json-array', default: [] },
active: { type: 'boolean', default: true },
meta_title: { type: 'text', default: '' },
meta_description: { type: 'text', default: '' },
},
},
};