feat: Runtime configuration and Docker deployment improvements

Frontend:
- Add runtime configuration service for deployment-time API URL injection
- Create docker-entrypoint.sh to generate config.json from environment variables
- Update ApiService, ApprovalService, and DocumentViewer to use RuntimeConfigService
- Add APP_INITIALIZER to load runtime config before app starts

Backend:
- Fix init-blockchain.js to properly quote mnemonic phrases in .env file
- Improve docker-entrypoint.sh with health checks and better error handling

Docker:
- Add API_BASE_URL environment variable to frontend container
- Update docker-compose.yml with clear documentation for remote deployment
- Reorganize .env.example with clear categories (REQUIRED FOR REMOTE, PRODUCTION, AUTO-GENERATED)

Workflow fixes:
- Fix DepartmentApproval interface to match backend schema
- Fix stage transformation for 0-indexed stageOrder
- Fix workflow list to show correct stage count from definition.stages

Cleanup:
- Move development artifacts to .trash directory
- Remove root-level package.json (was only for utility scripts)
- Add .trash/ to .gitignore
This commit is contained in:
Mahi
2026-02-08 18:44:05 -04:00
parent 2c10cd5662
commit d9de183e51
171 changed files with 10236 additions and 8386 deletions

View File

@@ -5,7 +5,7 @@ export async function up(knex: Knex): Promise<void> {
await knex.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
// Applicants table
await knex.schema.createTable('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();
@@ -21,7 +21,7 @@ export async function up(knex: Knex): Promise<void> {
});
// Departments table
await knex.schema.createTable('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();
@@ -39,7 +39,7 @@ export async function up(knex: Knex): Promise<void> {
});
// Workflows table
await knex.schema.createTable('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();
@@ -56,11 +56,16 @@ export async function up(knex: Knex): Promise<void> {
});
// License Requests table
await knex.schema.createTable('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
.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');
@@ -81,9 +86,14 @@ export async function up(knex: Knex): Promise<void> {
});
// Documents table
await knex.schema.createTable('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
.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);
@@ -98,9 +108,14 @@ export async function up(knex: Knex): Promise<void> {
});
// Document Versions table
await knex.schema.createTable('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
.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();
@@ -115,10 +130,20 @@ export async function up(knex: Knex): Promise<void> {
});
// Approvals table
await knex.schema.createTable('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
.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);
@@ -137,9 +162,15 @@ export async function up(knex: Knex): Promise<void> {
});
// Workflow States table
await knex.schema.createTable('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
.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('[]');
@@ -152,9 +183,14 @@ export async function up(knex: Knex): Promise<void> {
});
// Webhooks table
await knex.schema.createTable('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
.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();
@@ -166,7 +202,7 @@ export async function up(knex: Knex): Promise<void> {
});
// Webhook Logs table
await knex.schema.createTable('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();
@@ -185,7 +221,7 @@ export async function up(knex: Knex): Promise<void> {
});
// Audit Logs table
await knex.schema.createTable('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();
@@ -207,7 +243,7 @@ export async function up(knex: Knex): Promise<void> {
});
// Blockchain Transactions table
await knex.schema.createTable('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();