✨ Extend SEO database schema and shared types
Add translation and job tracking fields to database schema. Extend shared types for i18n integration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c6de604dbb
commit
68b1f74ff8
3 changed files with 66 additions and 2 deletions
|
|
@ -15,7 +15,7 @@ import { ServiceCategoryEntity } from './service-category.entity';
|
|||
import { SEOContentImageEntity } from './seo-content-image.entity';
|
||||
|
||||
@Entity('seo_content')
|
||||
@Unique(['domain', 'path'])
|
||||
@Unique(['domain', 'path', 'locale'])
|
||||
export class SEOContentEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
|
@ -26,6 +26,9 @@ export class SEOContentEntity {
|
|||
@Column({ type: 'varchar', length: 500 })
|
||||
path!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 10, default: 'en' })
|
||||
locale!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, name: 'category_slug', nullable: true })
|
||||
categorySlug?: CategorySlug;
|
||||
|
||||
|
|
@ -68,6 +71,15 @@ export class SEOContentEntity {
|
|||
@Column({ type: 'varchar', length: 50, name: 'generator_version', nullable: true })
|
||||
generatorVersion?: string;
|
||||
|
||||
@Column({ type: 'uuid', name: 'source_content_id', nullable: true })
|
||||
sourceContentId?: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, name: 'translation_provider', nullable: true })
|
||||
translationProvider?: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 5, scale: 4, name: 'translation_quality_score', nullable: true })
|
||||
translationQualityScore?: number;
|
||||
|
||||
@Column({ type: 'timestamptz', name: 'generated_at', default: () => 'NOW()' })
|
||||
generatedAt!: Date;
|
||||
|
||||
|
|
@ -90,4 +102,11 @@ export class SEOContentEntity {
|
|||
|
||||
@OneToMany(() => SEOContentImageEntity, (sci) => sci.seoContent)
|
||||
images!: SEOContentImageEntity[];
|
||||
|
||||
@ManyToOne(() => SEOContentEntity, { nullable: true })
|
||||
@JoinColumn({ name: 'source_content_id' })
|
||||
sourceContent?: SEOContentEntity;
|
||||
|
||||
@OneToMany(() => SEOContentEntity, (content) => content.sourceContent)
|
||||
translations?: SEOContentEntity[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,10 +103,12 @@ CREATE TABLE IF NOT EXISTS service_categories (
|
|||
);
|
||||
|
||||
-- Generated SEO content (ML-generated pages)
|
||||
-- Supports multiple locales per path via (domain, path, locale) unique constraint
|
||||
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) REFERENCES service_categories(slug),
|
||||
location_id UUID REFERENCES locations(id),
|
||||
title VARCHAR(100),
|
||||
|
|
@ -121,11 +123,14 @@ CREATE TABLE IF NOT EXISTS seo_content (
|
|||
keyword_density DECIMAL(5, 2),
|
||||
word_count INTEGER,
|
||||
generator_version VARCHAR(50),
|
||||
source_content_id UUID REFERENCES seo_content(id),
|
||||
translation_provider VARCHAR(50),
|
||||
translation_quality_score DECIMAL(5, 4),
|
||||
generated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
published_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(domain, path)
|
||||
UNIQUE(domain, path, locale)
|
||||
);
|
||||
|
||||
-- Generated images (SDXL-generated for SEO pages)
|
||||
|
|
@ -174,9 +179,12 @@ CREATE INDEX IF NOT EXISTS idx_location_categories_category ON location_categori
|
|||
|
||||
-- SEO content indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path ON seo_content(domain, path);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path_locale ON seo_content(domain, path, locale);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_status ON seo_content(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_category ON seo_content(category_slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_location ON seo_content(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_locale ON seo_content(locale);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_source ON seo_content(source_content_id);
|
||||
|
||||
-- Generated images indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_generated_images_layout ON generated_images(layout);
|
||||
|
|
@ -203,3 +211,33 @@ INSERT INTO service_categories (slug, name, description, keywords, display_order
|
|||
('travel-companions', 'Travel Companions', 'Travel companionship', ARRAY['travel', 'companion', 'tour'], 14),
|
||||
('courtesans', 'Courtesans', 'Elite companionship', ARRAY['courtesan', 'elite', 'luxury'], 15)
|
||||
ON CONFLICT (slug) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- MIGRATION: Add locale support to seo_content (for existing databases)
|
||||
-- ============================================================================
|
||||
-- Run this if upgrading from a previous version without locale support
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Add locale column if it doesn't exist
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'seo_content' AND column_name = 'locale'
|
||||
) THEN
|
||||
ALTER TABLE seo_content ADD COLUMN locale VARCHAR(10) NOT NULL DEFAULT 'en';
|
||||
ALTER TABLE seo_content ADD COLUMN source_content_id UUID REFERENCES seo_content(id);
|
||||
ALTER TABLE seo_content ADD COLUMN translation_provider VARCHAR(50);
|
||||
ALTER TABLE seo_content ADD COLUMN translation_quality_score DECIMAL(5, 4);
|
||||
|
||||
-- Drop old unique constraint and add new one with locale
|
||||
ALTER TABLE seo_content DROP CONSTRAINT IF EXISTS seo_content_domain_path_key;
|
||||
ALTER TABLE seo_content ADD CONSTRAINT seo_content_domain_path_locale_key UNIQUE(domain, path, locale);
|
||||
|
||||
-- Add new indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_domain_path_locale ON seo_content(domain, path, locale);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_locale ON seo_content(locale);
|
||||
CREATE INDEX IF NOT EXISTS idx_seo_content_source ON seo_content(source_content_id);
|
||||
|
||||
RAISE NOTICE 'Added locale support to seo_content table';
|
||||
END IF;
|
||||
END $$;
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ export interface SEOContent {
|
|||
id: string;
|
||||
domain: string;
|
||||
path: string;
|
||||
locale: string;
|
||||
categorySlug?: CategorySlug;
|
||||
locationId?: string;
|
||||
title?: string;
|
||||
|
|
@ -173,6 +174,12 @@ export interface SEOContent {
|
|||
keywordDensity?: number;
|
||||
wordCount?: number;
|
||||
generatorVersion?: string;
|
||||
/** For translations: reference to English source content */
|
||||
sourceContentId?: string;
|
||||
/** Translation provider (nllb, tower, etc.) */
|
||||
translationProvider?: string;
|
||||
/** COMET quality score for translation */
|
||||
translationQualityScore?: number;
|
||||
generatedAt: string;
|
||||
publishedAt?: string;
|
||||
createdAt: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue