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>
281 lines
9.5 KiB
PL/PgSQL
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';
|