opex792 commited on
Commit
69c22a9
·
verified ·
1 Parent(s): 38b088f

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +44 -38
server.js CHANGED
@@ -18,14 +18,11 @@ if (!ADMIN_PASSWORD) {
18
  process.exit(1);
19
  }
20
 
21
- // --- ИЗМЕНЕНИЕ ---
22
- // Путь к базе данных теперь указывает на папку 'data'
23
  const dataDir = path.join(__dirname, 'data');
24
  if (!fs.existsSync(dataDir)) {
25
  fs.mkdirSync(dataDir, { recursive: true });
26
  }
27
  const DB_PATH = path.join(dataDir, 'database.sqlite');
28
- // --- КОНЕЦ ИЗМЕНЕНИЯ ---
29
 
30
  const KEY_DEACTIVATION_THRESHOLD = 5;
31
  const KEY_COOLDOWN_SECONDS = 60;
@@ -41,6 +38,7 @@ const sequelize = new Sequelize({
41
  logging: false
42
  });
43
 
 
44
  const GeminiKey = sequelize.define('GeminiKey', {
45
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
46
  key: { type: DataTypes.STRING, allowNull: false, unique: true },
@@ -87,8 +85,8 @@ const GEMINI_DEFAULT_LIMITS = {
87
  'default': { rpm: 15, rpd: 1500, tpm: 1000000, tpd: 2000000 }
88
  };
89
 
 
90
  function getModelLimits(model) {
91
- // Ищем точное совпадение или совпадение по началу строки
92
  const foundModel = Object.keys(GEMINI_DEFAULT_LIMITS).find(k => model.startsWith(k));
93
  return GEMINI_DEFAULT_LIMITS[foundModel] || GEMINI_DEFAULT_LIMITS['default'];
94
  }
@@ -168,6 +166,26 @@ async function selectBestGeminiKey(model, attemptedKeys = new Set()) {
168
  return bestKey || activeKeys[0];
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  const authenticateServiceKey = async (req, res, next) => {
172
  const authHeader = req.headers.authorization;
173
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
@@ -222,14 +240,8 @@ const authenticateServiceKey = async (req, res, next) => {
222
  next();
223
  };
224
 
225
- const adminAuth = (req, res, next) => {
226
- const authHeader = req.headers.authorization;
227
- if (!authHeader || authHeader !== `Bearer ${ADMIN_PASSWORD}`) {
228
- return res.status(401).send('Unauthorized: Invalid admin credentials.');
229
- }
230
- next();
231
- };
232
 
 
233
  app.post(['/v1/chat/completions', '/v1beta/chat/completions'], authenticateServiceKey, async (req, res) => {
234
  if (!req.body || typeof req.body !== 'object') {
235
  return res.status(400).json({ error: 'Request body must be a valid JSON object.' });
@@ -256,7 +268,6 @@ app.post(['/v1/chat/completions', '/v1beta/chat/completions'], authenticateServi
256
  try {
257
  const geminiApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
258
 
259
- // OpenAI to Gemini Translation
260
  const geminiRequestBody = {
261
  contents: req.body.messages.map(msg => ({
262
  role: msg.role === 'assistant' ? 'model' : msg.role,
@@ -279,7 +290,6 @@ app.post(['/v1/chat/completions', '/v1beta/chat/completions'], authenticateServi
279
 
280
  const geminiResponse = response.data;
281
 
282
- // Gemini to OpenAI Translation
283
  const openAIResponse = {
284
  id: `chatcmpl-${uuidv4()}`,
285
  object: 'chat.completion',
@@ -371,7 +381,10 @@ app.post(['/v1/chat/completions', '/v1beta/chat/completions'], authenticateServi
371
  return res.status(lastStatusCode).json(finalError);
372
  });
373
 
 
 
374
  app.get('/admin', adminAuth, (req, res) => {
 
375
  res.send(`
376
  <html>
377
  <head><title>Admin Panel</title><meta name="viewport" content="width=device-width, initial-scale=1"></head>
@@ -379,13 +392,13 @@ app.get('/admin', adminAuth, (req, res) => {
379
  <h1>Gemini Proxy Admin Panel</h1>
380
  <h2>Upload Gemini Keys</h2>
381
  <p>Upload a .txt file with one Gemini API key per line.</p>
382
- <form action="/admin/upload-keys" method="post" enctype="multipart/form-data">
383
  <input type="file" name="keysFile" accept=".txt" required>
384
  <button type="submit">Upload</button>
385
  </form>
386
  <hr>
387
  <h2>Manage Service Keys</h2>
388
- <form action="/admin/service-keys" method="post" target="creation-result">
389
  <input type="text" name="owner" placeholder="Owner (e.g., user@example.com)" required style="width: 250px; padding: 5px;">
390
  <button type="submit">Create New Service Key</button>
391
  </form>
@@ -393,10 +406,10 @@ app.get('/admin', adminAuth, (req, res) => {
393
  <hr>
394
  <h2>Stats & Data</h2>
395
  <ul>
396
- <li><a href="/admin/stats" target="_blank">View Stats (JSON)</a></li>
397
- <li><a href="/admin/download/geminikeys">Download GeminiKeys Table (JSON)</a></li>
398
- <li><a href="/admin/download/requestlogs">Download RequestLogs Table (JSON)</a></li>
399
- <li><a href="/admin/download/servicekeys">Download ServiceKeys Table (JSON)</a></li>
400
  </ul>
401
  </body>
402
  </html>
@@ -411,21 +424,16 @@ app.post('/admin/upload-keys', adminAuth, async (req, res) => {
411
  const keys = keysFile.data.toString('utf8').split(/\r?\n/).filter(key => key.trim() !== '');
412
 
413
  let newKeysCount = 0;
414
- let failedKeys = [];
415
  for (const key of keys) {
416
  const trimmedKey = key.trim();
417
  if (!trimmedKey) continue;
418
- try {
419
- const [_, created] = await GeminiKey.findOrCreate({
420
- where: { key: trimmedKey },
421
- defaults: { key: trimmedKey }
422
- });
423
- if (created) newKeysCount++;
424
- } catch (error) {
425
- failedKeys.push(trimmedKey);
426
- }
427
  }
428
- res.send(`File processed successfully. Added ${newKeysCount} new unique keys. Failed to add ${failedKeys.length} keys (likely duplicates).`);
429
  });
430
 
431
  app.post('/admin/service-keys', adminAuth, async (req, res) => {
@@ -472,16 +480,14 @@ app.get('/admin/download/:table', adminAuth, async (req, res) => {
472
  const model = modelMap[table.toLowerCase()];
473
  if (!model) return res.status(404).send('Table not found');
474
 
475
- try {
476
- const data = await model.findAll({ order: [['createdAt', 'DESC']] });
477
- res.header('Content-Type', 'application/json');
478
- res.header('Content-Disposition', `attachment; filename=${table}.json`);
479
- res.send(JSON.stringify(data, null, 2));
480
- } catch (error) {
481
- res.status(500).send(`Error fetching data for table ${table}: ${error.message}`);
482
- }
483
  });
484
 
 
 
485
  app.get('/user/dashboard', authenticateServiceKey, async (req, res) => {
486
  const oneDayAgo = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
487
  const usageResult = await RequestLog.findOne({
 
18
  process.exit(1);
19
  }
20
 
 
 
21
  const dataDir = path.join(__dirname, 'data');
22
  if (!fs.existsSync(dataDir)) {
23
  fs.mkdirSync(dataDir, { recursive: true });
24
  }
25
  const DB_PATH = path.join(dataDir, 'database.sqlite');
 
26
 
27
  const KEY_DEACTIVATION_THRESHOLD = 5;
28
  const KEY_COOLDOWN_SECONDS = 60;
 
38
  logging: false
39
  });
40
 
41
+ // ... (Модели Sequelize остаются без изменений: GeminiKey, RequestLog, ServiceKey)
42
  const GeminiKey = sequelize.define('GeminiKey', {
43
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
44
  key: { type: DataTypes.STRING, allowNull: false, unique: true },
 
85
  'default': { rpm: 15, rpd: 1500, tpm: 1000000, tpd: 2000000 }
86
  };
87
 
88
+ // ... (Функции getModelLimits, safeParseInt, selectBestGeminiKey остаются без изменений)
89
  function getModelLimits(model) {
 
90
  const foundModel = Object.keys(GEMINI_DEFAULT_LIMITS).find(k => model.startsWith(k));
91
  return GEMINI_DEFAULT_LIMITS[foundModel] || GEMINI_DEFAULT_LIMITS['default'];
92
  }
 
166
  return bestKey || activeKeys[0];
167
  }
168
 
169
+
170
+ // --- ИЗМЕНЕНИЕ: Middleware для авторизации админа через query-параметр ---
171
+ const adminAuth = (req, res, next) => {
172
+ const password = req.query.password;
173
+ if (!password || password !== ADMIN_PASSWORD) {
174
+ // Ответ в HTML для удобства, если заходят через браузер
175
+ return res.status(401).send(`
176
+ <div style="font-family: sans-serif; text-align: center; padding-top: 50px;">
177
+ <h1>401 Unauthorized</h1>
178
+ <p>Требуется валидный пароль администратора в параметре URL.</p>
179
+ <p>Пример: <code>/admin?password=ВАШ_ПАРОЛЬ</code></p>
180
+ </div>
181
+ `);
182
+ }
183
+ // Сохраняем пароль для использования в шаблонах и ссылках
184
+ res.locals.adminPassword = password;
185
+ next();
186
+ };
187
+
188
+ // ... (authenticateServiceKey middleware остается без изменений)
189
  const authenticateServiceKey = async (req, res, next) => {
190
  const authHeader = req.headers.authorization;
191
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
 
240
  next();
241
  };
242
 
 
 
 
 
 
 
 
243
 
244
+ // ... (Основной эндпоинт /v1/chat/completions остается без изменений)
245
  app.post(['/v1/chat/completions', '/v1beta/chat/completions'], authenticateServiceKey, async (req, res) => {
246
  if (!req.body || typeof req.body !== 'object') {
247
  return res.status(400).json({ error: 'Request body must be a valid JSON object.' });
 
268
  try {
269
  const geminiApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
270
 
 
271
  const geminiRequestBody = {
272
  contents: req.body.messages.map(msg => ({
273
  role: msg.role === 'assistant' ? 'model' : msg.role,
 
290
 
291
  const geminiResponse = response.data;
292
 
 
293
  const openAIResponse = {
294
  id: `chatcmpl-${uuidv4()}`,
295
  object: 'chat.completion',
 
381
  return res.status(lastStatusCode).json(finalError);
382
  });
383
 
384
+ // --- ИЗМЕНЕНИЕ: Добавляем middleware adminAuth ко всем админским роутам ---
385
+ // --- ИЗМЕНЕНИЕ: Обновляем HTML админки для использования query-параметра ---
386
  app.get('/admin', adminAuth, (req, res) => {
387
+ const adminPassword = res.locals.adminPassword; // Получаем пароль из middleware
388
  res.send(`
389
  <html>
390
  <head><title>Admin Panel</title><meta name="viewport" content="width=device-width, initial-scale=1"></head>
 
392
  <h1>Gemini Proxy Admin Panel</h1>
393
  <h2>Upload Gemini Keys</h2>
394
  <p>Upload a .txt file with one Gemini API key per line.</p>
395
+ <form action="/admin/upload-keys?password=${adminPassword}" method="post" enctype="multipart/form-data">
396
  <input type="file" name="keysFile" accept=".txt" required>
397
  <button type="submit">Upload</button>
398
  </form>
399
  <hr>
400
  <h2>Manage Service Keys</h2>
401
+ <form action="/admin/service-keys?password=${adminPassword}" method="post" target="creation-result">
402
  <input type="text" name="owner" placeholder="Owner (e.g., user@example.com)" required style="width: 250px; padding: 5px;">
403
  <button type="submit">Create New Service Key</button>
404
  </form>
 
406
  <hr>
407
  <h2>Stats & Data</h2>
408
  <ul>
409
+ <li><a href="/admin/stats?password=${adminPassword}" target="_blank">View Stats (JSON)</a></li>
410
+ <li><a href="/admin/download/geminikeys?password=${adminPassword}">Download GeminiKeys Table (JSON)</a></li>
411
+ <li><a href="/admin/download/requestlogs?password=${adminPassword}">Download RequestLogs Table (JSON)</a></li>
412
+ <li><a href="/admin/download/servicekeys?password=${adminPassword}">Download ServiceKeys Table (JSON)</a></li>
413
  </ul>
414
  </body>
415
  </html>
 
424
  const keys = keysFile.data.toString('utf8').split(/\r?\n/).filter(key => key.trim() !== '');
425
 
426
  let newKeysCount = 0;
 
427
  for (const key of keys) {
428
  const trimmedKey = key.trim();
429
  if (!trimmedKey) continue;
430
+ const [_, created] = await GeminiKey.findOrCreate({
431
+ where: { key: trimmedKey },
432
+ defaults: { key: trimmedKey }
433
+ });
434
+ if (created) newKeysCount++;
 
 
 
 
435
  }
436
+ res.send(`File processed successfully. Added ${newKeysCount} new unique keys.`);
437
  });
438
 
439
  app.post('/admin/service-keys', adminAuth, async (req, res) => {
 
480
  const model = modelMap[table.toLowerCase()];
481
  if (!model) return res.status(404).send('Table not found');
482
 
483
+ const data = await model.findAll({ order: [['createdAt', 'DESC']] });
484
+ res.header('Content-Type', 'application/json');
485
+ res.header('Content-Disposition', `attachment; filename=${table}.json`);
486
+ res.send(JSON.stringify(data, null, 2));
 
 
 
 
487
  });
488
 
489
+
490
+ // ... (Роуты для пользователей /user/dashboard, /user/stats, /v1/models остаются без изменений)
491
  app.get('/user/dashboard', authenticateServiceKey, async (req, res) => {
492
  const oneDayAgo = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
493
  const usageResult = await RequestLog.findOne({