archicore 0.2.0 → 0.2.2
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/dist/cli/commands/init.js +37 -14
- package/dist/cli/commands/interactive.js +174 -21
- package/dist/github/github-service.d.ts +5 -1
- package/dist/github/github-service.js +21 -3
- package/dist/orchestrator/index.js +19 -0
- package/dist/semantic-memory/embedding-service.d.ts +8 -1
- package/dist/semantic-memory/embedding-service.js +141 -47
- package/dist/server/index.js +163 -1
- package/dist/server/routes/admin.js +439 -1
- package/dist/server/routes/api.js +17 -2
- package/dist/server/routes/auth.js +46 -0
- package/dist/server/routes/developer.js +1 -1
- package/dist/server/routes/device-auth.js +10 -1
- package/dist/server/routes/github.js +17 -4
- package/dist/server/routes/report-issue.d.ts +7 -0
- package/dist/server/routes/report-issue.js +307 -0
- package/dist/server/services/audit-service.d.ts +88 -0
- package/dist/server/services/audit-service.js +380 -0
- package/dist/server/services/auth-service.d.ts +32 -5
- package/dist/server/services/auth-service.js +347 -54
- package/dist/server/services/cache.d.ts +77 -0
- package/dist/server/services/cache.js +245 -0
- package/dist/server/services/database.d.ts +43 -0
- package/dist/server/services/database.js +221 -0
- package/dist/server/services/encryption.d.ts +48 -0
- package/dist/server/services/encryption.js +148 -0
- package/package.json +17 -2
|
@@ -5,7 +5,9 @@ export class EmbeddingService {
|
|
|
5
5
|
config;
|
|
6
6
|
initialized = false;
|
|
7
7
|
_isAvailable = false;
|
|
8
|
-
|
|
8
|
+
jinaApiKeys = [];
|
|
9
|
+
currentKeyIndex = 0;
|
|
10
|
+
keyFailures = new Map();
|
|
9
11
|
embeddingDimension = 1024; // Jina default, OpenAI small = 1536
|
|
10
12
|
constructor(config) {
|
|
11
13
|
this.config = config;
|
|
@@ -23,16 +25,21 @@ export class EmbeddingService {
|
|
|
23
25
|
}
|
|
24
26
|
try {
|
|
25
27
|
if (this.config.provider === 'jina') {
|
|
26
|
-
// Jina AI - free embeddings
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// Jina AI - free embeddings with key rotation support
|
|
29
|
+
// Support multiple keys: JINA_API_KEYS (comma-separated) or JINA_API_KEY (single)
|
|
30
|
+
const keysEnv = process.env.JINA_API_KEYS || process.env.JINA_API_KEY || '';
|
|
31
|
+
this.jinaApiKeys = keysEnv
|
|
32
|
+
.split(',')
|
|
33
|
+
.map(k => k.trim())
|
|
34
|
+
.filter(k => k.length > 0);
|
|
35
|
+
if (this.jinaApiKeys.length === 0) {
|
|
36
|
+
Logger.warn('JINA_API_KEY(S) not set - semantic search disabled');
|
|
30
37
|
Logger.info('Get free API key at: https://jina.ai/embeddings/');
|
|
31
38
|
return;
|
|
32
39
|
}
|
|
33
40
|
this.embeddingDimension = 1024;
|
|
34
41
|
this._isAvailable = true;
|
|
35
|
-
Logger.success(
|
|
42
|
+
Logger.success(`Jina AI embeddings enabled (${this.jinaApiKeys.length} key${this.jinaApiKeys.length > 1 ? 's' : ''} configured)`);
|
|
36
43
|
}
|
|
37
44
|
else {
|
|
38
45
|
// OpenAI embeddings
|
|
@@ -51,6 +58,45 @@ export class EmbeddingService {
|
|
|
51
58
|
Logger.warn('Embedding init failed: ' + error);
|
|
52
59
|
}
|
|
53
60
|
}
|
|
61
|
+
getCurrentJinaKey() {
|
|
62
|
+
if (this.jinaApiKeys.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
return this.jinaApiKeys[this.currentKeyIndex];
|
|
65
|
+
}
|
|
66
|
+
rotateToNextKey() {
|
|
67
|
+
if (this.jinaApiKeys.length <= 1)
|
|
68
|
+
return false;
|
|
69
|
+
const oldIndex = this.currentKeyIndex;
|
|
70
|
+
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.jinaApiKeys.length;
|
|
71
|
+
// Track failure for old key
|
|
72
|
+
const failure = this.keyFailures.get(oldIndex) || { count: 0, lastFail: new Date() };
|
|
73
|
+
failure.count++;
|
|
74
|
+
failure.lastFail = new Date();
|
|
75
|
+
this.keyFailures.set(oldIndex, failure);
|
|
76
|
+
Logger.warn(`Jina API key ${oldIndex + 1} exhausted/failed, rotating to key ${this.currentKeyIndex + 1}`);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
shouldSkipKey(index) {
|
|
80
|
+
const failure = this.keyFailures.get(index);
|
|
81
|
+
if (!failure)
|
|
82
|
+
return false;
|
|
83
|
+
// Skip key if it failed recently (within 5 minutes) and has many failures
|
|
84
|
+
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
|
85
|
+
return failure.count >= 3 && failure.lastFail > fiveMinutesAgo;
|
|
86
|
+
}
|
|
87
|
+
findWorkingKeyIndex() {
|
|
88
|
+
const startIndex = this.currentKeyIndex;
|
|
89
|
+
for (let i = 0; i < this.jinaApiKeys.length; i++) {
|
|
90
|
+
const index = (startIndex + i) % this.jinaApiKeys.length;
|
|
91
|
+
if (!this.shouldSkipKey(index)) {
|
|
92
|
+
return index;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// All keys are marked as failed, reset failures and try first key
|
|
96
|
+
Logger.warn('All Jina API keys marked as failed, resetting failure counters');
|
|
97
|
+
this.keyFailures.clear();
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
54
100
|
isAvailable() {
|
|
55
101
|
this.ensureInitialized();
|
|
56
102
|
return this._isAvailable;
|
|
@@ -60,7 +106,7 @@ export class EmbeddingService {
|
|
|
60
106
|
if (!this._isAvailable)
|
|
61
107
|
return new Array(this.embeddingDimension).fill(0);
|
|
62
108
|
try {
|
|
63
|
-
if (this.config.provider === 'jina' && this.
|
|
109
|
+
if (this.config.provider === 'jina' && this.jinaApiKeys.length > 0) {
|
|
64
110
|
return await this.generateJinaEmbedding(text);
|
|
65
111
|
}
|
|
66
112
|
else if (this.openai) {
|
|
@@ -73,28 +119,51 @@ export class EmbeddingService {
|
|
|
73
119
|
return new Array(this.embeddingDimension).fill(0);
|
|
74
120
|
}
|
|
75
121
|
}
|
|
76
|
-
async generateJinaEmbedding(text) {
|
|
77
|
-
|
|
122
|
+
async generateJinaEmbedding(text, retryCount = 0) {
|
|
123
|
+
const maxRetries = this.jinaApiKeys.length;
|
|
124
|
+
// Find a working key index before first attempt
|
|
125
|
+
if (retryCount === 0) {
|
|
126
|
+
this.currentKeyIndex = this.findWorkingKeyIndex();
|
|
127
|
+
}
|
|
128
|
+
const apiKey = this.getCurrentJinaKey();
|
|
129
|
+
if (!apiKey)
|
|
78
130
|
throw new Error('Jina API key not set');
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch('https://api.jina.ai/v1/embeddings', {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'Authorization': `Bearer ${apiKey}`
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
model: this.config.model || 'jina-embeddings-v3',
|
|
140
|
+
task: 'text-matching',
|
|
141
|
+
dimensions: 1024,
|
|
142
|
+
input: [text.substring(0, 8000)]
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const errorText = await response.text();
|
|
147
|
+
// Check for rate limit or auth errors - try next key
|
|
148
|
+
if (response.status === 429 || response.status === 401 || response.status === 403) {
|
|
149
|
+
if (retryCount < maxRetries - 1 && this.rotateToNextKey()) {
|
|
150
|
+
Logger.warn(`Jina API key error (${response.status}), trying next key...`);
|
|
151
|
+
return this.generateJinaEmbedding(text, retryCount + 1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Jina API error: ${response.status} ${errorText}`);
|
|
155
|
+
}
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
return data.data[0].embedding;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// Network error or other issue - try next key
|
|
161
|
+
if (retryCount < maxRetries - 1 && this.rotateToNextKey()) {
|
|
162
|
+
Logger.warn(`Jina API request failed, trying next key...`);
|
|
163
|
+
return this.generateJinaEmbedding(text, retryCount + 1);
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
95
166
|
}
|
|
96
|
-
const data = await response.json();
|
|
97
|
-
return data.data[0].embedding;
|
|
98
167
|
}
|
|
99
168
|
/**
|
|
100
169
|
* Generate embeddings for multiple texts - uses true batch API when available
|
|
@@ -110,30 +179,13 @@ export class EmbeddingService {
|
|
|
110
179
|
Logger.progress(`Generating embeddings for ${texts.length} texts...`);
|
|
111
180
|
try {
|
|
112
181
|
// Use true batch API for Jina (much faster!)
|
|
113
|
-
if (this.config.provider === 'jina' && this.
|
|
182
|
+
if (this.config.provider === 'jina' && this.jinaApiKeys.length > 0) {
|
|
114
183
|
const BATCH_SIZE = 500;
|
|
115
184
|
const allEmbeddings = [];
|
|
116
185
|
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
117
186
|
const batch = texts.slice(i, i + BATCH_SIZE).map(t => t.substring(0, 8000));
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
headers: {
|
|
121
|
-
'Content-Type': 'application/json',
|
|
122
|
-
'Authorization': `Bearer ${this.jinaApiKey}`
|
|
123
|
-
},
|
|
124
|
-
body: JSON.stringify({
|
|
125
|
-
model: this.config.model || 'jina-embeddings-v3',
|
|
126
|
-
task: 'text-matching',
|
|
127
|
-
dimensions: 1024,
|
|
128
|
-
input: batch
|
|
129
|
-
})
|
|
130
|
-
});
|
|
131
|
-
if (!response.ok) {
|
|
132
|
-
const error = await response.text();
|
|
133
|
-
throw new Error(`Jina API error: ${response.status} ${error}`);
|
|
134
|
-
}
|
|
135
|
-
const data = await response.json();
|
|
136
|
-
allEmbeddings.push(...data.data.map(d => d.embedding));
|
|
187
|
+
const batchEmbeddings = await this.generateJinaBatchWithRetry(batch);
|
|
188
|
+
allEmbeddings.push(...batchEmbeddings);
|
|
137
189
|
if (progressCallback) {
|
|
138
190
|
progressCallback(Math.min(i + BATCH_SIZE, texts.length), texts.length);
|
|
139
191
|
}
|
|
@@ -161,6 +213,48 @@ export class EmbeddingService {
|
|
|
161
213
|
return texts.map(() => new Array(this.embeddingDimension).fill(0));
|
|
162
214
|
}
|
|
163
215
|
}
|
|
216
|
+
async generateJinaBatchWithRetry(batch, retryCount = 0) {
|
|
217
|
+
const maxRetries = this.jinaApiKeys.length;
|
|
218
|
+
const apiKey = this.getCurrentJinaKey();
|
|
219
|
+
if (!apiKey)
|
|
220
|
+
throw new Error('Jina API key not set');
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch('https://api.jina.ai/v1/embeddings', {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
headers: {
|
|
225
|
+
'Content-Type': 'application/json',
|
|
226
|
+
'Authorization': `Bearer ${apiKey}`
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
model: this.config.model || 'jina-embeddings-v3',
|
|
230
|
+
task: 'text-matching',
|
|
231
|
+
dimensions: 1024,
|
|
232
|
+
input: batch
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const errorText = await response.text();
|
|
237
|
+
// Check for rate limit or auth errors - try next key
|
|
238
|
+
if (response.status === 429 || response.status === 401 || response.status === 403) {
|
|
239
|
+
if (retryCount < maxRetries - 1 && this.rotateToNextKey()) {
|
|
240
|
+
Logger.warn(`Jina API batch error (${response.status}), trying next key...`);
|
|
241
|
+
return this.generateJinaBatchWithRetry(batch, retryCount + 1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`Jina API error: ${response.status} ${errorText}`);
|
|
245
|
+
}
|
|
246
|
+
const data = await response.json();
|
|
247
|
+
return data.data.map(d => d.embedding);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
// Network error or other issue - try next key
|
|
251
|
+
if (retryCount < maxRetries - 1 && this.rotateToNextKey()) {
|
|
252
|
+
Logger.warn(`Jina API batch request failed, trying next key...`);
|
|
253
|
+
return this.generateJinaBatchWithRetry(batch, retryCount + 1);
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
164
258
|
async generateOpenAIEmbedding(text) {
|
|
165
259
|
if (!this.openai)
|
|
166
260
|
throw new Error('OpenAI client not initialized');
|
package/dist/server/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import morgan from 'morgan';
|
|
|
16
16
|
import rateLimit from 'express-rate-limit';
|
|
17
17
|
import { createServer } from 'http';
|
|
18
18
|
import path from 'path';
|
|
19
|
+
import fs from 'fs';
|
|
19
20
|
import { fileURLToPath } from 'url';
|
|
20
21
|
import { Logger } from '../utils/logger.js';
|
|
21
22
|
import { apiRouter } from './routes/api.js';
|
|
@@ -25,6 +26,10 @@ import { adminRouter } from './routes/admin.js';
|
|
|
25
26
|
import { developerRouter } from './routes/developer.js';
|
|
26
27
|
import { githubRouter } from './routes/github.js';
|
|
27
28
|
import deviceAuthRouter from './routes/device-auth.js';
|
|
29
|
+
import { reportIssueRouter } from './routes/report-issue.js';
|
|
30
|
+
import { cache } from './services/cache.js';
|
|
31
|
+
import { db } from './services/database.js';
|
|
32
|
+
import { AuthService } from './services/auth-service.js';
|
|
28
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
34
|
const __dirname = path.dirname(__filename);
|
|
30
35
|
// CORS whitelist - настройте под свои домены
|
|
@@ -53,6 +58,89 @@ const createRateLimiter = (windowMs, max, message) => rateLimit({
|
|
|
53
58
|
});
|
|
54
59
|
},
|
|
55
60
|
});
|
|
61
|
+
// Settings file path
|
|
62
|
+
const SETTINGS_FILE = path.join(process.cwd(), '.archicore', 'settings.json');
|
|
63
|
+
// Load settings helper
|
|
64
|
+
function loadMaintenanceSettings() {
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
67
|
+
const data = fs.readFileSync(SETTINGS_FILE, 'utf-8');
|
|
68
|
+
const settings = JSON.parse(data);
|
|
69
|
+
return {
|
|
70
|
+
enabled: settings.maintenance?.enabled || false,
|
|
71
|
+
message: settings.maintenance?.message || 'ArchiCore is currently undergoing maintenance.'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
Logger.error('Failed to load maintenance settings:', error);
|
|
77
|
+
}
|
|
78
|
+
return { enabled: false, message: '' };
|
|
79
|
+
}
|
|
80
|
+
// Maintenance middleware
|
|
81
|
+
const maintenanceMiddleware = async (req, res, next) => {
|
|
82
|
+
const maintenance = loadMaintenanceSettings();
|
|
83
|
+
// If maintenance is not enabled, continue
|
|
84
|
+
if (!maintenance.enabled) {
|
|
85
|
+
return next();
|
|
86
|
+
}
|
|
87
|
+
// Allow certain paths even in maintenance mode
|
|
88
|
+
const allowedPaths = [
|
|
89
|
+
'/api/auth',
|
|
90
|
+
'/api/admin',
|
|
91
|
+
'/auth',
|
|
92
|
+
'/login',
|
|
93
|
+
'/admin',
|
|
94
|
+
'/maintenance.html',
|
|
95
|
+
'/favicon.svg',
|
|
96
|
+
'/favicon.ico',
|
|
97
|
+
'/health',
|
|
98
|
+
'/api/admin/maintenance-status',
|
|
99
|
+
// Static assets
|
|
100
|
+
'/fonts/',
|
|
101
|
+
'/css/',
|
|
102
|
+
'/js/',
|
|
103
|
+
'/images/',
|
|
104
|
+
'/assets/'
|
|
105
|
+
];
|
|
106
|
+
// Check if path is allowed
|
|
107
|
+
const isAllowed = allowedPaths.some(p => req.path.startsWith(p));
|
|
108
|
+
if (isAllowed) {
|
|
109
|
+
return next();
|
|
110
|
+
}
|
|
111
|
+
// Check if user is admin via token
|
|
112
|
+
const authHeader = req.headers.authorization;
|
|
113
|
+
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
|
|
114
|
+
// Also check for token in cookies (for browser requests)
|
|
115
|
+
const cookieToken = req.headers.cookie?.split(';')
|
|
116
|
+
.find(c => c.trim().startsWith('archicore_token='))
|
|
117
|
+
?.split('=')[1];
|
|
118
|
+
const actualToken = token || cookieToken;
|
|
119
|
+
if (actualToken) {
|
|
120
|
+
try {
|
|
121
|
+
const authService = AuthService.getInstance();
|
|
122
|
+
const user = await authService.validateToken(actualToken);
|
|
123
|
+
if (user && user.tier === 'admin') {
|
|
124
|
+
return next(); // Admin can access
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// Token invalid, continue to maintenance page
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// For API requests, return JSON
|
|
132
|
+
if (req.path.startsWith('/api/')) {
|
|
133
|
+
res.status(503).json({
|
|
134
|
+
error: 'Service Unavailable',
|
|
135
|
+
message: maintenance.message,
|
|
136
|
+
maintenance: true
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// For browser requests, serve maintenance page
|
|
141
|
+
const maintenancePath = path.join(__dirname, '../../public/maintenance.html');
|
|
142
|
+
res.status(503).sendFile(maintenancePath);
|
|
143
|
+
};
|
|
56
144
|
export class ArchiCoreServer {
|
|
57
145
|
app;
|
|
58
146
|
server = null;
|
|
@@ -151,11 +239,14 @@ export class ArchiCoreServer {
|
|
|
151
239
|
res.setHeader('X-Request-ID', requestId);
|
|
152
240
|
next();
|
|
153
241
|
});
|
|
242
|
+
// Maintenance mode check
|
|
243
|
+
this.app.use(maintenanceMiddleware);
|
|
154
244
|
// Статические файлы (фронтенд)
|
|
155
245
|
const publicPath = path.join(__dirname, '../../public');
|
|
156
246
|
this.app.use(express.static(publicPath, {
|
|
157
247
|
maxAge: process.env.NODE_ENV === 'production' ? '1d' : 0,
|
|
158
248
|
etag: true,
|
|
249
|
+
index: false, // Отключаем автоматический index.html - используем явные маршруты
|
|
159
250
|
}));
|
|
160
251
|
}
|
|
161
252
|
setupRoutes() {
|
|
@@ -173,6 +264,8 @@ export class ArchiCoreServer {
|
|
|
173
264
|
this.app.use('/api', apiRouter);
|
|
174
265
|
// Upload маршруты
|
|
175
266
|
this.app.use('/api/upload', uploadRouter);
|
|
267
|
+
// Report issue routes
|
|
268
|
+
this.app.use('/api/report-issue', reportIssueRouter);
|
|
176
269
|
// Health check
|
|
177
270
|
this.app.get('/health', (_req, res) => {
|
|
178
271
|
res.json({
|
|
@@ -187,7 +280,62 @@ export class ArchiCoreServer {
|
|
|
187
280
|
const deviceAuthPath = path.join(__dirname, '../../public/device-auth.html');
|
|
188
281
|
res.sendFile(deviceAuthPath);
|
|
189
282
|
});
|
|
190
|
-
//
|
|
283
|
+
// Clean URL routing
|
|
284
|
+
// Landing page (главная)
|
|
285
|
+
this.app.get('/', (_req, res) => {
|
|
286
|
+
res.sendFile(path.join(__dirname, '../../public/landing.html'));
|
|
287
|
+
});
|
|
288
|
+
// Dashboard (после авторизации)
|
|
289
|
+
this.app.get('/dashboard', (_req, res) => {
|
|
290
|
+
res.sendFile(path.join(__dirname, '../../public/index.html'));
|
|
291
|
+
});
|
|
292
|
+
// Add new project page
|
|
293
|
+
this.app.get('/new', (_req, res) => {
|
|
294
|
+
res.sendFile(path.join(__dirname, '../../public/index.html'));
|
|
295
|
+
});
|
|
296
|
+
// Pricing page
|
|
297
|
+
this.app.get('/pricing', (_req, res) => {
|
|
298
|
+
res.sendFile(path.join(__dirname, '../../public/pricing.html'));
|
|
299
|
+
});
|
|
300
|
+
// API Dashboard page
|
|
301
|
+
this.app.get('/api-dashboard', (_req, res) => {
|
|
302
|
+
res.sendFile(path.join(__dirname, '../../public/api-dashboard.html'));
|
|
303
|
+
});
|
|
304
|
+
// Auth page
|
|
305
|
+
this.app.get('/auth', (_req, res) => {
|
|
306
|
+
res.sendFile(path.join(__dirname, '../../public/auth.html'));
|
|
307
|
+
});
|
|
308
|
+
// Login alias
|
|
309
|
+
this.app.get('/login', (_req, res) => {
|
|
310
|
+
res.sendFile(path.join(__dirname, '../../public/auth.html'));
|
|
311
|
+
});
|
|
312
|
+
// Register alias
|
|
313
|
+
this.app.get('/register', (_req, res) => {
|
|
314
|
+
res.redirect('/auth?mode=register');
|
|
315
|
+
});
|
|
316
|
+
// Admin page
|
|
317
|
+
this.app.get('/admin', (_req, res) => {
|
|
318
|
+
res.sendFile(path.join(__dirname, '../../public/admin.html'));
|
|
319
|
+
});
|
|
320
|
+
// Blog page
|
|
321
|
+
this.app.get('/blog', (_req, res) => {
|
|
322
|
+
res.sendFile(path.join(__dirname, '../../public/blog.html'));
|
|
323
|
+
});
|
|
324
|
+
// Report issue page (without .html extension)
|
|
325
|
+
this.app.get('/report-issue', (_req, res) => {
|
|
326
|
+
res.sendFile(path.join(__dirname, '../../public/report-issue.html'));
|
|
327
|
+
});
|
|
328
|
+
// Legal pages
|
|
329
|
+
this.app.get('/privacy', (_req, res) => {
|
|
330
|
+
res.sendFile(path.join(__dirname, '../../public/privacy.html'));
|
|
331
|
+
});
|
|
332
|
+
this.app.get('/terms', (_req, res) => {
|
|
333
|
+
res.sendFile(path.join(__dirname, '../../public/terms.html'));
|
|
334
|
+
});
|
|
335
|
+
this.app.get('/security', (_req, res) => {
|
|
336
|
+
res.sendFile(path.join(__dirname, '../../public/security.html'));
|
|
337
|
+
});
|
|
338
|
+
// SPA fallback - все остальные маршруты отдают index.html (dashboard)
|
|
191
339
|
this.app.get('/*splat', (_req, res) => {
|
|
192
340
|
const indexPath = path.join(__dirname, '../../public/index.html');
|
|
193
341
|
res.sendFile(indexPath);
|
|
@@ -208,6 +356,17 @@ export class ArchiCoreServer {
|
|
|
208
356
|
});
|
|
209
357
|
}
|
|
210
358
|
async start() {
|
|
359
|
+
// Initialize database
|
|
360
|
+
try {
|
|
361
|
+
await db.init();
|
|
362
|
+
const authService = AuthService.getInstance();
|
|
363
|
+
await authService.initDatabase();
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
Logger.warn('Database initialization failed, using JSON fallback:', error);
|
|
367
|
+
}
|
|
368
|
+
// Initialize cache
|
|
369
|
+
await cache.connect();
|
|
211
370
|
return new Promise((resolve) => {
|
|
212
371
|
this.server = createServer(this.app);
|
|
213
372
|
const host = this.config.host || '0.0.0.0';
|
|
@@ -221,6 +380,9 @@ export class ArchiCoreServer {
|
|
|
221
380
|
});
|
|
222
381
|
}
|
|
223
382
|
async stop() {
|
|
383
|
+
// Disconnect cache and database
|
|
384
|
+
await cache.disconnect();
|
|
385
|
+
await db.close();
|
|
224
386
|
return new Promise((resolve) => {
|
|
225
387
|
if (this.server) {
|
|
226
388
|
this.server.close(() => {
|