166 lines
7.4 KiB
TypeScript
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();
|