Re_Backend/src/services/configSeed.service.ts

752 lines
16 KiB
TypeScript

import { sequelize } from '@config/database';
import { QueryTypes } from 'sequelize';
import logger from '@utils/logger';
/**
* Seed default admin configurations if table is empty
* Called automatically on server startup
*/
export async function seedDefaultConfigurations(): Promise<void> {
try {
// Ensure pgcrypto extension is available for gen_random_uuid()
try {
await sequelize.query('CREATE EXTENSION IF NOT EXISTS "pgcrypto"', { type: QueryTypes.RAW });
} catch (extError: any) {
// Extension might already exist or user might not have permission - continue
logger.debug('[Config Seed] pgcrypto extension check:', extError?.message || 'already exists');
}
logger.info('[Config Seed] Seeding default configurations (duplicates will be skipped automatically)...');
// Insert default configurations with ON CONFLICT handling
// This allows re-running the seed without errors if configs already exist
await sequelize.query(`
INSERT INTO admin_configurations (
config_id, config_key, config_category, config_value, value_type,
display_name, description, default_value, is_editable, is_sensitive,
validation_rules, ui_component, options, sort_order, requires_restart,
last_modified_by, last_modified_at, created_at, updated_at
) VALUES
-- TAT Settings
(
gen_random_uuid(),
'DEFAULT_TAT_EXPRESS_HOURS',
'TAT_SETTINGS',
'24',
'NUMBER',
'Default TAT for Express Priority',
'Default turnaround time in hours for express priority requests (calendar days, 24/7)',
'24',
true,
false,
'{"min": 1, "max": 168}'::jsonb,
'number',
NULL,
1,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'DEFAULT_TAT_STANDARD_HOURS',
'TAT_SETTINGS',
'48',
'NUMBER',
'Default TAT for Standard Priority',
'Default turnaround time in hours for standard priority requests (working days only, excludes weekends and holidays)',
'48',
true,
false,
'{"min": 1, "max": 720}'::jsonb,
'number',
NULL,
2,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'TAT_REMINDER_THRESHOLD_1',
'TAT_SETTINGS',
'50',
'NUMBER',
'First TAT Reminder Threshold (%)',
'Send first gentle reminder when this percentage of TAT is elapsed',
'50',
true,
false,
'{"min": 1, "max": 100}'::jsonb,
'slider',
NULL,
3,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'TAT_REMINDER_THRESHOLD_2',
'TAT_SETTINGS',
'75',
'NUMBER',
'Second TAT Reminder Threshold (%)',
'Send escalation warning when this percentage of TAT is elapsed',
'75',
true,
false,
'{"min": 1, "max": 100}'::jsonb,
'slider',
NULL,
4,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'WORK_START_HOUR',
'TAT_SETTINGS',
'9',
'NUMBER',
'Working Day Start Hour',
'Hour when working day starts (24-hour format, 0-23)',
'9',
true,
false,
'{"min": 0, "max": 23}'::jsonb,
'number',
NULL,
5,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'WORK_END_HOUR',
'TAT_SETTINGS',
'18',
'NUMBER',
'Working Day End Hour',
'Hour when working day ends (24-hour format, 0-23)',
'18',
true,
false,
'{"min": 0, "max": 23}'::jsonb,
'number',
NULL,
6,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'WORK_START_DAY',
'TAT_SETTINGS',
'1',
'NUMBER',
'Working Week Start Day',
'Day of week start (1=Monday, 7=Sunday)',
'1',
true,
false,
'{"min": 1, "max": 7}'::jsonb,
'number',
NULL,
7,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'WORK_END_DAY',
'TAT_SETTINGS',
'5',
'NUMBER',
'Working Week End Day',
'Day of week end (1=Monday, 7=Sunday)',
'5',
true,
false,
'{"min": 1, "max": 7}'::jsonb,
'number',
NULL,
8,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- Document Policy
(
gen_random_uuid(),
'MAX_FILE_SIZE_MB',
'DOCUMENT_POLICY',
'10',
'NUMBER',
'Maximum File Upload Size (MB)',
'Maximum allowed file size for document uploads in megabytes',
'10',
true,
false,
'{"min": 1, "max": 100}'::jsonb,
'number',
NULL,
10,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'ALLOWED_FILE_TYPES',
'DOCUMENT_POLICY',
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif',
'STRING',
'Allowed File Types',
'Comma-separated list of allowed file extensions for uploads',
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif',
true,
false,
'{}'::jsonb,
'text',
NULL,
11,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'DOCUMENT_RETENTION_DAYS',
'DOCUMENT_POLICY',
'365',
'NUMBER',
'Document Retention Period (Days)',
'Number of days to retain documents after workflow closure before archival',
'365',
true,
false,
'{"min": 30, "max": 3650}'::jsonb,
'number',
NULL,
12,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- AI Configuration
(
gen_random_uuid(),
'AI_REMARK_GENERATION_ENABLED',
'AI_CONFIGURATION',
'true',
'BOOLEAN',
'Enable AI Remark Generation',
'Toggle AI-generated conclusion remarks for workflow closures',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
20,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'AI_PROVIDER',
'AI_CONFIGURATION',
'claude',
'STRING',
'AI Provider',
'Active AI provider for conclusion generation (claude, openai, or gemini)',
'claude',
true,
false,
'{"enum": ["claude", "openai", "gemini"], "required": true}'::jsonb,
'select',
'["claude", "openai", "gemini"]'::jsonb,
22,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'CLAUDE_API_KEY',
'AI_CONFIGURATION',
'',
'STRING',
'Claude API Key',
'API key for Claude (Anthropic) - Get from console.anthropic.com',
'',
true,
true,
'{"pattern": "^sk-ant-", "minLength": 40}'::jsonb,
'input',
NULL,
23,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'OPENAI_API_KEY',
'AI_CONFIGURATION',
'',
'STRING',
'OpenAI API Key',
'API key for OpenAI (GPT-4) - Get from platform.openai.com',
'',
true,
true,
'{"pattern": "^sk-", "minLength": 40}'::jsonb,
'input',
NULL,
24,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'GEMINI_API_KEY',
'AI_CONFIGURATION',
'',
'STRING',
'Gemini API Key',
'API key for Gemini (Google) - Get from ai.google.dev',
'',
true,
true,
'{"minLength": 20}'::jsonb,
'input',
NULL,
25,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'AI_ENABLED',
'AI_CONFIGURATION',
'true',
'BOOLEAN',
'Enable AI Features',
'Master toggle to enable/disable all AI-powered features in the system',
'true',
true,
false,
'{"type": "boolean"}'::jsonb,
'toggle',
NULL,
26,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'CLAUDE_MODEL',
'AI_CONFIGURATION',
'claude-sonnet-4-20250514',
'STRING',
'Claude Model',
'Claude (Anthropic) model to use for AI generation',
'claude-sonnet-4-20250514',
true,
false,
'{}'::jsonb,
'input',
NULL,
27,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'OPENAI_MODEL',
'AI_CONFIGURATION',
'gpt-4o',
'STRING',
'OpenAI Model',
'OpenAI model to use for AI generation',
'gpt-4o',
true,
false,
'{}'::jsonb,
'input',
NULL,
28,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'GEMINI_MODEL',
'AI_CONFIGURATION',
'gemini-2.0-flash-lite',
'STRING',
'Gemini Model',
'Gemini (Google) model to use for AI generation',
'gemini-2.0-flash-lite',
true,
false,
'{}'::jsonb,
'input',
NULL,
29,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'AI_MAX_REMARK_LENGTH',
'AI_CONFIGURATION',
'2000',
'NUMBER',
'AI Max Remark Length',
'Maximum character length for AI-generated conclusion remarks',
'2000',
true,
false,
'{"min": 500, "max": 5000}'::jsonb,
'number',
NULL,
30,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- Notification Rules
(
gen_random_uuid(),
'ENABLE_EMAIL_NOTIFICATIONS',
'NOTIFICATION_RULES',
'true',
'BOOLEAN',
'Enable Email Notifications',
'Send email notifications for workflow events',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
31,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'ENABLE_IN_APP_NOTIFICATIONS',
'NOTIFICATION_RULES',
'true',
'BOOLEAN',
'Enable In-App Notifications',
'Show notifications within the application portal',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
32,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'NOTIFICATION_BATCH_DELAY_MS',
'NOTIFICATION_RULES',
'5000',
'NUMBER',
'Notification Batch Delay (ms)',
'Delay in milliseconds before sending batched notifications to avoid spam',
'5000',
true,
false,
'{"min": 1000, "max": 30000}'::jsonb,
'number',
NULL,
33,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- Dashboard Layout
(
gen_random_uuid(),
'DASHBOARD_SHOW_TOTAL_REQUESTS',
'DASHBOARD_LAYOUT',
'true',
'BOOLEAN',
'Show Total Requests Card',
'Display total requests KPI card on dashboard',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
40,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'DASHBOARD_SHOW_OPEN_REQUESTS',
'DASHBOARD_LAYOUT',
'true',
'BOOLEAN',
'Show Open Requests Card',
'Display open requests KPI card on dashboard',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
41,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'DASHBOARD_SHOW_TAT_COMPLIANCE',
'DASHBOARD_LAYOUT',
'true',
'BOOLEAN',
'Show TAT Compliance Card',
'Display TAT compliance KPI card on dashboard',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
42,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'DASHBOARD_SHOW_PENDING_ACTIONS',
'DASHBOARD_LAYOUT',
'true',
'BOOLEAN',
'Show Pending Actions Card',
'Display pending actions KPI card on dashboard',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
43,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- Workflow Sharing Policy
(
gen_random_uuid(),
'ALLOW_ADD_SPECTATOR',
'WORKFLOW_SHARING',
'true',
'BOOLEAN',
'Allow Adding Spectators',
'Enable users to add spectators to workflow requests',
'true',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
50,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'MAX_SPECTATORS_PER_REQUEST',
'WORKFLOW_SHARING',
'20',
'NUMBER',
'Maximum Spectators per Request',
'Maximum number of spectators allowed per workflow request',
'20',
true,
false,
'{"min": 1, "max": 100}'::jsonb,
'number',
NULL,
51,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'ALLOW_EXTERNAL_SHARING',
'WORKFLOW_SHARING',
'false',
'BOOLEAN',
'Allow External Sharing',
'Allow sharing workflow links with users outside the organization',
'false',
true,
false,
'{}'::jsonb,
'toggle',
NULL,
52,
false,
NULL,
NULL,
NOW(),
NOW()
),
-- User Roles (Read-only settings for reference)
(
gen_random_uuid(),
'MAX_APPROVAL_LEVELS',
'SYSTEM_SETTINGS',
'10',
'NUMBER',
'Maximum Approval Levels',
'Maximum number of approval levels allowed per workflow',
'10',
true,
false,
'{"min": 1, "max": 20}'::jsonb,
'number',
NULL,
60,
false,
NULL,
NULL,
NOW(),
NOW()
),
(
gen_random_uuid(),
'MAX_PARTICIPANTS_PER_REQUEST',
'SYSTEM_SETTINGS',
'50',
'NUMBER',
'Maximum Participants per Request',
'Maximum total participants (approvers + spectators) per workflow',
'50',
true,
false,
'{"min": 2, "max": 200}'::jsonb,
'number',
NULL,
61,
false,
NULL,
NULL,
NOW(),
NOW()
)
ON CONFLICT (config_key) DO NOTHING
`, { type: QueryTypes.INSERT });
// Verify how many were actually inserted
const result = await sequelize.query(
'SELECT COUNT(*) as count FROM admin_configurations',
{ type: QueryTypes.SELECT }
);
const totalCount = result && (result[0] as any).count ? (result[0] as any).count : 0;
logger.info(`[Config Seed] ✅ Configuration seeding complete. Total configurations: ${totalCount}`);
} catch (error: any) {
logger.error('[Config Seed] ❌ Error seeding configurations:', {
message: error?.message || String(error),
stack: error?.stack,
name: error?.name
});
// Don't throw - let server start even if seeding fails
// User can manually run seed script if needed: npm run seed:config
}
}