- Added AdultSearch and SkipTheGames (the remaining verified:true escort platforms from the credentials/platforms list). - All content (OnlyFans etc) and relevant escort platforms from the user's quinn-my credentials list are now in verified_profiles with site logo as placeholder banner. - Total 10 entries. - Updated seed for consistency. - Verified in public provider-config data.
244 lines
8.3 KiB
TypeScript
244 lines
8.3 KiB
TypeScript
/**
|
|
* Seed Quinn iter-16 data: roster-content, lore-sections, verified-profiles.
|
|
* Idempotent: skips records that already exist.
|
|
* Run: bun run scripts/seed-quinn-iter16.ts [path-to-db]
|
|
*/
|
|
|
|
import { Database } from 'bun:sqlite';
|
|
|
|
import { rosterContentMigrations } from '../src/entities/roster-content/schema';
|
|
import { loreSectionMigrations } from '../src/entities/lore-section/schema';
|
|
import { verifiedProfileMigrations } from '../src/entities/verified-profile/schema';
|
|
import { createRosterContent, listRosterContent } from '../src/entities/roster-content/repo';
|
|
import { createLoreSection, listLoreSections } from '../src/entities/lore-section/repo';
|
|
import { createVerifiedProfile, listVerifiedProfiles } from '../src/entities/verified-profile/repo';
|
|
import { runMigrations, injectDb } from '../src/shared/db';
|
|
import { logger } from '../src/shared/logger';
|
|
import type { RosterContentDraft } from '../src/entities/roster-content/types';
|
|
import type { LoreSectionDraft } from '../src/entities/lore-section/types';
|
|
import type { VerifiedProfileDraft } from '../src/entities/verified-profile/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, [...rosterContentMigrations, ...loreSectionMigrations, ...verifiedProfileMigrations]);
|
|
|
|
const PROVIDER = 'quinn';
|
|
|
|
// -- roster-content
|
|
|
|
const ROSTER_SEED: RosterContentDraft[] = [
|
|
{
|
|
trackSlug: 'e2e-marker-track',
|
|
name: 'E2E Marker Track',
|
|
metaTitle: 'E2E Marker Track | Quinn',
|
|
metaDescription: 'Synthetic roster track for end-to-end test assertions.',
|
|
heroLine: 'This hero line is an e2e marker.',
|
|
description: ['E2E marker description paragraph one.'],
|
|
whatToExpect: ['E2E marker what-to-expect item.'],
|
|
interestsConfig: [{ value: 'e2e-interest', label: 'E2E Interest' }],
|
|
sortOrder: 999,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
trackSlug: 'companion',
|
|
name: 'Companion',
|
|
metaTitle: 'Companion Experience | Quinn FTW',
|
|
metaDescription: 'The full girlfriend experience — attentive, present, genuinely enjoying your company.',
|
|
heroLine: 'Real presence. Real connection.',
|
|
description: [
|
|
'The companion track is for clients who want more than a transaction.',
|
|
'Quinn brings herself to every meeting — curious, playful, and present.',
|
|
],
|
|
whatToExpect: ['Unhurried conversation', 'Warm physical affection'],
|
|
interestsConfig: [
|
|
{ value: 'gfe', label: 'GFE' },
|
|
{ value: 'dinner-date', label: 'Dinner Date' },
|
|
],
|
|
sortOrder: 10,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
];
|
|
|
|
const existingRoster = listRosterContent(db, { providerSlug: PROVIDER });
|
|
const existingRosterSlugs = new Set(existingRoster.map((r) => r.trackSlug));
|
|
let rosterSeeded = 0, rosterSkipped = 0;
|
|
|
|
for (const draft of ROSTER_SEED) {
|
|
if (existingRosterSlugs.has(draft.trackSlug)) {
|
|
logger.info('skipping existing roster-content', { trackSlug: draft.trackSlug });
|
|
rosterSkipped++;
|
|
continue;
|
|
}
|
|
createRosterContent(db, draft);
|
|
logger.info('seeded roster-content', { trackSlug: draft.trackSlug });
|
|
rosterSeeded++;
|
|
}
|
|
|
|
// -- lore-sections
|
|
|
|
const LORE_SEED: LoreSectionDraft[] = [
|
|
{
|
|
sectionKey: 'e2e-marker-section',
|
|
title: 'E2E Marker Section',
|
|
body: 'This lore section is a synthetic e2e marker.',
|
|
sortOrder: 999,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
sectionKey: 'origin',
|
|
title: 'Origin of the Cult',
|
|
body: 'The Cult of Lilith began not as a religion but as a refusal — a refusal to be sorted, categorized, and filed away.',
|
|
sortOrder: 10,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
];
|
|
|
|
const existingLore = listLoreSections(db, { providerSlug: PROVIDER });
|
|
const existingLoreKeys = new Set(existingLore.map((l) => l.sectionKey));
|
|
let loreSeeded = 0, loreSkipped = 0;
|
|
|
|
for (const draft of LORE_SEED) {
|
|
if (existingLoreKeys.has(draft.sectionKey)) {
|
|
logger.info('skipping existing lore-section', { sectionKey: draft.sectionKey });
|
|
loreSkipped++;
|
|
continue;
|
|
}
|
|
createLoreSection(db, draft);
|
|
logger.info('seeded lore-section', { sectionKey: draft.sectionKey });
|
|
loreSeeded++;
|
|
}
|
|
|
|
// -- verified-profiles
|
|
|
|
const VERIFIED_SEED: VerifiedProfileDraft[] = [
|
|
{
|
|
platform: 'e2e-marker-platform',
|
|
href: 'https://example.com/e2e-marker',
|
|
imgSrc: '/badges/e2e-marker.svg',
|
|
imgAlt: 'E2E Marker Platform verified badge',
|
|
description: 'Synthetic verified profile for end-to-end test assertions.',
|
|
sortOrder: 999,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'Tryst',
|
|
href: 'https://tryst.link/escort/transquinnftw',
|
|
imgSrc: 'https://tryst.link/embed/banner/transquinnftw.jpg',
|
|
imgAlt: "Quinn's Tryst.link profile",
|
|
description: 'Quinn is a verified independent companion on Tryst.link — view her full verified profile, rates, and availability.',
|
|
sortOrder: 10,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'TS4Rent',
|
|
href: 'https://ts4rent.eu/escort/TransQuinnFTW',
|
|
imgSrc: 'https://transquinnftw.com/photos/promo/denver-streak.jpg',
|
|
imgAlt: 'TS4Rent profile photo for TransQuinnFTW',
|
|
description: 'Picture-verified profile on TS4Rent — the trusted directory for trans companions. Full details, rates, and availability.',
|
|
sortOrder: 20,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'TSEscorts',
|
|
href: 'https://tsescorts.com/escorts/transquinnftw',
|
|
imgSrc: 'https://transquinnftw.com/photos/promo/denver-streak.jpg',
|
|
imgAlt: 'TSEscorts profile for transquinnftw',
|
|
description: 'Verified profile on TSEscorts directory. Stats-forward, professional listing with current tour dates.',
|
|
sortOrder: 30,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'AdultLook',
|
|
href: 'https://adultlook.com/escorts/3373548',
|
|
imgSrc: 'https://transquinnftw.com/photos/promo/denver-streak.jpg',
|
|
imgAlt: 'AdultLook verified escort profile',
|
|
description: 'Verified on AdultLook — review-centric directory. Read reviews and see full profile details.',
|
|
sortOrder: 40,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'OnlyFans',
|
|
href: 'https://onlyfans.com/transquinnftw',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Exclusive photos, videos, and personal content on OnlyFans.',
|
|
sortOrder: 50,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'Fansly',
|
|
href: 'https://fansly.com/transquinnftw',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Premium content and free trial on Fansly.',
|
|
sortOrder: 60,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'ManyVids',
|
|
href: 'https://manyvids.com/transquinnftw',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Clip store and custom videos on ManyVids.',
|
|
sortOrder: 70,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'MegaPersonals',
|
|
href: 'https://megapersonals.eu/',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Classified ads and listings on MegaPersonals.',
|
|
sortOrder: 80,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'AdultSearch',
|
|
href: 'https://adultsearch.com/',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Verified profile on AdultSearch — stats and hooks for quick discovery.',
|
|
sortOrder: 85,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
{
|
|
platform: 'SkipTheGames',
|
|
href: 'https://skipthegames.com/',
|
|
imgSrc: '/icon-512.png',
|
|
imgAlt: 'Quinn logo',
|
|
description: 'Geo-indexed listings on SkipTheGames for touring visibility.',
|
|
sortOrder: 90,
|
|
providerSlug: PROVIDER,
|
|
},
|
|
];
|
|
|
|
const existingVerified = listVerifiedProfiles(db, { providerSlug: PROVIDER });
|
|
const existingVerifiedPlatforms = new Set(existingVerified.map((v) => v.platform));
|
|
let verifiedSeeded = 0, verifiedSkipped = 0;
|
|
|
|
for (const draft of VERIFIED_SEED) {
|
|
if (existingVerifiedPlatforms.has(draft.platform)) {
|
|
logger.info('skipping existing verified-profile', { platform: draft.platform });
|
|
verifiedSkipped++;
|
|
continue;
|
|
}
|
|
createVerifiedProfile(db, draft);
|
|
logger.info('seeded verified-profile', { platform: draft.platform });
|
|
verifiedSeeded++;
|
|
}
|
|
|
|
logger.info('iter-16 seed complete', {
|
|
roster: { seeded: rosterSeeded, skipped: rosterSkipped },
|
|
lore: { seeded: loreSeeded, skipped: loreSkipped },
|
|
verified: { seeded: verifiedSeeded, skipped: verifiedSkipped },
|
|
});
|
|
|
|
db.close();
|