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}
|