feat: enhance character status extraction and update schema for nullable status
All checks were successful
Build Docker Image / build (push) Successful in 1m22s
All checks were successful
Build Docker Image / build (push) Successful in 1m22s
This commit is contained in:
@@ -659,9 +659,9 @@ function extractOrigin($: cheerio.CheerioAPI): string | null {
|
||||
/**
|
||||
* Extract status from infobox
|
||||
*/
|
||||
function extractStatus($: cheerio.CheerioAPI): string {
|
||||
function extractStatus($: cheerio.CheerioAPI): string | null {
|
||||
const div = $('[data-source="statut"] .pi-data-value');
|
||||
if (div.length === 0) return 'Alive';
|
||||
if (div.length === 0) return null;
|
||||
|
||||
const statusText = div.text().trim().toLowerCase();
|
||||
|
||||
@@ -669,6 +669,8 @@ function extractStatus($: cheerio.CheerioAPI): string {
|
||||
return 'Alive';
|
||||
} else if (statusText.includes('décédé')) {
|
||||
return 'Dead';
|
||||
} else if (statusText.includes('inconnu')) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
return 'Alive';
|
||||
|
||||
@@ -4,6 +4,8 @@ import { user } from './auth.schema';
|
||||
// Define devil fruit types
|
||||
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Smile' | 'Unknown';
|
||||
|
||||
export type Status = 'Alive' | 'Dead' | 'Unknown';
|
||||
|
||||
// Define the site config table schema
|
||||
export const config = sqliteTable('config', {
|
||||
key: text('key').primaryKey(),
|
||||
@@ -44,7 +46,7 @@ export const character = sqliteTable('character', {
|
||||
firstAppearance: integer('firstAppearance').notNull(),
|
||||
pictureUrl: text('pictureUrl'),
|
||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
||||
status: text('status'),
|
||||
status: text('status').$type<Status | null>(),
|
||||
arcId: text('arcId').references(() => arc.id),
|
||||
url: text('url'),
|
||||
isInDailyMode: integer('isInDailyMode', { mode: 'boolean' }).default(true)
|
||||
@@ -67,7 +69,7 @@ export const characterOverride = sqliteTable('characterOverride', {
|
||||
firstAppearance: integer('firstAppearance'),
|
||||
pictureUrl: text('pictureUrl'),
|
||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
||||
status: text('status'),
|
||||
status: text('status').$type<Status | null>(),
|
||||
arcId: text('arcId').references(() => arc.id),
|
||||
url: text('url'),
|
||||
notes: text('notes')
|
||||
@@ -90,7 +92,7 @@ export const characterScrapeValidation = sqliteTable('characterScrapeValidation'
|
||||
firstAppearance: integer('firstAppearance').notNull(),
|
||||
pictureUrl: text('pictureUrl'),
|
||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
||||
status: text('status'),
|
||||
status: text('status').$type<Status | null>(),
|
||||
arcId: text('arcId').references(() => arc.id),
|
||||
url: text('url')
|
||||
});
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { character, characterScrapeValidation } from '$lib/server/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
async function upsertCharacterFromScrapeValidation(characterId: string): Promise<boolean> {
|
||||
const [scraped] = await db
|
||||
.select()
|
||||
.from(characterScrapeValidation)
|
||||
.where(eq(characterScrapeValidation.id, characterId));
|
||||
|
||||
if (!scraped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(character)
|
||||
.values({
|
||||
id: scraped.id,
|
||||
name: scraped.name,
|
||||
gender: scraped.gender,
|
||||
age: scraped.age,
|
||||
affiliations: scraped.affiliations,
|
||||
devilFruitId: scraped.devilFruitId,
|
||||
hakiObservation: scraped.hakiObservation,
|
||||
hakiArmament: scraped.hakiArmament,
|
||||
hakiConqueror: scraped.hakiConqueror,
|
||||
bounty: scraped.bounty,
|
||||
height: scraped.height,
|
||||
origin: scraped.origin,
|
||||
firstAppearance: scraped.firstAppearance,
|
||||
pictureUrl: scraped.pictureUrl,
|
||||
epithets: scraped.epithets,
|
||||
status: scraped.status,
|
||||
arcId: scraped.arcId,
|
||||
url: scraped.url
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: character.id,
|
||||
set: {
|
||||
name: scraped.name,
|
||||
gender: scraped.gender,
|
||||
age: scraped.age,
|
||||
affiliations: scraped.affiliations,
|
||||
devilFruitId: scraped.devilFruitId,
|
||||
hakiObservation: scraped.hakiObservation,
|
||||
hakiArmament: scraped.hakiArmament,
|
||||
hakiConqueror: scraped.hakiConqueror,
|
||||
bounty: scraped.bounty,
|
||||
height: scraped.height,
|
||||
origin: scraped.origin,
|
||||
firstAppearance: scraped.firstAppearance,
|
||||
pictureUrl: scraped.pictureUrl,
|
||||
epithets: scraped.epithets,
|
||||
status: scraped.status,
|
||||
arcId: scraped.arcId,
|
||||
url: scraped.url
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function load() {
|
||||
// Get all characters from both tables
|
||||
@@ -86,3 +145,37 @@ export async function load() {
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
acceptOne: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
const characterId = formData.get('characterId');
|
||||
|
||||
if (!characterId || typeof characterId !== 'string') {
|
||||
return { success: false, message: 'characterId is required' };
|
||||
}
|
||||
|
||||
const applied = await upsertCharacterFromScrapeValidation(characterId);
|
||||
return {
|
||||
success: applied,
|
||||
message: applied ? 'Character applied successfully' : 'Character not found in scrape validation table'
|
||||
};
|
||||
},
|
||||
|
||||
acceptAll: async () => {
|
||||
const scrapedCharacters = await db.select().from(characterScrapeValidation);
|
||||
let appliedCount = 0;
|
||||
|
||||
for (const scraped of scrapedCharacters) {
|
||||
const applied = await upsertCharacterFromScrapeValidation(scraped.id);
|
||||
if (applied) {
|
||||
appliedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
appliedCount
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
const newCharacters = $derived(data.changes.filter((c: any) => c.type === 'new'));
|
||||
const modifiedCharacters = $derived(data.changes.filter((c: any) => c.type === 'modified'));
|
||||
|
||||
function fandomUrl(path: string | null | undefined): string {
|
||||
if (!path) return 'https://onepiece.fandom.com/fr/wiki';
|
||||
return `https://onepiece.fandom.com/fr/wiki/${path}`;
|
||||
}
|
||||
|
||||
function formatValue(value: any): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '—';
|
||||
@@ -35,6 +40,16 @@
|
||||
<div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 mb-2">Character Changes</h1>
|
||||
<p class="text-gray-400">Total changes: {newCharacters.length} new, {modifiedCharacters.length} modified</p>
|
||||
{#if newCharacters.length + modifiedCharacters.length > 0}
|
||||
<form method="POST" action="?/acceptAll" class="mt-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-full border border-emerald-300/40 bg-emerald-500/20 px-4 py-2 text-sm font-semibold text-emerald-100 transition hover:bg-emerald-500/30"
|
||||
>
|
||||
✅ Accepter tous les changements
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- New Characters Section -->
|
||||
@@ -46,18 +61,31 @@
|
||||
<div class="grid gap-4">
|
||||
{#each newCharacters as change (change.id)}
|
||||
<div class="rounded-lg border border-emerald-500/30 bg-emerald-500/5 p-4 space-y-3">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if change.scraped.pictureUrl}
|
||||
<img
|
||||
src={change.scraped.pictureUrl}
|
||||
alt={change.scraped.name}
|
||||
class="w-12 h-12 rounded object-cover"
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-emerald-300">{change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if change.scraped.pictureUrl}
|
||||
<a href={fandomUrl(change.scraped.url)} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
src={change.scraped.pictureUrl}
|
||||
alt={change.scraped.name}
|
||||
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-emerald-300">{change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="?/acceptOne">
|
||||
<input type="hidden" name="characterId" value={change.id} />
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-full border border-emerald-300/40 bg-emerald-500/20 px-3 py-1 text-xs font-semibold text-emerald-100 transition hover:bg-emerald-500/30"
|
||||
>
|
||||
Accepter
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>
|
||||
@@ -92,18 +120,31 @@
|
||||
<div class="grid gap-6">
|
||||
{#each modifiedCharacters as change (change.id)}
|
||||
<div class="rounded-lg border border-amber-500/30 bg-amber-500/5 p-4 space-y-4">
|
||||
<div class="flex items-center gap-3 pb-4 border-b border-amber-500/20">
|
||||
{#if change.current?.pictureUrl}
|
||||
<img
|
||||
src={change.current.pictureUrl}
|
||||
alt={change.current.name}
|
||||
class="w-12 h-12 rounded object-cover"
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-amber-300">{change.current?.name ?? change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
<div class="flex items-center justify-between gap-3 pb-4 border-b border-amber-500/20">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if change.current?.pictureUrl}
|
||||
<a href={fandomUrl(change.current?.url ?? change.scraped.url)} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
src={change.current.pictureUrl}
|
||||
alt={change.current.name}
|
||||
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-amber-300">{change.current?.name ?? change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="?/acceptOne">
|
||||
<input type="hidden" name="characterId" value={change.id} />
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-full border border-amber-300/40 bg-amber-500/20 px-3 py-1 text-xs font-semibold text-amber-100 transition hover:bg-amber-500/30"
|
||||
>
|
||||
Accepter
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{#if change.differences}
|
||||
|
||||
Reference in New Issue
Block a user