mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-23 23:28:04 +02:00
- Accueil : le bandeau « Faites partie de l'aventure » affiche le nombre d'adhérents validés (statut active) au lieu de « plus de 120 » codé en dur. - Pied de page : les boutons Facebook / LinkedIn ont la même largeur (grille à une colonne dimensionnée sur le plus long) et une icône à largeur fixe. - Catégories : référentiel unique dans src/db/categories.ts (104 métiers, dont assurance, bazar/discount, grande distribution, librairie-presse, tabac, vétérinaire, auto-école, etc.), inséré par la migration 0012 avec ON CONFLICT (slug) : les catégories manquantes apparaissent au prochain démarrage, sans écraser un libellé renommé depuis le backend. Le seed et l'action « Ajouter une catégorie » réutilisent ce référentiel et sa palette. - tests/categories.test.ts vérifie unicité des slugs, contraintes de la table et cohérence entre le référentiel et la migration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDbKbJrPWweXXYSxA8MXW7
141 lines
5.3 KiB
TypeScript
141 lines
5.3 KiB
TypeScript
import "dotenv/config";
|
|
import { Pool } from "pg";
|
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
import bcrypt from "bcryptjs";
|
|
import { randomInt } from "node:crypto";
|
|
import { eq } from "drizzle-orm";
|
|
import * as schema from "./schema";
|
|
import { categoryRows } from "./categories";
|
|
import {
|
|
DEMO_PASSWORD,
|
|
demoActivity,
|
|
demoMembers,
|
|
demoPromotions,
|
|
demoRequests,
|
|
demoUsers,
|
|
} from "./demo-data";
|
|
|
|
const {
|
|
categories,
|
|
members,
|
|
users,
|
|
promotions,
|
|
membershipRequests,
|
|
activityLog,
|
|
} = schema;
|
|
|
|
// Mot de passe initial lisible, tiré avec un aléa cryptographique.
|
|
function randomPassword(length = 16): string {
|
|
const alphabet = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
|
let out = "";
|
|
for (let i = 0; i < length; i++) out += alphabet[randomInt(alphabet.length)];
|
|
return out;
|
|
}
|
|
|
|
async function main() {
|
|
const connectionString = process.env.DATABASE_URL;
|
|
if (!connectionString) throw new Error("DATABASE_URL is not set");
|
|
|
|
const pool = new Pool({ connectionString, max: 1 });
|
|
const db = drizzle(pool, { schema });
|
|
|
|
console.log("Seeding database…");
|
|
|
|
// ---- Categories (métiers) : référentiel partagé (src/db/categories.ts) ----
|
|
// La migration 0012 insère déjà ce référentiel ; le seed le rejoue par sécurité
|
|
// pour une base créée sans migration, sans jamais écraser un libellé existant.
|
|
const categoryData = categoryRows();
|
|
|
|
for (const c of categoryData) {
|
|
await db.insert(categories).values(c).onConflictDoNothing({ target: categories.slug });
|
|
}
|
|
const cats = await db.select().from(categories);
|
|
const catId = (slug: string) => cats.find((c) => c.slug === slug)?.id ?? null;
|
|
|
|
// ---- Données de démonstration (SEED_DEMO=true uniquement) ----
|
|
//
|
|
// Par défaut la base reste vide : seuls le référentiel des catégories et le
|
|
// compte administrateur initial sont créés. Les adhérents, promotions,
|
|
// demandes, entrées de journal et comptes de démonstration ne servent qu'à
|
|
// un poste de développement.
|
|
const demo = (process.env.SEED_DEMO ?? "").trim().toLowerCase() === "true";
|
|
if (demo) {
|
|
for (const m of demoMembers(catId)) {
|
|
const existing = await db.select().from(members).where(eq(members.email, m.email));
|
|
if (existing.length === 0) await db.insert(members).values(m);
|
|
}
|
|
const allMembers = await db.select().from(members);
|
|
const memberId = (name: string) => allMembers.find((m) => m.name === name)?.id ?? null;
|
|
|
|
for (const p of demoPromotions(memberId)) {
|
|
const existing = await db.select().from(promotions).where(eq(promotions.title, p.title));
|
|
if (existing.length === 0) await db.insert(promotions).values(p);
|
|
}
|
|
|
|
for (const r of demoRequests) {
|
|
const existing = await db.select().from(membershipRequests).where(eq(membershipRequests.name, r.name));
|
|
if (existing.length === 0) await db.insert(membershipRequests).values(r);
|
|
}
|
|
|
|
const existingActivity = await db.select().from(activityLog);
|
|
if (existingActivity.length === 0) {
|
|
for (const a of demoActivity) await db.insert(activityLog).values(a);
|
|
}
|
|
|
|
for (const u of demoUsers(memberId)) {
|
|
const existing = await db.select({ id: users.id }).from(users).where(eq(users.email, u.email));
|
|
if (existing.length === 0) {
|
|
await db.insert(users).values({
|
|
email: u.email,
|
|
name: u.name,
|
|
passwordHash: await bcrypt.hash(DEMO_PASSWORD, 10),
|
|
role: u.role,
|
|
memberId: u.memberId,
|
|
});
|
|
}
|
|
}
|
|
console.log(` Données de démonstration insérées (SEED_DEMO=true) ; comptes de démo : mot de passe « ${DEMO_PASSWORD} ».`);
|
|
}
|
|
|
|
// ---- Comptes de connexion ----
|
|
//
|
|
// Le seed tourne à chaque démarrage du conteneur (SEED_ON_START). Il ne doit
|
|
// donc jamais créer en production de compte dont le mot de passe est connu
|
|
// de tous : les comptes de démonstration sont réservés à SEED_DEMO=true, et
|
|
// l'administrateur initial reçoit un mot de passe aléatoire (affiché une
|
|
// seule fois ici) à changer à la première connexion, sauf si
|
|
// SEED_ADMIN_PASSWORD est fourni explicitement.
|
|
const adminEmail = (process.env.SEED_ADMIN_EMAIL ?? "admin@plein-r.fr").trim().toLowerCase();
|
|
const adminName = process.env.SEED_ADMIN_NAME ?? "Administrateur Plein R";
|
|
const providedAdminPassword = (process.env.SEED_ADMIN_PASSWORD ?? "").trim();
|
|
|
|
const [existingAdmin] = await db.select({ id: users.id }).from(users).where(eq(users.email, adminEmail));
|
|
if (!existingAdmin) {
|
|
const generated = !providedAdminPassword;
|
|
const adminPassword = providedAdminPassword || randomPassword();
|
|
await db.insert(users).values({
|
|
email: adminEmail,
|
|
name: adminName,
|
|
passwordHash: await bcrypt.hash(adminPassword, 10),
|
|
role: "admin",
|
|
memberId: null,
|
|
mustChangePassword: true,
|
|
});
|
|
if (generated) {
|
|
console.log(" Compte administrateur créé. Mot de passe initial (affiché une seule fois) :");
|
|
console.log(` ${adminEmail} / ${adminPassword}`);
|
|
} else {
|
|
console.log(` Compte administrateur créé : ${adminEmail} (mot de passe fourni par SEED_ADMIN_PASSWORD).`);
|
|
}
|
|
console.log(" Un changement de mot de passe sera exigé à la première connexion.");
|
|
}
|
|
|
|
console.log("Seed complete.");
|
|
await pool.end();
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("Seed failed:", err);
|
|
process.exit(1);
|
|
});
|