Ajoute les inscriptions modifiables et les paramètres association

This commit is contained in:
Michael committed 2026-07-16 19:48:14 +02:00
1 parent c49c45e65f
commit cf67fdc9de
22 files changed
+442 -77

No files matched your search

+16 -9
View File
@@ -33,7 +33,7 @@ const adminOnly = requireAuth('admin');
const MEMBER_SQL = `
SELECT m.id, m.nom, m.secteur, m.categorie_id, c.name AS categorie, m.dirigeant,
m.adhesion, m.email, m.tel, m.site, m.presentation, m.valide,
m.adhesion, m.email, m.tel, m.site, m.adresse, m.presentation, m.valide,
m.logo_path, m.photo_path,
(u.id IS NOT NULL) AS has_login,
COALESCE(u.must_change_password, false) AS must_change_password,
@@ -53,6 +53,11 @@ const INSCR_SQL = `
i.rencontre_id, r.titre AS rencontre
FROM inscriptions i JOIN rencontres r ON r.id = i.rencontre_id`;
async function loadAssociationSettings() {
const result = await query(`SELECT key, value FROM site_content WHERE key LIKE 'association_%'`);
return Object.fromEntries(result.rows.map((row) => [row.key, row.value]));
}
// ---------- Dashboard (admin only) ----------
adminRouter.get('/dashboard', adminOnly, async (_req, res, next) => {
try {
@@ -89,10 +94,10 @@ adminRouter.post('/members', validate(adminMemberSchema), async (req, res, next)
try {
const d = req.data;
const result = await query(
`INSERT INTO members (nom, secteur, categorie_id, dirigeant, adhesion, email, tel, site, presentation, valide)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id`,
`INSERT INTO members (nom, secteur, categorie_id, dirigeant, adhesion, email, tel, site, adresse, presentation, valide)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
[d.nom, d.secteur, d.categorie_id ?? null, d.dirigeant, new Date().getFullYear(),
d.email || null, d.tel, d.site, d.presentation, d.valide ?? true]
d.email || null, d.tel, d.site, d.adresse, d.presentation, d.valide ?? true]
);
const id = result.rows[0].id;
// A login account (with a temporary password) is only created when an
@@ -120,10 +125,10 @@ adminRouter.put('/members/:id', validate(idParam, 'params'), validate(adminMembe
const d = req.data;
const result = await query(
`UPDATE members SET nom=$1, secteur=$2, categorie_id=$3, dirigeant=$4, email=$5,
tel=$6, site=$7, presentation=$8, valide=COALESCE($9, valide), updated_at=now()
WHERE id=$10 RETURNING id`,
tel=$6, site=$7, adresse=$8, presentation=$9, valide=COALESCE($10, valide), updated_at=now()
WHERE id=$11 RETURNING id`,
[d.nom, d.secteur, d.categorie_id ?? null, d.dirigeant, d.email || null,
d.tel, d.site, d.presentation, d.valide ?? null, req.params.id]
d.tel, d.site, d.adresse, d.presentation, d.valide ?? null, req.params.id]
);
if (result.rowCount === 0) return res.status(404).json({ error: 'Membre introuvable' });
res.json({ ok: true });
@@ -257,7 +262,8 @@ adminRouter.post('/rencontres/:id/email', validate(idParam, 'params'), validate(
// invalid base — keep the fallback
}
}
res.json(buildInvitationEmail({ rencontre: result.rows[0], baseUrl, texts }));
const association = await loadAssociationSettings();
res.json(buildInvitationEmail({ rencontre: result.rows[0], baseUrl, texts, association }));
} catch (err) {
next(err);
}
@@ -281,7 +287,8 @@ adminRouter.post('/emails/preview', adminOnly, validate(customEmailSchema), asyn
const parsed = new URL(base);
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') baseUrl = parsed.origin;
}
res.json(buildCustomEmail({ content, baseUrl }));
const association = await loadAssociationSettings();
res.json(buildCustomEmail({ content, baseUrl, association }));
} catch (err) {
next(err);
}
+22 -5
View File
@@ -11,7 +11,7 @@ memberRouter.use(requireAuth('member'));
const PROFILE_SQL = `
SELECT m.id, m.nom, m.secteur, m.categorie_id, c.name AS categorie, m.dirigeant,
m.adhesion, m.email, m.tel, m.site, m.presentation, m.valide,
m.adhesion, m.email, m.tel, m.site, m.adresse, m.presentation, m.valide,
m.logo_path, m.photo_path
FROM members m LEFT JOIN categories c ON c.id = m.categorie_id
WHERE m.id = $1`;
@@ -28,12 +28,12 @@ memberRouter.get('/profile', async (req, res, next) => {
memberRouter.put('/profile', validate(memberProfileSchema), async (req, res, next) => {
try {
const { nom, secteur, categorie_id, dirigeant, email, tel, site, presentation } = req.data;
const { nom, secteur, categorie_id, dirigeant, email, tel, site, adresse, presentation } = req.data;
await query(
`UPDATE members SET nom=$1, secteur=$2, categorie_id=$3, dirigeant=$4, email=$5,
tel=$6, site=$7, presentation=$8, updated_at=now()
WHERE id=$9`,
[nom, secteur, categorie_id ?? null, dirigeant, email || null, tel, site, presentation, req.user.memberId]
tel=$6, site=$7, adresse=$8, presentation=$9, updated_at=now()
WHERE id=$10`,
[nom, secteur, categorie_id ?? null, dirigeant, email || null, tel, site, adresse, presentation, req.user.memberId]
);
const result = await query(PROFILE_SQL, [req.user.memberId]);
res.json({ member: result.rows[0] });
@@ -42,6 +42,23 @@ memberRouter.put('/profile', validate(memberProfileSchema), async (req, res, nex
}
});
memberRouter.get('/inscriptions', async (req, res, next) => {
try {
const result = await query(
`SELECT i.rencontre_id, COUNT(*)::int AS participants,
bool_and(i.statut = 'confirmee') AS confirmee
FROM inscriptions i
JOIN rencontres r ON r.id = i.rencontre_id
WHERE i.member_id = $1 AND r.date_renc >= CURRENT_DATE
GROUP BY i.rencontre_id`,
[req.user.memberId]
);
res.json({ inscriptions: result.rows });
} catch (err) {
next(err);
}
});
function imageRoute(column) {
return (req, res, next) => {
imageUpload(req, res, async (err) => {
+1 -1
View File
@@ -14,7 +14,7 @@ publicRouter.get('/bootstrap', async (_req, res, next) => {
query('SELECT key, value FROM site_content'),
query('SELECT id, name FROM categories ORDER BY id'),
query(
`SELECT m.id, m.nom, m.secteur, m.dirigeant, m.adhesion, m.email, m.tel, m.site,
`SELECT m.id, m.nom, m.secteur, m.dirigeant, m.adhesion, m.email, m.tel, m.site, m.adresse,
m.presentation, m.logo_path, m.photo_path, c.name AS categorie
FROM members m LEFT JOIN categories c ON c.id = m.categorie_id
WHERE m.valide = true ORDER BY m.nom`