ferrero-naming-tool/database/init.sql
DJP 7233a49252 Add v2 with PostgreSQL tracking ID system for asset lifecycle management
Implements complete tracking ID system for linking derivative assets to master files with automatic metadata inheritance.

Major features:
- v2 web interface (public-v2/) with tracking ID support
- PostgreSQL database (Docker) for master asset metadata storage
- 6-character alphanumeric tracking IDs with collision-free generation
- OpenText DAM integration with metadata import script
- Upload simulator showing filename transformations (strips job number and tracking ID)
- Visual transformation flow display
- Complete asset lifecycle event logging with triggers
- Database schema with optimized indexes and views
- Helper scripts for database management (db-start.sh, db-stop.sh)

Technical implementation:
- PostgreSQL 15 in Docker on port 5433 (md5 auth for MAMP compatibility)
- PHP PDO with singleton database class
- Automatic event logging via PostgreSQL triggers
- JSON storage for naming convention data (shared with v1)
- Case-sensitive tracking ID input fields

Both v1 (simple) and v2 (tracking) versions coexist for different workflows.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 12:24:35 -04:00

279 lines
9.4 KiB
PL/PgSQL

-- Ferrero Asset Tracking Database Schema
-- Phase 1: Asset Ingestion
-- Create extension for UUID generation (optional, for future use)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Master Assets Table
-- Stores master assets ingested from OpenText DAM
CREATE TABLE master_assets (
id SERIAL PRIMARY KEY,
tracking_id VARCHAR(6) UNIQUE NOT NULL,
opentext_id VARCHAR(255) NOT NULL,
original_filename VARCHAR(500) NOT NULL,
file_extension VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Metadata fields
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),
-- Additional metadata
file_size_bytes BIGINT,
mime_type VARCHAR(100),
width_px INTEGER,
height_px INTEGER,
-- Tags and categories
tags TEXT[], -- PostgreSQL array for tags
categories TEXT[],
description TEXT,
-- Status tracking
status VARCHAR(50) DEFAULT 'active',
is_deleted BOOLEAN DEFAULT FALSE,
deleted_at TIMESTAMP,
-- Audit fields
ingested_by VARCHAR(255),
ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Derivative Assets Table
-- Tracks derivative assets linked to master assets
CREATE TABLE derivative_assets (
id SERIAL PRIMARY KEY,
tracking_id VARCHAR(6) NOT NULL,
derivative_filename VARCHAR(500) NOT NULL,
file_extension VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Link to master asset
master_asset_id INTEGER REFERENCES master_assets(id) ON DELETE CASCADE,
-- Override metadata (language and asset type can differ from master)
language_code VARCHAR(3),
language_name VARCHAR(100),
asset_type VARCHAR(3),
asset_type_name VARCHAR(255),
duration_seconds INTEGER,
aspect_ratio VARCHAR(10),
-- Additional derivative info
omg_job_number VARCHAR(10),
spot_version VARCHAR(10),
has_master_flag BOOLEAN DEFAULT FALSE,
-- Status
status VARCHAR(50) DEFAULT 'active',
uploaded_at TIMESTAMP,
-- Audit fields
created_by VARCHAR(255),
FOREIGN KEY (tracking_id) REFERENCES master_assets(tracking_id)
);
-- Asset Lifecycle Events Table
-- Tracks all events in asset lifecycle
CREATE TABLE asset_lifecycle_events (
id SERIAL PRIMARY KEY,
tracking_id VARCHAR(6) NOT NULL,
event_type VARCHAR(50) NOT NULL, -- 'ingested', 'derivative_created', 'uploaded', 'deleted', etc.
event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
event_data JSONB, -- Flexible field for event-specific data
user_id VARCHAR(255),
ip_address INET,
user_agent TEXT
);
-- Tracking ID Generation Log
-- Keeps track of all generated tracking IDs to prevent collisions
CREATE TABLE tracking_id_log (
id SERIAL PRIMARY KEY,
tracking_id VARCHAR(6) UNIQUE NOT NULL,
generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
assigned_to_asset BOOLEAN DEFAULT FALSE,
assigned_at TIMESTAMP
);
-- Indexes for performance
CREATE INDEX idx_master_assets_tracking_id ON master_assets(tracking_id);
CREATE INDEX idx_master_assets_opentext_id ON master_assets(opentext_id);
CREATE INDEX idx_master_assets_brand_code ON master_assets(brand_code);
CREATE INDEX idx_master_assets_status ON master_assets(status);
CREATE INDEX idx_master_assets_created_at ON master_assets(created_at);
CREATE INDEX idx_derivative_assets_tracking_id ON derivative_assets(tracking_id);
CREATE INDEX idx_derivative_assets_master_id ON derivative_assets(master_asset_id);
CREATE INDEX idx_derivative_assets_omg_job ON derivative_assets(omg_job_number);
CREATE INDEX idx_lifecycle_events_tracking_id ON asset_lifecycle_events(tracking_id);
CREATE INDEX idx_lifecycle_events_type ON asset_lifecycle_events(event_type);
CREATE INDEX idx_lifecycle_events_timestamp ON asset_lifecycle_events(event_timestamp);
CREATE INDEX idx_tracking_log_id ON tracking_id_log(tracking_id);
-- Function to 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';
-- Trigger for master_assets updated_at
CREATE TRIGGER update_master_assets_updated_at
BEFORE UPDATE ON master_assets
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Function to log lifecycle events automatically
CREATE OR REPLACE FUNCTION log_master_asset_event()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO asset_lifecycle_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'master_ingested',
jsonb_build_object(
'opentext_id', NEW.opentext_id,
'filename', NEW.original_filename,
'brand_code', NEW.brand_code
));
ELSIF TG_OP = 'UPDATE' AND OLD.status != NEW.status THEN
INSERT INTO asset_lifecycle_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'status_changed',
jsonb_build_object(
'old_status', OLD.status,
'new_status', NEW.status
));
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO asset_lifecycle_events (tracking_id, event_type, event_data)
VALUES (OLD.tracking_id, 'master_deleted',
jsonb_build_object(
'opentext_id', OLD.opentext_id,
'filename', OLD.original_filename
));
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
-- Trigger for automatic lifecycle logging
CREATE TRIGGER log_master_asset_changes
AFTER INSERT OR UPDATE OR DELETE ON master_assets
FOR EACH ROW
EXECUTE FUNCTION log_master_asset_event();
-- Function to log derivative asset events
CREATE OR REPLACE FUNCTION log_derivative_asset_event()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO asset_lifecycle_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'derivative_created',
jsonb_build_object(
'filename', NEW.derivative_filename,
'omg_job_number', NEW.omg_job_number,
'master_asset_id', NEW.master_asset_id
));
ELSIF TG_OP = 'UPDATE' AND NEW.uploaded_at IS NOT NULL AND OLD.uploaded_at IS NULL THEN
INSERT INTO asset_lifecycle_events (tracking_id, event_type, event_data)
VALUES (NEW.tracking_id, 'derivative_uploaded',
jsonb_build_object(
'filename', NEW.derivative_filename,
'uploaded_at', NEW.uploaded_at
));
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
-- Trigger for derivative asset lifecycle logging
CREATE TRIGGER log_derivative_asset_changes
AFTER INSERT OR UPDATE ON derivative_assets
FOR EACH ROW
EXECUTE FUNCTION log_derivative_asset_event();
-- View for complete asset information
CREATE VIEW v_master_assets_complete AS
SELECT
ma.id,
ma.tracking_id,
ma.opentext_id,
ma.original_filename,
ma.file_extension,
ma.original_filename || ma.file_extension as full_filename,
ma.brand_code,
ma.brand_name,
ma.country_code,
ma.country_name,
ma.language_code,
ma.language_name,
ma.subject_title,
ma.asset_type,
ma.asset_type_name,
ma.duration_seconds,
ma.aspect_ratio,
ma.file_size_bytes,
ma.mime_type,
ma.width_px,
ma.height_px,
ma.tags,
ma.categories,
ma.description,
ma.status,
ma.created_at,
ma.updated_at,
ma.ingested_by,
COUNT(DISTINCT da.id) as derivative_count
FROM master_assets ma
LEFT JOIN derivative_assets da ON ma.id = da.master_asset_id
WHERE ma.is_deleted = FALSE
GROUP BY ma.id;
-- View for derivative assets with master info
CREATE VIEW v_derivatives_with_master AS
SELECT
da.id as derivative_id,
da.derivative_filename,
da.omg_job_number,
da.tracking_id,
ma.opentext_id,
ma.original_filename as master_filename,
ma.brand_code,
ma.brand_name,
da.language_code as derivative_language_code,
da.language_name as derivative_language_name,
ma.language_code as master_language_code,
ma.language_name as master_language_name,
da.asset_type as derivative_asset_type,
ma.asset_type as master_asset_type,
da.created_at as derivative_created_at,
da.uploaded_at as derivative_uploaded_at,
ma.created_at as master_created_at
FROM derivative_assets da
JOIN master_assets ma ON da.master_asset_id = ma.id
WHERE ma.is_deleted = FALSE;
-- Comments for documentation
COMMENT ON TABLE master_assets IS 'Master assets ingested from OpenText DAM';
COMMENT ON TABLE derivative_assets IS 'Derivative assets created from master assets';
COMMENT ON TABLE asset_lifecycle_events IS 'Complete audit log of all asset lifecycle events';
COMMENT ON TABLE tracking_id_log IS 'Log of all generated tracking IDs for collision prevention';
COMMENT ON COLUMN master_assets.tracking_id IS '6-character alphanumeric unique identifier';
COMMENT ON COLUMN master_assets.opentext_id IS 'Original ID from OpenText DAM system';
COMMENT ON COLUMN master_assets.tags IS 'Array of tags for categorization';
COMMENT ON COLUMN asset_lifecycle_events.event_data IS 'JSONB field for flexible event-specific data';