Initial commit — TdB RdB frise narrative
Express.js + NocoDB + SortableJS drag-and-drop Design V2 (Playfair Display + Lora, palette chaude) Auth cookie, proxy NocoDB, édition en modal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const PASSWORD = process.env.TDB_PASSWORD || 'rdb2026';
|
||||
const SESSION_SECRET = process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex');
|
||||
const NOCODB_URL = process.env.NOCODB_URL || 'https://nocodb.hub.delvarre.net';
|
||||
const NOCODB_TOKEN = process.env.NOCODB_TOKEN || '';
|
||||
const NOCODB_BASE_ID = process.env.NOCODB_BASE_ID || '';
|
||||
const NOCODB_TABLE_ID = process.env.NOCODB_TABLE_ID || '';
|
||||
|
||||
// Auth
|
||||
function makeToken(ts) {
|
||||
return crypto.createHmac('sha256', SESSION_SECRET).update(`tdb-${ts}`).digest('hex');
|
||||
}
|
||||
|
||||
function isAuth(req) {
|
||||
const ts = req.cookies?.tdb_ts;
|
||||
const tok = req.cookies?.tdb_tok;
|
||||
if (!ts || !tok) return false;
|
||||
if (Date.now() - Number(ts) > 7 * 24 * 3600 * 1000) return false;
|
||||
return tok === makeToken(ts);
|
||||
}
|
||||
|
||||
// Login
|
||||
app.post('/api/login', (req, res) => {
|
||||
if (req.body?.password !== PASSWORD) return res.status(401).json({ error: 'Mot de passe incorrect' });
|
||||
const ts = String(Date.now());
|
||||
const tok = makeToken(ts);
|
||||
const opts = { httpOnly: true, sameSite: 'lax', maxAge: 7 * 24 * 3600 * 1000, secure: false };
|
||||
res.cookie('tdb_ts', ts, opts);
|
||||
res.cookie('tdb_tok', tok, opts);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Auth middleware for API
|
||||
function requireAuth(req, res, next) {
|
||||
if (!isAuth(req)) return res.status(401).json({ error: 'Non authentifié' });
|
||||
next();
|
||||
}
|
||||
|
||||
// Proxy NocoDB — list records
|
||||
app.get('/api/records', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const url = `${NOCODB_URL}/api/v1/db/data/noco/${NOCODB_BASE_ID}/${NOCODB_TABLE_ID}?limit=200&sort=-Ordre`;
|
||||
const r = await fetch(url, { headers: { 'xc-token': NOCODB_TOKEN } });
|
||||
const data = await r.json();
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy NocoDB — create record
|
||||
app.post('/api/records', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const url = `${NOCODB_URL}/api/v1/db/data/noco/${NOCODB_BASE_ID}/${NOCODB_TABLE_ID}`;
|
||||
const r = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'xc-token': NOCODB_TOKEN, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(req.body),
|
||||
});
|
||||
const data = await r.json();
|
||||
res.status(r.status).json(data);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy NocoDB — update record
|
||||
app.patch('/api/records/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const url = `${NOCODB_URL}/api/v1/db/data/noco/${NOCODB_BASE_ID}/${NOCODB_TABLE_ID}/${req.params.id}`;
|
||||
const r = await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: { 'xc-token': NOCODB_TOKEN, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(req.body),
|
||||
});
|
||||
const data = await r.json();
|
||||
res.status(r.status).json(data);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy NocoDB — delete record
|
||||
app.delete('/api/records/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const url = `${NOCODB_URL}/api/v1/db/data/noco/${NOCODB_BASE_ID}/${NOCODB_TABLE_ID}/${req.params.id}`;
|
||||
const r = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: { 'xc-token': NOCODB_TOKEN },
|
||||
});
|
||||
res.status(r.status).json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy NocoDB — get column options (for dynamic selects)
|
||||
app.get('/api/columns', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const url = `${NOCODB_URL}/api/v2/meta/tables/${NOCODB_TABLE_ID}`;
|
||||
const r = await fetch(url, { headers: { 'xc-token': NOCODB_TOKEN } });
|
||||
const table = await r.json();
|
||||
const selectCols = {};
|
||||
for (const col of table.columns || []) {
|
||||
if (col.uidt === 'SingleSelect' || col.uidt === 'MultiSelect') {
|
||||
selectCols[col.title] = {
|
||||
type: col.uidt,
|
||||
options: (col.colOptions?.options || []).map(o => o.title),
|
||||
};
|
||||
}
|
||||
}
|
||||
res.json(selectCols);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Bulk update order (for drag-and-drop)
|
||||
app.post('/api/reorder', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const updates = req.body; // [{id, Acte, Ordre}, ...]
|
||||
for (const u of updates) {
|
||||
const url = `${NOCODB_URL}/api/v1/db/data/noco/${NOCODB_BASE_ID}/${NOCODB_TABLE_ID}/${u.id}`;
|
||||
await fetch(url, {
|
||||
method: 'PATCH',
|
||||
headers: { 'xc-token': NOCODB_TOKEN, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ Acte: u.Acte, Ordre: u.Ordre }),
|
||||
});
|
||||
}
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Static files
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// SPA fallback
|
||||
app.get('*', (req, res) => {
|
||||
if (isAuth(req)) {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
} else {
|
||||
res.sendFile(path.join(__dirname, 'public', 'login.html'));
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => console.log(`TdB RdB — port ${PORT}`));
|
||||
Reference in New Issue
Block a user