ferrero-naming-tool/database/init.sql
DJP 504c07ddbc Add Linux production deployment and upload_directory field
Deployment Features:
- Production-ready Linux deployment script with automated setup
- Standalone database deployment for multi-application access
- Environment-based configuration with security hardening
- Automated daily backups with 30-day retention
- Firewall configuration (UFW/firewalld)
- Health checks and monitoring
- System-wide management commands

Database Schema Updates:
- Added upload_directory field to master_assets table
- Migration script for existing databases (001_add_upload_directory.sql)
- Updated views to include upload_directory
- Updated seed data with sample upload paths

Script Enhancements:
- import_opentext_asset.py now accepts upload_directory parameter
- import-asset.sh wrapper updated for upload_directory
- Migration application script (apply-migrations.sh)

Production Features:
- MD5 authentication for MAMP PHP compatibility
- Docker Compose production configuration
- Automated backup cron job (2 AM daily)
- Management scripts: ferrero-db-status.sh, ferrero-db-backup.sh, ferrero-db-restart.sh
- Comprehensive deployment logging

Documentation:
- DEPLOY-LINUX.md - Complete Linux deployment guide
- DATABASE-DEPLOYMENT-PACKAGE.md - Quick start guide
- Updated README-POSTGRES.md references

Security:
- Password change instructions
- Firewall configuration
- Network access restrictions
- SSL/TLS recommendations

The database can now be deployed as a standalone service on Linux servers
and accessed by multiple Ferrero applications for asset tracking.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 12:09:36 -04:00

281 lines
9.5 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),
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';