@vezlo/assistant-server 1.2.0 → 1.4.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 (35) hide show
  1. package/README.md +152 -36
  2. package/bin/vezlo-server.js +1 -1
  3. package/dist/knexfile.d.ts.map +1 -1
  4. package/dist/knexfile.js +7 -0
  5. package/dist/knexfile.js.map +1 -1
  6. package/dist/src/config/global.d.ts.map +1 -1
  7. package/dist/src/config/global.js +5 -2
  8. package/dist/src/config/global.js.map +1 -1
  9. package/dist/src/config/swagger.d.ts.map +1 -1
  10. package/dist/src/config/swagger.js +16 -2
  11. package/dist/src/config/swagger.js.map +1 -1
  12. package/dist/src/server.js +129 -2
  13. package/dist/src/server.js.map +1 -1
  14. package/dist/src/services/AIService.d.ts +0 -1
  15. package/dist/src/services/AIService.d.ts.map +1 -1
  16. package/dist/src/services/AIService.js +0 -30
  17. package/dist/src/services/AIService.js.map +1 -1
  18. package/dist/src/services/MigrationService.d.ts +42 -0
  19. package/dist/src/services/MigrationService.d.ts.map +1 -0
  20. package/dist/src/services/MigrationService.js +227 -0
  21. package/dist/src/services/MigrationService.js.map +1 -0
  22. package/dist/src/storage/MessageRepository.d.ts.map +1 -1
  23. package/dist/src/storage/MessageRepository.js +24 -3
  24. package/dist/src/storage/MessageRepository.js.map +1 -1
  25. package/dist/src/storage/SupabaseStorage.d.ts.map +1 -1
  26. package/dist/src/storage/SupabaseStorage.js +4 -0
  27. package/dist/src/storage/SupabaseStorage.js.map +1 -1
  28. package/dist/src/types/index.d.ts +0 -8
  29. package/dist/src/types/index.d.ts.map +1 -1
  30. package/docker-compose.yml +1 -1
  31. package/env.example +6 -6
  32. package/knexfile.ts +8 -0
  33. package/package.json +16 -15
  34. package/scripts/setup.js +277 -151
  35. package/scripts/validate-db.js +24 -7
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,197 @@ 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);
103
- } else {
104
- log('\nāš ļø Skipping database setup.', 'yellow');
105
- log(' Run "npx vezlo-setup-db" later to create tables.\n', 'yellow');
106
- }
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' };
107
99
 
108
- // Final Instructions
100
+ // Final Instructions / Summary
109
101
  log('\n═══════════════════════════════════════════════════════════', 'green');
110
102
  log(' šŸŽ‰ Setup Complete!', 'bright');
111
103
  log('═══════════════════════════════════════════════════════════\n', 'green');
112
104
 
113
- log('Next steps:');
105
+ // Summary
106
+ log('Summary:', 'bright');
107
+ const supaStatus = validationStatus.supabaseApi === 'success' ? 'OK' : (validationStatus.supabaseApi === 'failed' ? 'FAILED' : 'UNKNOWN');
108
+ log(` Supabase API: ${supaStatus === 'OK' ? colors.green + 'OK' : supaStatus === 'FAILED' ? colors.red + 'FAILED' : colors.yellow + 'UNKNOWN'}${colors.reset}`);
109
+ log(` Database: ${validationStatus.database === 'success' ? colors.green + 'OK' : colors.red + (validationStatus.database === 'skipped' ? 'SKIPPED' : 'FAILED')}${colors.reset}`);
110
+ log(` Migrations: ${migrationStatus.migrations === 'success' ? colors.green + 'OK' : migrationStatus.migrations === 'skipped' ? colors.yellow + 'SKIPPED' : colors.red + 'FAILED'}${colors.reset}`);
111
+
112
+ log('\nNext steps:');
114
113
  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');
114
+ if (migrationStatus.migrations !== 'success') {
115
+ log(' 2. Run database migrations: ' + colors.bright + 'npm run migrate:latest' + colors.reset);
116
+ log(' Or via API (after starting server): ' + colors.bright + 'GET /api/migrate?key=$MIGRATION_SECRET_KEY' + colors.reset);
117
+ log(' 3. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
118
+ log(' 4. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
119
+ log(' 5. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
120
+ log(' 6. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
121
+ } else {
122
+ log(' 2. Start the server: ' + colors.bright + 'vezlo-server' + colors.reset);
123
+ log(' 3. Visit: ' + colors.bright + 'http://localhost:3000/health' + colors.reset);
124
+ log(' 4. API docs: ' + colors.bright + 'http://localhost:3000/docs' + colors.reset);
125
+ log(' 5. Test API: ' + colors.bright + 'curl http://localhost:3000/health' + colors.reset + '\n');
126
+ }
118
127
 
119
128
  rl.close();
129
+ // Ensure graceful exit even if any handles remain
130
+ setImmediate(() => process.exit(0));
120
131
  }
121
132
 
122
133
  async function setupSupabase() {
123
134
  log('\nšŸ“¦ Supabase Configuration\n', 'blue');
124
135
  log('You can find these values in your Supabase Dashboard:', 'yellow');
125
- log(' Settings > API > Project URL & API Keys\n', 'yellow');
136
+ log(' • API keys & URL: Settings > API > Project URL & API Keys', 'yellow');
137
+ log(' • Database params: Settings > Database > Connection info', 'yellow');
138
+ log(' • Optional pooling: Connect > Connection Pooling > Session Pooler > View parameters\n', 'yellow');
126
139
 
140
+ // Get Supabase URL and extract project ID for defaults
127
141
  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');
142
+ const projectId = supabaseUrl.match(/https:\/\/(.+?)\.supabase\.co/)?.[1];
143
+
144
+ if (!projectId) {
145
+ log('\nāŒ Invalid Supabase URL format. Please use: https://your-project.supabase.co', 'red');
146
+ throw new Error('Invalid Supabase URL');
147
+ }
133
148
 
149
+ const supabaseServiceKey = await question('Supabase Service Role Key:');
150
+ const supabaseAnonKey = await question('Supabase Anon Key (optional, press Enter to skip):');
151
+
152
+ // Show defaults and ask for each database parameter
153
+ log('\nšŸ“Š Database Connection Details:', 'blue');
154
+
155
+ const dbHost = await question(`Database Host (default: db.${projectId}.supabase.co):`) || `db.${projectId}.supabase.co`;
156
+ const dbPort = await question('Database Port (default: 5432):') || '5432';
157
+ const dbName = await question('Database Name (default: postgres):') || 'postgres';
158
+ const dbUser = await question(`Database User (default: postgres.${projectId}):`) || `postgres.${projectId}`;
159
+ const dbPassword = await question('Database Password (from Settings > Database):');
160
+
161
+ // Validate Supabase connection (same as validate script)
162
+ log('\nšŸ”„ Validating Supabase connection...', 'yellow');
163
+
134
164
  try {
135
165
  const client = createClient(supabaseUrl.trim(), supabaseServiceKey.trim());
136
- const { data, error } = await client.from('_test').select('*').limit(1);
166
+ const { error } = await client.from('vezlo_conversations').select('count').limit(0);
137
167
 
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');
142
- } else {
143
- log('āœ… Connection successful!\n', 'green');
168
+ if (error && error.code !== 'PGRST116') {
169
+ throw error;
144
170
  }
171
+
172
+ log('āœ… Supabase connection successful!\n', 'green');
145
173
  } catch (err) {
146
- log(`\nāš ļø Warning: Could not verify connection`, 'yellow');
147
- log('Continuing with setup...\n', 'yellow');
174
+ log(`āŒ Supabase connection failed: ${err.message}`, 'red');
175
+ log('āš ļø Continuing setup. You can fix credentials and rerun validations later.', 'yellow');
148
176
  }
149
177
 
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` : '';
178
+ // Validate database connection (same as validate script)
179
+ log('\nšŸ”„ Validating database connection...', 'yellow');
180
+
181
+ let client;
182
+ try {
183
+ const { Client } = require('pg');
184
+
185
+ client = new Client({
186
+ host: dbHost.trim(),
187
+ port: parseInt(dbPort.trim()),
188
+ database: dbName.trim(),
189
+ user: dbUser.trim(),
190
+ password: dbPassword.trim(),
191
+ ssl: { rejectUnauthorized: false }
192
+ });
153
193
 
154
- log('Database connection details:', 'blue');
155
- log(` Host: ${dbHost}`);
156
- log(` Port: 5432`);
157
- log(` Database: postgres`);
158
- log(` User: postgres\n`);
194
+ // Handle connection errors quietly for normal shutdowns
195
+ client.on('error', (err) => {
196
+ const msg = (err && err.message) ? err.message : String(err);
197
+ if (msg && (msg.includes('client_termination') || msg.includes(':shutdown'))) {
198
+ return; // ignore normal termination noise
199
+ }
200
+ console.error('Database connection error:', msg);
201
+ });
159
202
 
160
- const dbPassword = await question('Supabase Database Password (from Settings > Database):');
203
+ await client.connect();
204
+ log('āœ… Database connection successful!\n', 'green');
205
+ await client.end();
206
+ } catch (err) {
207
+ log(`āŒ Database connection failed: ${err.message}`, 'red');
208
+ log('āš ļø Continuing setup. Migrations will be skipped; see summary for next steps.', 'yellow');
209
+ }
161
210
 
162
211
  return {
163
212
  SUPABASE_URL: supabaseUrl.trim(),
164
- SUPABASE_ANON_KEY: supabaseAnonKey.trim(),
213
+ SUPABASE_ANON_KEY: supabaseAnonKey.trim() || '',
165
214
  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',
215
+ SUPABASE_DB_HOST: dbHost.trim(),
216
+ SUPABASE_DB_PORT: dbPort.trim(),
217
+ SUPABASE_DB_NAME: dbName.trim(),
218
+ SUPABASE_DB_USER: dbUser.trim(),
170
219
  SUPABASE_DB_PASSWORD: dbPassword.trim(),
171
220
  PORT: '3000',
172
221
  NODE_ENV: 'development',
173
- CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
222
+ CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173',
223
+ BASE_URL: 'http://localhost:3000'
174
224
  };
175
225
  }
176
226
 
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
- }
227
+ // Handle errors and cleanup
228
+ process.on('SIGINT', () => {
229
+ log('\n\nāš ļø Setup cancelled by user', 'yellow');
230
+ rl.close();
231
+ process.exit(0);
232
+ });
197
233
 
198
- async function loadExistingConfig() {
199
- const envPath = path.join(process.cwd(), '.env');
234
+ // Run the wizard
235
+ main().catch(error => {
236
+ log(`\nāŒ Setup failed: ${error.message}`, 'red');
237
+ rl.close();
238
+ process.exit(1);
239
+ });
200
240
 
201
- if (!fs.existsSync(envPath)) {
202
- log('\nāŒ No .env file found in current directory', 'red');
203
- throw new Error('.env file not found');
241
+ async function saveEnvFile(envPath, config) {
242
+ // Don't overwrite existing .env
243
+ if (fs.existsSync(envPath)) {
244
+ log('\nāš ļø .env already exists. Skipping overwrite. Please review values manually.', 'yellow');
245
+ return false;
204
246
  }
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;
247
+ // Generate a secure migration secret if not provided
248
+ try {
249
+ const crypto = require('crypto');
250
+ if (!config.MIGRATION_SECRET_KEY) {
251
+ config.MIGRATION_SECRET_KEY = crypto.randomBytes(32).toString('hex');
217
252
  }
218
- });
219
-
220
- return config;
221
- }
222
-
223
- async function saveEnvFile(envPath, config) {
253
+ } catch (_) {
254
+ // Fallback simple key if crypto unavailable (very unlikely)
255
+ config.MIGRATION_SECRET_KEY = config.MIGRATION_SECRET_KEY || `msk_${Date.now()}`;
256
+ }
224
257
  const envContent = `# Vezlo Assistant Server Configuration
225
258
  # Generated by setup wizard on ${new Date().toISOString()}
226
259
 
@@ -232,6 +265,9 @@ LOG_LEVEL=info
232
265
  # CORS Configuration
233
266
  CORS_ORIGINS=${config.CORS_ORIGINS || 'http://localhost:3000,http://localhost:5173'}
234
267
 
268
+ # Swagger Base URL
269
+ BASE_URL=${config.BASE_URL || 'http://localhost:3000'}
270
+
235
271
  # Rate Limiting
236
272
  RATE_LIMIT_WINDOW=60000
237
273
  RATE_LIMIT_MAX=100
@@ -251,8 +287,8 @@ SUPABASE_DB_PASSWORD=${config.SUPABASE_DB_PASSWORD || ''}
251
287
  # OpenAI Configuration
252
288
  OPENAI_API_KEY=${config.OPENAI_API_KEY || 'sk-your-openai-api-key'}
253
289
  AI_MODEL=${config.AI_MODEL || 'gpt-4o'}
254
- AI_TEMPERATURE=0.7
255
- AI_MAX_TOKENS=1000
290
+ AI_TEMPERATURE=${config.AI_TEMPERATURE || '0.7'}
291
+ AI_MAX_TOKENS=${config.AI_MAX_TOKENS || '1000'}
256
292
 
257
293
  # Organization Settings
258
294
  ORGANIZATION_NAME=Vezlo
@@ -261,66 +297,156 @@ ASSISTANT_NAME=Vezlo Assistant
261
297
  # Knowledge Base
262
298
  CHUNK_SIZE=1000
263
299
  CHUNK_OVERLAP=200
300
+
301
+ # Migration Security
302
+ MIGRATION_SECRET_KEY=${config.MIGRATION_SECRET_KEY}
264
303
  `;
265
304
 
266
305
  fs.writeFileSync(envPath, envContent, 'utf8');
306
+ return true;
267
307
  }
268
308
 
269
- async function setupDatabase(config) {
270
- log('\nšŸ”„ Setting up database tables...', 'yellow');
309
+ async function validateEnvironment(config) {
310
+ log('šŸ”„ Validating environment configuration...', 'yellow');
271
311
 
312
+ // Test Supabase connection (same as validate script)
272
313
  try {
273
- const { Client } = require('pg');
314
+ const client = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_KEY);
315
+ const { error } = await client.from('vezlo_conversations').select('count').limit(0);
316
+
317
+ if (error && error.code !== 'PGRST116') {
318
+ throw error;
319
+ }
320
+
321
+ log('āœ… Supabase API connection validated', 'green');
322
+ } catch (err) {
323
+ log(`āŒ Supabase API validation failed: ${err.message}`, 'red');
324
+ // non-blocking; proceed to DB check anyway
325
+ // return partial status so caller can decide
326
+ // (we'll still attempt DB validation)
327
+ }
274
328
 
275
- const client = new Client({
329
+ // Test database connection (same as validate script)
330
+ log('\nšŸ”„ Validating database connection...', 'yellow');
331
+ let client;
332
+ try {
333
+ const { Client } = require('pg');
334
+
335
+ client = new Client({
276
336
  host: config.SUPABASE_DB_HOST,
277
- port: parseInt(config.SUPABASE_DB_PORT || '5432'),
337
+ port: parseInt(config.SUPABASE_DB_PORT),
278
338
  database: config.SUPABASE_DB_NAME,
279
339
  user: config.SUPABASE_DB_USER,
280
340
  password: config.SUPABASE_DB_PASSWORD,
281
341
  ssl: { rejectUnauthorized: false }
282
342
  });
283
343
 
344
+ // Handle connection errors quietly for normal shutdowns
345
+ client.on('error', (err) => {
346
+ const msg = (err && err.message) ? err.message : String(err);
347
+ if (msg && (msg.includes('client_termination') || msg.includes(':shutdown'))) {
348
+ return; // ignore normal termination noise
349
+ }
350
+ console.error('Database connection error:', msg);
351
+ });
352
+
284
353
  await client.connect();
285
- log('āœ… Connected to database', 'green');
354
+ log('āœ… Database connection validated', 'green');
355
+ await client.end();
356
+ } catch (err) {
357
+ log(`āŒ Database validation failed: ${err.message}`, 'red');
358
+ log('āš ļø Continuing setup; migrations will be skipped.', 'yellow');
359
+ return { supabaseApi: 'unknown', database: 'failed' };
360
+ }
286
361
 
287
- // Read schema file
288
- const schemaPath = path.join(__dirname, '..', 'database-schema.sql');
362
+ log('āœ… Environment validation complete!\n', 'green');
363
+ return { supabaseApi: 'success', database: 'success' };
364
+ }
289
365
 
290
- if (!fs.existsSync(schemaPath)) {
291
- log('āŒ database-schema.sql not found', 'red');
292
- return;
293
- }
366
+ async function setupMigrations(config, validationStatus) {
367
+ log('šŸ”„ Checking migration status...', 'yellow');
294
368
 
295
- const schema = fs.readFileSync(schemaPath, 'utf8');
369
+ if (validationStatus.database !== 'success') {
370
+ log('āš ļø Skipping migrations because database validation failed.', 'yellow');
371
+ return { migrations: 'skipped' };
372
+ }
296
373
 
297
- log('šŸ”„ Creating tables...', 'yellow');
298
- await client.query(schema);
374
+ try {
375
+ const { Client } = require('pg');
376
+ const client = new Client({
377
+ host: config.SUPABASE_DB_HOST,
378
+ port: parseInt(config.SUPABASE_DB_PORT),
379
+ database: config.SUPABASE_DB_NAME,
380
+ user: config.SUPABASE_DB_USER,
381
+ password: config.SUPABASE_DB_PASSWORD,
382
+ ssl: { rejectUnauthorized: false }
383
+ });
299
384
 
300
- log('āœ… Database tables created successfully!', 'green');
385
+ await client.connect();
301
386
 
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
387
+ // Check if migrations table exists
388
+ const migrationTableExists = await client.query(`
389
+ SELECT EXISTS (
390
+ SELECT FROM information_schema.tables
391
+ WHERE table_schema = 'public'
392
+ AND table_name = 'knex_migrations'
393
+ );
309
394
  `);
310
395
 
311
- log('\nšŸ“Š Verified tables:', 'blue');
312
- result.rows.forEach(row => {
313
- log(` āœ“ ${row.table_name}`, 'green');
314
- });
315
- log('');
396
+ if (!migrationTableExists.rows[0].exists) {
397
+ log('šŸ“‹ No migrations table found. Database needs initial setup.', 'blue');
398
+
399
+ const runMigrations = await question('Run initial database migrations now? (y/n):');
400
+
401
+ if (runMigrations.toLowerCase() === 'y') {
402
+ log('šŸ”„ Running migrations...', 'yellow');
403
+
404
+ // Set environment variables for migration
405
+ process.env.SUPABASE_DB_HOST = config.SUPABASE_DB_HOST;
406
+ process.env.SUPABASE_DB_PORT = config.SUPABASE_DB_PORT;
407
+ process.env.SUPABASE_DB_NAME = config.SUPABASE_DB_NAME;
408
+ process.env.SUPABASE_DB_USER = config.SUPABASE_DB_USER;
409
+ process.env.SUPABASE_DB_PASSWORD = config.SUPABASE_DB_PASSWORD;
410
+
411
+ const { execSync } = require('child_process');
412
+ execSync('npm run migrate:latest', { stdio: 'inherit' });
413
+
414
+ log('āœ… Migrations completed successfully!', 'green');
415
+ return { migrations: 'success' };
416
+ } else {
417
+ log('\nāš ļø Migrations skipped. You can run them later using:', 'yellow');
418
+ log(' npm run migrate:latest', 'cyan');
419
+ log(' Or via API: GET /api/migrate?key=your-migration-secret\n', 'cyan');
420
+ return { migrations: 'skipped' };
421
+ }
422
+ } else {
423
+ // Check migration status
424
+ const migrationStatus = await client.query(`
425
+ SELECT COUNT(*) as count FROM knex_migrations;
426
+ `);
427
+
428
+ log(`šŸ“Š Found ${migrationStatus.rows[0].count} completed migrations`, 'blue');
429
+
430
+ const runPending = await question('Check for pending migrations? (y/n):');
431
+
432
+ if (runPending.toLowerCase() === 'y') {
433
+ log('šŸ”„ Checking for pending migrations...', 'yellow');
434
+
435
+ const { execSync } = require('child_process');
436
+ execSync('npm run migrate:latest', { stdio: 'inherit' });
437
+
438
+ log('āœ… Migration check completed!', 'green');
439
+ return { migrations: 'success' };
440
+ }
441
+ }
316
442
 
317
443
  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');
444
+ } catch (err) {
445
+ log(`āŒ Migration setup failed: ${err.message}`, 'red');
446
+ log('\nYou can run migrations manually later:', 'yellow');
447
+ log(' npm run migrate:latest', 'cyan');
448
+ log(' Or via API: GET /api/migrate?key=your-migration-secret\n', 'cyan');
449
+ return { migrations: 'failed' };
324
450
  }
325
451
  }
326
452
 
@@ -74,10 +74,11 @@ async function validateDatabase() {
74
74
  // Test database connection and validate tables
75
75
  log('Validating database tables...', 'yellow');
76
76
 
77
+ let client;
77
78
  try {
78
79
  const { Client } = require('pg');
79
80
 
80
- const client = new Client({
81
+ client = new Client({
81
82
  host: process.env.SUPABASE_DB_HOST,
82
83
  port: parseInt(process.env.SUPABASE_DB_PORT || '5432'),
83
84
  database: process.env.SUPABASE_DB_NAME || 'postgres',
@@ -86,14 +87,19 @@ async function validateDatabase() {
86
87
  ssl: { rejectUnauthorized: false }
87
88
  });
88
89
 
90
+ // Handle connection errors
91
+ client.on('error', (err) => {
92
+ console.error('Database connection error:', err.message);
93
+ });
94
+
89
95
  await client.connect();
90
96
 
91
97
  // Check required tables
92
98
  const requiredTables = [
93
- 'conversations',
94
- 'messages',
95
- 'message_feedback',
96
- 'knowledge_items'
99
+ 'vezlo_conversations',
100
+ 'vezlo_messages',
101
+ 'vezlo_message_feedback',
102
+ 'vezlo_knowledge_items'
97
103
  ];
98
104
 
99
105
  const result = await client.query(`
@@ -111,7 +117,9 @@ async function validateDatabase() {
111
117
  log(`\nāŒ Missing required tables:`, 'red');
112
118
  missingTables.forEach(table => log(` - ${table}`, 'red'));
113
119
  log('\nRun the setup wizard: ' + colors.bright + 'npx vezlo-setup' + colors.reset + '\n', 'yellow');
114
- await client.end();
120
+ if (client) {
121
+ await client.end();
122
+ }
115
123
  process.exit(1);
116
124
  }
117
125
 
@@ -156,10 +164,19 @@ async function validateDatabase() {
156
164
  log('Your server is ready to start:', 'cyan');
157
165
  log(' ' + colors.bright + 'vezlo-server' + colors.reset + '\n');
158
166
 
159
- await client.end();
167
+ if (client) {
168
+ await client.end();
169
+ }
160
170
 
161
171
  } catch (error) {
162
172
  log(`\nāŒ Database validation failed: ${error.message}\n`, 'red');
173
+ if (client) {
174
+ try {
175
+ await client.end();
176
+ } catch (closeError) {
177
+ // Ignore close errors
178
+ }
179
+ }
163
180
  process.exit(1);
164
181
  }
165
182
  }