-- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- ============================================= -- MIGRATION 1: Initial Schema -- ============================================= -- Applicants table CREATE TABLE IF NOT EXISTS applicants ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), digilocker_id VARCHAR(255) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, phone VARCHAR(20), wallet_address VARCHAR(42), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_applicant_digilocker ON applicants(digilocker_id); CREATE INDEX IF NOT EXISTS idx_applicant_email ON applicants(email); -- Departments table CREATE TABLE IF NOT EXISTS departments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code VARCHAR(50) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, wallet_address VARCHAR(42) UNIQUE, api_key_hash VARCHAR(255), api_secret_hash VARCHAR(255), webhook_url VARCHAR(500), webhook_secret_hash VARCHAR(255), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, description TEXT, contact_email VARCHAR(255), contact_phone VARCHAR(20), last_webhook_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_department_code ON departments(code); CREATE INDEX IF NOT EXISTS idx_department_active ON departments(is_active); -- Workflows table CREATE TABLE IF NOT EXISTS workflows ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), workflow_type VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, description TEXT, version INTEGER NOT NULL DEFAULT 1, definition JSONB NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_by UUID, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_workflow_type ON workflows(workflow_type); CREATE INDEX IF NOT EXISTS idx_workflow_active ON workflows(is_active); -- License Requests table CREATE TABLE IF NOT EXISTS license_requests ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_number VARCHAR(50) NOT NULL UNIQUE, token_id BIGINT, applicant_id UUID NOT NULL REFERENCES applicants(id) ON DELETE CASCADE, request_type VARCHAR(100) NOT NULL, workflow_id UUID REFERENCES workflows(id) ON DELETE SET NULL, status VARCHAR(50) NOT NULL DEFAULT 'DRAFT', metadata JSONB, current_stage_id VARCHAR(100), blockchain_tx_hash VARCHAR(66), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, submitted_at TIMESTAMP, approved_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_request_number ON license_requests(request_number); CREATE INDEX IF NOT EXISTS idx_request_applicant ON license_requests(applicant_id); CREATE INDEX IF NOT EXISTS idx_request_status ON license_requests(status); CREATE INDEX IF NOT EXISTS idx_request_type ON license_requests(request_type); CREATE INDEX IF NOT EXISTS idx_request_created ON license_requests(created_at); CREATE INDEX IF NOT EXISTS idx_request_status_type ON license_requests(status, request_type); -- Documents table CREATE TABLE IF NOT EXISTS documents ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL REFERENCES license_requests(id) ON DELETE CASCADE, doc_type VARCHAR(100) NOT NULL, original_filename VARCHAR(255) NOT NULL, current_version INTEGER NOT NULL DEFAULT 1, current_hash VARCHAR(66) NOT NULL, minio_bucket VARCHAR(100) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_document_request ON documents(request_id); CREATE INDEX IF NOT EXISTS idx_document_type ON documents(doc_type); -- Document Versions table CREATE TABLE IF NOT EXISTS document_versions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE, version INTEGER NOT NULL, hash VARCHAR(66) NOT NULL, minio_path VARCHAR(500) NOT NULL, file_size BIGINT NOT NULL, mime_type VARCHAR(100) NOT NULL, uploaded_by UUID NOT NULL, blockchain_tx_hash VARCHAR(66), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(document_id, version) ); CREATE INDEX IF NOT EXISTS idx_docversion_document ON document_versions(document_id); -- Approvals table CREATE TABLE IF NOT EXISTS approvals ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL REFERENCES license_requests(id) ON DELETE CASCADE, department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE, status VARCHAR(50) NOT NULL DEFAULT 'PENDING', remarks TEXT, remarks_hash VARCHAR(66), reviewed_documents JSONB, blockchain_tx_hash VARCHAR(66), is_active BOOLEAN NOT NULL DEFAULT true, invalidated_at TIMESTAMP, invalidation_reason VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_approval_request ON approvals(request_id); CREATE INDEX IF NOT EXISTS idx_approval_department ON approvals(department_id); CREATE INDEX IF NOT EXISTS idx_approval_status ON approvals(status); CREATE INDEX IF NOT EXISTS idx_approval_request_dept ON approvals(request_id, department_id); -- Workflow States table CREATE TABLE IF NOT EXISTS workflow_states ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL UNIQUE REFERENCES license_requests(id) ON DELETE CASCADE, current_stage_id VARCHAR(100) NOT NULL, completed_stages JSONB NOT NULL DEFAULT '[]', pending_approvals JSONB NOT NULL DEFAULT '[]', execution_log JSONB NOT NULL DEFAULT '[]', stage_started_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_wfstate_request ON workflow_states(request_id); -- Webhooks table CREATE TABLE IF NOT EXISTS webhooks ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), department_id UUID NOT NULL REFERENCES departments(id) ON DELETE CASCADE, url VARCHAR(500) NOT NULL, events JSONB NOT NULL, secret_hash VARCHAR(255) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_webhook_department ON webhooks(department_id); -- Webhook Logs table CREATE TABLE IF NOT EXISTS webhook_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), webhook_id UUID NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE, event_type VARCHAR(100) NOT NULL, payload JSONB NOT NULL, response_status INTEGER, response_body TEXT, response_time INTEGER, retry_count INTEGER NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'PENDING', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_webhooklog_webhook ON webhook_logs(webhook_id); CREATE INDEX IF NOT EXISTS idx_webhooklog_event ON webhook_logs(event_type); CREATE INDEX IF NOT EXISTS idx_webhooklog_status ON webhook_logs(status); CREATE INDEX IF NOT EXISTS idx_webhooklog_created ON webhook_logs(created_at); -- Audit Logs table CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), entity_type VARCHAR(50) NOT NULL, entity_id UUID NOT NULL, action VARCHAR(50) NOT NULL, actor_type VARCHAR(50) NOT NULL, actor_id UUID, old_value JSONB, new_value JSONB, ip_address VARCHAR(45), user_agent TEXT, correlation_id VARCHAR(100), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_logs(entity_type, entity_id); CREATE INDEX IF NOT EXISTS idx_audit_entitytype ON audit_logs(entity_type); 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 INDEX IF NOT EXISTS idx_audit_correlation ON audit_logs(correlation_id); -- Blockchain Transactions table CREATE TABLE IF NOT EXISTS blockchain_transactions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tx_hash VARCHAR(66) NOT NULL UNIQUE, tx_type VARCHAR(50) NOT NULL, related_entity_type VARCHAR(50) NOT NULL, related_entity_id UUID NOT NULL, from_address VARCHAR(42) NOT NULL, to_address VARCHAR(42), status VARCHAR(20) NOT NULL DEFAULT 'PENDING', block_number BIGINT, gas_used BIGINT, error_message TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, confirmed_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_bctx_hash ON blockchain_transactions(tx_hash); CREATE INDEX IF NOT EXISTS idx_bctx_type ON blockchain_transactions(tx_type); CREATE INDEX IF NOT EXISTS idx_bctx_status ON blockchain_transactions(status); CREATE INDEX IF NOT EXISTS idx_bctx_entity ON blockchain_transactions(related_entity_id); CREATE INDEX IF NOT EXISTS idx_bctx_created ON blockchain_transactions(created_at); -- ============================================= -- MIGRATION 2: Users, Wallets, Events, Logs -- ============================================= -- Users table for email/password authentication CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL CHECK (role IN ('ADMIN', 'DEPARTMENT', 'CITIZEN')), department_id UUID REFERENCES departments(id) ON DELETE SET NULL, wallet_address VARCHAR(42), wallet_encrypted_key TEXT, phone VARCHAR(20), is_active BOOLEAN NOT NULL DEFAULT true, last_login_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_user_email ON users(email); CREATE INDEX IF NOT EXISTS idx_user_role ON users(role); CREATE INDEX IF NOT EXISTS idx_user_department ON users(department_id); CREATE INDEX IF NOT EXISTS idx_user_active ON users(is_active); -- Wallets table for storing encrypted private keys CREATE TABLE IF NOT EXISTS wallets ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), address VARCHAR(42) NOT NULL UNIQUE, encrypted_private_key TEXT NOT NULL, owner_type VARCHAR(20) NOT NULL CHECK (owner_type IN ('USER', 'DEPARTMENT')), owner_id UUID NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_wallet_address ON wallets(address); CREATE INDEX IF NOT EXISTS idx_wallet_owner ON wallets(owner_type, owner_id); -- Blockchain events table CREATE TABLE IF NOT EXISTS blockchain_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), tx_hash VARCHAR(66) NOT NULL, event_name VARCHAR(100) NOT NULL, contract_address VARCHAR(42) NOT NULL, block_number BIGINT NOT NULL, log_index INTEGER NOT NULL, args JSONB NOT NULL, decoded_args JSONB, related_entity_type VARCHAR(50), related_entity_id UUID, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(tx_hash, log_index) ); CREATE INDEX IF NOT EXISTS idx_event_tx ON blockchain_events(tx_hash); CREATE INDEX IF NOT EXISTS idx_event_name ON blockchain_events(event_name); CREATE INDEX IF NOT EXISTS idx_event_contract ON blockchain_events(contract_address); CREATE INDEX IF NOT EXISTS idx_event_block ON blockchain_events(block_number); CREATE INDEX IF NOT EXISTS idx_event_created ON blockchain_events(created_at); CREATE INDEX IF NOT EXISTS idx_event_entity ON blockchain_events(related_entity_type, related_entity_id); -- Application logs table CREATE TABLE IF NOT EXISTS application_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), level VARCHAR(10) NOT NULL CHECK (level IN ('DEBUG', 'INFO', 'WARN', 'ERROR')), module VARCHAR(100) NOT NULL, message TEXT NOT NULL, context JSONB, stack_trace TEXT, user_id UUID, correlation_id VARCHAR(100), ip_address VARCHAR(45), user_agent TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_applog_level ON application_logs(level); CREATE INDEX IF NOT EXISTS idx_applog_module ON application_logs(module); CREATE INDEX IF NOT EXISTS idx_applog_user ON application_logs(user_id); CREATE INDEX IF NOT EXISTS idx_applog_correlation ON application_logs(correlation_id); CREATE INDEX IF NOT EXISTS idx_applog_created ON application_logs(created_at);