@vezlo/assistant-server 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +140 -43
  2. package/bin/vezlo-server.js +1 -1
  3. package/database-schema.sql +193 -33
  4. package/dist/knexfile.d.ts.map +1 -1
  5. package/dist/knexfile.js +17 -8
  6. package/dist/knexfile.js.map +1 -1
  7. package/dist/src/config/database.d.ts.map +1 -1
  8. package/dist/src/config/database.js +9 -1
  9. package/dist/src/config/database.js.map +1 -1
  10. package/dist/src/config/global.d.ts.map +1 -1
  11. package/dist/src/config/global.js +5 -2
  12. package/dist/src/config/global.js.map +1 -1
  13. package/dist/src/config/knex.d.ts.map +1 -1
  14. package/dist/src/config/knex.js +22 -2
  15. package/dist/src/config/knex.js.map +1 -1
  16. package/dist/src/config/swagger.d.ts.map +1 -1
  17. package/dist/src/config/swagger.js +34 -73
  18. package/dist/src/config/swagger.js.map +1 -1
  19. package/dist/src/controllers/ApiKeyController.d.ts +17 -0
  20. package/dist/src/controllers/ApiKeyController.d.ts.map +1 -0
  21. package/dist/src/controllers/ApiKeyController.js +84 -0
  22. package/dist/src/controllers/ApiKeyController.js.map +1 -0
  23. package/dist/src/controllers/AuthController.d.ts +14 -0
  24. package/dist/src/controllers/AuthController.d.ts.map +1 -0
  25. package/dist/src/controllers/AuthController.js +212 -0
  26. package/dist/src/controllers/AuthController.js.map +1 -0
  27. package/dist/src/controllers/ChatController.d.ts +8 -5
  28. package/dist/src/controllers/ChatController.d.ts.map +1 -1
  29. package/dist/src/controllers/ChatController.js +139 -31
  30. package/dist/src/controllers/ChatController.js.map +1 -1
  31. package/dist/src/controllers/KnowledgeController.d.ts +5 -4
  32. package/dist/src/controllers/KnowledgeController.d.ts.map +1 -1
  33. package/dist/src/controllers/KnowledgeController.js +54 -16
  34. package/dist/src/controllers/KnowledgeController.js.map +1 -1
  35. package/dist/src/middleware/auth.d.ts +51 -0
  36. package/dist/src/middleware/auth.d.ts.map +1 -0
  37. package/dist/src/middleware/auth.js +232 -0
  38. package/dist/src/middleware/auth.js.map +1 -0
  39. package/dist/src/middleware/errorHandler.d.ts.map +1 -1
  40. package/dist/src/middleware/errorHandler.js +13 -19
  41. package/dist/src/middleware/errorHandler.js.map +1 -1
  42. package/dist/src/migrations/001_initial_schema.d.ts.map +1 -1
  43. package/dist/src/migrations/001_initial_schema.js +39 -64
  44. package/dist/src/migrations/001_initial_schema.js.map +1 -1
  45. package/dist/src/migrations/002_multitenancy_schema.d.ts +4 -0
  46. package/dist/src/migrations/002_multitenancy_schema.d.ts.map +1 -0
  47. package/dist/src/migrations/002_multitenancy_schema.js +119 -0
  48. package/dist/src/migrations/002_multitenancy_schema.js.map +1 -0
  49. package/dist/src/schemas/AuthSchemas.d.ts +89 -0
  50. package/dist/src/schemas/AuthSchemas.d.ts.map +1 -0
  51. package/dist/src/schemas/AuthSchemas.js +63 -0
  52. package/dist/src/schemas/AuthSchemas.js.map +1 -0
  53. package/dist/src/schemas/CommonSchemas.d.ts +62 -0
  54. package/dist/src/schemas/CommonSchemas.d.ts.map +1 -0
  55. package/dist/src/schemas/CommonSchemas.js +65 -0
  56. package/dist/src/schemas/CommonSchemas.js.map +1 -0
  57. package/dist/src/schemas/ConversationSchemas.d.ts +64 -27
  58. package/dist/src/schemas/ConversationSchemas.d.ts.map +1 -1
  59. package/dist/src/schemas/ConversationSchemas.js +28 -9
  60. package/dist/src/schemas/ConversationSchemas.js.map +1 -1
  61. package/dist/src/schemas/FeedbackSchemas.d.ts +43 -5
  62. package/dist/src/schemas/FeedbackSchemas.d.ts.map +1 -1
  63. package/dist/src/schemas/FeedbackSchemas.js +20 -2
  64. package/dist/src/schemas/FeedbackSchemas.js.map +1 -1
  65. package/dist/src/schemas/KnowledgeSchemas.d.ts +114 -35
  66. package/dist/src/schemas/KnowledgeSchemas.d.ts.map +1 -1
  67. package/dist/src/schemas/KnowledgeSchemas.js +58 -16
  68. package/dist/src/schemas/KnowledgeSchemas.js.map +1 -1
  69. package/dist/src/schemas/MessageSchemas.d.ts +57 -8
  70. package/dist/src/schemas/MessageSchemas.d.ts.map +1 -1
  71. package/dist/src/schemas/MessageSchemas.js +22 -3
  72. package/dist/src/schemas/MessageSchemas.js.map +1 -1
  73. package/dist/src/schemas/index.d.ts +410 -68
  74. package/dist/src/schemas/index.d.ts.map +1 -1
  75. package/dist/src/schemas/index.js +8 -2
  76. package/dist/src/schemas/index.js.map +1 -1
  77. package/dist/src/server.js +1047 -615
  78. package/dist/src/server.js.map +1 -1
  79. package/dist/src/services/AIService.d.ts +1 -2
  80. package/dist/src/services/AIService.d.ts.map +1 -1
  81. package/dist/src/services/AIService.js +6 -32
  82. package/dist/src/services/AIService.js.map +1 -1
  83. package/dist/src/services/ApiKeyService.d.ts +38 -0
  84. package/dist/src/services/ApiKeyService.d.ts.map +1 -0
  85. package/dist/src/services/ApiKeyService.js +123 -0
  86. package/dist/src/services/ApiKeyService.js.map +1 -0
  87. package/dist/src/services/KnowledgeBaseService.d.ts +2 -2
  88. package/dist/src/services/KnowledgeBaseService.d.ts.map +1 -1
  89. package/dist/src/services/KnowledgeBaseService.js +9 -2
  90. package/dist/src/services/KnowledgeBaseService.js.map +1 -1
  91. package/dist/src/services/MigrationService.d.ts +1 -1
  92. package/dist/src/services/MigrationService.d.ts.map +1 -1
  93. package/dist/src/services/MigrationService.js +4 -8
  94. package/dist/src/services/MigrationService.js.map +1 -1
  95. package/dist/src/services/SetupService.d.ts +102 -0
  96. package/dist/src/services/SetupService.d.ts.map +1 -0
  97. package/dist/src/services/SetupService.js +343 -0
  98. package/dist/src/services/SetupService.js.map +1 -0
  99. package/dist/src/storage/ConversationRepository.d.ts.map +1 -1
  100. package/dist/src/storage/ConversationRepository.js +42 -8
  101. package/dist/src/storage/ConversationRepository.js.map +1 -1
  102. package/dist/src/storage/MessageRepository.d.ts.map +1 -1
  103. package/dist/src/storage/MessageRepository.js +23 -27
  104. package/dist/src/storage/MessageRepository.js.map +1 -1
  105. package/dist/src/types/index.d.ts +0 -8
  106. package/dist/src/types/index.d.ts.map +1 -1
  107. package/env.example +7 -5
  108. package/knexfile.ts +17 -8
  109. package/package.json +25 -16
  110. package/scripts/generate-key.js +124 -0
  111. package/scripts/seed-default.js +72 -0
  112. package/scripts/setup.js +410 -149
  113. package/scripts/validate-db.js +46 -13
package/scripts/setup.js CHANGED
@@ -39,36 +39,18 @@ function question(prompt) {
39
39
  async function main() {
40
40
  console.clear();
41
41
  log('\nšŸš€ Vezlo Assistant Server Setup Wizard\n', 'bright');
42
- log('This wizard will help you configure your server in 3 easy steps:\n', 'blue');
43
- log(' 1. Database Connection (Supabase or PostgreSQL)');
42
+ log('This wizard will help you configure your server in 4 easy steps:\n', 'blue');
43
+ log(' 1. Supabase Database Configuration');
44
44
  log(' 2. OpenAI API Configuration');
45
- log(' 3. Automatic Table Creation\n');
45
+ log(' 3. Environment Validation');
46
+ log(' 4. Database Migration Setup\n');
46
47
 
47
- // Step 1: Database Type Selection
48
+ // Step 1: Supabase Configuration
48
49
  log('\n═══════════════════════════════════════════════════════════', 'cyan');
49
- log(' STEP 1: Database Configuration', 'bright');
50
+ log(' STEP 1: Supabase Database Configuration', 'bright');
50
51
  log('═══════════════════════════════════════════════════════════\n', 'cyan');
51
52
 
52
- log('Choose your database type:');
53
- log(' [1] Supabase (Recommended)');
54
- log(' [2] PostgreSQL (Direct Connection)');
55
- log(' [3] Use existing .env file\n');
56
-
57
- const dbChoice = await question('Enter your choice (1-3):');
58
-
59
- let config = {};
60
-
61
- if (dbChoice === '1') {
62
- config = await setupSupabase();
63
- } else if (dbChoice === '2') {
64
- config = await setupPostgreSQL();
65
- } else if (dbChoice === '3') {
66
- config = await loadExistingConfig();
67
- } else {
68
- log('\nāŒ Invalid choice. Exiting...', 'red');
69
- rl.close();
70
- return;
71
- }
53
+ const config = await setupSupabase();
72
54
 
73
55
  // Step 2: OpenAI Configuration
74
56
  log('\n═══════════════════════════════════════════════════════════', 'cyan');
@@ -81,146 +63,247 @@ async function main() {
81
63
  const aiModel = await question('AI Model (default: gpt-4o):') || 'gpt-4o';
82
64
  config.AI_MODEL = aiModel.trim();
83
65
 
66
+ const aiTemperature = await question('AI Temperature (default: 0.7):') || '0.7';
67
+ config.AI_TEMPERATURE = aiTemperature.trim();
68
+
69
+ const aiMaxTokens = await question('AI Max Tokens (default: 1000):') || '1000';
70
+ config.AI_MAX_TOKENS = aiMaxTokens.trim();
71
+
84
72
  // Step 3: Save Configuration
85
73
  log('\n═══════════════════════════════════════════════════════════', 'cyan');
86
74
  log(' STEP 3: Save Configuration', 'bright');
87
75
  log('═══════════════════════════════════════════════════════════\n', 'cyan');
88
76
 
89
77
  const envPath = path.join(process.cwd(), '.env');
90
- await saveEnvFile(envPath, config);
91
-
92
- log('\nāœ… Configuration saved to .env', 'green');
78
+ log('Preparing to write environment configuration (.env)...', 'yellow');
79
+ const createdEnv = await saveEnvFile(envPath, config);
80
+ if (createdEnv) {
81
+ log(`āœ… Configuration saved to ${envPath}`, 'green');
82
+ } else {
83
+ log('ā„¹ļø Using existing .env (no overwrite). Review values as needed.', 'yellow');
84
+ }
93
85
 
94
- // Step 4: Database Setup
86
+ // Step 4: Environment Validation
95
87
  log('\n═══════════════════════════════════════════════════════════', 'cyan');
96
- log(' STEP 4: Database Setup', 'bright');
88
+ log(' STEP 4: Environment Validation', 'bright');
97
89
  log('═══════════════════════════════════════════════════════════\n', 'cyan');
98
90
 
99
- const setupDb = await question('Setup database tables now? (y/n):');
91
+ const validationStatus = await validateEnvironment(config);
100
92
 
101
- if (setupDb.toLowerCase() === 'y') {
102
- await setupDatabase(config);
93
+ // Step 5: Database Migration Setup
94
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
95
+ log(' STEP 5: Database Migration Setup', 'bright');
96
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
97
+
98
+ const migrationStatus = await setupMigrations(config, validationStatus) || { migrations: validationStatus.database === 'success' ? 'success' : 'skipped' };
99
+
100
+ // Step 6: Default Data Setup (only if migrations succeeded)
101
+ if (migrationStatus.migrations === 'success') {
102
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
103
+ log(' STEP 6: Default Data Setup', 'bright');
104
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
105
+
106
+ const defaultDataStatus = await setupDefaultData(config);
107
+ migrationStatus.defaultData = defaultDataStatus;
108
+
109
+ // Step 7: API Key Generation (only if default data setup succeeded)
110
+ if (defaultDataStatus === 'success') {
111
+ log('\n═══════════════════════════════════════════════════════════', 'cyan');
112
+ log(' STEP 7: API Key Generation', 'bright');
113
+ log('═══════════════════════════════════════════════════════════\n', 'cyan');
114
+
115
+ const apiKeyStatus = await setupApiKey(config);
116
+ migrationStatus.apiKey = apiKeyStatus;
117
+ } else {
118
+ migrationStatus.apiKey = 'skipped';
119
+ }
103
120
  } else {
104
- log('\nāš ļø Skipping database setup.', 'yellow');
105
- log(' Run "npx vezlo-setup-db" later to create tables.\n', 'yellow');
121
+ migrationStatus.defaultData = 'skipped';
122
+ migrationStatus.apiKey = 'skipped';
106
123
  }
107
124
 
108
- // Final Instructions
125
+ // Final Instructions / Summary
109
126
  log('\n═══════════════════════════════════════════════════════════', 'green');
110
127
  log(' šŸŽ‰ Setup Complete!', 'bright');
111
128
  log('═══════════════════════════════════════════════════════════\n', 'green');
112
129
 
113
- log('Next steps:');
130
+ // Summary
131
+ log('Summary:', 'bright');
132
+ const supaStatus = validationStatus.supabaseApi === 'success' ? 'OK' : (validationStatus.supabaseApi === 'failed' ? 'FAILED' : 'UNKNOWN');
133
+ log(` Supabase API: ${supaStatus === 'OK' ? colors.green + 'OK' : supaStatus === 'FAILED' ? colors.red + 'FAILED' : colors.yellow + 'UNKNOWN'}${colors.reset}`);
134
+ log(` Database: ${validationStatus.database === 'success' ? colors.green + 'OK' : colors.red + (validationStatus.database === 'skipped' ? 'SKIPPED' : 'FAILED')}${colors.reset}`);
135
+ log(` Migrations: ${migrationStatus.migrations === 'success' ? colors.green + 'OK' : migrationStatus.migrations === 'skipped' ? colors.yellow + 'SKIPPED' : colors.red + 'FAILED'}${colors.reset}`);
136
+ log(` Default Data: ${migrationStatus.defaultData === 'success' ? colors.green + 'OK' : migrationStatus.defaultData === 'skipped' ? colors.yellow + 'SKIPPED' : colors.red + 'FAILED'}${colors.reset}`);
137
+ log(` API Key: ${migrationStatus.apiKey === 'success' ? colors.green + 'OK' : migrationStatus.apiKey === 'skipped' ? colors.yellow + 'SKIPPED' : colors.red + 'FAILED'}${colors.reset}`);
138
+
139
+ log('\nNext steps:');
114
140
  log(' 1. Review your .env file');
115
- log(' 2. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
116
- log(' 3. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
117
- log(' 4. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset + '\n');
141
+ if (migrationStatus.migrations !== 'success') {
142
+ log('\nāš ļø IMPORTANT: Migrations were not run. You must run migrations first before seeding default data.', 'yellow');
143
+ log(' 2. Run database migrations: ' + colors.bright + 'npm run migrate:latest' + colors.reset);
144
+ log(' 3. Then run seed: ' + colors.bright + 'npm run seed-default' + colors.reset + ' (only after migrations complete)', 'yellow');
145
+ log(' 4. Generate API key: ' + colors.bright + 'npm run generate-key' + colors.reset + ' (if not already done)');
146
+ log(' 5. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
147
+ log(' 6. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
148
+ log(' 7. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
149
+ log(' 8. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
150
+ } else if (migrationStatus.defaultData !== 'success') {
151
+ log(' 2. Setup default data: ' + colors.bright + 'npm run seed-default' + colors.reset);
152
+ log(' 3. Generate API key: ' + colors.bright + 'npm run generate-key' + colors.reset + ' (after default data is created)');
153
+ log(' 4. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
154
+ log(' 5. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
155
+ log(' 6. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
156
+ log(' 7. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
157
+ } else if (migrationStatus.apiKey !== 'success') {
158
+ log(' 2. Generate API key: ' + colors.bright + 'npm run generate-key' + colors.reset + ' (for library integration)');
159
+ log(' 3. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
160
+ log(' 4. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
161
+ log(' 5. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
162
+ log(' 6. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
163
+ } else {
164
+ log(' 2. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
165
+ log(' 3. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
166
+ log(' 4. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
167
+ log(' 5. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
168
+ }
118
169
 
119
170
  rl.close();
171
+ // Ensure graceful exit even if any handles remain
172
+ setImmediate(() => process.exit(0));
120
173
  }
121
174
 
122
175
  async function setupSupabase() {
123
176
  log('\nšŸ“¦ Supabase Configuration\n', 'blue');
124
177
  log('You can find these values in your Supabase Dashboard:', 'yellow');
125
- log(' Settings > API > Project URL & API Keys\n', 'yellow');
178
+ log(' • API keys & URL: Settings > API > Project URL & API Keys', 'yellow');
179
+ log(' • Database params: Settings > Database > Connection info', 'yellow');
180
+ log(' • Optional pooling: Connect > Connection Pooling > Session Pooler > View parameters\n', 'yellow');
126
181
 
182
+ // Get Supabase URL and extract project ID for defaults
127
183
  const supabaseUrl = await question('Supabase Project URL (https://xxx.supabase.co):');
128
- const supabaseAnonKey = await question('Supabase Anon Key:');
129
- const supabaseServiceKey = await question('Supabase Service Role Key:');
130
-
131
- // Validate connection
132
- log('\nšŸ”„ Testing connection...', 'yellow');
184
+ const projectId = supabaseUrl.match(/https:\/\/(.+?)\.supabase\.co/)?.[1];
185
+
186
+ if (!projectId) {
187
+ log('\nāŒ Invalid Supabase URL format. Please use: https://your-project.supabase.co', 'red');
188
+ throw new Error('Invalid Supabase URL');
189
+ }
133
190
 
191
+ const supabaseServiceKey = await question('Supabase Service Role Key:');
192
+ const supabaseAnonKey = await question('Supabase Anon Key (optional, press Enter to skip):');
193
+
194
+ // Show defaults and ask for each database parameter
195
+ log('\nšŸ“Š Database Connection Details:', 'blue');
196
+
197
+ const dbHost = await question(`Database Host (default: db.${projectId}.supabase.co):`) || `db.${projectId}.supabase.co`;
198
+ const dbPort = await question('Database Port (default: 5432):') || '5432';
199
+ const dbName = await question('Database Name (default: postgres):') || 'postgres';
200
+ const dbUser = await question(`Database User (default: postgres.${projectId}):`) || `postgres.${projectId}`;
201
+ const dbPassword = await question('Database Password (from Settings > Database):');
202
+
203
+ // Validate Supabase connection (same as validate script)
204
+ log('\nšŸ”„ Validating Supabase connection...', 'yellow');
205
+
134
206
  try {
135
207
  const client = createClient(supabaseUrl.trim(), supabaseServiceKey.trim());
136
- const { data, error } = await client.from('_test').select('*').limit(1);
137
-
138
- // This will fail but confirms we can connect
139
- if (error && error.code !== 'PGRST204' && error.code !== '42P01') {
140
- log(`\nāš ļø Warning: ${error.message}`, 'yellow');
141
- log('Continuing with setup...\n', 'yellow');
208
+ const { error } = await client.from('vezlo_conversations').select('count').limit(0);
209
+
210
+ if (error) {
211
+ // Check for table not found errors (normal before migrations run)
212
+ if (error.code === 'PGRST116' ||
213
+ error.message.includes('does not exist') ||
214
+ error.message.includes('Could not find the table')) {
215
+ log('āœ… Supabase connection successful!', 'green');
216
+ log('āš ļø Note: Table not found - this is normal before running migrations\n', 'yellow');
217
+ } else {
218
+ throw error;
219
+ }
142
220
  } else {
143
- log('āœ… Connection successful!\n', 'green');
221
+ log('āœ… Supabase connection successful!\n', 'green');
144
222
  }
145
223
  } catch (err) {
146
- log(`\nāš ļø Warning: Could not verify connection`, 'yellow');
147
- log('Continuing with setup...\n', 'yellow');
224
+ log(`āŒ Supabase connection failed: ${err.message}`, 'red');
225
+ log('āš ļø This might be because migrations haven\'t run yet, or check your credentials.', 'yellow');
148
226
  }
149
227
 
150
- // Extract database connection info from Supabase URL
151
- const projectId = supabaseUrl.match(/https:\/\/(.+?)\.supabase\.co/)?.[1];
152
- const dbHost = projectId ? `db.${projectId}.supabase.co` : '';
228
+ // Validate database connection (same as validate script)
229
+ log('\nšŸ”„ Validating database connection...', 'yellow');
230
+
231
+ let client;
232
+ try {
233
+ const { Client } = require('pg');
234
+
235
+ client = new Client({
236
+ host: dbHost.trim(),
237
+ port: parseInt(dbPort.trim()),
238
+ database: dbName.trim(),
239
+ user: dbUser.trim(),
240
+ password: dbPassword.trim(),
241
+ ssl: { rejectUnauthorized: false }
242
+ });
153
243
 
154
- log('Database connection details:', 'blue');
155
- log(` Host: ${dbHost}`);
156
- log(` Port: 5432`);
157
- log(` Database: postgres`);
158
- log(` User: postgres\n`);
244
+ // Handle connection errors quietly for normal shutdowns
245
+ client.on('error', (err) => {
246
+ const msg = (err && err.message) ? err.message : String(err);
247
+ if (msg && (msg.includes('client_termination') || msg.includes(':shutdown'))) {
248
+ return; // ignore normal termination noise
249
+ }
250
+ console.error('Database connection error:', msg);
251
+ });
159
252
 
160
- const dbPassword = await question('Supabase Database Password (from Settings > Database):');
253
+ await client.connect();
254
+ log('āœ… Database connection successful!\n', 'green');
255
+ await client.end();
256
+ } catch (err) {
257
+ log(`āŒ Database connection failed: ${err.message}`, 'red');
258
+ log('āš ļø Continuing setup. Migrations will be skipped; see summary for next steps.', 'yellow');
259
+ }
161
260
 
162
261
  return {
163
262
  SUPABASE_URL: supabaseUrl.trim(),
164
- SUPABASE_ANON_KEY: supabaseAnonKey.trim(),
263
+ SUPABASE_ANON_KEY: supabaseAnonKey.trim() || '',
165
264
  SUPABASE_SERVICE_KEY: supabaseServiceKey.trim(),
166
- SUPABASE_DB_HOST: dbHost,
167
- SUPABASE_DB_PORT: '5432',
168
- SUPABASE_DB_NAME: 'postgres',
169
- SUPABASE_DB_USER: 'postgres',
265
+ SUPABASE_DB_HOST: dbHost.trim(),
266
+ SUPABASE_DB_PORT: dbPort.trim(),
267
+ SUPABASE_DB_NAME: dbName.trim(),
268
+ SUPABASE_DB_USER: dbUser.trim(),
170
269
  SUPABASE_DB_PASSWORD: dbPassword.trim(),
171
270
  PORT: '3000',
172
271
  NODE_ENV: 'development',
173
- CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
272
+ CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173',
273
+ BASE_URL: 'http://localhost:3000'
174
274
  };
175
275
  }
176
276
 
177
- async function setupPostgreSQL() {
178
- log('\nšŸ—„ļø PostgreSQL Configuration\n', 'blue');
179
-
180
- const host = await question('Database Host (localhost):') || 'localhost';
181
- const port = await question('Database Port (5432):') || '5432';
182
- const database = await question('Database Name (postgres):') || 'postgres';
183
- const user = await question('Database User (postgres):') || 'postgres';
184
- const password = await question('Database Password:');
185
-
186
- return {
187
- SUPABASE_DB_HOST: host.trim(),
188
- SUPABASE_DB_PORT: port.trim(),
189
- SUPABASE_DB_NAME: database.trim(),
190
- SUPABASE_DB_USER: user.trim(),
191
- SUPABASE_DB_PASSWORD: password.trim(),
192
- PORT: '3000',
193
- NODE_ENV: 'development',
194
- CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
195
- };
196
- }
277
+ // Handle errors and cleanup
278
+ process.on('SIGINT', () => {
279
+ log('\n\nāš ļø Setup cancelled by user', 'yellow');
280
+ rl.close();
281
+ process.exit(0);
282
+ });
197
283
 
198
- async function loadExistingConfig() {
199
- const envPath = path.join(process.cwd(), '.env');
284
+ // Run the wizard
285
+ main().catch(error => {
286
+ log(`\nāŒ Setup failed: ${error.message}`, 'red');
287
+ rl.close();
288
+ process.exit(1);
289
+ });
200
290
 
201
- if (!fs.existsSync(envPath)) {
202
- log('\nāŒ No .env file found in current directory', 'red');
203
- throw new Error('.env file not found');
291
+ async function saveEnvFile(envPath, config) {
292
+ // Don't overwrite existing .env
293
+ if (fs.existsSync(envPath)) {
294
+ log('\nāš ļø .env already exists. Skipping overwrite. Please review values manually.', 'yellow');
295
+ return false;
204
296
  }
205
-
206
- log('\nāœ… Loading configuration from .env\n', 'green');
207
-
208
- const envContent = fs.readFileSync(envPath, 'utf8');
209
- const config = {};
210
-
211
- envContent.split('\n').forEach(line => {
212
- const match = line.match(/^([^=:#]+)=(.*)$/);
213
- if (match) {
214
- const key = match[1].trim();
215
- const value = match[2].trim();
216
- config[key] = value;
297
+ // Generate a secure migration secret if not provided
298
+ try {
299
+ const crypto = require('crypto');
300
+ if (!config.MIGRATION_SECRET_KEY) {
301
+ config.MIGRATION_SECRET_KEY = crypto.randomBytes(32).toString('hex');
217
302
  }
218
- });
219
-
220
- return config;
221
- }
222
-
223
- async function saveEnvFile(envPath, config) {
303
+ } catch (_) {
304
+ // Fallback simple key if crypto unavailable (very unlikely)
305
+ config.MIGRATION_SECRET_KEY = config.MIGRATION_SECRET_KEY || `msk_${Date.now()}`;
306
+ }
224
307
  const envContent = `# Vezlo Assistant Server Configuration
225
308
  # Generated by setup wizard on ${new Date().toISOString()}
226
309
 
@@ -232,6 +315,9 @@ LOG_LEVEL=info
232
315
  # CORS Configuration
233
316
  CORS_ORIGINS=${config.CORS_ORIGINS || 'http://localhost:3000,http://localhost:5173'}
234
317
 
318
+ # Swagger Base URL
319
+ BASE_URL=${config.BASE_URL || 'http://localhost:3000'}
320
+
235
321
  # Rate Limiting
236
322
  RATE_LIMIT_WINDOW=60000
237
323
  RATE_LIMIT_MAX=100
@@ -251,8 +337,8 @@ SUPABASE_DB_PASSWORD=${config.SUPABASE_DB_PASSWORD || ''}
251
337
  # OpenAI Configuration
252
338
  OPENAI_API_KEY=${config.OPENAI_API_KEY || 'sk-your-openai-api-key'}
253
339
  AI_MODEL=${config.AI_MODEL || 'gpt-4o'}
254
- AI_TEMPERATURE=0.7
255
- AI_MAX_TOKENS=1000
340
+ AI_TEMPERATURE=${config.AI_TEMPERATURE || '0.7'}
341
+ AI_MAX_TOKENS=${config.AI_MAX_TOKENS || '1000'}
256
342
 
257
343
  # Organization Settings
258
344
  ORGANIZATION_NAME=Vezlo
@@ -261,66 +347,174 @@ ASSISTANT_NAME=Vezlo Assistant
261
347
  # Knowledge Base
262
348
  CHUNK_SIZE=1000
263
349
  CHUNK_OVERLAP=200
350
+
351
+ # Migration Security
352
+ MIGRATION_SECRET_KEY=${config.MIGRATION_SECRET_KEY}
264
353
  `;
265
354
 
266
355
  fs.writeFileSync(envPath, envContent, 'utf8');
356
+ return true;
267
357
  }
268
358
 
269
- async function setupDatabase(config) {
270
- log('\nšŸ”„ Setting up database tables...', 'yellow');
359
+ async function validateEnvironment(config) {
360
+ log('šŸ”„ Validating environment configuration...', 'yellow');
271
361
 
362
+ // Test Supabase connection (same as validate script)
272
363
  try {
273
- const { Client } = require('pg');
364
+ const client = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_KEY);
365
+ const { error } = await client.from('vezlo_conversations').select('count').limit(0);
366
+
367
+ if (error) {
368
+ // Check for table not found errors (normal before migrations run)
369
+ if (error.code === 'PGRST116' ||
370
+ error.message.includes('does not exist') ||
371
+ error.message.includes('Could not find the table')) {
372
+ log('āœ… Supabase API connection validated', 'green');
373
+ log('āš ļø Note: Table not found - this is normal before running migrations', 'yellow');
374
+ } else {
375
+ throw error;
376
+ }
377
+ } else {
378
+ log('āœ… Supabase API connection validated', 'green');
379
+ }
380
+ } catch (err) {
381
+ log(`āŒ Supabase API validation failed: ${err.message}`, 'red');
382
+ // non-blocking; proceed to DB check anyway
383
+ // return partial status so caller can decide
384
+ // (we'll still attempt DB validation)
385
+ }
274
386
 
275
- const client = new Client({
387
+ // Test database connection (same as validate script)
388
+ log('\nšŸ”„ Validating database connection...', 'yellow');
389
+ let client;
390
+ try {
391
+ const { Client } = require('pg');
392
+
393
+ client = new Client({
276
394
  host: config.SUPABASE_DB_HOST,
277
- port: parseInt(config.SUPABASE_DB_PORT || '5432'),
395
+ port: parseInt(config.SUPABASE_DB_PORT),
278
396
  database: config.SUPABASE_DB_NAME,
279
397
  user: config.SUPABASE_DB_USER,
280
398
  password: config.SUPABASE_DB_PASSWORD,
281
399
  ssl: { rejectUnauthorized: false }
282
400
  });
283
401
 
402
+ // Handle connection errors quietly for normal shutdowns
403
+ client.on('error', (err) => {
404
+ const msg = (err && err.message) ? err.message : String(err);
405
+ if (msg && (msg.includes('client_termination') || msg.includes(':shutdown'))) {
406
+ return; // ignore normal termination noise
407
+ }
408
+ console.error('Database connection error:', msg);
409
+ });
410
+
284
411
  await client.connect();
285
- log('āœ… Connected to database', 'green');
412
+ log('āœ… Database connection validated', 'green');
413
+ await client.end();
414
+ } catch (err) {
415
+ log(`āŒ Database validation failed: ${err.message}`, 'red');
416
+ log('āš ļø Continuing setup; migrations will be skipped.', 'yellow');
417
+ return { supabaseApi: 'unknown', database: 'failed' };
418
+ }
286
419
 
287
- // Read schema file
288
- const schemaPath = path.join(__dirname, '..', 'database-schema.sql');
420
+ log('āœ… Environment validation complete!\n', 'green');
421
+ return { supabaseApi: 'success', database: 'success' };
422
+ }
289
423
 
290
- if (!fs.existsSync(schemaPath)) {
291
- log('āŒ database-schema.sql not found', 'red');
292
- return;
293
- }
424
+ async function setupMigrations(config, validationStatus) {
425
+ log('šŸ”„ Checking migration status...', 'yellow');
294
426
 
295
- const schema = fs.readFileSync(schemaPath, 'utf8');
427
+ if (validationStatus.database !== 'success') {
428
+ log('āš ļø Skipping migrations because database validation failed.', 'yellow');
429
+ return { migrations: 'skipped' };
430
+ }
296
431
 
297
- log('šŸ”„ Creating tables...', 'yellow');
298
- await client.query(schema);
432
+ try {
433
+ const { Client } = require('pg');
434
+ const client = new Client({
435
+ host: config.SUPABASE_DB_HOST,
436
+ port: parseInt(config.SUPABASE_DB_PORT),
437
+ database: config.SUPABASE_DB_NAME,
438
+ user: config.SUPABASE_DB_USER,
439
+ password: config.SUPABASE_DB_PASSWORD,
440
+ ssl: { rejectUnauthorized: false }
441
+ });
299
442
 
300
- log('āœ… Database tables created successfully!', 'green');
443
+ await client.connect();
301
444
 
302
- // Verify tables
303
- const result = await client.query(`
304
- SELECT table_name
305
- FROM information_schema.tables
306
- WHERE table_schema = 'public'
307
- AND table_name IN ('conversations', 'messages', 'message_feedback', 'knowledge_items')
308
- ORDER BY table_name
445
+ // Check if migrations table exists
446
+ const migrationTableExists = await client.query(`
447
+ SELECT EXISTS (
448
+ SELECT FROM information_schema.tables
449
+ WHERE table_schema = 'public'
450
+ AND table_name = 'knex_migrations'
451
+ );
309
452
  `);
310
453
 
311
- log('\nšŸ“Š Verified tables:', 'blue');
312
- result.rows.forEach(row => {
313
- log(` āœ“ ${row.table_name}`, 'green');
314
- });
315
- log('');
454
+ if (!migrationTableExists.rows[0].exists) {
455
+ log('šŸ“‹ No migrations table found. Database needs initial setup.', 'blue');
456
+
457
+ const runMigrations = await question('Run initial database migrations now? (y/n):');
458
+
459
+ if (runMigrations.toLowerCase() === 'y') {
460
+ log('šŸ”„ Running migrations...', 'yellow');
461
+
462
+ // Set environment variables for migration
463
+ process.env.SUPABASE_DB_HOST = config.SUPABASE_DB_HOST;
464
+ process.env.SUPABASE_DB_PORT = config.SUPABASE_DB_PORT;
465
+ process.env.SUPABASE_DB_NAME = config.SUPABASE_DB_NAME;
466
+ process.env.SUPABASE_DB_USER = config.SUPABASE_DB_USER;
467
+ process.env.SUPABASE_DB_PASSWORD = config.SUPABASE_DB_PASSWORD;
468
+
469
+ const { execSync } = require('child_process');
470
+ execSync('npm run migrate:latest', { stdio: 'inherit' });
471
+
472
+ log('āœ… Migrations completed successfully!', 'green');
473
+ return { migrations: 'success' };
474
+ } else {
475
+ log('\nāš ļø Migrations skipped by user.', 'yellow');
476
+ log(' You can run them later using: ' + colors.cyan + 'npm run migrate:latest' + colors.reset, 'yellow');
477
+ log(' Or via API: ' + colors.cyan + 'GET /api/migrate?key=your-migration-secret' + colors.reset, 'yellow');
478
+ log('\n āš ļø Note: Default data seeding will be skipped until migrations are run.\n', 'yellow');
479
+ return { migrations: 'skipped' };
480
+ }
481
+ } else {
482
+ // Check migration status
483
+ const migrationStatus = await client.query(`
484
+ SELECT COUNT(*) as count FROM knex_migrations;
485
+ `);
486
+
487
+ log(`šŸ“Š Found ${migrationStatus.rows[0].count} completed migrations`, 'blue');
488
+
489
+ const runPending = await question('Check for pending migrations? (y/n):');
490
+
491
+ if (runPending.toLowerCase() === 'y') {
492
+ log('šŸ”„ Checking for pending migrations...', 'yellow');
493
+
494
+ const { execSync } = require('child_process');
495
+ execSync('npm run migrate:latest', { stdio: 'inherit' });
496
+
497
+ log('āœ… Migration check completed!', 'green');
498
+ await client.end();
499
+ return { migrations: 'success' };
500
+ } else {
501
+ log('āš ļø Migration check skipped by user.', 'yellow');
502
+ log(' You can run them later using: ' + colors.cyan + 'npm run migrate:latest' + colors.reset, 'yellow');
503
+ log(' Or via API: ' + colors.cyan + 'GET /api/migrate?key=your-migration-secret' + colors.reset, 'yellow');
504
+ log('\n āš ļø Note: Default data seeding will be skipped until migrations are run.\n', 'yellow');
505
+ await client.end();
506
+ return { migrations: 'skipped' };
507
+ }
508
+ }
316
509
 
317
510
  await client.end();
318
-
319
- } catch (error) {
320
- log(`\nāŒ Database setup failed: ${error.message}`, 'red');
321
- log('\nYou can manually run the setup later:', 'yellow');
322
- log(' 1. Copy database-schema.sql to your Supabase SQL Editor', 'yellow');
323
- log(' 2. Execute the SQL to create tables\n', 'yellow');
511
+ return { migrations: 'skipped' };
512
+ } catch (err) {
513
+ log(`āŒ Migration setup failed: ${err.message}`, 'red');
514
+ log('\nYou can run migrations manually later:', 'yellow');
515
+ log(' npm run migrate:latest', 'cyan');
516
+ log(' Or via API: GET /api/migrate?key=your-migration-secret\n', 'cyan');
517
+ return { migrations: 'failed' };
324
518
  }
325
519
  }
326
520
 
@@ -337,3 +531,70 @@ main().catch(error => {
337
531
  rl.close();
338
532
  process.exit(1);
339
533
  });
534
+ async function setupDefaultData(config) {
535
+ log('šŸ”„ Setting up default company and admin user...', 'yellow');
536
+
537
+ try {
538
+ // Import the setup service dynamically
539
+ const { runDefaultSetup } = await import('./seed-default.js');
540
+
541
+ // Set environment variables for the setup
542
+ process.env.SUPABASE_URL = config.SUPABASE_URL;
543
+ process.env.SUPABASE_SERVICE_KEY = config.SUPABASE_SERVICE_KEY;
544
+ process.env.DEFAULT_ADMIN_EMAIL = config.DEFAULT_ADMIN_EMAIL || 'admin@vezlo.org';
545
+ process.env.DEFAULT_ADMIN_PASSWORD = config.DEFAULT_ADMIN_PASSWORD || 'admin123';
546
+ process.env.ORGANIZATION_NAME = config.ORGANIZATION_NAME || 'Vezlo';
547
+ process.env.JWT_SECRET = config.JWT_SECRET || require('crypto').randomBytes(32).toString('hex');
548
+
549
+ // Run the default setup
550
+ await runDefaultSetup();
551
+
552
+ log('āœ… Default data setup completed successfully!', 'green');
553
+ return 'success';
554
+ } catch (err) {
555
+ log(`āŒ Default data setup failed: ${err.message}`, 'red');
556
+ log('\nYou can run default data setup manually later:', 'yellow');
557
+ log(' npm run seed-default', 'cyan');
558
+ return 'failed';
559
+ }
560
+ }
561
+
562
+ async function setupApiKey(config) {
563
+ log('šŸ”„ Generating API key for library integration...', 'yellow');
564
+
565
+ try {
566
+ // Import the API key generator dynamically
567
+ const { generateApiKey } = await import('./generate-key.js');
568
+
569
+ // Set environment variables for the setup (already set in setupDefaultData)
570
+
571
+ // Run the API key generator in quiet mode
572
+ const result = await generateApiKey({ quiet: true });
573
+
574
+ if (result.success) {
575
+ // Show API key details
576
+ log('āœ… API key generated successfully!', 'green');
577
+ log('\nšŸ“‹ API Key Details:', 'bright');
578
+ log(` Company: ${result.company}`, 'reset');
579
+ log(` User: ${result.user.name}`, 'reset');
580
+ log(` API Key: ${result.apiKey}`, 'bright');
581
+ log('\nāš ļø IMPORTANT: Save this key securely. It will not be shown again.', 'yellow');
582
+
583
+ // Show usage example
584
+ log('\nšŸ”§ Usage Example:', 'bright');
585
+ log(` curl -X POST http://localhost:3000/api/knowledge/items \\
586
+ -H "X-API-Key: ${result.apiKey}" \\
587
+ -H "Content-Type: application/json" \\
588
+ -d '{"title": "Example", "type": "document", "content": "Example content"}'`, 'cyan');
589
+
590
+ return 'success';
591
+ } else {
592
+ throw new Error(result.error);
593
+ }
594
+ } catch (err) {
595
+ log(`āŒ API key generation failed: ${err.message}`, 'red');
596
+ log('\nYou can generate an API key manually later:', 'yellow');
597
+ log(' npm run generate-key', 'cyan');
598
+ return 'failed';
599
+ }
600
+ }