refactor: standardize quotation marks across all files and improve code consistency
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
// Script simple pour initialiser la base de données
|
||||
// Utilisez Prisma Studio ou les API routes pour créer les données initiales
|
||||
|
||||
console.log("Pour initialiser la base de données:")
|
||||
console.log("1. Lancez Prisma Studio: pnpm db:studio")
|
||||
console.log("2. Créez manuellement le dossier racine 'folder-root' avec:")
|
||||
console.log(" - id: folder-root")
|
||||
console.log(" - name: Mes Comptes")
|
||||
console.log(" - parentId: null")
|
||||
console.log(" - color: #6366f1")
|
||||
console.log(" - icon: folder")
|
||||
console.log("3. Créez les catégories par défaut via l'interface web")
|
||||
|
||||
console.log("Pour initialiser la base de données:");
|
||||
console.log("1. Lancez Prisma Studio: pnpm db:studio");
|
||||
console.log("2. Créez manuellement le dossier racine 'folder-root' avec:");
|
||||
console.log(" - id: folder-root");
|
||||
console.log(" - name: Mes Comptes");
|
||||
console.log(" - parentId: null");
|
||||
console.log(" - color: #6366f1");
|
||||
console.log(" - icon: folder");
|
||||
console.log("3. Créez les catégories par défaut via l'interface web");
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
import { defaultCategories, type CategoryDefinition } from "../lib/defaults"
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { defaultCategories, type CategoryDefinition } from "../lib/defaults";
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log("🏷️ Synchronisation des catégories hiérarchiques...")
|
||||
console.log(` ${defaultCategories.length} catégories à synchroniser\n`)
|
||||
console.log("🏷️ Synchronisation des catégories hiérarchiques...");
|
||||
console.log(` ${defaultCategories.length} catégories à synchroniser\n`);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PHASE 0: Nettoyage des doublons existants
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
console.log("═".repeat(50))
|
||||
console.log("PHASE 0: Nettoyage des doublons")
|
||||
console.log("═".repeat(50))
|
||||
console.log("═".repeat(50));
|
||||
console.log("PHASE 0: Nettoyage des doublons");
|
||||
console.log("═".repeat(50));
|
||||
|
||||
const allExisting = await prisma.category.findMany()
|
||||
const byNormalizedName = new Map<string, typeof allExisting>()
|
||||
const allExisting = await prisma.category.findMany();
|
||||
const byNormalizedName = new Map<string, typeof allExisting>();
|
||||
|
||||
for (const cat of allExisting) {
|
||||
const normalized = normalizeName(cat.name)
|
||||
const normalized = normalizeName(cat.name);
|
||||
if (!byNormalizedName.has(normalized)) {
|
||||
byNormalizedName.set(normalized, [])
|
||||
byNormalizedName.set(normalized, []);
|
||||
}
|
||||
byNormalizedName.get(normalized)!.push(cat)
|
||||
byNormalizedName.get(normalized)!.push(cat);
|
||||
}
|
||||
|
||||
let merged = 0
|
||||
let merged = 0;
|
||||
for (const [_normalized, cats] of byNormalizedName) {
|
||||
if (cats.length > 1) {
|
||||
// Garder celui avec emoji
|
||||
let keeper = cats[0]
|
||||
let keeper = cats[0];
|
||||
for (const cat of cats) {
|
||||
if (/[\u{1F300}-\u{1F9FF}]/u.test(cat.name)) {
|
||||
keeper = cat
|
||||
break
|
||||
keeper = cat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const toDelete: typeof cats = []
|
||||
const toDelete: typeof cats = [];
|
||||
for (const cat of cats) {
|
||||
if (cat.id !== keeper.id) toDelete.push(cat)
|
||||
if (cat.id !== keeper.id) toDelete.push(cat);
|
||||
}
|
||||
|
||||
for (const dup of toDelete) {
|
||||
@@ -46,92 +46,109 @@ async function main() {
|
||||
await prisma.transaction.updateMany({
|
||||
where: { categoryId: dup.id },
|
||||
data: { categoryId: keeper.id },
|
||||
})
|
||||
});
|
||||
// Transférer enfants
|
||||
await prisma.category.updateMany({
|
||||
where: { parentId: dup.id },
|
||||
data: { parentId: keeper.id },
|
||||
})
|
||||
});
|
||||
// Supprimer doublon
|
||||
await prisma.category.delete({ where: { id: dup.id } })
|
||||
console.log(`🗑️ Fusionné: "${dup.name}" → "${keeper.name}"`)
|
||||
merged++
|
||||
await prisma.category.delete({ where: { id: dup.id } });
|
||||
console.log(`🗑️ Fusionné: "${dup.name}" → "${keeper.name}"`);
|
||||
merged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(merged > 0 ? ` ${merged} doublons fusionnés` : " Aucun doublon ✓")
|
||||
console.log(
|
||||
merged > 0 ? ` ${merged} doublons fusionnés` : " Aucun doublon ✓",
|
||||
);
|
||||
|
||||
// Séparer parents et enfants
|
||||
const parentCategories = defaultCategories.filter((c) => c.parentSlug === null)
|
||||
const childCategories = defaultCategories.filter((c) => c.parentSlug !== null)
|
||||
const parentCategories = defaultCategories.filter(
|
||||
(c) => c.parentSlug === null,
|
||||
);
|
||||
const childCategories = defaultCategories.filter(
|
||||
(c) => c.parentSlug !== null,
|
||||
);
|
||||
|
||||
console.log(`\n📁 ${parentCategories.length} catégories parentes`)
|
||||
console.log(` └─ ${childCategories.length} sous-catégories\n`)
|
||||
console.log(`\n📁 ${parentCategories.length} catégories parentes`);
|
||||
console.log(` └─ ${childCategories.length} sous-catégories\n`);
|
||||
|
||||
// Map slug -> id (pour résoudre les parentId)
|
||||
const slugToId = new Map<string, string>()
|
||||
const slugToId = new Map<string, string>();
|
||||
|
||||
let created = 0
|
||||
let updated = 0
|
||||
let unchanged = 0
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let unchanged = 0;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PHASE 1: Créer/MAJ les catégories parentes
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
console.log("═".repeat(50))
|
||||
console.log("PHASE 1: Catégories parentes")
|
||||
console.log("═".repeat(50))
|
||||
console.log("═".repeat(50));
|
||||
console.log("PHASE 1: Catégories parentes");
|
||||
console.log("═".repeat(50));
|
||||
|
||||
for (const category of parentCategories) {
|
||||
const result = await upsertCategory(category, null)
|
||||
slugToId.set(category.slug, result.id)
|
||||
const result = await upsertCategory(category, null);
|
||||
slugToId.set(category.slug, result.id);
|
||||
|
||||
if (result.action === "created") created++
|
||||
else if (result.action === "updated") updated++
|
||||
else unchanged++
|
||||
if (result.action === "created") created++;
|
||||
else if (result.action === "updated") updated++;
|
||||
else unchanged++;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PHASE 2: Créer/MAJ les sous-catégories
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
console.log("\n" + "═".repeat(50))
|
||||
console.log("PHASE 2: Sous-catégories")
|
||||
console.log("═".repeat(50))
|
||||
console.log("\n" + "═".repeat(50));
|
||||
console.log("PHASE 2: Sous-catégories");
|
||||
console.log("═".repeat(50));
|
||||
|
||||
for (const category of childCategories) {
|
||||
const parentId = slugToId.get(category.parentSlug!)
|
||||
const parentId = slugToId.get(category.parentSlug!);
|
||||
if (!parentId) {
|
||||
console.log(`⚠️ Parent introuvable pour: ${category.name} (parentSlug: ${category.parentSlug})`)
|
||||
continue
|
||||
console.log(
|
||||
`⚠️ Parent introuvable pour: ${category.name} (parentSlug: ${category.parentSlug})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await upsertCategory(category, parentId)
|
||||
slugToId.set(category.slug, result.id)
|
||||
const result = await upsertCategory(category, parentId);
|
||||
slugToId.set(category.slug, result.id);
|
||||
|
||||
if (result.action === "created") created++
|
||||
else if (result.action === "updated") updated++
|
||||
else unchanged++
|
||||
if (result.action === "created") created++;
|
||||
else if (result.action === "updated") updated++;
|
||||
else unchanged++;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// RÉSUMÉ
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
console.log("\n" + "═".repeat(50))
|
||||
console.log("📊 RÉSUMÉ CATÉGORIES:")
|
||||
console.log("═".repeat(50))
|
||||
console.log(` ✅ Créées: ${created}`)
|
||||
console.log(` ✏️ Mises à jour: ${updated}`)
|
||||
console.log(` ⏭️ Inchangées: ${unchanged}`)
|
||||
console.log("\n" + "═".repeat(50));
|
||||
console.log("📊 RÉSUMÉ CATÉGORIES:");
|
||||
console.log("═".repeat(50));
|
||||
console.log(` ✅ Créées: ${created}`);
|
||||
console.log(` ✏️ Mises à jour: ${updated}`);
|
||||
console.log(` ⏭️ Inchangées: ${unchanged}`);
|
||||
|
||||
// Stats finales
|
||||
const totalCategories = await prisma.category.count()
|
||||
const parentCount = await prisma.category.count({ where: { parentId: null } })
|
||||
const childCount = await prisma.category.count({ where: { NOT: { parentId: null } } })
|
||||
const totalKeywords = defaultCategories.reduce((sum, c) => sum + c.keywords.length, 0)
|
||||
const totalCategories = await prisma.category.count();
|
||||
const parentCount = await prisma.category.count({
|
||||
where: { parentId: null },
|
||||
});
|
||||
const childCount = await prisma.category.count({
|
||||
where: { NOT: { parentId: null } },
|
||||
});
|
||||
const totalKeywords = defaultCategories.reduce(
|
||||
(sum, c) => sum + c.keywords.length,
|
||||
0,
|
||||
);
|
||||
|
||||
console.log("\n📈 Base de données:")
|
||||
console.log(` Total catégories: ${totalCategories} (${parentCount} parents, ${childCount} enfants)`)
|
||||
console.log(` Total keywords: ${totalKeywords}`)
|
||||
console.log("\n📈 Base de données:");
|
||||
console.log(
|
||||
` Total catégories: ${totalCategories} (${parentCount} parents, ${childCount} enfants)`,
|
||||
);
|
||||
console.log(` Total keywords: ${totalKeywords}`);
|
||||
}
|
||||
|
||||
// Normaliser un nom (enlever emojis, espaces multiples, lowercase)
|
||||
@@ -141,42 +158,51 @@ function normalizeName(name: string): string {
|
||||
.replace(/[^\w\sÀ-ÿ]/g, "") // Keep only alphanumeric and accents
|
||||
.replace(/\s+/g, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function upsertCategory(
|
||||
category: CategoryDefinition,
|
||||
parentId: string | null
|
||||
parentId: string | null,
|
||||
): Promise<{ id: string; action: "created" | "updated" | "unchanged" }> {
|
||||
// Chercher par nom exact d'abord
|
||||
let existing = await prisma.category.findFirst({
|
||||
where: { name: category.name },
|
||||
})
|
||||
});
|
||||
|
||||
// Si pas trouvé, chercher par nom normalisé (sans emoji) dans TOUTES les catégories
|
||||
if (!existing) {
|
||||
const allCategories = await prisma.category.findMany()
|
||||
const normalizedTarget = normalizeName(category.name)
|
||||
const allCategories = await prisma.category.findMany();
|
||||
const normalizedTarget = normalizeName(category.name);
|
||||
for (const cat of allCategories) {
|
||||
if (normalizeName(cat.name) === normalizedTarget) {
|
||||
existing = cat
|
||||
console.log(` 🔗 Match normalisé: "${cat.name}" → "${category.name}"`)
|
||||
break
|
||||
existing = cat;
|
||||
console.log(
|
||||
` 🔗 Match normalisé: "${cat.name}" → "${category.name}"`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
// Comparer pour voir si mise à jour nécessaire
|
||||
const existingKeywords = JSON.parse(existing.keywords) as string[]
|
||||
const existingKeywords = JSON.parse(existing.keywords) as string[];
|
||||
const keywordsChanged =
|
||||
JSON.stringify(existingKeywords.sort()) !== JSON.stringify([...category.keywords].sort())
|
||||
const nameChanged = existing.name !== category.name
|
||||
const colorChanged = existing.color !== category.color
|
||||
const iconChanged = existing.icon !== category.icon
|
||||
const parentChanged = existing.parentId !== parentId
|
||||
JSON.stringify(existingKeywords.sort()) !==
|
||||
JSON.stringify([...category.keywords].sort());
|
||||
const nameChanged = existing.name !== category.name;
|
||||
const colorChanged = existing.color !== category.color;
|
||||
const iconChanged = existing.icon !== category.icon;
|
||||
const parentChanged = existing.parentId !== parentId;
|
||||
|
||||
if (nameChanged || keywordsChanged || colorChanged || iconChanged || parentChanged) {
|
||||
if (
|
||||
nameChanged ||
|
||||
keywordsChanged ||
|
||||
colorChanged ||
|
||||
iconChanged ||
|
||||
parentChanged
|
||||
) {
|
||||
await prisma.category.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
@@ -186,18 +212,22 @@ async function upsertCategory(
|
||||
keywords: JSON.stringify(category.keywords),
|
||||
parentId: parentId,
|
||||
},
|
||||
})
|
||||
console.log(`✏️ MAJ: ${existing.name}${nameChanged ? ` → ${category.name}` : ""}`)
|
||||
});
|
||||
console.log(
|
||||
`✏️ MAJ: ${existing.name}${nameChanged ? ` → ${category.name}` : ""}`,
|
||||
);
|
||||
if (keywordsChanged) {
|
||||
console.log(` └─ Keywords: ${existingKeywords.length} → ${category.keywords.length}`)
|
||||
console.log(
|
||||
` └─ Keywords: ${existingKeywords.length} → ${category.keywords.length}`,
|
||||
);
|
||||
}
|
||||
if (parentChanged) {
|
||||
console.log(` └─ Parent modifié`)
|
||||
console.log(` └─ Parent modifié`);
|
||||
}
|
||||
return { id: existing.id, action: "updated" }
|
||||
return { id: existing.id, action: "updated" };
|
||||
}
|
||||
|
||||
return { id: existing.id, action: "unchanged" }
|
||||
return { id: existing.id, action: "unchanged" };
|
||||
}
|
||||
|
||||
// Créer nouvelle catégorie
|
||||
@@ -209,17 +239,19 @@ async function upsertCategory(
|
||||
keywords: JSON.stringify(category.keywords),
|
||||
parentId: parentId,
|
||||
},
|
||||
})
|
||||
console.log(`✅ Créée: ${category.name}${category.keywords.length > 0 ? ` (${category.keywords.length} keywords)` : ""}`)
|
||||
});
|
||||
console.log(
|
||||
`✅ Créée: ${category.name}${category.keywords.length > 0 ? ` (${category.keywords.length} keywords)` : ""}`,
|
||||
);
|
||||
|
||||
return { id: created.id, action: "created" }
|
||||
return { id: created.id, action: "created" };
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error("❌ Erreur:", e)
|
||||
process.exit(1)
|
||||
console.error("❌ Erreur:", e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user