File: /var/www/indoadvisory_new/web2/webapp/migrations/001_create_tables.sql
-- Indo Advisory Database Schema
-- PostgreSQL Migration 001: Create initial tables
-- Run: node scripts/migrate.js
-- Enable UUID extension for better primary keys
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Users table (Admin authentication)
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'admin' CHECK (role IN ('admin', 'editor', 'viewer')),
avatar_url TEXT,
is_active BOOLEAN DEFAULT true,
last_login TIMESTAMP WITH TIME ZONE,
failed_login_attempts INTEGER DEFAULT 0,
locked_until TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Team members table (Public display)
CREATE TABLE IF NOT EXISTS team_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
position_en VARCHAR(255) NOT NULL,
position_id VARCHAR(255) NOT NULL,
bio_en TEXT,
bio_id TEXT,
email VARCHAR(255),
phone VARCHAR(50),
linkedin_url TEXT,
avatar_url TEXT,
display_order INTEGER DEFAULT 0,
is_featured BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Clients table (Portfolio showcase)
CREATE TABLE IF NOT EXISTS clients (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
company_name VARCHAR(255) NOT NULL,
industry_en VARCHAR(255),
industry_id VARCHAR(255),
logo_url TEXT,
website_url TEXT,
project_title_en VARCHAR(255),
project_title_id VARCHAR(255),
project_description_en TEXT,
project_description_id TEXT,
project_value DECIMAL(15,2),
project_year INTEGER,
project_status VARCHAR(50) DEFAULT 'completed' CHECK (project_status IN ('completed', 'ongoing', 'planned')),
case_study_en TEXT,
case_study_id TEXT,
testimonial_en TEXT,
testimonial_id TEXT,
testimonial_author VARCHAR(255),
testimonial_position VARCHAR(255),
display_order INTEGER DEFAULT 0,
is_featured BOOLEAN DEFAULT false,
is_showcase BOOLEAN DEFAULT true,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Articles/News table (Content management)
CREATE TABLE IF NOT EXISTS articles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title_en VARCHAR(500) NOT NULL,
title_id VARCHAR(500) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
summary_en TEXT,
summary_id TEXT,
content_en TEXT NOT NULL,
content_id TEXT NOT NULL,
featured_image_url TEXT,
category_en VARCHAR(100),
category_id VARCHAR(100),
tags TEXT[], -- PostgreSQL array for tags
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
published_at TIMESTAMP WITH TIME ZONE,
is_published BOOLEAN DEFAULT false,
is_featured BOOLEAN DEFAULT false,
view_count INTEGER DEFAULT 0,
seo_title_en VARCHAR(255),
seo_title_id VARCHAR(255),
seo_description_en TEXT,
seo_description_id TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Company settings (Site configuration)
CREATE TABLE IF NOT EXISTS settings (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key VARCHAR(255) UNIQUE NOT NULL,
value_en TEXT,
value_id TEXT,
description TEXT,
is_public BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Contact inquiries
CREATE TABLE IF NOT EXISTS inquiries (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(50),
company VARCHAR(255),
subject VARCHAR(500),
message TEXT NOT NULL,
inquiry_type VARCHAR(100) DEFAULT 'general',
status VARCHAR(50) DEFAULT 'new' CHECK (status IN ('new', 'in_progress', 'resolved', 'closed')),
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL,
notes TEXT,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Audit log for enterprise security
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
action VARCHAR(255) NOT NULL,
resource_type VARCHAR(100),
resource_id UUID,
ip_address INET,
user_agent TEXT,
details JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- File uploads tracking
CREATE TABLE IF NOT EXISTS uploads (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
original_name VARCHAR(500) NOT NULL,
filename VARCHAR(500) NOT NULL,
file_path TEXT NOT NULL,
mime_type VARCHAR(255),
file_size INTEGER,
is_image BOOLEAN DEFAULT false,
alt_text VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes for performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
CREATE INDEX IF NOT EXISTS idx_team_display_order ON team_members(display_order);
CREATE INDEX IF NOT EXISTS idx_team_featured ON team_members(is_featured);
CREATE INDEX IF NOT EXISTS idx_team_active ON team_members(is_active);
CREATE INDEX IF NOT EXISTS idx_clients_display_order ON clients(display_order);
CREATE INDEX IF NOT EXISTS idx_clients_featured ON clients(is_featured);
CREATE INDEX IF NOT EXISTS idx_clients_showcase ON clients(is_showcase);
CREATE INDEX IF NOT EXISTS idx_clients_active ON clients(is_active);
CREATE INDEX IF NOT EXISTS idx_articles_slug ON articles(slug);
CREATE INDEX IF NOT EXISTS idx_articles_published ON articles(is_published);
CREATE INDEX IF NOT EXISTS idx_articles_featured ON articles(is_featured);
CREATE INDEX IF NOT EXISTS idx_articles_published_at ON articles(published_at);
CREATE INDEX IF NOT EXISTS idx_articles_author ON articles(author_id);
CREATE INDEX IF NOT EXISTS idx_inquiries_status ON inquiries(status);
CREATE INDEX IF NOT EXISTS idx_inquiries_created ON inquiries(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at);
-- Create trigger for updated_at timestamps
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- Apply triggers to all tables with updated_at
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_team_updated_at BEFORE UPDATE ON team_members FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_clients_updated_at BEFORE UPDATE ON clients FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_articles_updated_at BEFORE UPDATE ON articles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_settings_updated_at BEFORE UPDATE ON settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_inquiries_updated_at BEFORE UPDATE ON inquiries FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();