From d75c74ac3c0f4fb41c0ac346d1d02abdf8340d12 Mon Sep 17 00:00:00 2001 From: whidix Date: Tue, 14 Apr 2026 21:56:26 +0200 Subject: [PATCH] Refactor character affiliations to singular form - Updated character data structure to replace 'affiliations' and 'frAffiliations' with 'affiliation' and 'frAffiliation'. - Modified related functions and components to accommodate the new structure. - Adjusted database schema and server-side logic to reflect the changes in character affiliation handling. - Ensured all references in the UI components and data import/export scripts are updated accordingly. --- drizzle/0003_mixed_ben_grimm.sql | 8 + drizzle/meta/0003_snapshot.json | 1185 +++++++++++++++++ drizzle/meta/_journal.json | 7 + scripts/import-json.ts | 12 +- scripts/scrape-onepiece.ts | 31 +- src/lib/components/GuessHistoryTable.svelte | 78 +- src/lib/components/HintsPanel.svelte | 5 +- src/lib/server/daily-character.ts | 4 +- src/lib/server/db/schema.ts | 8 +- .../admin/character-changes/+page.server.ts | 9 +- .../(admin)/admin/characters/+page.server.ts | 2 +- .../(admin)/admin/characters/+page.svelte | 14 +- 12 files changed, 1256 insertions(+), 107 deletions(-) create mode 100644 drizzle/0003_mixed_ben_grimm.sql create mode 100644 drizzle/meta/0003_snapshot.json diff --git a/drizzle/0003_mixed_ben_grimm.sql b/drizzle/0003_mixed_ben_grimm.sql new file mode 100644 index 0000000..3e2381e --- /dev/null +++ b/drizzle/0003_mixed_ben_grimm.sql @@ -0,0 +1,8 @@ +ALTER TABLE `character` ADD `affiliation` text;--> statement-breakpoint +ALTER TABLE `character` ADD `fr_affiliation` text;--> statement-breakpoint +ALTER TABLE `character` DROP COLUMN `affiliations`;--> statement-breakpoint +ALTER TABLE `character` DROP COLUMN `fr_affiliations`;--> statement-breakpoint +ALTER TABLE `character_scrape_validation` ADD `affiliation` text;--> statement-breakpoint +ALTER TABLE `character_scrape_validation` ADD `fr_affiliation` text;--> statement-breakpoint +ALTER TABLE `character_scrape_validation` DROP COLUMN `affiliations`;--> statement-breakpoint +ALTER TABLE `character_scrape_validation` DROP COLUMN `fr_affiliations`; \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..164237f --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,1185 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "736137e1-d840-4f5b-a1b4-d50648839073", + "prevId": "f3540f13-a6c4-4c52-ac29-6330ffce33fd", + "tables": { + "arc": { + "name": "arc", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_chapter": { + "name": "start_chapter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_chapter": { + "name": "end_chapter", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character": { + "name": "character", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gender": { + "name": "gender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affiliation": { + "name": "affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_affiliation": { + "name": "fr_affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "devil_fruit_id": { + "name": "devil_fruit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haki_observation": { + "name": "haki_observation", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_armament": { + "name": "haki_armament", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_conqueror": { + "name": "haki_conqueror", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "bounty": { + "name": "bounty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "height": { + "name": "height", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_origin": { + "name": "fr_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_appearance": { + "name": "first_appearance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epithets": { + "name": "epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_epithets": { + "name": "fr_epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "arc_id": { + "name": "arc_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_url": { + "name": "fr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_in_daily_mode": { + "name": "is_in_daily_mode", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "character_devil_fruit_id_devil_fruit_id_fk": { + "name": "character_devil_fruit_id_devil_fruit_id_fk", + "tableFrom": "character", + "tableTo": "devil_fruit", + "columnsFrom": [ + "devil_fruit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "character_arc_id_arc_id_fk": { + "name": "character_arc_id_arc_id_fk", + "tableFrom": "character", + "tableTo": "arc", + "columnsFrom": [ + "arc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character_history": { + "name": "character_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "character_id": { + "name": "character_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "won": { + "name": "won", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "character_history_date_unique": { + "name": "character_history_date_unique", + "columns": [ + "date" + ], + "isUnique": true + } + }, + "foreignKeys": { + "character_history_character_id_character_id_fk": { + "name": "character_history_character_id_character_id_fk", + "tableFrom": "character_history", + "tableTo": "character", + "columnsFrom": [ + "character_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character_scrape_validation": { + "name": "character_scrape_validation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gender": { + "name": "gender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affiliation": { + "name": "affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_affiliation": { + "name": "fr_affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "devil_fruit_id": { + "name": "devil_fruit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haki_observation": { + "name": "haki_observation", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_armament": { + "name": "haki_armament", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_conqueror": { + "name": "haki_conqueror", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "bounty": { + "name": "bounty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_origin": { + "name": "fr_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_appearance": { + "name": "first_appearance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epithets": { + "name": "epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_epithets": { + "name": "fr_epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "arc_id": { + "name": "arc_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_url": { + "name": "fr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_deleted": { + "name": "is_deleted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": { + "name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk", + "tableFrom": "character_scrape_validation", + "tableTo": "devil_fruit", + "columnsFrom": [ + "devil_fruit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "character_scrape_validation_arc_id_arc_id_fk": { + "name": "character_scrape_validation_arc_id_arc_id_fk", + "tableFrom": "character_scrape_validation", + "tableTo": "arc", + "columnsFrom": [ + "arc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "config": { + "name": "config", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "devil_fruit": { + "name": "devil_fruit", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "devil_fruit_name_unique": { + "name": "devil_fruit_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "friendship": { + "name": "friendship", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addressee_id": { + "name": "addressee_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "friendship_requester_id_addressee_id_unique": { + "name": "friendship_requester_id_addressee_id_unique", + "columns": [ + "requester_id", + "addressee_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "friendship_requester_id_user_id_fk": { + "name": "friendship_requester_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "requester_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "friendship_addressee_id_user_id_fk": { + "name": "friendship_addressee_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "addressee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_character_history": { + "name": "user_character_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "character_history_id": { + "name": "character_history_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "try_count": { + "name": "try_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tried_character_ids": { + "name": "tried_character_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_character_history_user_id_character_history_id_unique": { + "name": "user_character_history_user_id_character_history_id_unique", + "columns": [ + "user_id", + "character_history_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_character_history_user_id_user_id_fk": { + "name": "user_character_history_user_id_user_id_fk", + "tableFrom": "user_character_history", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_character_history_character_history_id_character_history_id_fk": { + "name": "user_character_history_character_history_id_character_history_id_fk", + "tableFrom": "user_character_history", + "tableTo": "character_history", + "columnsFrom": [ + "character_history_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 7a27821..5008b3a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1775950314114, "tag": "0002_old_earthquake", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1776195681488, + "tag": "0003_mixed_ben_grimm", + "breakpoints": true } ] } \ No newline at end of file diff --git a/scripts/import-json.ts b/scripts/import-json.ts index 3e96eeb..b02bff9 100644 --- a/scripts/import-json.ts +++ b/scripts/import-json.ts @@ -28,8 +28,8 @@ type CharacterRecord = { frName?: string | null; gender?: string | null; age?: number | null; - affiliations?: string[] | string | null; - frAffiliations?: string[] | string | null; + affiliation?: string | null; + frAffiliation?: string | null; devilFruitId?: string | null; hakiObservation?: boolean; hakiArmament?: boolean; @@ -123,8 +123,8 @@ function transformCharacterData(item: CharacterRecord) { frName: toNullable(item.frName), gender: toNullable(item.gender), age: toNullable(item.age), - affiliations: toJsonArray(item.affiliations), - frAffiliations: toJsonArray(item.frAffiliations), + affiliation: toNullable(item.affiliation), + frAffiliation: toNullable(item.frAffiliation), devilFruitId: toNullable(item.devilFruitId), hakiObservation: !!item.hakiObservation, hakiArmament: !!item.hakiArmament, @@ -369,8 +369,8 @@ async function importFromJson(): Promise { frName: row.frName, gender: row.gender, age: row.age, - affiliations: row.affiliations, - frAffiliations: row.frAffiliations, + affiliation: row.affiliation, + frAffiliation: row.frAffiliation, devilFruitId: row.devilFruitId, hakiObservation: row.hakiObservation, hakiArmament: row.hakiArmament, diff --git a/scripts/scrape-onepiece.ts b/scripts/scrape-onepiece.ts index 61c7086..e0bb374 100644 --- a/scripts/scrape-onepiece.ts +++ b/scripts/scrape-onepiece.ts @@ -23,8 +23,8 @@ interface Character { frOrigin: string | null; devilFruitId: string | null; devilFruitUrl: string | null; - affiliations: string[]; - frAffiliations: string[] | null; + affiliation: string | null; + frAffiliation: string | null; bounty: number | null; hakiObservation: boolean; hakiArmament: boolean; @@ -370,7 +370,7 @@ async function fetchAllCharacters(arcsList: Arc[]): Promise { Age: data.age, Status: data.status, Epithets: data.epithets.join(', '), - Affiliations: data.affiliations.join(', '), + Affiliation: data.affiliation, DevilFruitId: data.devilFruitId, DevilFruitUrl: data.devilFruitUrl, HakiObservation: data.hakiObservation ? 'Yes' : 'No', @@ -453,8 +453,8 @@ async function fetchCharacter( // Extract age const age = extractAge($); - // Extract affiliations - const affiliations = await extractAffiliations($, 'en'); + // Extract affiliation + const affiliation = await extractAffiliations($, 'en'); // Extract epithets const epithets = extractEpithets($); @@ -513,7 +513,7 @@ async function fetchCharacter( let frName = frjsonData?.parse?.title || null; - const frAffiliations = frjsonData + const frAffiliation = frjsonData ? await extractAffiliations(cheerio.load(frjsonData.parse?.text?.['*'] || ''), 'fr') : null; @@ -542,8 +542,8 @@ async function fetchCharacter( frOrigin, devilFruitId, devilFruitUrl, - affiliations, - frAffiliations, + affiliation, + frAffiliation, bounty, hakiObservation, hakiArmament, @@ -591,15 +591,15 @@ function extractAge($: cheerio.CheerioAPI): number | null { /** * Extract affiliations from infobox */ -async function extractAffiliations($: cheerio.CheerioAPI, lang: string): Promise { +async function extractAffiliations($: cheerio.CheerioAPI, lang: string): Promise { const div = $('[data-source="affiliation"] .pi-data-value'); - if (div.length === 0) return []; + if (div.length === 0) return null; const cleanedDiv = div.clone(); cleanedDiv.find('sup').remove(); const text = cleanedDiv.html(); - if (!text) return []; + if (!text) return null; // Resolve affiliations from linked page titles. const links = cleanedDiv.find('a').toArray(); @@ -624,14 +624,14 @@ async function extractAffiliations($: cheerio.CheerioAPI, lang: string): Promise const uniqueLinks = Array.from(new Set(linkValues.filter(Boolean))); if (uniqueLinks.length > 0) { - return uniqueLinks; + return uniqueLinks[0]; } } // Fallback to parsing text const cleanText = text.replace(/<[^>]*>/g, '').trim(); const parts = cleanText.split(/\s*\n\s*|\s*;\s*|\s*,\s*/).filter(Boolean); - return parts.length > 0 ? parts : []; + return parts.length > 0 ? parts[0] : null; } /** @@ -869,9 +869,8 @@ async function saveToCSV(characters: Character[]): Promise { status: c.status || '', epithets: Array.isArray(c.epithets) ? c.epithets.join(', ') : c.epithets || '', devilFruitId: c.devilFruitId || '', - affiliations: Array.isArray(c.affiliations) - ? c.affiliations.join(', ') - : c.affiliations || '', + affiliation: c.affiliation || '', + frAffiliation: c.frAffiliation || '', bounty: c.bounty ?? 0, hakiObservation: c.hakiObservation ? 1 : 0, hakiArmament: c.hakiArmament ? 1 : 0, diff --git a/src/lib/components/GuessHistoryTable.svelte b/src/lib/components/GuessHistoryTable.svelte index 3c0f6e9..b589cdd 100644 --- a/src/lib/components/GuessHistoryTable.svelte +++ b/src/lib/components/GuessHistoryTable.svelte @@ -8,7 +8,7 @@ export let columnVisibility: { status?: boolean; gender?: boolean; - affiliations?: boolean; + affiliation?: boolean; devilFruitType?: boolean; haki?: boolean; bounty?: boolean; @@ -18,52 +18,6 @@ arc?: boolean; }; - function normalizeAffiliations(value: unknown): string[] { - if (Array.isArray(value)) { - return value - .map((entry) => (typeof entry === 'string' ? entry.trim() : '')) - .filter((entry) => entry.length > 0); - } - - if (typeof value === 'string') { - const trimmed = value.trim(); - if (trimmed.length === 0) { - return []; - } - - if (trimmed.startsWith('[')) { - try { - const parsed = JSON.parse(trimmed); - if (Array.isArray(parsed)) { - return parsed - .map((entry) => (typeof entry === 'string' ? entry.trim() : '')) - .filter((entry) => entry.length > 0); - } - } catch { - return []; - } - } - - return trimmed - .split(',') - .map((entry) => entry.trim()) - .filter((entry) => entry.length > 0); - } - - return []; - } - - function firstAffiliation(value: unknown): string | null { - const affiliations = normalizeAffiliations(value); - return affiliations.length > 0 ? affiliations[0] : null; - } - - function hasMatchingPrimaryAffiliation(characterAffiliations: unknown, dailyAffiliations: unknown): boolean { - const characterPrimary = firstAffiliation(characterAffiliations); - const dailyPrimary = firstAffiliation(dailyAffiliations); - return characterPrimary === dailyPrimary; - } - $: isFrench = $language === 'fr'; function getDisplayName(character: CharacterWithRelations): string { @@ -106,6 +60,14 @@ return character.arcName; } + function getDislayAffiliation(character: CharacterWithRelations): string | null { + if (isFrench && typeof character.frAffiliation === 'string' && character.frAffiliation.length > 0) { + return character.frAffiliation; + } + + return character.affiliation; + } + function hasMatchingArc(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean { return getDisplayArcName(characterEntry) === getDisplayArcName(dailyEntry); } @@ -156,7 +118,7 @@

{/if} - {#if columnVisibility.affiliations !== false} + {#if columnVisibility.affiliation !== false}
@@ -319,23 +281,15 @@ {/if} - {#if columnVisibility.affiliations !== false} + {#if columnVisibility.affiliation !== false}
- {#if firstAffiliation(character.affiliations)} -

- {firstAffiliation(character.affiliations)} -

- {:else} -

- - -

- {/if} +

+ {getDislayAffiliation(character) || $t.game.components.guessHistory.unknown} +

{/if} diff --git a/src/lib/components/HintsPanel.svelte b/src/lib/components/HintsPanel.svelte index 1c9c79c..935baf5 100644 --- a/src/lib/components/HintsPanel.svelte +++ b/src/lib/components/HintsPanel.svelte @@ -86,10 +86,7 @@ >

{$t.game.components.hints.affiliation}

{#if showHintAffiliation} - {@const affiliations = typeof dailyCharacter.affiliations === 'string' - ? ((dailyCharacter.affiliations as string).includes('[') ? JSON.parse(dailyCharacter.affiliations) : (dailyCharacter.affiliations as string).split(',').map((a: string) => a.trim())) - : dailyCharacter.affiliations} -

{Array.isArray(affiliations) ? affiliations[0] : affiliations || $t.game.components.hints.unknown}

+

{isFrench && dailyCharacter.frAffiliation ? dailyCharacter.frAffiliation : dailyCharacter.affiliation || $t.game.components.hints.unknown}

{:else if Math.max(0, 15 - selectedCharacters.length) > 0}

{Math.max(0, 15 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}

{:else} diff --git a/src/lib/server/daily-character.ts b/src/lib/server/daily-character.ts index 3f7510a..5a6e3e9 100644 --- a/src/lib/server/daily-character.ts +++ b/src/lib/server/daily-character.ts @@ -11,8 +11,8 @@ const characterWithRelationsSelect = { frName: character.frName, gender: character.gender, age: character.age, - affiliations: character.affiliations, - frAffiliations: character.frAffiliations, + affiliation: character.affiliation, + frAffiliation: character.frAffiliation, devilFruitId: character.devilFruitId, devilFruitName: devilFruit.name, devilFruitType: devilFruit.type, diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 775998a..0278b12 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -43,8 +43,8 @@ export const character = sqliteTable('character', { frName: text('fr_name'), gender: text('gender'), age: integer('age'), - affiliations: text('affiliations', { mode: 'json' }).$type(), - frAffiliations: text('fr_affiliations', { mode: 'json' }).$type(), + affiliation: text('affiliation'), + frAffiliation: text('fr_affiliation'), devilFruitId: text('devil_fruit_id').references(() => devilFruit.id), hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false), hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false), @@ -73,8 +73,8 @@ export const characterScrapeValidation = sqliteTable('character_scrape_validatio frName: text('fr_name'), gender: text('gender'), age: integer('age'), - affiliations: text('affiliations', { mode: 'json' }).$type(), - frAffiliations: text('fr_affiliations', { mode: 'json' }).$type(), + affiliation: text('affiliation'), + frAffiliation: text('fr_affiliation'), devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }), hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false), hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false), diff --git a/src/routes/(admin)/admin/character-changes/+page.server.ts b/src/routes/(admin)/admin/character-changes/+page.server.ts index 0be2b4d..e741561 100644 --- a/src/routes/(admin)/admin/character-changes/+page.server.ts +++ b/src/routes/(admin)/admin/character-changes/+page.server.ts @@ -34,7 +34,8 @@ async function applyCharacterChangeFromScrapeValidation(characterId: string): Pr frName: scraped.frName, gender: scraped.gender, age: scraped.age, - affiliations: scraped.affiliations, + affiliation: scraped.affiliation, + frAffiliation: scraped.frAffiliation, devilFruitId: scraped.devilFruitId, hakiObservation: scraped.hakiObservation, hakiArmament: scraped.hakiArmament, @@ -59,7 +60,8 @@ async function applyCharacterChangeFromScrapeValidation(characterId: string): Pr frName: scraped.frName, gender: scraped.gender, age: scraped.age, - affiliations: scraped.affiliations, + affiliation: scraped.affiliation, + frAffiliation: scraped.frAffiliation, devilFruitId: scraped.devilFruitId, hakiObservation: scraped.hakiObservation, hakiArmament: scraped.hakiArmament, @@ -129,7 +131,8 @@ export async function load() { 'frName', 'gender', 'age', - 'affiliations', + 'affiliation', + 'frAffiliation', 'devilFruitId', 'hakiObservation', 'hakiArmament', diff --git a/src/routes/(admin)/admin/characters/+page.server.ts b/src/routes/(admin)/admin/characters/+page.server.ts index 4de7b78..9c43730 100644 --- a/src/routes/(admin)/admin/characters/+page.server.ts +++ b/src/routes/(admin)/admin/characters/+page.server.ts @@ -26,7 +26,7 @@ export const load: PageServerLoad = async () => { name: character.name, gender: character.gender, age: character.age, - affiliations: normalizeArray(character.affiliations), + affiliation: character.affiliation, devilFruitId: character.devilFruitId, hakiObservation: character.hakiObservation, hakiArmament: character.hakiArmament, diff --git a/src/routes/(admin)/admin/characters/+page.svelte b/src/routes/(admin)/admin/characters/+page.svelte index 2abcee6..e8a624b 100644 --- a/src/routes/(admin)/admin/characters/+page.svelte +++ b/src/routes/(admin)/admin/characters/+page.svelte @@ -44,7 +44,7 @@ bounty: 0, height: 0, origin: '', - affiliations: '', + affiliation: '', epithets: '', pictureUrl: '', url: '', @@ -101,7 +101,7 @@ bounty: override.bounty ?? null, height: override.height ?? null, origin: override.origin ?? '', - affiliations: override.affiliations ?? '', + affiliation: override.affiliation ?? '', epithets: override.epithets ?? '', pictureUrl: override.pictureUrl ?? '', url: override.url ?? '', @@ -127,7 +127,7 @@ bounty: 0, height: 0, origin: '', - affiliations: '', + affiliation: '', epithets: '', pictureUrl: '', url: '', @@ -341,12 +341,8 @@ {char.gender || '-'} - {#if char.affiliations} - {#if Array.isArray(char.affiliations) && char.affiliations.length > 0} - {char.affiliations[0]} - {:else} - {char.affiliations} - {/if} + {#if char.affiliation} + {char.affiliation} {:else} - {/if}