Maj Section Général

This commit is contained in:
2026-06-15 23:03:37 +02:00
parent 01526a5551
commit 032a370e7d
15 changed files with 492 additions and 57 deletions
+14
View File
@@ -1858,4 +1858,18 @@ console.log('[DB] Migrations 2FA OK');
}
// ── Migration : colonnes paramètres généraux dans smtp_config ─────────────
{
const cols = db.prepare("PRAGMA table_info(smtp_config)").all().map(c => c.name);
if (!cols.includes('allow_registration')) {
db.exec("ALTER TABLE smtp_config ADD COLUMN allow_registration INTEGER NOT NULL DEFAULT 1");
console.log('[DB] Colonne smtp_config.allow_registration ajoutée');
}
if (!cols.includes('min_password_length')) {
db.exec("ALTER TABLE smtp_config ADD COLUMN min_password_length INTEGER NOT NULL DEFAULT 8");
console.log('[DB] Colonne smtp_config.min_password_length ajoutée');
}
}
export default db;
+73
View File
@@ -0,0 +1,73 @@
/**
* /api/admin/general — Paramètres généraux de l'application
* Protégé par requireAuth + requireAdmin (appliqués dans server.js).
*
* GET / — retourne les 4 champs généraux
* PATCH / — met à jour un ou plusieurs champs
*
* Route publique complémentaire : /api/app-info (server.js) expose
* allowRegistration et minPasswordLength sans auth.
*/
import { Router } from 'express';
import { z } from 'zod';
import db from '../db/index.js';
const router = Router();
function ensureRow() {
const exists = db.prepare('SELECT id FROM smtp_config WHERE id = 1').get();
if (!exists) {
db.prepare(`
INSERT INTO smtp_config (id, enabled, host, port, secure, email, username, password,
allow_unauth, app_name, app_url, allow_registration, min_password_length)
VALUES (1, 0, '', 587, 0, '', '', '', 0, 'Crowdlending Tracker', '', 1, 8)
`).run();
}
}
router.get('/', (_req, res, next) => {
try {
ensureRow();
const row = db.prepare('SELECT app_name, app_url, allow_registration, min_password_length FROM smtp_config WHERE id = 1').get();
res.json({
appName: row.app_name || 'Crowdlending Tracker',
appUrl: row.app_url || '',
allowRegistration: row.allow_registration !== 0,
minPasswordLength: row.min_password_length || 8,
});
} catch (e) { next(e); }
});
const PatchSchema = z.object({
appName: z.string().min(1).max(100).optional(),
appUrl: z.string().max(500).optional(),
allowRegistration: z.boolean().optional(),
minPasswordLength: z.number().int().min(6).max(64).optional(),
});
router.patch('/', (req, res, next) => {
try {
ensureRow();
const body = PatchSchema.parse(req.body);
const row = db.prepare('SELECT app_name, app_url, allow_registration, min_password_length FROM smtp_config WHERE id = 1').get();
db.prepare(`
UPDATE smtp_config SET
app_name = ?,
app_url = ?,
allow_registration = ?,
min_password_length = ?
WHERE id = 1
`).run(
body.appName !== undefined ? body.appName : (row.app_name || 'Crowdlending Tracker'),
body.appUrl !== undefined ? body.appUrl : (row.app_url || ''),
body.allowRegistration !== undefined ? (body.allowRegistration ? 1 : 0) : (row.allow_registration !== 0 ? 1 : 0),
body.minPasswordLength !== undefined ? body.minPasswordLength : (row.min_password_length || 8),
);
res.json({ ok: true });
} catch (e) { next(e); }
});
export default router;
+132
View File
@@ -0,0 +1,132 @@
/**
* /api/admin/smtp — Gestion de la configuration SMTP
* Protégé par requireAuth + requireAdmin (appliqués dans server.js).
*/
import { Router } from 'express';
import { z } from 'zod';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
import { sendMail, buildEmailHtml, getSmtpConfig } from '../utils/mailer.js';
const router = Router();
// ── Helpers ─────────────────────────────────────────────────────────────────
function ensureRow() {
const exists = db.prepare('SELECT id FROM smtp_config WHERE id = 1').get();
if (!exists) {
db.prepare(`
INSERT INTO smtp_config (id, enabled, host, port, secure, email, username, password,
allow_unauth, app_name, app_url, allow_registration, min_password_length)
VALUES (1, 0, '', 587, 0, '', '', '', 0, 'Crowdlending Tracker', '', 1, 8)
`).run();
}
}
// ── GET /admin/smtp ─────────────────────────────────────────────────────────
router.get('/', (_req, res, next) => {
try {
ensureRow();
const row = db.prepare('SELECT * FROM smtp_config WHERE id = 1').get();
res.json({
enabled: !!row.enabled,
host: row.host || '',
port: row.port || 587,
secure: !!row.secure,
email: row.email || '',
username: row.username || '',
hasPassword: !!(row.password),
allowUnauth: !!row.allow_unauth,
appName: row.app_name || 'Crowdlending Tracker',
appUrl: row.app_url || '',
allowRegistration: row.allow_registration !== 0,
minPasswordLength: row.min_password_length || 8,
});
} catch (e) { next(e); }
});
// ── PUT /admin/smtp ─────────────────────────────────────────────────────────
const SmtpSchema = z.object({
enabled: z.boolean().optional(),
host: z.string().optional(),
port: z.number().int().min(1).max(65535).optional(),
secure: z.boolean().optional(),
email: z.string().optional(),
username: z.string().optional(),
password: z.string().optional(),
allowUnauth: z.boolean().optional(),
appName: z.string().max(100).optional(),
appUrl: z.string().max(500).optional(),
});
router.put('/', (req, res, next) => {
try {
ensureRow();
const body = SmtpSchema.parse(req.body);
const row = db.prepare('SELECT * FROM smtp_config WHERE id = 1').get();
const updates = {
enabled: body.enabled !== undefined ? (body.enabled ? 1 : 0) : row.enabled,
host: body.host !== undefined ? body.host : row.host,
port: body.port !== undefined ? body.port : row.port,
secure: body.secure !== undefined ? (body.secure ? 1 : 0) : row.secure,
email: body.email !== undefined ? body.email : row.email,
username: body.username !== undefined ? body.username : row.username,
password: body.password !== undefined ? body.password : row.password,
allow_unauth: body.allowUnauth !== undefined ? (body.allowUnauth ? 1 : 0) : row.allow_unauth,
app_name: body.appName !== undefined ? body.appName : row.app_name,
app_url: body.appUrl !== undefined ? body.appUrl : row.app_url,
};
db.prepare(`
UPDATE smtp_config SET
enabled=?, host=?, port=?, secure=?, email=?, username=?, password=?,
allow_unauth=?, app_name=?, app_url=?
WHERE id=1
`).run(
updates.enabled, updates.host, updates.port, updates.secure,
updates.email, updates.username, updates.password, updates.allow_unauth,
updates.app_name, updates.app_url,
);
res.json({ ok: true });
} catch (e) { next(e); }
});
// ── POST /admin/smtp/test ───────────────────────────────────────────────────
router.post('/test', async (req, res, next) => {
try {
const { to } = z.object({ to: z.string().email() }).parse(req.body);
const cfg = getSmtpConfig();
if (!cfg.host) throw new HttpError(400, 'Aucun serveur SMTP configuré.');
await sendMail({
to,
subject: `Test SMTP — ${cfg.appName}`,
html: buildEmailHtml({
title: 'Email de test',
body: `<p>Si vous recevez cet email, votre configuration SMTP fonctionne correctement.</p>`,
}),
});
res.json({ ok: true, msg: `Email de test envoyé à ${to}.` });
} catch (e) { next(e); }
});
// ── GET /admin/smtp/env ─────────────────────────────────────────────────────
router.get('/env', (_req, res) => {
res.json({
host: process.env.SMTP_HOST || '',
port: parseInt(process.env.SMTP_PORT || '587', 10),
email: process.env.SMTP_EMAIL || '',
username: process.env.SMTP_USERNAME || '',
appName: process.env.APP_NAME || '',
appUrl: process.env.APP_URL || '',
});
});
export default router;
+10 -3
View File
@@ -35,6 +35,8 @@ import { startAutoStatutJob } from './jobs/autoStatut.js';
import adminRouter from './routes/admin.js';
import invitationsRouter from './routes/invitations.js';
import auditLogsRouter from './routes/auditLogs.js';
import smtpRouter from './routes/smtp.js';
import generalRouter from './routes/general.js';
import tauxCreditImpotRouter from './routes/tauxCreditImpot.js';
import referentielRouter from './routes/referentiel.js';
import referentielPublicRouter from './routes/referentielPublic.js';
@@ -78,12 +80,15 @@ app.get('/api/app-info', (_, res) => {
try {
const cfg = getSmtpConfig();
const icon = db.prepare(`SELECT filename FROM app_icons WHERE name = 'logo-app' LIMIT 1`).get();
const row = db.prepare('SELECT allow_registration, min_password_length FROM smtp_config WHERE id = 1').get();
res.json({
appName: cfg.appName || 'Crowdlending Tracker',
iconUrl: icon ? `/api/icons-files/${icon.filename}` : null,
appName: cfg.appName || 'Crowdlending Tracker',
iconUrl: icon ? `/api/icons-files/${icon.filename}` : null,
allowRegistration: row ? row.allow_registration !== 0 : true,
minPasswordLength: row ? (row.min_password_length || 8) : 8,
});
} catch {
res.json({ appName: 'Crowdlending Tracker', iconUrl: null });
res.json({ appName: 'Crowdlending Tracker', iconUrl: null, allowRegistration: true, minPasswordLength: 8 });
}
});
@@ -111,6 +116,8 @@ app.use('/api/preferences', requireAuth, preferencesRouter);
app.use('/api/icons', requireAuth, iconsRouter);
app.use('/api/admin', requireAuth, requireAdmin, adminRouter);
app.use('/api/admin/audit-logs', requireAuth, requireAdmin, auditLogsRouter);
app.use('/api/admin/smtp', requireAuth, requireAdmin, smtpRouter);
app.use('/api/admin/general', requireAuth, requireAdmin, generalRouter);
// Invitations : routes admin protégées + routes publiques (register/validate)
app.use('/api/admin/invitations', requireAuth, requireAdmin, invitationsRouter);
app.use('/api/invitations', invitationsRouter);