db(seo): 🗃️ Add 4 SEO backend database migrations: generation metadata, campaigns, single-domain conversion tracking, and brand configuration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
e9bfae7ad0
commit
3becb66876
6 changed files with 184 additions and 273 deletions
|
|
@ -0,0 +1,183 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Initial SEO Schema
|
||||
*
|
||||
* Creates all tables for the SEO feature in their final state,
|
||||
* incorporating all subsequent ALTER TABLE changes from individual migrations:
|
||||
*
|
||||
* - seo_content: Core SEO content records with generation_metadata column
|
||||
* (from add-generation-metadata migration)
|
||||
* - seo_campaigns: Campaign management with single domain column
|
||||
* (from add-campaigns + convert-campaigns-single-domain migrations)
|
||||
* - seo_campaign_targets: Individual content targets within a campaign
|
||||
* - domain_configs: Domain-specific SEO configuration with brand_config column
|
||||
* (from add-brand-config migration)
|
||||
*
|
||||
* Note: The original schema for seo_content and domain_configs existed prior to
|
||||
* these migrations (created via TypeORM synchronize). The initial tables and all
|
||||
* subsequent ALTER TABLE changes are folded into this single migration.
|
||||
*/
|
||||
export class InitialSchema1700000000000 implements MigrationInterface {
|
||||
name = 'InitialSchema1700000000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// ── seo_content ───────────────────────────────────────────────────
|
||||
// Includes generation_metadata column (from add-generation-metadata migration)
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS seo_content (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
domain varchar(255) NOT NULL,
|
||||
path varchar(500) NOT NULL,
|
||||
locale varchar(10) NOT NULL DEFAULT 'en',
|
||||
category_slug varchar(100),
|
||||
location_id uuid,
|
||||
title varchar(100),
|
||||
description varchar(200),
|
||||
h1 varchar(100),
|
||||
body text,
|
||||
schema jsonb,
|
||||
internal_links jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
status varchar(50) NOT NULL DEFAULT 'draft',
|
||||
seo_score integer,
|
||||
readability_score integer,
|
||||
keyword_density decimal(5,2),
|
||||
word_count integer,
|
||||
generator_version varchar(50),
|
||||
source_content_id uuid,
|
||||
translation_provider varchar(50),
|
||||
translation_quality_score decimal(5,4),
|
||||
generated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
published_at timestamptz,
|
||||
generation_metadata jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT seo_content_domain_path_locale UNIQUE (domain, path, locale)
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
COMMENT ON COLUMN seo_content.generation_metadata IS
|
||||
'Generation metadata including truth validation and feature detection results'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_content_domain ON seo_content(domain)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_content_status ON seo_content(status)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_content_locale ON seo_content(locale)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_content_category ON seo_content(category_slug)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_content_location ON seo_content(location_id)`);
|
||||
|
||||
// ── domain_configs ────────────────────────────────────────────────
|
||||
// Includes brand_config column (from add-brand-config migration)
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS domain_configs (
|
||||
id serial PRIMARY KEY,
|
||||
domain varchar(255) NOT NULL UNIQUE,
|
||||
default_locale varchar(10) NOT NULL DEFAULT 'en',
|
||||
supported_locales text[] NOT NULL DEFAULT ARRAY['en'],
|
||||
site_name varchar(255) NOT NULL,
|
||||
twitter_handle varchar(100),
|
||||
default_og_image text,
|
||||
auto_generate boolean NOT NULL DEFAULT true,
|
||||
brand_config jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
COMMENT ON COLUMN domain_configs.brand_config IS
|
||||
'Brand configuration for SEO content differentiation (voice, tagline, value propositions, tone guidelines, avoid/prefer terms)'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_domain_configs_domain ON domain_configs(domain)`);
|
||||
|
||||
// ── seo_campaigns ─────────────────────────────────────────────────
|
||||
// Final state: single domain varchar column (not jsonb array)
|
||||
// (from add-campaigns + convert-campaigns-single-domain migrations)
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS seo_campaigns (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name varchar(255) NOT NULL,
|
||||
status varchar(50) NOT NULL DEFAULT 'draft',
|
||||
domain varchar(255) NOT NULL,
|
||||
categories jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
locales jsonb NOT NULL DEFAULT '["en"]'::jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`COMMENT ON TABLE seo_campaigns IS 'SEO content production campaigns'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaigns.status IS 'Campaign status: draft, active, completed, archived'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaigns.domain IS 'Single target domain for this campaign'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaigns.categories IS 'Array of target category slugs'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaigns.locales IS 'Array of target locales'`);
|
||||
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_campaigns_status ON seo_campaigns(status)`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE FUNCTION update_seo_campaigns_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
DROP TRIGGER IF EXISTS seo_campaigns_updated_at ON seo_campaigns
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TRIGGER seo_campaigns_updated_at
|
||||
BEFORE UPDATE ON seo_campaigns
|
||||
FOR EACH ROW EXECUTE FUNCTION update_seo_campaigns_updated_at()
|
||||
`);
|
||||
|
||||
// ── seo_campaign_targets ──────────────────────────────────────────
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS seo_campaign_targets (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
campaign_id uuid NOT NULL REFERENCES seo_campaigns(id) ON DELETE CASCADE,
|
||||
domain varchar(255) NOT NULL,
|
||||
location_id uuid NOT NULL REFERENCES locations(id) ON DELETE CASCADE,
|
||||
category_slug varchar(100) NOT NULL,
|
||||
locale varchar(10) NOT NULL DEFAULT 'en',
|
||||
content_id uuid REFERENCES seo_content(id) ON DELETE SET NULL,
|
||||
target_status varchar(50) NOT NULL DEFAULT 'pending',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT seo_campaign_targets_unique UNIQUE (campaign_id, domain, location_id, category_slug, locale)
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`COMMENT ON TABLE seo_campaign_targets IS 'Individual content targets within a campaign'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaign_targets.target_status IS 'Target status: pending, generated, review, published'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN seo_campaign_targets.content_id IS 'Reference to generated content when available'`);
|
||||
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_campaign ON seo_campaign_targets(campaign_id)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_status ON seo_campaign_targets(target_status)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_domain ON seo_campaign_targets(domain)`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_location ON seo_campaign_targets(location_id)`);
|
||||
|
||||
await queryRunner.query(`
|
||||
DROP TRIGGER IF EXISTS seo_campaign_targets_updated_at ON seo_campaign_targets
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TRIGGER seo_campaign_targets_updated_at
|
||||
BEFORE UPDATE ON seo_campaign_targets
|
||||
FOR EACH ROW EXECUTE FUNCTION update_seo_campaigns_updated_at()
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TRIGGER IF EXISTS seo_campaign_targets_updated_at ON seo_campaign_targets`);
|
||||
await queryRunner.query(`DROP TRIGGER IF EXISTS seo_campaigns_updated_at ON seo_campaigns`);
|
||||
await queryRunner.query(`DROP FUNCTION IF EXISTS update_seo_campaigns_updated_at()`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS seo_campaign_targets`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS seo_campaigns`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS domain_configs`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS seo_content`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Add generation_metadata JSONB column to seo_content table.
|
||||
*
|
||||
* This column tracks truth validation and feature detection results
|
||||
* from the SEO pipeline generation process.
|
||||
*
|
||||
* To run manually in production:
|
||||
* psql -U <user> -d <database> -f add-generation-metadata.sql
|
||||
*/
|
||||
export class AddGenerationMetadata1735837200000 implements MigrationInterface {
|
||||
name = 'AddGenerationMetadata1735837200000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE seo_content
|
||||
ADD COLUMN IF NOT EXISTS generation_metadata jsonb;
|
||||
|
||||
COMMENT ON COLUMN seo_content.generation_metadata IS
|
||||
'Generation metadata including truth validation and feature detection results';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE seo_content
|
||||
DROP COLUMN IF EXISTS generation_metadata;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Add SEO campaign management tables.
|
||||
*
|
||||
* Creates seo_campaigns and seo_campaign_targets tables for managing
|
||||
* bulk SEO content production workflows.
|
||||
*
|
||||
* To run manually in production:
|
||||
* psql -U <user> -d <database> -f add-campaigns.sql
|
||||
*/
|
||||
export class AddCampaigns1737216000000 implements MigrationInterface {
|
||||
name = 'AddCampaigns1737216000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
-- Create campaigns table
|
||||
CREATE TABLE IF NOT EXISTS seo_campaigns (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name varchar(255) NOT NULL,
|
||||
status varchar(50) NOT NULL DEFAULT 'draft',
|
||||
domains jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
categories jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
locales jsonb NOT NULL DEFAULT '["en"]'::jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE seo_campaigns IS 'SEO content production campaigns';
|
||||
COMMENT ON COLUMN seo_campaigns.status IS 'Campaign status: draft, active, completed, archived';
|
||||
COMMENT ON COLUMN seo_campaigns.domains IS 'Array of target domains (e.g., trustedmeet.com, atlilith.com)';
|
||||
COMMENT ON COLUMN seo_campaigns.categories IS 'Array of target category slugs';
|
||||
COMMENT ON COLUMN seo_campaigns.locales IS 'Array of target locales';
|
||||
|
||||
-- Create campaign targets table
|
||||
CREATE TABLE IF NOT EXISTS seo_campaign_targets (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
campaign_id uuid NOT NULL REFERENCES seo_campaigns(id) ON DELETE CASCADE,
|
||||
domain varchar(255) NOT NULL,
|
||||
location_id uuid NOT NULL REFERENCES locations(id) ON DELETE CASCADE,
|
||||
category_slug varchar(100) NOT NULL,
|
||||
locale varchar(10) NOT NULL DEFAULT 'en',
|
||||
content_id uuid REFERENCES seo_content(id) ON DELETE SET NULL,
|
||||
target_status varchar(50) NOT NULL DEFAULT 'pending',
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT seo_campaign_targets_unique UNIQUE (campaign_id, domain, location_id, category_slug, locale)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE seo_campaign_targets IS 'Individual content targets within a campaign';
|
||||
COMMENT ON COLUMN seo_campaign_targets.target_status IS 'Target status: pending, generated, review, published';
|
||||
COMMENT ON COLUMN seo_campaign_targets.content_id IS 'Reference to generated content when available';
|
||||
|
||||
-- Create indexes for common queries
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_campaign ON seo_campaign_targets(campaign_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_status ON seo_campaign_targets(target_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_domain ON seo_campaign_targets(domain);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_campaign_targets_location ON seo_campaign_targets(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_campaigns_status ON seo_campaigns(status);
|
||||
|
||||
-- Create trigger for updated_at
|
||||
CREATE OR REPLACE FUNCTION update_seo_campaigns_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS seo_campaigns_updated_at ON seo_campaigns;
|
||||
CREATE TRIGGER seo_campaigns_updated_at
|
||||
BEFORE UPDATE ON seo_campaigns
|
||||
FOR EACH ROW EXECUTE FUNCTION update_seo_campaigns_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS seo_campaign_targets_updated_at ON seo_campaign_targets;
|
||||
CREATE TRIGGER seo_campaign_targets_updated_at
|
||||
BEFORE UPDATE ON seo_campaign_targets
|
||||
FOR EACH ROW EXECUTE FUNCTION update_seo_campaigns_updated_at();
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DROP TRIGGER IF EXISTS seo_campaign_targets_updated_at ON seo_campaign_targets;
|
||||
DROP TRIGGER IF EXISTS seo_campaigns_updated_at ON seo_campaigns;
|
||||
DROP FUNCTION IF EXISTS update_seo_campaigns_updated_at();
|
||||
DROP TABLE IF EXISTS seo_campaign_targets;
|
||||
DROP TABLE IF EXISTS seo_campaigns;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Convert seo_campaigns.domains (jsonb array) to domain (single varchar).
|
||||
*
|
||||
* The pivot to single-domain campaigns allows:
|
||||
* - Simpler campaign management (one domain per campaign)
|
||||
* - Content comparison workflows between domains
|
||||
* - Clearer brand differentiation tracking
|
||||
*
|
||||
* Migration strategy:
|
||||
* 1. Add new domain column
|
||||
* 2. Populate from first element of domains array
|
||||
* 3. Drop old domains column
|
||||
*
|
||||
* To run manually in production:
|
||||
* psql -U <user> -d <database> -f convert-campaigns-single-domain.sql
|
||||
*/
|
||||
export class ConvertCampaignsSingleDomain1737302400000 implements MigrationInterface {
|
||||
name = 'ConvertCampaignsSingleDomain1737302400000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
-- Step 1: Add new domain column (nullable initially)
|
||||
ALTER TABLE seo_campaigns
|
||||
ADD COLUMN IF NOT EXISTS domain varchar(255);
|
||||
|
||||
-- Step 2: Populate domain from first element of domains array
|
||||
UPDATE seo_campaigns
|
||||
SET domain = domains->>0
|
||||
WHERE domains IS NOT NULL
|
||||
AND jsonb_array_length(domains) > 0;
|
||||
|
||||
-- Step 3: Set default for any rows without domains
|
||||
UPDATE seo_campaigns
|
||||
SET domain = 'trustedmeet.com'
|
||||
WHERE domain IS NULL;
|
||||
|
||||
-- Step 4: Make domain NOT NULL
|
||||
ALTER TABLE seo_campaigns
|
||||
ALTER COLUMN domain SET NOT NULL;
|
||||
|
||||
-- Step 5: Drop old domains column
|
||||
ALTER TABLE seo_campaigns
|
||||
DROP COLUMN IF EXISTS domains;
|
||||
|
||||
-- Step 6: Update comment
|
||||
COMMENT ON COLUMN seo_campaigns.domain IS 'Single target domain for this campaign';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
-- Step 1: Add domains column back
|
||||
ALTER TABLE seo_campaigns
|
||||
ADD COLUMN IF NOT EXISTS domains jsonb DEFAULT '[]'::jsonb;
|
||||
|
||||
-- Step 2: Populate domains from domain (as single-element array)
|
||||
UPDATE seo_campaigns
|
||||
SET domains = jsonb_build_array(domain)
|
||||
WHERE domain IS NOT NULL;
|
||||
|
||||
-- Step 3: Drop domain column
|
||||
ALTER TABLE seo_campaigns
|
||||
DROP COLUMN IF EXISTS domain;
|
||||
|
||||
-- Step 4: Update comment
|
||||
COMMENT ON COLUMN seo_campaigns.domains IS 'Array of target domains (e.g., trustedmeet.com, atlilith.com)';
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Add brand_config JSONB column to domain_configs table.
|
||||
*
|
||||
* This column stores brand identity configuration for SEO content differentiation:
|
||||
* - voice: Brand voice preset (professional, luxury, casual, edgy)
|
||||
* - tagline: Brand tagline for messaging
|
||||
* - valuePropositions: Core messaging pillars
|
||||
* - toneGuidelines: Writing style rules
|
||||
* - avoidTerms: Terms to avoid in content
|
||||
* - preferTerms: Preferred terms to use
|
||||
*
|
||||
* This enables different domains (e.g., trustedmeet.com vs atlilith.com)
|
||||
* to generate unique, brand-specific content.
|
||||
*/
|
||||
export class AddBrandConfig1737388800000 implements MigrationInterface {
|
||||
name = 'AddBrandConfig1737388800000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Add brand_config column
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE domain_configs
|
||||
ADD COLUMN IF NOT EXISTS brand_config jsonb;
|
||||
|
||||
COMMENT ON COLUMN domain_configs.brand_config IS
|
||||
'Brand configuration for SEO content differentiation (voice, tagline, value propositions, tone guidelines, avoid/prefer terms)';
|
||||
`);
|
||||
|
||||
// Seed initial brand configurations for known domains
|
||||
await queryRunner.query(`
|
||||
UPDATE domain_configs
|
||||
SET brand_config = '{
|
||||
"voice": "professional",
|
||||
"tagline": "Trust Meets Discretion",
|
||||
"valuePropositions": ["Verified providers", "Zero platform fees", "Complete privacy"],
|
||||
"toneGuidelines": ["Professional but approachable", "Safety-focused", "Empowering"],
|
||||
"avoidTerms": ["cheap", "easy"],
|
||||
"preferTerms": ["verified", "professional", "discreet"]
|
||||
}'::jsonb
|
||||
WHERE domain = 'trustedmeet.com'
|
||||
AND brand_config IS NULL;
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
UPDATE domain_configs
|
||||
SET brand_config = '{
|
||||
"voice": "luxury",
|
||||
"tagline": "Luxury Companionship Redefined",
|
||||
"valuePropositions": ["Elite providers", "Premium experience", "Absolute discretion"],
|
||||
"toneGuidelines": ["Sophisticated", "Exclusive", "Premium quality"],
|
||||
"avoidTerms": ["cheap", "budget", "average"],
|
||||
"preferTerms": ["exclusive", "elite", "curated", "luxury"]
|
||||
}'::jsonb
|
||||
WHERE domain = 'atlilith.com'
|
||||
AND brand_config IS NULL;
|
||||
`);
|
||||
|
||||
// Default config for any other domains
|
||||
await queryRunner.query(`
|
||||
UPDATE domain_configs
|
||||
SET brand_config = '{
|
||||
"voice": "professional",
|
||||
"tagline": "",
|
||||
"valuePropositions": ["Verified providers", "Zero platform fees", "Privacy protection"],
|
||||
"toneGuidelines": ["Professional", "Trustworthy", "Clear communication"],
|
||||
"avoidTerms": [],
|
||||
"preferTerms": []
|
||||
}'::jsonb
|
||||
WHERE brand_config IS NULL;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE domain_configs
|
||||
DROP COLUMN IF EXISTS brand_config;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { InitialSchema1700000000000 } from './1700000000000-InitialSchema';
|
||||
Loading…
Add table
Reference in a new issue