Guide — Interroger et modifier la base de données via des scripts Prisma jetables¶
Contexte : technique utilisée par Claude Code (session du 2026-07-10) pour répondre à des questions ponctuelles sur les données ProjetESM (compter des enfants, retrouver un compte utilisateur, réinitialiser un mot de passe...) sans passer par l'API HTTP ni par un client SQL externe.
Principe¶
Un script TypeScript jetable qui :
1. charge les variables d'environnement du backend (BackEnd/.env, notamment DATABASE_URL),
2. instancie PrismaClient (le client généré à partir de prisma/schema.prisma),
3. exécute une ou plusieurs requêtes,
4. affiche le résultat en console,
5. est supprimé juste après (fichier _tmp_*.ts, jamais commité).
Tout se lance depuis le dossier BackEnd/ du projet :
cd /home/kanmaber/projets/ProjetESM/BackEnd
Template de base¶
cat > prisma/_tmp_ma_requete.ts <<'EOF'
import { config } from 'dotenv';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
config({ path: path.resolve(__dirname, '../.env') });
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// 👉 requête ici
}
main().catch(console.error).finally(() => prisma.$disconnect());
EOF
npx tsx prisma/_tmp_ma_requete.ts
rm -f prisma/_tmp_ma_requete.ts
Pourquoi ce chemin précis :
- Le fichier doit être sous BackEnd/ (ex: BackEnd/prisma/) pour que
import { PrismaClient } from '@prisma/client' résolve vers le client généré dans
BackEnd/node_modules.
- config({ path: ... }) pointe explicitement vers BackEnd/.env — sans ça, Prisma ne connaît
pas DATABASE_URL et échoue.
- Le heredoc <<'EOF' (avec guillemets autour du premier EOF) empêche le shell d'interpréter
les caractères spéciaux/accents du script — cf. règle projet "sed échoue sur l'UTF-8, utiliser
des outils qui respectent l'encodage".
- Si erreur @prisma/client did not initialize yet : lancer d'abord npx prisma generate.
Exécution exacte utilisée en session (comptage des maîtres coraniques par commune) :
cat > prisma/_tmp_count_maitres.ts <<'EOF'
import { config } from 'dotenv';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
config({ path: path.resolve(__dirname, '../.env') });
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const total = await prisma.maitreCoranique.count();
const parCommune = await prisma.maitreCoranique.groupBy({
by: ['communeCode'],
_count: true,
});
console.log('TOTAL maîtres coraniques:', total);
console.log('Par commune:', JSON.stringify(parCommune, null, 2));
}
main().catch(console.error).finally(() => prisma.$disconnect());
EOF
npx tsx prisma/_tmp_count_maitres.ts
rm -f prisma/_tmp_count_maitres.ts
Les noms de modèles (enfant, utilisateur, maitreCoranique, accompagnement...) et leurs
champs correspondent exactement à ceux de BackEnd/prisma/schema.prisma — c'est la référence à
consulter pour savoir ce qui est disponible sur chaque table.
LECTURE — exemples¶
Compter des enregistrements¶
await prisma.enfant.count();
await prisma.enfant.count({ where: { communeCode: 'KANDI' } });
await prisma.enfant.count({ where: { statutValidation: 'EN_ATTENTE' } });
Grouper / statistiques¶
await prisma.enfant.groupBy({
by: ['statutValidation'],
_count: true,
});
await prisma.enfant.groupBy({
by: ['communeCode', 'genre'],
_count: true,
});
Chercher un enregistrement précis¶
// Par identifiant unique (email, id...)
await prisma.utilisateur.findUnique({
where: { email: 'x@y.com' },
select: { id: true, email: true, actif: true, communeCode: true },
});
// Par critère non-unique (retourne le premier trouvé, ou null)
await prisma.enfant.findFirst({
where: { prenom: 'A', nom: 'A' },
});
Lister avec filtres et relations¶
await prisma.enfant.findMany({
where: { communeCode: 'KANDI', statutValidation: 'EN_ATTENTE' },
select: {
id: true,
prenom: true,
nom: true,
idUtilisateur: true,
utilisateur: { select: { email: true } }, // relation incluse
},
orderBy: { createdAt: 'desc' },
take: 20, // limiter le nombre de résultats
});
ÉCRITURE — création, modification, suppression¶
⚠️ Ces opérations touchent la vraie base de données, pas un environnement isolé. Avant de les
lancer :
- vérifier qu'on cible bien le bon enregistrement (id ou critère précis, jamais un where trop
large sur updateMany/deleteMany sans avoir d'abord fait un findMany de vérification),
toujours regarder le résultat, jamais deviner) ;
- pour deleteMany, updateMany ou toute suppression, s'assurer que le destinataire de la
commande (Bernard) en confirme l'usage — ce ne sont pas des opérations à lancer par réflexe.
Créer un enregistrement¶
async function main() {
const nouveauMaitre = await prisma.maitreCoranique.create({
data: {
nom: 'Dupont',
prenom: 'Jean',
communeCode: 'KANDI',
idUtilisateur: 'cmqjhrhs30001nh5zrri51xf9', // id d'un utilisateur existant
},
});
console.log('Créé:', nouveauMaitre);
}
Modifier un enregistrement¶
async function main() {
const misAJour = await prisma.enfant.update({
where: { id: 'cmrcnze2f0001u3cwjt5340bm' },
data: { communeCode: 'NDALI' },
});
console.log('Modifié:', misAJour);
}
Exemple réel utilisé en session (réinitialisation de mot de passe, avec bcryptjs pour respecter
le hachage attendu par ServiceHashage) :
import bcrypt from 'bcryptjs';
// ...
async function main() {
const email = 'flow.test.backup@projetesm.test';
const hash = await bcrypt.hash('TestFlow1234!', 10); // 10 rounds = même config que le backend
await prisma.utilisateur.update({
where: { email },
data: { motDePasse: hash, loginAttempts: 0, lockedUntil: null },
});
}
Supprimer un enregistrement¶
async function main() {
// Toujours vérifier AVANT de supprimer
const cible = await prisma.enfant.findUnique({ where: { id: 'xxx' } });
console.log('À supprimer:', cible);
if (cible) {
await prisma.enfant.delete({ where: { id: 'xxx' } });
console.log('Supprimé.');
}
}
upsert (créer si absent, modifier si présent)¶
await prisma.profilPermission.upsert({
where: { profilId_permissionId: { profilId: 'xxx', permissionId: 'yyy' } },
update: {},
create: { profilId: 'xxx', permissionId: 'yyy' },
});
Transaction (plusieurs opérations, tout ou rien)¶
await prisma.$transaction([
prisma.enfant.update({ where: { id: 'xxx' }, data: { statutValidation: 'VALIDE' } }),
prisma.mesureIndicateur.create({ data: { /* ... */ } }),
]);
Règles de prudence¶
- Lecture (
count,findMany,findUnique,groupBy) : sans risque, à utiliser librement. - Écriture (
create,update,upsert) : vérifier l'identifiant/critère avant d'exécuter, relire le résultat affiché. - Suppression (
delete,deleteMany) ou modification en masse (updateMany) : action difficile à annuler → toujours confirmer avec Bernard avant de lancer, jamais par automatisme. - Ne jamais coller le contenu de
DATABASE_URL(mot de passe de la base) dans une sortie affichée ou un fichier commité — le script charge.enven interne sans l'afficher. - Toujours supprimer le script
_tmp_*.tsaprès usage : ce ne sont pas des scripts métier destinés à rester dans le dépôt (contrairement àprisma/create-comptes-test.ts,prisma/list-comptes-test.ts, etc. qui sont des utilitaires réutilisables et commités).
Repères utiles¶
- Schéma complet des tables et champs :
BackEnd/prisma/schema.prisma - Règle de hachage des mots de passe :
BackEnd/src/Modules/Auth/infrastructure/services/ServiceHashage.ts(bcrypt, 10 rounds) - Règle métier de longueur minimale de mot de passe (12 caractères) :
BackEnd/src/Modules/Auth/infrastructure/services/validation/ServiceValidationAuth.ts(CONFIG_AUTH_DEFAUT.longueurMinMotDePasse) - Règle d'autorisation de modification des données de terrain (
peutModifier) :BackEnd/src/shared/infrastructure/services/ServiceAutorisationDonnees.ts