feat(api): GET /api/bms/diag-log e /:file pra ler logs do servidor
Endpoints novos só backend (não muda APK): - GET /api/bms/diag-log: lista files do user (name, size, mtime, sorted desc) - GET /api/bms/diag-log/:file: retorna conteúdo plain text - Path traversal protegido (regex sanitiza nome do arquivo) - Filtra por user_id (não vê logs de outros users) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f32428980
commit
b81521043e
1 changed files with 33 additions and 1 deletions
|
|
@ -135,7 +135,7 @@ app.post('/api/bms/diag-log', requireAuth, (req, res) => {
|
||||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const file = path.join(dir, `${req.user.id}-${ts}.txt`);
|
const file = path.join(dir, `${req.user.id}-${ts}.txt`);
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(file, log.slice(0, 50000)); // cap em 50KB
|
fs.writeFileSync(file, log.slice(0, 50000));
|
||||||
db.audit(req.user.id, 'bms_diag_log', 'bluetooth', null, { bytes: log.length, file: path.basename(file) }, req.ip);
|
db.audit(req.user.id, 'bms_diag_log', 'bluetooth', null, { bytes: log.length, file: path.basename(file) }, req.ip);
|
||||||
res.json({ ok: true, file: path.basename(file) });
|
res.json({ ok: true, file: path.basename(file) });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -143,6 +143,38 @@ app.post('/api/bms/diag-log', requireAuth, (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Lista logs disponíveis (debug)
|
||||||
|
app.get('/api/bms/diag-log', requireAuth, (req, res) => {
|
||||||
|
const dir = path.join(db.dataDir, 'diag-logs');
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(dir)) return res.json({ files: [] });
|
||||||
|
const files = fs.readdirSync(dir)
|
||||||
|
.filter(f => f.startsWith(`${req.user.id}-`))
|
||||||
|
.map(f => {
|
||||||
|
const stat = fs.statSync(path.join(dir, f));
|
||||||
|
return { name: f, size: stat.size, mtime: stat.mtime };
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.mtime - a.mtime);
|
||||||
|
res.json({ files });
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lê conteúdo de um log específico
|
||||||
|
app.get('/api/bms/diag-log/:file', requireAuth, (req, res) => {
|
||||||
|
const file = req.params.file.replace(/[^a-zA-Z0-9._-]/g, '');
|
||||||
|
if (!file.startsWith(`${req.user.id}-`)) return res.status(403).json({ error: 'forbidden' });
|
||||||
|
const fullPath = path.join(db.dataDir, 'diag-logs', file);
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(fullPath)) return res.status(404).json({ error: 'not found' });
|
||||||
|
const content = fs.readFileSync(fullPath, 'utf8');
|
||||||
|
res.type('text/plain').send(content);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ===== Google Login via OAuth redirect (pra apps Capacitor onde GSI popup não funciona) =====
|
// ===== Google Login via OAuth redirect (pra apps Capacitor onde GSI popup não funciona) =====
|
||||||
// In-memory store: session_id → { tokens, createdAt }. Auto-expira em 10min.
|
// In-memory store: session_id → { tokens, createdAt }. Auto-expira em 10min.
|
||||||
const pendingGoogleSessions = new Map();
|
const pendingGoogleSessions = new Map();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue