From 6402c378dd00a57403be94341e1688cf8abc684b Mon Sep 17 00:00:00 2001 From: whidix Date: Tue, 3 Mar 2026 19:58:02 +0100 Subject: [PATCH] feat: implement character changes page with new and modified character listings --- scripts/import-json.ts | 36 ++--- src/routes/(admin)/admin/+layout.svelte | 1 + .../admin/character-changes/+page.server.ts | 88 +++++++++++ .../admin/character-changes/+page.svelte | 146 ++++++++++++++++++ 4 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 src/routes/(admin)/admin/character-changes/+page.server.ts create mode 100644 src/routes/(admin)/admin/character-changes/+page.svelte diff --git a/scripts/import-json.ts b/scripts/import-json.ts index ac7421b..0383a39 100644 --- a/scripts/import-json.ts +++ b/scripts/import-json.ts @@ -327,8 +327,8 @@ async function importFromJson(): Promise { } } } else { - // Check for changes and update scrapeValidation table - console.log('Characters table not empty, checking for changes...\n'); + // Update scrapeValidation table + console.log('Characters table not empty, updating scrapeValidation table for changes...\n'); for (let i = 0; i < characters.length; i++) { const item = characters[i]; @@ -340,33 +340,19 @@ async function importFromJson(): Promise { .where(eq(character.id, item.id)); lastSql = selectQuery.toSQL(); - const [dbCharacter] = await selectQuery; const jsonData = transformCharacterData(item); - const changed = hasChanged(jsonData, dbCharacter); - if (changed) { - // Update scrapeValidation table with changes - const upsertQuery = db - .insert(characterScrapeValidation) - .values(jsonData) - .onConflictDoUpdate({ - target: characterScrapeValidation.id, - set: jsonData - }); - - lastSql = upsertQuery.toSQL(); - await upsertQuery; - } else { - // No changes, delete from scrapeValidation if it exists - const deleteQuery = db - .delete(characterScrapeValidation) - .where(eq(characterScrapeValidation.id, item.id)); - - lastSql = deleteQuery.toSQL(); - await deleteQuery; - } + const upsertQuery = db + .insert(characterScrapeValidation) + .values(jsonData) + .onConflictDoUpdate({ + target: characterScrapeValidation.id, + set: jsonData + }); + lastSql = upsertQuery.toSQL(); + await upsertQuery; successCount++; process.stdout.write(`\rProcessed: ${successCount}/${characters.length}`); } catch (error) { diff --git a/src/routes/(admin)/admin/+layout.svelte b/src/routes/(admin)/admin/+layout.svelte index 683ea23..6ba6ae6 100644 --- a/src/routes/(admin)/admin/+layout.svelte +++ b/src/routes/(admin)/admin/+layout.svelte @@ -7,6 +7,7 @@ const navItems = [ { href: '/admin', label: 'Dashboard', icon: '📊' }, { href: '/admin/characters', label: 'Characters', icon: '🗣️' }, + { href: '/admin/character-changes', label: 'Changes', icon: '🔄' }, { href: '/admin/devil-fruits', label: 'Devil Fruits', icon: '🍎' }, { href: '/admin/arcs', label: 'Arcs', icon: '📚' }, { href: '/admin/users', label: 'Users', icon: '👥' }, diff --git a/src/routes/(admin)/admin/character-changes/+page.server.ts b/src/routes/(admin)/admin/character-changes/+page.server.ts new file mode 100644 index 0000000..7d12078 --- /dev/null +++ b/src/routes/(admin)/admin/character-changes/+page.server.ts @@ -0,0 +1,88 @@ +import { db } from '$lib/server/db'; +import { character, characterScrapeValidation } from '$lib/server/db/schema'; + +export async function load() { + // Get all characters from both tables + const currentCharacters = await db.select().from(character); + const scrapedCharacters = await db.select().from(characterScrapeValidation); + + // Create a map for quick lookup + const currentCharMap = new Map(currentCharacters.map(c => [c.id, c])); + + // Compare and categorize changes + const changes: { + type: 'new' | 'modified'; + id: string; + scraped: (typeof scrapedCharacters)[0]; + current?: (typeof currentCharacters)[0]; + differences?: Record; + }[] = []; + + for (const scraped of scrapedCharacters) { + const current = currentCharMap.get(scraped.id); + + if (!current) { + // New character + changes.push({ + type: 'new', + id: scraped.id, + scraped + }); + } else { + // Check if different + const differences: Record = {}; + const fieldsToCompare = [ + 'name', + 'gender', + 'age', + 'affiliations', + 'devilFruitId', + 'hakiObservation', + 'hakiArmament', + 'hakiConqueror', + 'bounty', + 'height', + 'origin', + 'firstAppearance', + 'pictureUrl', + 'epithets', + 'status', + 'arcId', + 'url' + ]; + + for (const field of fieldsToCompare) { + const currentValue = current[field as keyof typeof current]; + const scrapedValue = scraped[field as keyof typeof scraped]; + + // Deep comparison for JSON fields + if (JSON.stringify(currentValue) !== JSON.stringify(scrapedValue)) { + differences[field] = { + current: currentValue, + scraped: scrapedValue + }; + } + } + + if (Object.keys(differences).length > 0) { + changes.push({ + type: 'modified', + id: scraped.id, + scraped, + current, + differences + }); + } + } + } + + return { + changes: changes.sort((a, b) => { + // Show 'new' first, then 'modified' + if (a.type !== b.type) { + return a.type === 'new' ? -1 : 1; + } + return a.id.localeCompare(b.id); + }) + }; +} diff --git a/src/routes/(admin)/admin/character-changes/+page.svelte b/src/routes/(admin)/admin/character-changes/+page.svelte new file mode 100644 index 0000000..a70e4dc --- /dev/null +++ b/src/routes/(admin)/admin/character-changes/+page.svelte @@ -0,0 +1,146 @@ + + + + Admin - Character Changes + + +
+
+

Character Changes

+

Total changes: {newCharacters.length} new, {modifiedCharacters.length} modified

+
+ + + {#if newCharacters.length > 0} +
+

+ 🆕 New Characters ({newCharacters.length}) +

+
+ {#each newCharacters as change (change.id)} +
+
+ {#if change.scraped.pictureUrl} + {change.scraped.name} + {/if} +
+

{change.scraped.name}

+

{change.id}

+
+
+
+
+ Status: + {formatValue(change.scraped.status)} +
+
+ Gender: + {formatValue(change.scraped.gender)} +
+
+ Age: + {formatValue(change.scraped.age)} +
+
+ Bounty: + {formatValue(change.scraped.bounty)} +
+
+
+ {/each} +
+
+ {/if} + + + {#if modifiedCharacters.length > 0} +
+

+ ✏️ Modified Characters ({modifiedCharacters.length}) +

+
+ {#each modifiedCharacters as change (change.id)} +
+
+ {#if change.current?.pictureUrl} + {change.current.name} + {/if} +
+

{change.current?.name ?? change.scraped.name}

+

{change.id}

+
+
+ + {#if change.differences} +
+ {#each Object.entries(change.differences) as [field, diff]} +
+

{field}

+
+
+

Current:

+

{formatValue(diff.current)}

+
+
+

Scraped:

+

{formatValue(diff.scraped)}

+
+
+
+ {/each} +
+ {/if} +
+ {/each} +
+
+ {/if} + + {#if newCharacters.length === 0 && modifiedCharacters.length === 0} +
+

Aucun changement détecté. Les tables character et characterScrapeValidation sont synchronisées.

+
+ {/if} +
+ +