mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-23 07:08:05 +02:00
Backfill: creer les comptes des adherents existants
- Action createMissingMemberAccounts : cree un compte de connexion (mdp temporaire a changer) pour chaque adherent ayant un e-mail mais pas encore de compte. Ignore ceux sans e-mail ou e-mail deja pris. - Banniere sur /backend/adherents : compte des fiches sans compte + bouton "Creer les comptes manquants" (pas besoin de CLI serveur) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
119ad4665a
commit
839aa5d045
2 files changed
+82
-1
No files matched your search
@@ -156,6 +156,49 @@ export async function addMember(formData: FormData) {
|
|||||||
revalidatePath("/");
|
revalidatePath("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crée des comptes de connexion pour les adhérents existants qui n'en ont pas
|
||||||
|
// encore (ceux ajoutés avant l'arrivée des comptes adhérent). Chaque compte
|
||||||
|
// reçoit un mot de passe temporaire à changer à la première connexion.
|
||||||
|
export async function createMissingMemberAccounts() {
|
||||||
|
const { role } = await requireRole();
|
||||||
|
if (!can(role, "manageMembers")) throw new Error("Accès refusé");
|
||||||
|
|
||||||
|
const allMembers = await db
|
||||||
|
.select({ id: members.id, name: members.name, email: members.email })
|
||||||
|
.from(members);
|
||||||
|
const allUsers = await db
|
||||||
|
.select({ email: users.email, memberId: users.memberId })
|
||||||
|
.from(users);
|
||||||
|
|
||||||
|
const takenEmails = new Set(allUsers.map((u) => u.email.toLowerCase()));
|
||||||
|
const linkedMemberIds = new Set(allUsers.map((u) => u.memberId).filter((x): x is number => x != null));
|
||||||
|
|
||||||
|
let created = 0;
|
||||||
|
for (const m of allMembers) {
|
||||||
|
if (linkedMemberIds.has(m.id)) continue;
|
||||||
|
const email = (m.email ?? "").trim().toLowerCase();
|
||||||
|
if (!email || takenEmails.has(email)) continue; // pas d'e-mail ou déjà pris : on saute
|
||||||
|
|
||||||
|
const tempPassword = generateTempPassword();
|
||||||
|
await db.insert(users).values({
|
||||||
|
name: m.name,
|
||||||
|
email,
|
||||||
|
role: "member",
|
||||||
|
memberId: m.id,
|
||||||
|
passwordHash: await bcrypt.hash(tempPassword, 10),
|
||||||
|
tempPassword,
|
||||||
|
mustChangePassword: true,
|
||||||
|
});
|
||||||
|
takenEmails.add(email);
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (created > 0) {
|
||||||
|
await logActivity(`${created} compte(s) adhérent créé(s) pour les fiches existantes`, "#2C6FB3");
|
||||||
|
}
|
||||||
|
revalidatePath("/backend/adherents");
|
||||||
|
}
|
||||||
|
|
||||||
// Réinitialise le mot de passe d'un adhérent : nouveau mot de passe temporaire
|
// Réinitialise le mot de passe d'un adhérent : nouveau mot de passe temporaire
|
||||||
// visible par l'admin jusqu'à la prochaine connexion de l'adhérent.
|
// visible par l'admin jusqu'à la prochaine connexion de l'adhérent.
|
||||||
export async function resetMemberPassword(formData: FormData) {
|
export async function resetMemberPassword(formData: FormData) {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { redirect } from "next/navigation";
|
|||||||
import { asc, eq, ilike } from "drizzle-orm";
|
import { asc, eq, ilike } from "drizzle-orm";
|
||||||
import { auth } from "@/auth";
|
import { auth } from "@/auth";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { categories, members } from "@/db/schema";
|
import { categories, members, users } from "@/db/schema";
|
||||||
import { can } from "@/lib/rbac";
|
import { can } from "@/lib/rbac";
|
||||||
|
import { createMissingMemberAccounts } from "../actions";
|
||||||
import { AddMemberPanel } from "./AddMemberPanel";
|
import { AddMemberPanel } from "./AddMemberPanel";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -45,10 +46,47 @@ export default async function AdherentsPage({
|
|||||||
.where(search ? ilike(members.name, `%${search}%`) : undefined)
|
.where(search ? ilike(members.name, `%${search}%`) : undefined)
|
||||||
.orderBy(asc(members.name));
|
.orderBy(asc(members.name));
|
||||||
|
|
||||||
|
// Adhérents sans compte de connexion (créés avant l'arrivée des comptes).
|
||||||
|
const accountRows = await db
|
||||||
|
.select({ email: users.email, memberId: users.memberId })
|
||||||
|
.from(users);
|
||||||
|
const linkedMemberIds = new Set(accountRows.map((u) => u.memberId).filter((x): x is number => x != null));
|
||||||
|
const takenEmails = new Set(accountRows.map((u) => u.email.toLowerCase()));
|
||||||
|
const allForCount = await db.select({ id: members.id, email: members.email }).from(members);
|
||||||
|
const missingAccounts = allForCount.filter((m) => {
|
||||||
|
if (linkedMemberIds.has(m.id)) return false;
|
||||||
|
const e = (m.email ?? "").trim().toLowerCase();
|
||||||
|
return !!e && !takenEmails.has(e);
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
async function handleBackfill() {
|
||||||
|
"use server";
|
||||||
|
await createMissingMemberAccounts();
|
||||||
|
redirect("/backend/adherents");
|
||||||
|
}
|
||||||
|
|
||||||
const COLS = "1.6fr 1fr 1.2fr 0.9fr 0.8fr";
|
const COLS = "1.6fr 1fr 1.2fr 0.9fr 0.8fr";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
|
||||||
|
{missingAccounts > 0 && (
|
||||||
|
<form
|
||||||
|
action={handleBackfill}
|
||||||
|
style={{ background: "#fbeede", border: "1px solid #ecd8b8", borderRadius: 12, padding: "14px 18px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, flexWrap: "wrap" }}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 13.5, color: "#9a6638" }}>
|
||||||
|
<strong>{missingAccounts}</strong> adhérent(s) avec un e-mail n'ont pas encore de compte de connexion.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="font-display"
|
||||||
|
style={{ border: "none", background: "#9a6638", color: "#fff", fontWeight: 700, fontSize: 13.5, padding: "10px 18px", borderRadius: 10, cursor: "pointer", whiteSpace: "nowrap" }}
|
||||||
|
>
|
||||||
|
Créer les comptes manquants
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
<AddMemberPanel categories={cats} />
|
<AddMemberPanel categories={cats} />
|
||||||
|
|
||||||
<div style={{ background: "#fff", border: "1px solid #e6dcc6", borderRadius: 16, overflow: "hidden" }}>
|
<div style={{ background: "#fff", border: "1px solid #e6dcc6", borderRadius: 16, overflow: "hidden" }}>
|
||||||
|
|||||||
Reference in new issue
Block a user