@vezlo/assistant-server 1.0.0 ā 1.2.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.
- package/README.md +130 -27
- package/database-schema.sql +48 -47
- package/dist/src/config/logger.d.ts.map +1 -1
- package/dist/src/config/logger.js +28 -17
- package/dist/src/config/logger.js.map +1 -1
- package/dist/src/config/swagger.js +2 -2
- package/dist/src/config/swagger.js.map +1 -1
- package/dist/src/controllers/KnowledgeController.d.ts +4 -1
- package/dist/src/controllers/KnowledgeController.d.ts.map +1 -1
- package/dist/src/controllers/KnowledgeController.js +34 -1
- package/dist/src/controllers/KnowledgeController.js.map +1 -1
- package/dist/src/schemas/KnowledgeSchemas.d.ts +25 -0
- package/dist/src/schemas/KnowledgeSchemas.d.ts.map +1 -1
- package/dist/src/schemas/KnowledgeSchemas.js +14 -0
- package/dist/src/schemas/KnowledgeSchemas.js.map +1 -1
- package/dist/src/schemas/index.d.ts +25 -0
- package/dist/src/schemas/index.d.ts.map +1 -1
- package/dist/src/server.js +30 -4
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/KnowledgeBaseService.js +2 -2
- package/dist/src/services/KnowledgeBaseService.js.map +1 -1
- package/dist/src/storage/FeedbackRepository.js +4 -4
- package/dist/src/storage/FeedbackRepository.js.map +1 -1
- package/dist/src/storage/MessageRepository.js +4 -4
- package/dist/src/storage/MessageRepository.js.map +1 -1
- package/dist/src/storage/SupabaseStorage.js +9 -9
- package/dist/src/storage/SupabaseStorage.js.map +1 -1
- package/package.json +10 -3
- package/scripts/entrypoint.sh +21 -0
- package/scripts/setup.js +339 -0
- package/scripts/validate-db.js +171 -0
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vezlo Assistant Server Setup Wizard
|
|
5
|
+
* Interactive CLI to configure database and environment
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
12
|
+
|
|
13
|
+
const rl = readline.createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
output: process.stdout
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// ANSI color codes
|
|
19
|
+
const colors = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bright: '\x1b[1m',
|
|
22
|
+
green: '\x1b[32m',
|
|
23
|
+
yellow: '\x1b[33m',
|
|
24
|
+
blue: '\x1b[34m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
cyan: '\x1b[36m'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function log(message, color = 'reset') {
|
|
30
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function question(prompt) {
|
|
34
|
+
return new Promise(resolve => {
|
|
35
|
+
rl.question(`${colors.cyan}${prompt}${colors.reset} `, resolve);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
console.clear();
|
|
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)');
|
|
44
|
+
log(' 2. OpenAI API Configuration');
|
|
45
|
+
log(' 3. Automatic Table Creation\n');
|
|
46
|
+
|
|
47
|
+
// Step 1: Database Type Selection
|
|
48
|
+
log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā', 'cyan');
|
|
49
|
+
log(' STEP 1: Database Configuration', 'bright');
|
|
50
|
+
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n', 'cyan');
|
|
51
|
+
|
|
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
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 2: OpenAI Configuration
|
|
74
|
+
log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā', 'cyan');
|
|
75
|
+
log(' STEP 2: OpenAI API Configuration', 'bright');
|
|
76
|
+
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n', 'cyan');
|
|
77
|
+
|
|
78
|
+
const openaiKey = await question('Enter your OpenAI API key (sk-...):');
|
|
79
|
+
config.OPENAI_API_KEY = openaiKey.trim();
|
|
80
|
+
|
|
81
|
+
const aiModel = await question('AI Model (default: gpt-4o):') || 'gpt-4o';
|
|
82
|
+
config.AI_MODEL = aiModel.trim();
|
|
83
|
+
|
|
84
|
+
// Step 3: Save Configuration
|
|
85
|
+
log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā', 'cyan');
|
|
86
|
+
log(' STEP 3: Save Configuration', 'bright');
|
|
87
|
+
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n', 'cyan');
|
|
88
|
+
|
|
89
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
90
|
+
await saveEnvFile(envPath, config);
|
|
91
|
+
|
|
92
|
+
log('\nā
Configuration saved to .env', 'green');
|
|
93
|
+
|
|
94
|
+
// Step 4: Database Setup
|
|
95
|
+
log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā', 'cyan');
|
|
96
|
+
log(' STEP 4: Database Setup', 'bright');
|
|
97
|
+
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n', 'cyan');
|
|
98
|
+
|
|
99
|
+
const setupDb = await question('Setup database tables now? (y/n):');
|
|
100
|
+
|
|
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
|
+
}
|
|
107
|
+
|
|
108
|
+
// Final Instructions
|
|
109
|
+
log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā', 'green');
|
|
110
|
+
log(' š Setup Complete!', 'bright');
|
|
111
|
+
log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n', 'green');
|
|
112
|
+
|
|
113
|
+
log('Next steps:');
|
|
114
|
+
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');
|
|
118
|
+
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function setupSupabase() {
|
|
123
|
+
log('\nš¦ Supabase Configuration\n', 'blue');
|
|
124
|
+
log('You can find these values in your Supabase Dashboard:', 'yellow');
|
|
125
|
+
log(' Settings > API > Project URL & API Keys\n', 'yellow');
|
|
126
|
+
|
|
127
|
+
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');
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
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');
|
|
142
|
+
} else {
|
|
143
|
+
log('ā
Connection successful!\n', 'green');
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log(`\nā ļø Warning: Could not verify connection`, 'yellow');
|
|
147
|
+
log('Continuing with setup...\n', 'yellow');
|
|
148
|
+
}
|
|
149
|
+
|
|
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` : '';
|
|
153
|
+
|
|
154
|
+
log('Database connection details:', 'blue');
|
|
155
|
+
log(` Host: ${dbHost}`);
|
|
156
|
+
log(` Port: 5432`);
|
|
157
|
+
log(` Database: postgres`);
|
|
158
|
+
log(` User: postgres\n`);
|
|
159
|
+
|
|
160
|
+
const dbPassword = await question('Supabase Database Password (from Settings > Database):');
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
SUPABASE_URL: supabaseUrl.trim(),
|
|
164
|
+
SUPABASE_ANON_KEY: supabaseAnonKey.trim(),
|
|
165
|
+
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',
|
|
170
|
+
SUPABASE_DB_PASSWORD: dbPassword.trim(),
|
|
171
|
+
PORT: '3000',
|
|
172
|
+
NODE_ENV: 'development',
|
|
173
|
+
CORS_ORIGINS: 'http://localhost:3000,http://localhost:5173'
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
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
|
+
}
|
|
197
|
+
|
|
198
|
+
async function loadExistingConfig() {
|
|
199
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(envPath)) {
|
|
202
|
+
log('\nā No .env file found in current directory', 'red');
|
|
203
|
+
throw new Error('.env file not found');
|
|
204
|
+
}
|
|
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;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return config;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function saveEnvFile(envPath, config) {
|
|
224
|
+
const envContent = `# Vezlo Assistant Server Configuration
|
|
225
|
+
# Generated by setup wizard on ${new Date().toISOString()}
|
|
226
|
+
|
|
227
|
+
# Server Configuration
|
|
228
|
+
PORT=${config.PORT || '3000'}
|
|
229
|
+
NODE_ENV=${config.NODE_ENV || 'development'}
|
|
230
|
+
LOG_LEVEL=info
|
|
231
|
+
|
|
232
|
+
# CORS Configuration
|
|
233
|
+
CORS_ORIGINS=${config.CORS_ORIGINS || 'http://localhost:3000,http://localhost:5173'}
|
|
234
|
+
|
|
235
|
+
# Rate Limiting
|
|
236
|
+
RATE_LIMIT_WINDOW=60000
|
|
237
|
+
RATE_LIMIT_MAX=100
|
|
238
|
+
|
|
239
|
+
# Supabase Configuration
|
|
240
|
+
${config.SUPABASE_URL ? `SUPABASE_URL=${config.SUPABASE_URL}` : '# SUPABASE_URL=https://your-project.supabase.co'}
|
|
241
|
+
${config.SUPABASE_ANON_KEY ? `SUPABASE_ANON_KEY=${config.SUPABASE_ANON_KEY}` : '# SUPABASE_ANON_KEY=your-anon-key'}
|
|
242
|
+
${config.SUPABASE_SERVICE_KEY ? `SUPABASE_SERVICE_KEY=${config.SUPABASE_SERVICE_KEY}` : '# SUPABASE_SERVICE_KEY=your-service-role-key'}
|
|
243
|
+
|
|
244
|
+
# Database Configuration
|
|
245
|
+
SUPABASE_DB_HOST=${config.SUPABASE_DB_HOST || 'localhost'}
|
|
246
|
+
SUPABASE_DB_PORT=${config.SUPABASE_DB_PORT || '5432'}
|
|
247
|
+
SUPABASE_DB_NAME=${config.SUPABASE_DB_NAME || 'postgres'}
|
|
248
|
+
SUPABASE_DB_USER=${config.SUPABASE_DB_USER || 'postgres'}
|
|
249
|
+
SUPABASE_DB_PASSWORD=${config.SUPABASE_DB_PASSWORD || ''}
|
|
250
|
+
|
|
251
|
+
# OpenAI Configuration
|
|
252
|
+
OPENAI_API_KEY=${config.OPENAI_API_KEY || 'sk-your-openai-api-key'}
|
|
253
|
+
AI_MODEL=${config.AI_MODEL || 'gpt-4o'}
|
|
254
|
+
AI_TEMPERATURE=0.7
|
|
255
|
+
AI_MAX_TOKENS=1000
|
|
256
|
+
|
|
257
|
+
# Organization Settings
|
|
258
|
+
ORGANIZATION_NAME=Vezlo
|
|
259
|
+
ASSISTANT_NAME=Vezlo Assistant
|
|
260
|
+
|
|
261
|
+
# Knowledge Base
|
|
262
|
+
CHUNK_SIZE=1000
|
|
263
|
+
CHUNK_OVERLAP=200
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function setupDatabase(config) {
|
|
270
|
+
log('\nš Setting up database tables...', 'yellow');
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const { Client } = require('pg');
|
|
274
|
+
|
|
275
|
+
const client = new Client({
|
|
276
|
+
host: config.SUPABASE_DB_HOST,
|
|
277
|
+
port: parseInt(config.SUPABASE_DB_PORT || '5432'),
|
|
278
|
+
database: config.SUPABASE_DB_NAME,
|
|
279
|
+
user: config.SUPABASE_DB_USER,
|
|
280
|
+
password: config.SUPABASE_DB_PASSWORD,
|
|
281
|
+
ssl: { rejectUnauthorized: false }
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await client.connect();
|
|
285
|
+
log('ā
Connected to database', 'green');
|
|
286
|
+
|
|
287
|
+
// Read schema file
|
|
288
|
+
const schemaPath = path.join(__dirname, '..', 'database-schema.sql');
|
|
289
|
+
|
|
290
|
+
if (!fs.existsSync(schemaPath)) {
|
|
291
|
+
log('ā database-schema.sql not found', 'red');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const schema = fs.readFileSync(schemaPath, 'utf8');
|
|
296
|
+
|
|
297
|
+
log('š Creating tables...', 'yellow');
|
|
298
|
+
await client.query(schema);
|
|
299
|
+
|
|
300
|
+
log('ā
Database tables created successfully!', 'green');
|
|
301
|
+
|
|
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
|
|
309
|
+
`);
|
|
310
|
+
|
|
311
|
+
log('\nš Verified tables:', 'blue');
|
|
312
|
+
result.rows.forEach(row => {
|
|
313
|
+
log(` ā ${row.table_name}`, 'green');
|
|
314
|
+
});
|
|
315
|
+
log('');
|
|
316
|
+
|
|
317
|
+
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');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Handle errors and cleanup
|
|
328
|
+
process.on('SIGINT', () => {
|
|
329
|
+
log('\n\nā ļø Setup cancelled by user', 'yellow');
|
|
330
|
+
rl.close();
|
|
331
|
+
process.exit(0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Run the wizard
|
|
335
|
+
main().catch(error => {
|
|
336
|
+
log(`\nā Setup failed: ${error.message}`, 'red');
|
|
337
|
+
rl.close();
|
|
338
|
+
process.exit(1);
|
|
339
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vezlo Assistant Server - Database Validation
|
|
5
|
+
* Validates database connection and table setup
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const dotenv = require('dotenv');
|
|
9
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
10
|
+
|
|
11
|
+
// Load environment variables
|
|
12
|
+
dotenv.config();
|
|
13
|
+
|
|
14
|
+
const colors = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
bright: '\x1b[1m'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function log(message, color = 'reset') {
|
|
24
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function validateDatabase() {
|
|
28
|
+
log('\nš Validating Database Configuration\n', 'cyan');
|
|
29
|
+
|
|
30
|
+
// Check environment variables
|
|
31
|
+
log('Checking environment variables...', 'yellow');
|
|
32
|
+
|
|
33
|
+
const requiredVars = [
|
|
34
|
+
'SUPABASE_URL',
|
|
35
|
+
'SUPABASE_SERVICE_KEY',
|
|
36
|
+
'SUPABASE_DB_HOST',
|
|
37
|
+
'SUPABASE_DB_PASSWORD'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const missing = requiredVars.filter(key => !process.env[key]);
|
|
41
|
+
|
|
42
|
+
if (missing.length > 0) {
|
|
43
|
+
log(`\nā Missing required environment variables:`, 'red');
|
|
44
|
+
missing.forEach(key => log(` - ${key}`, 'red'));
|
|
45
|
+
log('\nRun the setup wizard: ' + colors.bright + 'npx vezlo-setup' + colors.reset + '\n', 'yellow');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
log('ā
Environment variables configured\n', 'green');
|
|
50
|
+
|
|
51
|
+
// Test Supabase connection
|
|
52
|
+
log('Testing Supabase connection...', 'yellow');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const supabase = createClient(
|
|
56
|
+
process.env.SUPABASE_URL,
|
|
57
|
+
process.env.SUPABASE_SERVICE_KEY
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Try to query a table
|
|
61
|
+
const { error } = await supabase.from('vezlo_conversations').select('count').limit(0);
|
|
62
|
+
|
|
63
|
+
if (error && error.code !== 'PGRST116') {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log('ā
Supabase connection successful\n', 'green');
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
log(`ā Supabase connection failed: ${error.message}\n`, 'red');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Test database connection and validate tables
|
|
75
|
+
log('Validating database tables...', 'yellow');
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const { Client } = require('pg');
|
|
79
|
+
|
|
80
|
+
const client = new Client({
|
|
81
|
+
host: process.env.SUPABASE_DB_HOST,
|
|
82
|
+
port: parseInt(process.env.SUPABASE_DB_PORT || '5432'),
|
|
83
|
+
database: process.env.SUPABASE_DB_NAME || 'postgres',
|
|
84
|
+
user: process.env.SUPABASE_DB_USER || 'postgres',
|
|
85
|
+
password: process.env.SUPABASE_DB_PASSWORD,
|
|
86
|
+
ssl: { rejectUnauthorized: false }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await client.connect();
|
|
90
|
+
|
|
91
|
+
// Check required tables
|
|
92
|
+
const requiredTables = [
|
|
93
|
+
'conversations',
|
|
94
|
+
'messages',
|
|
95
|
+
'message_feedback',
|
|
96
|
+
'knowledge_items'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const result = await client.query(`
|
|
100
|
+
SELECT table_name
|
|
101
|
+
FROM information_schema.tables
|
|
102
|
+
WHERE table_schema = 'public'
|
|
103
|
+
AND table_name = ANY($1)
|
|
104
|
+
ORDER BY table_name
|
|
105
|
+
`, [requiredTables]);
|
|
106
|
+
|
|
107
|
+
const existingTables = result.rows.map(row => row.table_name);
|
|
108
|
+
const missingTables = requiredTables.filter(t => !existingTables.includes(t));
|
|
109
|
+
|
|
110
|
+
if (missingTables.length > 0) {
|
|
111
|
+
log(`\nā Missing required tables:`, 'red');
|
|
112
|
+
missingTables.forEach(table => log(` - ${table}`, 'red'));
|
|
113
|
+
log('\nRun the setup wizard: ' + colors.bright + 'npx vezlo-setup' + colors.reset + '\n', 'yellow');
|
|
114
|
+
await client.end();
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
log('ā
All required tables exist\n', 'green');
|
|
119
|
+
|
|
120
|
+
// Check table structure
|
|
121
|
+
log('Checking table structure...', 'yellow');
|
|
122
|
+
|
|
123
|
+
const schemaCheck = await client.query(`
|
|
124
|
+
SELECT
|
|
125
|
+
t.table_name,
|
|
126
|
+
COUNT(c.column_name) as column_count
|
|
127
|
+
FROM information_schema.tables t
|
|
128
|
+
LEFT JOIN information_schema.columns c
|
|
129
|
+
ON c.table_name = t.table_name
|
|
130
|
+
AND c.table_schema = t.table_schema
|
|
131
|
+
WHERE t.table_schema = 'public'
|
|
132
|
+
AND t.table_name = ANY($1)
|
|
133
|
+
GROUP BY t.table_name
|
|
134
|
+
ORDER BY t.table_name
|
|
135
|
+
`, [requiredTables]);
|
|
136
|
+
|
|
137
|
+
log('\nš Table Structure:', 'cyan');
|
|
138
|
+
schemaCheck.rows.forEach(row => {
|
|
139
|
+
log(` ā ${row.table_name} (${row.column_count} columns)`, 'green');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Check for vector extension
|
|
143
|
+
const vectorCheck = await client.query(`
|
|
144
|
+
SELECT EXISTS(
|
|
145
|
+
SELECT 1 FROM pg_extension WHERE extname = 'vector'
|
|
146
|
+
) as has_vector
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
if (vectorCheck.rows[0].has_vector) {
|
|
150
|
+
log(' ā pgvector extension enabled', 'green');
|
|
151
|
+
} else {
|
|
152
|
+
log(' ā ļø pgvector extension not enabled (semantic search disabled)', 'yellow');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
log('\nā
Database validation complete!\n', 'green');
|
|
156
|
+
log('Your server is ready to start:', 'cyan');
|
|
157
|
+
log(' ' + colors.bright + 'vezlo-server' + colors.reset + '\n');
|
|
158
|
+
|
|
159
|
+
await client.end();
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
log(`\nā Database validation failed: ${error.message}\n`, 'red');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Run validation
|
|
168
|
+
validateDatabase().catch(error => {
|
|
169
|
+
log(`\nā Validation failed: ${error.message}\n`, 'red');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|