import type { Knex } from 'knex'; export async function up(knex: Knex): Promise { // Enable UUID extension await knex.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); // Applicants table await knex.schema.createTable('applicants', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('digilocker_id', 255).notNullable().unique(); table.string('name', 255).notNullable(); table.string('email', 255).notNullable(); table.string('phone', 20); table.string('wallet_address', 42); table.boolean('is_active').notNullable().defaultTo(true); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('digilocker_id', 'idx_applicant_digilocker'); table.index('email', 'idx_applicant_email'); }); // Departments table await knex.schema.createTable('departments', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('code', 50).notNullable().unique(); table.string('name', 255).notNullable(); table.string('wallet_address', 42).unique(); table.string('api_key_hash', 255); table.string('api_secret_hash', 255); table.string('webhook_url', 500); table.string('webhook_secret_hash', 255); table.boolean('is_active').notNullable().defaultTo(true); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('code', 'idx_department_code'); table.index('is_active', 'idx_department_active'); }); // Workflows table await knex.schema.createTable('workflows', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('workflow_type', 100).notNullable().unique(); table.string('name', 255).notNullable(); table.text('description'); table.integer('version').notNullable().defaultTo(1); table.jsonb('definition').notNullable(); table.boolean('is_active').notNullable().defaultTo(true); table.uuid('created_by'); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('workflow_type', 'idx_workflow_type'); table.index('is_active', 'idx_workflow_active'); }); // License Requests table await knex.schema.createTable('license_requests', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('request_number', 50).notNullable().unique(); table.bigInteger('token_id'); table .uuid('applicant_id') .notNullable() .references('id') .inTable('applicants') .onDelete('CASCADE'); table.string('request_type', 100).notNullable(); table.uuid('workflow_id').references('id').inTable('workflows').onDelete('SET NULL'); table.string('status', 50).notNullable().defaultTo('DRAFT'); table.jsonb('metadata'); table.string('current_stage_id', 100); table.string('blockchain_tx_hash', 66); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('submitted_at'); table.timestamp('approved_at'); table.index('request_number', 'idx_request_number'); table.index('applicant_id', 'idx_request_applicant'); table.index('status', 'idx_request_status'); table.index('request_type', 'idx_request_type'); table.index('created_at', 'idx_request_created'); table.index(['status', 'request_type'], 'idx_request_status_type'); }); // Documents table await knex.schema.createTable('documents', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table .uuid('request_id') .notNullable() .references('id') .inTable('license_requests') .onDelete('CASCADE'); table.string('doc_type', 100).notNullable(); table.string('original_filename', 255).notNullable(); table.integer('current_version').notNullable().defaultTo(1); table.string('current_hash', 66).notNullable(); table.string('minio_bucket', 100).notNullable(); table.boolean('is_active').notNullable().defaultTo(true); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('request_id', 'idx_document_request'); table.index('doc_type', 'idx_document_type'); }); // Document Versions table await knex.schema.createTable('document_versions', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table .uuid('document_id') .notNullable() .references('id') .inTable('documents') .onDelete('CASCADE'); table.integer('version').notNullable(); table.string('hash', 66).notNullable(); table.string('minio_path', 500).notNullable(); table.bigInteger('file_size').notNullable(); table.string('mime_type', 100).notNullable(); table.uuid('uploaded_by').notNullable(); table.string('blockchain_tx_hash', 66); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.unique(['document_id', 'version'], { indexName: 'uq_document_version' }); table.index('document_id', 'idx_docversion_document'); }); // Approvals table await knex.schema.createTable('approvals', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table .uuid('request_id') .notNullable() .references('id') .inTable('license_requests') .onDelete('CASCADE'); table .uuid('department_id') .notNullable() .references('id') .inTable('departments') .onDelete('CASCADE'); table.string('status', 50).notNullable().defaultTo('PENDING'); table.text('remarks'); table.string('remarks_hash', 66); table.jsonb('reviewed_documents'); table.string('blockchain_tx_hash', 66); table.boolean('is_active').notNullable().defaultTo(true); table.timestamp('invalidated_at'); table.string('invalidation_reason', 255); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('request_id', 'idx_approval_request'); table.index('department_id', 'idx_approval_department'); table.index('status', 'idx_approval_status'); table.index(['request_id', 'department_id'], 'idx_approval_request_dept'); }); // Workflow States table await knex.schema.createTable('workflow_states', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table .uuid('request_id') .notNullable() .unique() .references('id') .inTable('license_requests') .onDelete('CASCADE'); table.string('current_stage_id', 100).notNullable(); table.jsonb('completed_stages').notNullable().defaultTo('[]'); table.jsonb('pending_approvals').notNullable().defaultTo('[]'); table.jsonb('execution_log').notNullable().defaultTo('[]'); table.timestamp('stage_started_at'); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('request_id', 'idx_wfstate_request'); }); // Webhooks table await knex.schema.createTable('webhooks', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table .uuid('department_id') .notNullable() .references('id') .inTable('departments') .onDelete('CASCADE'); table.string('url', 500).notNullable(); table.jsonb('events').notNullable(); table.string('secret_hash', 255).notNullable(); table.boolean('is_active').notNullable().defaultTo(true); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now()); table.index('department_id', 'idx_webhook_department'); }); // Webhook Logs table await knex.schema.createTable('webhook_logs', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.uuid('webhook_id').notNullable().references('id').inTable('webhooks').onDelete('CASCADE'); table.string('event_type', 100).notNullable(); table.jsonb('payload').notNullable(); table.integer('response_status'); table.text('response_body'); table.integer('response_time'); table.integer('retry_count').notNullable().defaultTo(0); table.string('status', 20).notNullable().defaultTo('PENDING'); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.index('webhook_id', 'idx_webhooklog_webhook'); table.index('event_type', 'idx_webhooklog_event'); table.index('status', 'idx_webhooklog_status'); table.index('created_at', 'idx_webhooklog_created'); }); // Audit Logs table await knex.schema.createTable('audit_logs', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('entity_type', 50).notNullable(); table.uuid('entity_id').notNullable(); table.string('action', 50).notNullable(); table.string('actor_type', 50).notNullable(); table.uuid('actor_id'); table.jsonb('old_value'); table.jsonb('new_value'); table.string('ip_address', 45); table.text('user_agent'); table.string('correlation_id', 100); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.index(['entity_type', 'entity_id'], 'idx_audit_entity'); table.index('entity_type', 'idx_audit_entitytype'); table.index('action', 'idx_audit_action'); table.index('created_at', 'idx_audit_created'); table.index('correlation_id', 'idx_audit_correlation'); }); // Blockchain Transactions table await knex.schema.createTable('blockchain_transactions', table => { table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); table.string('tx_hash', 66).notNullable().unique(); table.string('tx_type', 50).notNullable(); table.string('related_entity_type', 50).notNullable(); table.uuid('related_entity_id').notNullable(); table.string('from_address', 42).notNullable(); table.string('to_address', 42); table.string('status', 20).notNullable().defaultTo('PENDING'); table.bigInteger('block_number'); table.bigInteger('gas_used'); table.text('error_message'); table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); table.timestamp('confirmed_at'); table.index('tx_hash', 'idx_bctx_hash'); table.index('tx_type', 'idx_bctx_type'); table.index('status', 'idx_bctx_status'); table.index('related_entity_id', 'idx_bctx_entity'); table.index('created_at', 'idx_bctx_created'); }); } export async function down(knex: Knex): Promise { await knex.schema.dropTableIfExists('blockchain_transactions'); await knex.schema.dropTableIfExists('audit_logs'); await knex.schema.dropTableIfExists('webhook_logs'); await knex.schema.dropTableIfExists('webhooks'); await knex.schema.dropTableIfExists('workflow_states'); await knex.schema.dropTableIfExists('approvals'); await knex.schema.dropTableIfExists('document_versions'); await knex.schema.dropTableIfExists('documents'); await knex.schema.dropTableIfExists('license_requests'); await knex.schema.dropTableIfExists('workflows'); await knex.schema.dropTableIfExists('departments'); await knex.schema.dropTableIfExists('applicants'); }