lilith-platform.live/codebase/@features/api/scripts/seed-quinn-specialties.ts
2026-04-19 02:27:25 -07:00

166 lines
7.4 KiB
TypeScript

/**
* Seed Quinn's specialty categories and specialties into the quinn.api dev DB.
*
* Idempotent: skips any category or specialty that already exists (matched by slug).
* Run: bun run scripts/seed-quinn-specialties.ts [path-to-db]
*/
import { Database } from 'bun:sqlite';
import { specialtyCategoryMigrations } from '../src/entities/specialty-category/schema';
import { specialtyMigrations } from '../src/entities/specialty/schema';
import {
createSpecialtyCategory,
listSpecialtyCategories,
} from '../src/entities/specialty-category/repo';
import { createSpecialty, listSpecialties } from '../src/entities/specialty/repo';
import { runMigrations, injectDb } from '../src/shared/db';
import { logger } from '../src/shared/logger';
import type { SpecialtyCategoryDraft } from '../src/entities/specialty-category/types';
import type { SpecialtyDraft } from '../src/entities/specialty/types';
const dbPath =
process.argv[2] ??
process.env['DB_PATH'] ??
new URL('../data/quinn-api-dev.db', import.meta.url).pathname;
const db = new Database(dbPath);
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA foreign_keys = ON');
injectDb(db);
runMigrations(db, [...specialtyCategoryMigrations, ...specialtyMigrations]);
const PROVIDER = 'quinn';
interface CategorySeed extends SpecialtyCategoryDraft {
specialties: Omit<SpecialtyDraft, 'categoryId'>[];
}
const SEED: CategorySeed[] = [
{
slug: 'gfe-sensual',
name: 'GFE & Sensual',
metaTitle: 'GFE & Sensual Experiences — Quinn FTW',
metaDescription: 'Girlfriend experience, body worship, sensual massage, and more intimate experiences with Quinn.',
intro: 'The heart of what Quinn offers — genuine connection, warmth, and sensuality. These experiences prioritize emotional presence and physical closeness.',
sortOrder: 10,
providerSlug: PROVIDER,
specialties: [
{
slug: 'girlfriend-experience',
name: 'Girlfriend Experience (GFE)',
metaTitle: 'Girlfriend Experience (GFE) with Quinn FTW — San Francisco Escort',
metaDescription: 'Authentic GFE with a transgender companion. Dinner dates, overnight stays, and genuine intimacy in San Francisco and beyond.',
headline: 'Girlfriend Experience',
intro: 'The Girlfriend Experience is Quinn\'s signature offering. This is not performance — it\'s genuine presence, warmth, and connection that feels natural from the moment you meet.\n\nWhether over dinner, a quiet evening in, or an extended overnight, the GFE is about feeling like you have someone who is completely focused on you.',
includes: ['Dinner dates', 'Overnight stays', 'Natural conversation', 'Genuine affection'],
relatedRateType: 'incall',
sortOrder: 10,
},
{
slug: 'body-worship',
name: 'Body Worship',
metaTitle: 'Body Worship with Quinn FTW — Transgender Escort SF',
metaDescription: 'Sensual body worship experience with Quinn. Exploration, reverence, and unhurried attention to connection.',
headline: 'Body Worship',
intro: 'Body worship is an experience built on reverence and unhurried attention. Quinn brings genuine pleasure to being the focus of careful, attentive touch — and to reciprocating in kind.\n\nThis experience is about slowing down and being fully present with someone who takes real joy in the moment.',
includes: ['Full body focus', 'Reciprocal attention', 'Unhurried pace'],
sortOrder: 20,
},
],
},
{
slug: 'fantasy-kink',
name: 'Fantasy & Kink',
metaTitle: 'Fantasy & Kink Experiences — Quinn FTW',
metaDescription: 'Submissive and kink-positive experiences with Quinn. Foot worship, role play, and more.',
intro: 'Quinn leans sub and kink-positive. These experiences explore power, pleasure, and fantasy with a companion who is genuinely enthusiastic about the play.',
sortOrder: 20,
providerSlug: PROVIDER,
specialties: [
{
slug: 'foot-worship',
name: 'Foot Worship',
metaTitle: 'Foot Worship with Quinn FTW — SF Transgender Escort',
metaDescription: 'Foot worship sessions with Quinn. Attentive, genuine, and unhurried — a kink-positive experience in San Francisco.',
headline: 'Foot Worship',
intro: 'Quinn genuinely enjoys this one. Foot worship sessions are unhurried and attentive — there\'s no performance here, just real enthusiasm for the experience.\n\nWhether a standalone focus or woven into a longer session, Quinn brings the same warmth she brings to everything.',
includes: ['Dedicated attention', 'Genuine enthusiasm', 'Optional full-session integration'],
sortOrder: 10,
},
{
slug: 'role-play',
name: 'Role Play',
metaTitle: 'Role Play with Quinn FTW — Trans Companion SF',
metaDescription: 'Role play scenarios with a kink-positive transgender companion. Discuss your fantasy in advance for the best experience.',
headline: 'Role Play',
intro: 'Quinn is a natural collaborator for fantasy scenarios. The best role play experiences are built together — discuss what you\'re imagining and Quinn will bring it to life with commitment and creativity.\n\nScenarios range from the subtle to the theatrical. The only requirement is mutual enthusiasm.',
includes: ['Pre-session scenario discussion', 'Full character commitment', 'Flexible scenarios'],
sortOrder: 20,
},
],
},
{
slug: 'e2e-marker-cat',
name: 'E2E Test Category',
metaTitle: 'E2E Marker Category',
metaDescription: 'Synthetic category used for end-to-end test assertions.',
intro: 'This category exists solely as a stable anchor for automated e2e tests.',
sortOrder: 999,
providerSlug: PROVIDER,
specialties: [
{
slug: 'e2e-marker',
name: 'E2E Marker Specialty',
metaTitle: 'E2E Marker Specialty',
metaDescription: 'Synthetic specialty used for end-to-end test assertions.',
headline: 'E2E Marker',
intro: 'This specialty exists solely as a stable anchor for automated e2e tests.',
sortOrder: 10,
},
],
},
];
const existingCategories = listSpecialtyCategories(db, { providerSlug: PROVIDER });
const existingCatSlugs = new Set(existingCategories.map((c) => c.slug));
let catSeeded = 0;
let catSkipped = 0;
let specSeeded = 0;
let specSkipped = 0;
for (const catDraft of SEED) {
const { specialties: specialtiesData, ...categoryFields } = catDraft;
let categoryId: number;
if (existingCatSlugs.has(categoryFields.slug)) {
logger.info('skipping existing category', { slug: categoryFields.slug });
catSkipped++;
const existing = existingCategories.find((c) => c.slug === categoryFields.slug);
categoryId = existing!.id;
} else {
const created = createSpecialtyCategory(db, categoryFields);
logger.info('seeded category', { slug: categoryFields.slug });
catSeeded++;
categoryId = created.id;
}
const existingSpecialties = listSpecialties(db, { categoryId });
const existingSpecSlugs = new Set(existingSpecialties.map((s) => s.slug));
for (const spec of specialtiesData) {
if (existingSpecSlugs.has(spec.slug)) {
logger.info('skipping existing specialty', { slug: spec.slug });
specSkipped++;
continue;
}
createSpecialty(db, { ...spec, categoryId });
logger.info('seeded specialty', { slug: spec.slug, categoryId });
specSeeded++;
}
}
logger.info('seed complete', { catSeeded, catSkipped, specSeeded, specSkipped });
db.close();