Files
Goa-gel-fullstack/backend/scripts/create-all-tables.sql
Mahi 80566bf0a2 feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with:

Backend:
- NestJS API with JWT authentication
- PostgreSQL database with Knex ORM
- Redis caching and session management
- MinIO document storage
- Hyperledger Besu blockchain integration
- Multi-department workflow system
- Comprehensive API tests (266/282 passing)

Frontend:
- Angular 21 with standalone components
- Angular Material + TailwindCSS UI
- Visual workflow builder
- Document upload with progress tracking
- Blockchain explorer integration
- Role-based dashboards (Admin, Department, Citizen)
- E2E tests with Playwright (37 tests)

Infrastructure:
- Docker Compose orchestration
- Blockscout blockchain explorer
- Development and production configurations
2026-02-07 10:23:29 -04:00

305 lines
12 KiB
SQL

-- 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);