File size: 4,024 Bytes
81d0aa7
 
 
 
 
 
 
 
 
cb71b43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3cc6c54
 
cb71b43
81d0aa7
 
 
 
 
 
 
 
 
3cc6c54
81d0aa7
3cc6c54
81d0aa7
cb71b43
 
 
3cc6c54
 
cb71b43
3cc6c54
cb71b43
 
 
 
81d0aa7
 
 
cb71b43
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// server.js
const express = require('express');
const path = require('path');
const rateLimit = require('express-rate-limit');
const auth = require('./auth');

const app = express();
const PORT = process.env.PORT || 7860;

// --- REQUEST LOGGER (MORGAN-LIKE) ---
app.use((req, res, next) => {
  const start = Date.now();
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
  res.on('finish', () => {
    const dur = Date.now() - start;
    const log = { ts: new Date().toISOString(), level: 'INFO', type: 'HTTP', method: req.method, url: req.originalUrl, status: res.statusCode, durationMs: dur, ip, ua: req.headers['user-agent']?.substring(0,50) };
    // Colorize status for console readability
    const color = res.statusCode >= 500 ? '\x1b[31m' : res.statusCode >= 400 ? '\x1b[33m' : '\x1b[32m';
    console.log(`${color}${log.method} ${log.url} ${log.status}\x1b[0m ${log.durationMs}ms | IP: ${log.ip}`);
    // Structured JSON for HF Logs parsing
    console.log(JSON.stringify(log));
  });
  next();
});

app.use(express.json({ limit: '10mb' }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 300, message: 'Trop de requêtes', standardHeaders: true, legacyHeaders: false }));

const authMiddleware = (req, res, next) => {
  const h = req.headers.authorization;
  if (!h?.startsWith('Bearer ')) return res.status(401).json({ error: 'Token manquant' });
  const payload = auth.verifyToken(h.split(' ')[1]);
  if (!payload) return res.status(401).json({ error: 'Token invalide/expiré' });
  req.user = payload; next();
};

// PUBLIC
app.get('/api/captcha', (req, res) => res.json(auth.generateCaptcha()));
app.get('/api/tos/version', (req, res) => res.json({ version: auth.TOS_VERSION }));

app.post('/api/register', (req, res) => { try { res.json(auth.registerUser(req.body.email, req.body.password, req.body.captchaToken, req.body.captchaAnswer, req.body.acceptTos)); } catch (e) { res.status(400).json({ error: e.message }); } });
app.post('/api/login', (req, res) => { try { const r = auth.loginUser(req.body.email, req.body.password); if (r.tosRequired) return res.status(403).json({ tosRequired: true, tempUser: r.tempUser }); res.json(r); } catch (e) { res.status(401).json({ error: e.message }); } });
app.post('/api/tos/accept', (req, res) => { try { res.json(auth.acceptTos(req.body.userId)); } catch (e) { res.status(400).json({ error: e.message }); } });

// PROTECTED
app.get('/api/me', authMiddleware, (req, res) => { const s = auth.getUserStats(req.user.uid); const g = auth.getGlobalUsed(); res.json({ user: req.user, stats: s, globalUsed: g, globalLimit: auth.GLOBAL_QUOTA_BYTES }); });
app.get('/api/databases', authMiddleware, (req, res) => res.json(auth.listDatabases(req.user.uid)));
app.post('/api/databases', authMiddleware, (req, res) => { try { res.status(201).json(auth.createDatabase(req.user.uid, req.body.name, req.body.captchaToken, req.body.captchaAnswer)); } catch (e) { res.status(400).json({ error: e.message }); } });
app.delete('/api/databases/:id', authMiddleware, (req, res) => { auth.deleteDatabase(req.user.uid, req.params.id); res.json({ success: true }); });
app.post('/api/databases/:id/query', authMiddleware, (req, res) => { const { sql, params } = req.body; if (!sql) return res.status(400).json({ error: 'SQL requis' }); if (/^\s*(PRAGMA|ATTACH|DETACH|VACUUM|BACKUP)/i.test(sql)) return res.status(403).json({ error: 'Commande interdite' }); res.json(auth.executeSql(req.user.uid, req.params.id, sql, params || [])); });
app.get('/api/databases/:id/download', authMiddleware, (req, res) => { const fp = auth.getDatabaseFilePath(req.user.uid, req.params.id); if (!fp || !require('fs').existsSync(fp)) return res.status(404).send('Not found'); res.download(fp, `db_${req.params.id}.sqlite`); });

app.get('*', (req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));

app.listen(PORT, () => console.log(`\x1b[32m🚀 Server ready on port ${PORT} | Data: /data\x1b[0m`));