- 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>
353 lines
12 KiB
PL/PgSQL
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;
|