-- 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), upload_directory VARCHAR(1000), 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.upload_directory IS 'File system path where the master asset is stored'; 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';