Checkpoint: Correctif de première installation : après les migrations Drizzle, le serveur crée le compte administrateur Itinova requis avec mot de passe haché, sans doublon. Validation complète réussie : 62 tests, TypeScript et build.
All checks were successful
Validation applicative / TypeScript, tests et build (push) Successful in 1h3m35s
All checks were successful
Validation applicative / TypeScript, tests et build (push) Successful in 1h3m35s
This commit is contained in:
@@ -1,6 +1,35 @@
|
||||
import { migrate } from "drizzle-orm/mysql2/migrator";
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createPool } from "mysql2/promise";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { users } from "../drizzle/schema";
|
||||
|
||||
const INITIAL_ADMIN_LOGIN = "adminItinova";
|
||||
const INITIAL_ADMIN_PASSWORD = process.env.INITIAL_ADMIN_PASSWORD ?? "Itinova69!";
|
||||
|
||||
async function ensureInitialAdmin(pool: ReturnType<typeof createPool>) {
|
||||
const database = drizzle(pool);
|
||||
const existingAdmin = await database
|
||||
.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(eq(users.login, INITIAL_ADMIN_LOGIN))
|
||||
.limit(1);
|
||||
|
||||
if (existingAdmin.length > 0) return;
|
||||
|
||||
const passwordHash = await bcrypt.hash(INITIAL_ADMIN_PASSWORD, 12);
|
||||
await database.insert(users).values({
|
||||
login: INITIAL_ADMIN_LOGIN,
|
||||
email: "adminItinova@santinova-soft.org",
|
||||
firstName: "Admin",
|
||||
lastName: "Itinova",
|
||||
passwordHash,
|
||||
role: "admin",
|
||||
isActive: true,
|
||||
});
|
||||
console.log("[Database] Compte administrateur initial créé.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronise la base avec les migrations versionnées avant d'exposer l'API.
|
||||
@@ -16,7 +45,9 @@ export async function runDatabaseMigrations() {
|
||||
|
||||
const pool = createPool(databaseUrl);
|
||||
try {
|
||||
await migrate(drizzle(pool), { migrationsFolder: "./drizzle" });
|
||||
const database = drizzle(pool);
|
||||
await migrate(database, { migrationsFolder: "./drizzle" });
|
||||
await ensureInitialAdmin(pool);
|
||||
console.log("[Database] Migrations Drizzle synchronisées.");
|
||||
} finally {
|
||||
await pool.end();
|
||||
|
||||
14
server/initialAdminBootstrap.test.ts
Normal file
14
server/initialAdminBootstrap.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("bootstrap du compte administrateur", () => {
|
||||
it("crée le compte administrateur requis après migration sans stocker de mot de passe en clair", async () => {
|
||||
const bootstrap = await readFile(resolve(process.cwd(), "server/databaseMigrations.ts"), "utf8");
|
||||
|
||||
expect(bootstrap).toContain('const INITIAL_ADMIN_LOGIN = "adminItinova"');
|
||||
expect(bootstrap).toContain("await ensureInitialAdmin(pool)");
|
||||
expect(bootstrap).toContain("await bcrypt.hash(INITIAL_ADMIN_PASSWORD");
|
||||
expect(bootstrap).not.toContain("passwordHash: INITIAL_ADMIN_PASSWORD,");
|
||||
});
|
||||
});
|
||||
1
todo.md
1
todo.md
@@ -216,3 +216,4 @@
|
||||
- [x] Créer et synchroniser le dépôt Gitea production Budget SI avant le déploiement du serveur de production
|
||||
- [ ] Initialiser et vérifier le schéma de la base Budget SI de production avant la validation administrateur
|
||||
- [ ] Exécuter les migrations Drizzle au démarrage du conteneur pour les futurs déploiements de production
|
||||
- [ ] Créer le compte administrateur initial requis lorsque la base Budget SI est vide
|
||||
|
||||
Reference in New Issue
Block a user