ferrero-opentext/Python-Version/database/init.sql
DJP 5f6d24c550 Fix timestamp bug in campaign status recording
- Fixed database.py line 479: Changed 'CURRENT_TIMESTAMP' string to actual datetime
- Added datetime import for proper UTC timestamp generation
- This fixes the PostgreSQL error: invalid input syntax for type timestamp
- Added migration file for campaign_status table

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 16:34:46 -05:00

353 lines
12 KiB
PL/PgSQL

-- Ferrero Asset Tracking Database - Initialization Script
-- PostgreSQL 15+
-- Last Updated: November 5, 2025
\echo 'Creating Ferrero Asset Tracking database structure...'
-- Create extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- For text search
-- Set timezone
SET timezone = 'UTC';
\echo 'Creating tables...'
-- ============================================================================
-- Table: master_assets
-- Purpose: Stores master assets downloaded from DAM with tracking IDs
-- ============================================================================
CREATE TABLE IF NOT EXISTS master_assets (
-- Primary Key
id SERIAL PRIMARY KEY,
-- Tracking & Identification
tracking_id VARCHAR(6) UNIQUE NOT NULL,
opentext_id VARCHAR(255) NOT NULL,
-- File Information
original_filename VARCHAR(500) NOT NULL,
file_extension VARCHAR(20),
file_size_bytes BIGINT,
mime_type VARCHAR(100),
-- Metadata Fields (extracted for quick access)
brand_code VARCHAR(5),
brand_name VARCHAR(255),
country_code VARCHAR(2),
country_name VARCHAR(255),
language_code VARCHAR(3),
language_name VARCHAR(100),
subject_title VARCHAR(255),
asset_type VARCHAR(3),
asset_type_name VARCHAR(255),
duration_seconds INTEGER,
aspect_ratio VARCHAR(10),
width_px INTEGER,
height_px INTEGER,
-- Campaign Relationships
global_master_campaign_id VARCHAR(50),
global_master_folder_id VARCHAR(255),
local_campaign_id VARCHAR(50),
-- Workflow Information
upload_directory VARCHAR(1000),
description TEXT,
-- Full Metadata Storage (JSONB - Complete metadata, no truncation)
full_metadata JSONB,
-- Tags and Categories
tags TEXT[],
categories TEXT[],
-- Status
status VARCHAR(50) DEFAULT 'active',
is_deleted BOOLEAN DEFAULT FALSE,
deleted_at TIMESTAMP,
-- Tracking
ingested_by VARCHAR(255),
ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
\echo 'Table master_assets created'
-- ============================================================================
-- Table: derivative_assets
-- Purpose: Tracks derivative assets (localized versions) from master assets
-- ============================================================================
CREATE TABLE IF NOT EXISTS derivative_assets (
-- Primary Key
id SERIAL PRIMARY KEY,
-- Link to Master Asset
tracking_id VARCHAR(6) NOT NULL REFERENCES master_assets(tracking_id),
master_asset_id INTEGER REFERENCES master_assets(id) ON DELETE CASCADE,
-- Derivative File Information
derivative_filename VARCHAR(500),
file_extension VARCHAR(20),
dam_asset_id VARCHAR(255),
-- Upload Information
upload_status VARCHAR(50) DEFAULT 'pending',
upload_error TEXT,
uploaded_by VARCHAR(255),
uploaded_at TIMESTAMP,
-- Status
status VARCHAR(50) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
\echo 'Table derivative_assets created'
-- ============================================================================
-- Table: asset_events
-- Purpose: Audit log for all asset operations
-- ============================================================================
CREATE TABLE IF NOT EXISTS asset_events (
id SERIAL PRIMARY KEY,
tracking_id VARCHAR(6),
event_type VARCHAR(100) NOT NULL,
event_data JSONB,
event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255)
);
\echo 'Table asset_events created'
-- ============================================================================
-- Table: workflow_state
-- Purpose: Tracks workflow execution state for monitoring
-- ============================================================================
CREATE TABLE IF NOT EXISTS workflow_state (
id SERIAL PRIMARY KEY,
workflow_name VARCHAR(100) NOT NULL,
campaign_id VARCHAR(255),
last_run_at TIMESTAMP,
last_status VARCHAR(50),
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
\echo 'Table workflow_state created'
-- ============================================================================
-- Table: campaign_status
-- Purpose: Tracks campaign processing and live status (prevents duplicate webhooks)
-- ============================================================================
CREATE TABLE IF NOT EXISTS campaign_status (
-- Primary Key
id SERIAL PRIMARY KEY,
-- Campaign Identification
campaign_id VARCHAR(255) UNIQUE NOT NULL, -- DAM campaign folder ID (unique!)
campaign_number VARCHAR(50) NOT NULL, -- C000000078
campaign_name VARCHAR(500) NOT NULL,
-- Live Status
live_campaign VARCHAR(3) NOT NULL, -- 'YES' or 'NO'
status VARCHAR(10) NOT NULL, -- A1, A2, A4, A5, B1, B2, etc.
-- Webhook Tracking
webhook_sent BOOLEAN DEFAULT FALSE,
webhook_sent_at TIMESTAMP,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
\echo 'Table campaign_status created'
\echo 'Tables created successfully'
-- ============================================================================
-- INDEXES
-- ============================================================================
\echo 'Creating indexes...'
-- master_assets indexes
CREATE INDEX IF NOT EXISTS idx_master_assets_tracking_id ON master_assets(tracking_id);
CREATE INDEX IF NOT EXISTS idx_master_assets_opentext_id ON master_assets(opentext_id);
CREATE INDEX IF NOT EXISTS idx_master_assets_status ON master_assets(status);
CREATE INDEX IF NOT EXISTS idx_master_assets_brand_code ON master_assets(brand_code);
CREATE INDEX IF NOT EXISTS idx_master_assets_created_at ON master_assets(created_at);
CREATE INDEX IF NOT EXISTS idx_master_assets_global_master ON master_assets(global_master_campaign_id);
CREATE INDEX IF NOT EXISTS idx_master_assets_local_campaign ON master_assets(local_campaign_id);
CREATE INDEX IF NOT EXISTS idx_master_assets_opentext_local ON master_assets(opentext_id, local_campaign_id);
-- derivative_assets indexes
CREATE INDEX IF NOT EXISTS idx_derivative_tracking_id ON derivative_assets(tracking_id);
CREATE INDEX IF NOT EXISTS idx_derivative_status ON derivative_assets(upload_status);
CREATE INDEX IF NOT EXISTS idx_derivative_created_at ON derivative_assets(created_at);
-- asset_events indexes
CREATE INDEX IF NOT EXISTS idx_events_tracking_id ON asset_events(tracking_id);
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON asset_events(event_timestamp);
CREATE INDEX IF NOT EXISTS idx_events_type ON asset_events(event_type);
-- workflow_state indexes
CREATE INDEX IF NOT EXISTS idx_workflow_name ON workflow_state(workflow_name);
CREATE INDEX IF NOT EXISTS idx_workflow_campaign ON workflow_state(campaign_id);
-- campaign_status indexes
CREATE INDEX IF NOT EXISTS idx_campaign_status_campaign_id ON campaign_status(campaign_id);
CREATE INDEX IF NOT EXISTS idx_campaign_status_number ON campaign_status(campaign_number);
CREATE INDEX IF NOT EXISTS idx_campaign_status_status ON campaign_status(status);
CREATE INDEX IF NOT EXISTS idx_campaign_status_live ON campaign_status(live_campaign);
CREATE INDEX IF NOT EXISTS idx_campaign_status_webhook_sent ON campaign_status(webhook_sent);
\echo 'Indexes created successfully'
-- ============================================================================
-- FUNCTIONS
-- ============================================================================
\echo 'Creating functions...'
-- Function: Update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Function: Log master asset events
CREATE OR REPLACE FUNCTION log_master_asset_event()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO asset_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'master_asset_created', row_to_json(NEW)::jsonb);
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO asset_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'master_asset_updated',
jsonb_build_object('old', row_to_json(OLD)::jsonb, 'new', row_to_json(NEW)::jsonb));
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO asset_events (tracking_id, event_type, event_data)
VALUES (OLD.tracking_id, 'master_asset_deleted', row_to_json(OLD)::jsonb);
RETURN OLD;
END IF;
END;
$$ LANGUAGE plpgsql;
\echo 'Functions created successfully'
-- ============================================================================
-- TRIGGERS
-- ============================================================================
\echo 'Creating triggers...'
-- Trigger: Auto-update updated_at on master_assets
DROP TRIGGER IF EXISTS update_master_assets_updated_at ON master_assets;
CREATE TRIGGER update_master_assets_updated_at
BEFORE UPDATE ON master_assets
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Trigger: Log master asset changes
DROP TRIGGER IF EXISTS log_master_asset_changes ON master_assets;
CREATE TRIGGER log_master_asset_changes
AFTER INSERT OR UPDATE OR DELETE ON master_assets
FOR EACH ROW
EXECUTE FUNCTION log_master_asset_event();
-- Trigger: Auto-update updated_at on derivative_assets
DROP TRIGGER IF EXISTS update_derivative_assets_updated_at ON derivative_assets;
CREATE TRIGGER update_derivative_assets_updated_at
BEFORE UPDATE ON derivative_assets
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Trigger: Auto-update updated_at on workflow_state
DROP TRIGGER IF EXISTS update_workflow_state_updated_at ON workflow_state;
CREATE TRIGGER update_workflow_state_updated_at
BEFORE UPDATE ON workflow_state
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Trigger: Auto-update updated_at on campaign_status
DROP TRIGGER IF EXISTS update_campaign_status_updated_at ON campaign_status;
CREATE TRIGGER update_campaign_status_updated_at
BEFORE UPDATE ON campaign_status
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
\echo 'Triggers created successfully'
-- ============================================================================
-- GRANTS
-- ============================================================================
\echo 'Setting up permissions...'
-- Grant all privileges on tables
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ferrero_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ferrero_user;
-- Grant usage on schema
GRANT USAGE ON SCHEMA public TO ferrero_user;
\echo 'Permissions granted'
-- ============================================================================
-- VERIFICATION
-- ============================================================================
\echo ''
\echo '============================================================'
\echo 'Database initialization complete!'
\echo '============================================================'
\echo ''
\echo 'Tables created:'
\echo ' - master_assets (35 columns)'
\echo ' - derivative_assets'
\echo ' - asset_events'
\echo ' - workflow_state'
\echo ''
\echo 'Indexes created: 12'
\echo 'Triggers created: 4'
\echo 'Functions created: 2'
\echo ''
\echo 'Ready to use!'
\echo '============================================================'
-- Show table counts
SELECT
'master_assets' as table_name,
COUNT(*) as row_count
FROM master_assets
UNION ALL
SELECT
'derivative_assets' as table_name,
COUNT(*) as row_count
FROM derivative_assets
UNION ALL
SELECT
'asset_events' as table_name,
COUNT(*) as row_count
FROM asset_events
UNION ALL
SELECT
'workflow_state' as table_name,
COUNT(*) as row_count
FROM workflow_state;