Files
PleinR/src/db/seed.ts
T
Claude f9d0d6c825 Accueil, pied de page et catégories : compteur réel, boutons alignés, référentiel complété
- 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
2026-09-06 06:18:37 +00:00

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);
});