Compare commits
33 Commits
a91b298ee5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ef6bf9862e | |||
| d75c74ac3c | |||
| fa14156d82 | |||
| 29297d3773 | |||
| 28bb8f526b | |||
| 288271fb04 | |||
| fb64c84a17 | |||
| 81e205dd4e | |||
| ded1c8313d | |||
| 4426b5d28a | |||
| 5ad0428420 | |||
| 7760570365 | |||
| 5fde54a2a7 | |||
| 2a3c82f777 | |||
| 835163f5bb | |||
| 5020393b22 | |||
| 94393851c8 | |||
| 997b2f1781 | |||
| bd121b7d85 | |||
| 6d2dccd47f | |||
| 5fdde9d177 | |||
| b1cc691422 | |||
| 8b08950719 | |||
| fd83ac911a | |||
| eeccf812cf | |||
| 9485d9841c | |||
| 31308ef126 | |||
| 57a0427e77 | |||
| 3bd2506c2f | |||
| e5a21cb0af | |||
| 4e95abf09f | |||
| 66afda5101 | |||
| a041a8caf5 |
@@ -1,156 +0,0 @@
|
|||||||
CREATE TABLE `arc` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`startChapter` integer NOT NULL,
|
|
||||||
`endChapter` integer,
|
|
||||||
`url` text
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `character` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`gender` text,
|
|
||||||
`age` integer,
|
|
||||||
`affiliations` text,
|
|
||||||
`devilFruitId` text,
|
|
||||||
`hakiObservation` integer DEFAULT false,
|
|
||||||
`hakiArmament` integer DEFAULT false,
|
|
||||||
`hakiConqueror` integer DEFAULT false,
|
|
||||||
`bounty` integer DEFAULT 0,
|
|
||||||
`height` real,
|
|
||||||
`origin` text,
|
|
||||||
`firstAppearance` integer NOT NULL,
|
|
||||||
`pictureUrl` text,
|
|
||||||
`epithets` text,
|
|
||||||
`status` text,
|
|
||||||
`arcId` text,
|
|
||||||
`url` text,
|
|
||||||
`isInDailyMode` integer DEFAULT true,
|
|
||||||
FOREIGN KEY (`devilFruitId`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`arcId`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `characterHistory` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`characterId` text,
|
|
||||||
`date` text,
|
|
||||||
`won` integer DEFAULT 0 NOT NULL,
|
|
||||||
`createdAt` integer NOT NULL,
|
|
||||||
`updatedAt` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `characterOverride` (
|
|
||||||
`characterId` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text,
|
|
||||||
`gender` text,
|
|
||||||
`age` integer,
|
|
||||||
`affiliations` text,
|
|
||||||
`devilFruitId` text,
|
|
||||||
`hakiObservation` integer,
|
|
||||||
`hakiArmament` integer,
|
|
||||||
`hakiConqueror` integer,
|
|
||||||
`bounty` integer,
|
|
||||||
`height` real,
|
|
||||||
`origin` text,
|
|
||||||
`firstAppearance` integer NOT NULL,
|
|
||||||
`pictureUrl` text,
|
|
||||||
`epithets` text,
|
|
||||||
`status` text,
|
|
||||||
`arcId` text,
|
|
||||||
`url` text,
|
|
||||||
`notes` text,
|
|
||||||
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`devilFruitId`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`arcId`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `characterScrapeValidation` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`gender` text,
|
|
||||||
`age` integer,
|
|
||||||
`affiliations` text,
|
|
||||||
`devilFruitId` text,
|
|
||||||
`hakiObservation` integer DEFAULT false,
|
|
||||||
`hakiArmament` integer DEFAULT false,
|
|
||||||
`hakiConqueror` integer DEFAULT false,
|
|
||||||
`bounty` integer,
|
|
||||||
`height` real,
|
|
||||||
`origin` text,
|
|
||||||
`firstAppearance` integer NOT NULL,
|
|
||||||
`pictureUrl` text,
|
|
||||||
`epithets` text,
|
|
||||||
`status` text,
|
|
||||||
`arcId` text,
|
|
||||||
`url` text,
|
|
||||||
FOREIGN KEY (`devilFruitId`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`arcId`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `config` (
|
|
||||||
`key` text PRIMARY KEY NOT NULL,
|
|
||||||
`value` text
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `devilFruit` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`type` text,
|
|
||||||
`url` text
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `devilFruit_name_unique` ON `devilFruit` (`name`);--> statement-breakpoint
|
|
||||||
CREATE TABLE `account` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`account_id` text NOT NULL,
|
|
||||||
`provider_id` text NOT NULL,
|
|
||||||
`user_id` text NOT NULL,
|
|
||||||
`access_token` text,
|
|
||||||
`refresh_token` text,
|
|
||||||
`id_token` text,
|
|
||||||
`access_token_expires_at` integer,
|
|
||||||
`refresh_token_expires_at` integer,
|
|
||||||
`scope` text,
|
|
||||||
`password` text,
|
|
||||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
|
||||||
`updated_at` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint
|
|
||||||
CREATE TABLE `session` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`expires_at` integer NOT NULL,
|
|
||||||
`token` text NOT NULL,
|
|
||||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
|
||||||
`updated_at` integer NOT NULL,
|
|
||||||
`ip_address` text,
|
|
||||||
`user_agent` text,
|
|
||||||
`user_id` text NOT NULL,
|
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
|
|
||||||
CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint
|
|
||||||
CREATE TABLE `user` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`email` text NOT NULL,
|
|
||||||
`email_verified` integer DEFAULT false NOT NULL,
|
|
||||||
`image` text,
|
|
||||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
|
||||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
|
|
||||||
CREATE TABLE `verification` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`identifier` text NOT NULL,
|
|
||||||
`value` text NOT NULL,
|
|
||||||
`expires_at` integer NOT NULL,
|
|
||||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
|
||||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);
|
|
||||||
199
drizzle/0000_huge_doctor_octopus.sql
Normal file
199
drizzle/0000_huge_doctor_octopus.sql
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
CREATE TABLE `arc` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`fr_name` text,
|
||||||
|
`start_chapter` integer NOT NULL,
|
||||||
|
`end_chapter` integer,
|
||||||
|
`url` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `character` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`fr_name` text,
|
||||||
|
`gender` text,
|
||||||
|
`age` integer,
|
||||||
|
`affiliations` text,
|
||||||
|
`fr_affiliations` text,
|
||||||
|
`devil_fruit_id` text,
|
||||||
|
`haki_observation` integer DEFAULT false,
|
||||||
|
`haki_armament` integer DEFAULT false,
|
||||||
|
`haki_conqueror` integer DEFAULT false,
|
||||||
|
`bounty` integer DEFAULT 0,
|
||||||
|
`height` real,
|
||||||
|
`origin` text,
|
||||||
|
`fr_origin` text,
|
||||||
|
`first_appearance` integer NOT NULL,
|
||||||
|
`picture_url` text,
|
||||||
|
`epithets` text,
|
||||||
|
`fr_epithets` text,
|
||||||
|
`status` text,
|
||||||
|
`arc_id` text,
|
||||||
|
`url` text,
|
||||||
|
`fr_url` text,
|
||||||
|
`is_in_daily_mode` integer DEFAULT false,
|
||||||
|
FOREIGN KEY (`devil_fruit_id`) REFERENCES `devil_fruit`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`arc_id`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `character_history` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`character_id` text,
|
||||||
|
`date` integer NOT NULL,
|
||||||
|
`won` integer DEFAULT 0 NOT NULL,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`character_id`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `character_history_date_unique` ON `character_history` (`date`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `character_override` (
|
||||||
|
`character_id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text,
|
||||||
|
`gender` text,
|
||||||
|
`age` integer,
|
||||||
|
`affiliations` text,
|
||||||
|
`fr_affiliations` text,
|
||||||
|
`devil_fruit_id` text,
|
||||||
|
`haki_observation` integer,
|
||||||
|
`haki_armament` integer,
|
||||||
|
`haki_conqueror` integer,
|
||||||
|
`bounty` integer,
|
||||||
|
`height` real,
|
||||||
|
`origin` text,
|
||||||
|
`fr_origin` text,
|
||||||
|
`first_appearance` integer,
|
||||||
|
`picture_url` text,
|
||||||
|
`epithets` text,
|
||||||
|
`fr_epithets` text,
|
||||||
|
`status` text,
|
||||||
|
`arc_id` text,
|
||||||
|
`url` text,
|
||||||
|
`fr_url` text,
|
||||||
|
`notes` text,
|
||||||
|
FOREIGN KEY (`character_id`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`devil_fruit_id`) REFERENCES `devil_fruit`(`id`) ON UPDATE no action ON DELETE set null,
|
||||||
|
FOREIGN KEY (`arc_id`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `character_scrape_validation` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`fr_name` text,
|
||||||
|
`gender` text,
|
||||||
|
`age` integer,
|
||||||
|
`affiliations` text,
|
||||||
|
`fr_affiliations` text,
|
||||||
|
`devil_fruit_id` text,
|
||||||
|
`haki_observation` integer DEFAULT false,
|
||||||
|
`haki_armament` integer DEFAULT false,
|
||||||
|
`haki_conqueror` integer DEFAULT false,
|
||||||
|
`bounty` integer,
|
||||||
|
`height` real,
|
||||||
|
`origin` text,
|
||||||
|
`fr_origin` text,
|
||||||
|
`first_appearance` integer NOT NULL,
|
||||||
|
`picture_url` text,
|
||||||
|
`epithets` text,
|
||||||
|
`fr_epithets` text,
|
||||||
|
`status` text,
|
||||||
|
`arc_id` text,
|
||||||
|
`url` text,
|
||||||
|
`fr_url` text,
|
||||||
|
FOREIGN KEY (`devil_fruit_id`) REFERENCES `devil_fruit`(`id`) ON UPDATE no action ON DELETE set null,
|
||||||
|
FOREIGN KEY (`arc_id`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `config` (
|
||||||
|
`key` text PRIMARY KEY NOT NULL,
|
||||||
|
`value` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `devil_fruit` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`type` text,
|
||||||
|
`url` text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `devil_fruit_name_unique` ON `devil_fruit` (`name`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `friendship` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`requester_id` text NOT NULL,
|
||||||
|
`addressee_id` text NOT NULL,
|
||||||
|
`status` text DEFAULT 'pending' NOT NULL,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`requester_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`addressee_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `friendship_requester_id_addressee_id_unique` ON `friendship` (`requester_id`,`addressee_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `user_character_history` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text,
|
||||||
|
`character_history_id` text,
|
||||||
|
`try_count` integer NOT NULL,
|
||||||
|
`tried_character_ids` text,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY (`character_history_id`) REFERENCES `character_history`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_character_history_user_id_character_history_id_unique` ON `user_character_history` (`user_id`,`character_history_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `account` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`account_id` text NOT NULL,
|
||||||
|
`provider_id` text NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`access_token` text,
|
||||||
|
`refresh_token` text,
|
||||||
|
`id_token` text,
|
||||||
|
`access_token_expires_at` integer,
|
||||||
|
`refresh_token_expires_at` integer,
|
||||||
|
`scope` text,
|
||||||
|
`password` text,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `session` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`token` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
`ip_address` text,
|
||||||
|
`user_agent` text,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`email_verified` integer DEFAULT false NOT NULL,
|
||||||
|
`image` text,
|
||||||
|
`is_admin` integer DEFAULT false NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_username_unique` ON `user` (`username`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `verification` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`identifier` text NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);
|
||||||
1
drizzle/0001_fuzzy_talisman.sql
Normal file
1
drizzle/0001_fuzzy_talisman.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `character_scrape_validation` ADD `is_deleted` integer DEFAULT false;
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_characterOverride` (
|
|
||||||
`characterId` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text,
|
|
||||||
`gender` text,
|
|
||||||
`age` integer,
|
|
||||||
`affiliations` text,
|
|
||||||
`devilFruitId` text,
|
|
||||||
`hakiObservation` integer,
|
|
||||||
`hakiArmament` integer,
|
|
||||||
`hakiConqueror` integer,
|
|
||||||
`bounty` integer,
|
|
||||||
`height` real,
|
|
||||||
`origin` text,
|
|
||||||
`firstAppearance` integer,
|
|
||||||
`pictureUrl` text,
|
|
||||||
`epithets` text,
|
|
||||||
`status` text,
|
|
||||||
`arcId` text,
|
|
||||||
`url` text,
|
|
||||||
`notes` text,
|
|
||||||
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`devilFruitId`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`arcId`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_characterOverride`("characterId", "name", "gender", "age", "affiliations", "devilFruitId", "hakiObservation", "hakiArmament", "hakiConqueror", "bounty", "height", "origin", "firstAppearance", "pictureUrl", "epithets", "status", "arcId", "url", "notes") SELECT "characterId", "name", "gender", "age", "affiliations", "devilFruitId", "hakiObservation", "hakiArmament", "hakiConqueror", "bounty", "height", "origin", "firstAppearance", "pictureUrl", "epithets", "status", "arcId", "url", "notes" FROM `characterOverride`;--> statement-breakpoint
|
|
||||||
DROP TABLE `characterOverride`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_characterOverride` RENAME TO `characterOverride`;--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE `user` ADD `is_admin` integer DEFAULT false NOT NULL;
|
|
||||||
1
drizzle/0002_old_earthquake.sql
Normal file
1
drizzle/0002_old_earthquake.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE `character_override`;
|
||||||
8
drizzle/0003_mixed_ben_grimm.sql
Normal file
8
drizzle/0003_mixed_ben_grimm.sql
Normal file
@@ -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`;
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_characterHistory` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`characterId` text,
|
|
||||||
`date` integer NOT NULL,
|
|
||||||
`won` integer DEFAULT 0 NOT NULL,
|
|
||||||
`createdAt` integer NOT NULL,
|
|
||||||
`updatedAt` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_characterHistory`("id", "characterId", "date", "won", "createdAt", "updatedAt") SELECT "id", "characterId", "date", "won", "createdAt", "updatedAt" FROM `characterHistory`;--> statement-breakpoint
|
|
||||||
DROP TABLE `characterHistory`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_characterHistory` RENAME TO `characterHistory`;--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `characterHistory_date_unique` ON `characterHistory` (`date`);
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE `userCharacterHistory` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`userId` text,
|
|
||||||
`characterId` text,
|
|
||||||
`tryCount` integer NOT NULL,
|
|
||||||
`createdAt` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`characterId`) REFERENCES `character`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_userCharacterHistory` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`userId` text,
|
|
||||||
`characterHistoryId` text,
|
|
||||||
`tryCount` integer NOT NULL,
|
|
||||||
`createdAt` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`characterHistoryId`) REFERENCES `characterHistory`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_userCharacterHistory`("id", "userId", "characterHistoryId", "tryCount", "createdAt") SELECT "id", "userId", "characterId", "tryCount", "createdAt" FROM `userCharacterHistory`;--> statement-breakpoint
|
|
||||||
DROP TABLE `userCharacterHistory`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_userCharacterHistory` RENAME TO `userCharacterHistory`;--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX `userCharacterHistory_userId_characterHistoryId_unique` ON `userCharacterHistory` (`userId`,`characterHistoryId`);
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_character` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`gender` text,
|
|
||||||
`age` integer,
|
|
||||||
`affiliations` text,
|
|
||||||
`devilFruitId` text,
|
|
||||||
`hakiObservation` integer DEFAULT false,
|
|
||||||
`hakiArmament` integer DEFAULT false,
|
|
||||||
`hakiConqueror` integer DEFAULT false,
|
|
||||||
`bounty` integer DEFAULT 0,
|
|
||||||
`height` real,
|
|
||||||
`origin` text,
|
|
||||||
`firstAppearance` integer NOT NULL,
|
|
||||||
`pictureUrl` text,
|
|
||||||
`epithets` text,
|
|
||||||
`status` text,
|
|
||||||
`arcId` text,
|
|
||||||
`url` text,
|
|
||||||
`isInDailyMode` integer DEFAULT false,
|
|
||||||
FOREIGN KEY (`devilFruitId`) REFERENCES `devilFruit`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`arcId`) REFERENCES `arc`(`id`) ON UPDATE no action ON DELETE no action
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_character`("id", "name", "gender", "age", "affiliations", "devilFruitId", "hakiObservation", "hakiArmament", "hakiConqueror", "bounty", "height", "origin", "firstAppearance", "pictureUrl", "epithets", "status", "arcId", "url", "isInDailyMode") SELECT "id", "name", "gender", "age", "affiliations", "devilFruitId", "hakiObservation", "hakiArmament", "hakiConqueror", "bounty", "height", "origin", "firstAppearance", "pictureUrl", "epithets", "status", "arcId", "url", "isInDailyMode" FROM `character`;--> statement-breakpoint
|
|
||||||
DROP TABLE `character`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_character` RENAME TO `character`;--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
CREATE TABLE `friendship` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`requesterId` text NOT NULL,
|
|
||||||
`addresseeId` text NOT NULL,
|
|
||||||
`status` text DEFAULT 'pending' NOT NULL,
|
|
||||||
`createdAt` integer NOT NULL,
|
|
||||||
`updatedAt` integer NOT NULL,
|
|
||||||
FOREIGN KEY (`requesterId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
||||||
FOREIGN KEY (`addresseeId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `friendship_requesterId_addresseeId_unique` ON `friendship` (`requesterId`,`addresseeId`);
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
ALTER TABLE `user` ADD `username` text;--> statement-breakpoint
|
|
||||||
UPDATE `user`
|
|
||||||
SET `username` = `name`
|
|
||||||
WHERE `username` IS NULL OR trim(`username`) = '';--> statement-breakpoint
|
|
||||||
UPDATE `user`
|
|
||||||
SET `username` = `username` || '_' || substr(`id`, 1, 6)
|
|
||||||
WHERE `id` IN (
|
|
||||||
SELECT u1.`id`
|
|
||||||
FROM `user` u1
|
|
||||||
JOIN `user` u2
|
|
||||||
ON lower(u1.`username`) = lower(u2.`username`)
|
|
||||||
AND u1.`id` > u2.`id`
|
|
||||||
);--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_user` (
|
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
|
||||||
`name` text NOT NULL,
|
|
||||||
`username` text NOT NULL,
|
|
||||||
`email` text NOT NULL,
|
|
||||||
`email_verified` integer DEFAULT false NOT NULL,
|
|
||||||
`image` text,
|
|
||||||
`is_admin` integer DEFAULT false NOT NULL,
|
|
||||||
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
|
|
||||||
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
|
|
||||||
);--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_user` (
|
|
||||||
`id`,
|
|
||||||
`name`,
|
|
||||||
`username`,
|
|
||||||
`email`,
|
|
||||||
`email_verified`,
|
|
||||||
`image`,
|
|
||||||
`is_admin`,
|
|
||||||
`created_at`,
|
|
||||||
`updated_at`
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
`id`,
|
|
||||||
`name`,
|
|
||||||
`username`,
|
|
||||||
`email`,
|
|
||||||
`email_verified`,
|
|
||||||
`image`,
|
|
||||||
`is_admin`,
|
|
||||||
`created_at`,
|
|
||||||
`updated_at`
|
|
||||||
FROM `user`;--> statement-breakpoint
|
|
||||||
DROP TABLE `user`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_user` RENAME TO `user`;--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `user_username_unique` ON `user` (`username`);--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "d1237d76-8f1c-4721-b8dd-d31082ed7b9a",
|
"id": "4b4f14a1-b37b-44f4-aed3-7289bd8cb6a0",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"arc": {
|
"arc": {
|
||||||
@@ -21,15 +21,22 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"startChapter": {
|
"fr_name": {
|
||||||
"name": "startChapter",
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"start_chapter": {
|
||||||
|
"name": "start_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"endChapter": {
|
"end_chapter": {
|
||||||
"name": "endChapter",
|
"name": "end_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -66,6 +73,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -87,31 +101,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -140,15 +161,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -161,6 +189,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -168,8 +203,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -182,23 +217,30 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"isInDailyMode": {
|
"fr_url": {
|
||||||
"name": "isInDailyMode",
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_in_daily_mode": {
|
||||||
|
"name": "is_in_daily_mode",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": true
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"character_devilFruitId_devilFruit_id_fk": {
|
"character_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "character_devilFruitId_devilFruit_id_fk",
|
"name": "character_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
@@ -206,17 +248,17 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"character_arcId_arc_id_fk": {
|
"character_arc_id_arc_id_fk": {
|
||||||
"name": "character_arcId_arc_id_fk",
|
"name": "character_arc_id_arc_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,8 +266,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterHistory": {
|
"character_history": {
|
||||||
"name": "characterHistory",
|
"name": "character_history",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -234,8 +276,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -243,9 +285,9 @@
|
|||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"type": "text",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"won": {
|
"won": {
|
||||||
@@ -256,34 +298,42 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": 0
|
"default": 0
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"created_at": {
|
||||||
"name": "createdAt",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updated_at": {
|
||||||
"name": "updatedAt",
|
"name": "updated_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {
|
||||||
|
"character_history_date_unique": {
|
||||||
|
"name": "character_history_date_unique",
|
||||||
|
"columns": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterHistory_characterId_character_id_fk": {
|
"character_history_character_id_character_id_fk": {
|
||||||
"name": "characterHistory_characterId_character_id_fk",
|
"name": "character_history_character_id_character_id_fk",
|
||||||
"tableFrom": "characterHistory",
|
"tableFrom": "character_history",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -291,11 +341,11 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterOverride": {
|
"character_override": {
|
||||||
"name": "characterOverride",
|
"name": "character_override",
|
||||||
"columns": {
|
"columns": {
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": true,
|
"primaryKey": true,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -329,29 +379,36 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -378,15 +435,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
"type": "integer",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"first_appearance": {
|
||||||
"name": "pictureUrl",
|
"name": "first_appearance",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"picture_url": {
|
||||||
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -399,6 +463,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -406,8 +477,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -420,6 +491,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_url": {
|
||||||
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"name": "notes",
|
"name": "notes",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -430,43 +508,43 @@
|
|||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterOverride_characterId_character_id_fk": {
|
"character_override_character_id_character_id_fk": {
|
||||||
"name": "characterOverride_characterId_character_id_fk",
|
"name": "character_override_character_id_character_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterOverride_devilFruitId_devilFruit_id_fk": {
|
"character_override_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterOverride_devilFruitId_devilFruit_id_fk",
|
"name": "character_override_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterOverride_arcId_arc_id_fk": {
|
"character_override_arc_id_arc_id_fk": {
|
||||||
"name": "characterOverride_arcId_arc_id_fk",
|
"name": "character_override_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -474,8 +552,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterScrapeValidation": {
|
"character_scrape_validation": {
|
||||||
"name": "characterScrapeValidation",
|
"name": "character_scrape_validation",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -491,6 +569,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -512,31 +597,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -564,15 +656,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -585,6 +684,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -592,8 +698,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -605,34 +711,41 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"fr_url": {
|
||||||
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterScrapeValidation_devilFruitId_devilFruit_id_fk": {
|
"character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterScrapeValidation_devilFruitId_devilFruit_id_fk",
|
"name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterScrapeValidation_arcId_arc_id_fk": {
|
"character_scrape_validation_arc_id_arc_id_fk": {
|
||||||
"name": "characterScrapeValidation_arcId_arc_id_fk",
|
"name": "character_scrape_validation_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -664,8 +777,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"devilFruit": {
|
"devil_fruit": {
|
||||||
"name": "devilFruit",
|
"name": "devil_fruit",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -697,8 +810,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"devilFruit_name_unique": {
|
"devil_fruit_name_unique": {
|
||||||
"name": "devilFruit_name_unique",
|
"name": "devil_fruit_name_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
@@ -710,6 +823,183 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"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": {
|
"account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -947,6 +1237,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -969,6 +1266,14 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"is_admin": {
|
||||||
|
"name": "is_admin",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -987,6 +1292,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
"user_email_unique": {
|
"user_email_unique": {
|
||||||
"name": "user_email_unique",
|
"name": "user_email_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "23b693a1-eebd-499e-9755-27a732e1afc1",
|
"id": "9a965dd1-d97c-4142-a795-0558214180a4",
|
||||||
"prevId": "d1237d76-8f1c-4721-b8dd-d31082ed7b9a",
|
"prevId": "4b4f14a1-b37b-44f4-aed3-7289bd8cb6a0",
|
||||||
"tables": {
|
"tables": {
|
||||||
"arc": {
|
"arc": {
|
||||||
"name": "arc",
|
"name": "arc",
|
||||||
@@ -21,15 +21,22 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"startChapter": {
|
"fr_name": {
|
||||||
"name": "startChapter",
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"start_chapter": {
|
||||||
|
"name": "start_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"endChapter": {
|
"end_chapter": {
|
||||||
"name": "endChapter",
|
"name": "end_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -66,6 +73,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -87,31 +101,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -140,15 +161,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -161,6 +189,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -168,8 +203,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -182,23 +217,30 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"isInDailyMode": {
|
"fr_url": {
|
||||||
"name": "isInDailyMode",
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_in_daily_mode": {
|
||||||
|
"name": "is_in_daily_mode",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": true
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"character_devilFruitId_devilFruit_id_fk": {
|
"character_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "character_devilFruitId_devilFruit_id_fk",
|
"name": "character_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
@@ -206,17 +248,17 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"character_arcId_arc_id_fk": {
|
"character_arc_id_arc_id_fk": {
|
||||||
"name": "character_arcId_arc_id_fk",
|
"name": "character_arc_id_arc_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,8 +266,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterHistory": {
|
"character_history": {
|
||||||
"name": "characterHistory",
|
"name": "character_history",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -234,8 +276,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -243,9 +285,9 @@
|
|||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"type": "text",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"won": {
|
"won": {
|
||||||
@@ -256,34 +298,42 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": 0
|
"default": 0
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"created_at": {
|
||||||
"name": "createdAt",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updated_at": {
|
||||||
"name": "updatedAt",
|
"name": "updated_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {
|
||||||
|
"character_history_date_unique": {
|
||||||
|
"name": "character_history_date_unique",
|
||||||
|
"columns": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterHistory_characterId_character_id_fk": {
|
"character_history_character_id_character_id_fk": {
|
||||||
"name": "characterHistory_characterId_character_id_fk",
|
"name": "character_history_character_id_character_id_fk",
|
||||||
"tableFrom": "characterHistory",
|
"tableFrom": "character_history",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -291,11 +341,11 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterOverride": {
|
"character_override": {
|
||||||
"name": "characterOverride",
|
"name": "character_override",
|
||||||
"columns": {
|
"columns": {
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": true,
|
"primaryKey": true,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -329,29 +379,36 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -378,15 +435,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -399,6 +463,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -406,8 +477,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -420,6 +491,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_url": {
|
||||||
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"name": "notes",
|
"name": "notes",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -430,43 +508,43 @@
|
|||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterOverride_characterId_character_id_fk": {
|
"character_override_character_id_character_id_fk": {
|
||||||
"name": "characterOverride_characterId_character_id_fk",
|
"name": "character_override_character_id_character_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterOverride_devilFruitId_devilFruit_id_fk": {
|
"character_override_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterOverride_devilFruitId_devilFruit_id_fk",
|
"name": "character_override_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterOverride_arcId_arc_id_fk": {
|
"character_override_arc_id_arc_id_fk": {
|
||||||
"name": "characterOverride_arcId_arc_id_fk",
|
"name": "character_override_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterOverride",
|
"tableFrom": "character_override",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -474,8 +552,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterScrapeValidation": {
|
"character_scrape_validation": {
|
||||||
"name": "characterScrapeValidation",
|
"name": "character_scrape_validation",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -491,6 +569,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -512,31 +597,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -564,15 +656,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -585,6 +684,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -592,8 +698,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -605,34 +711,49 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": 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": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterScrapeValidation_devilFruitId_devilFruit_id_fk": {
|
"character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterScrapeValidation_devilFruitId_devilFruit_id_fk",
|
"name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterScrapeValidation_arcId_arc_id_fk": {
|
"character_scrape_validation_arc_id_arc_id_fk": {
|
||||||
"name": "characterScrapeValidation_arcId_arc_id_fk",
|
"name": "character_scrape_validation_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -664,8 +785,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"devilFruit": {
|
"devil_fruit": {
|
||||||
"name": "devilFruit",
|
"name": "devil_fruit",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -697,8 +818,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"devilFruit_name_unique": {
|
"devil_fruit_name_unique": {
|
||||||
"name": "devilFruit_name_unique",
|
"name": "devil_fruit_name_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
@@ -710,6 +831,183 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"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": {
|
"account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -947,6 +1245,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -969,6 +1274,14 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"is_admin": {
|
||||||
|
"name": "is_admin",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -987,6 +1300,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
"user_email_unique": {
|
"user_email_unique": {
|
||||||
"name": "user_email_unique",
|
"name": "user_email_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "4fa96ce4-93c4-4d2d-9f9a-5badf47dfb05",
|
"id": "f3540f13-a6c4-4c52-ac29-6330ffce33fd",
|
||||||
"prevId": "23b693a1-eebd-499e-9755-27a732e1afc1",
|
"prevId": "9a965dd1-d97c-4142-a795-0558214180a4",
|
||||||
"tables": {
|
"tables": {
|
||||||
"arc": {
|
"arc": {
|
||||||
"name": "arc",
|
"name": "arc",
|
||||||
@@ -21,15 +21,22 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"startChapter": {
|
"fr_name": {
|
||||||
"name": "startChapter",
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"start_chapter": {
|
||||||
|
"name": "start_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"endChapter": {
|
"end_chapter": {
|
||||||
"name": "endChapter",
|
"name": "end_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -66,6 +73,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -87,31 +101,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -140,15 +161,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -161,6 +189,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -168,8 +203,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -182,23 +217,30 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"isInDailyMode": {
|
"fr_url": {
|
||||||
"name": "isInDailyMode",
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_in_daily_mode": {
|
||||||
|
"name": "is_in_daily_mode",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": true
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"character_devilFruitId_devilFruit_id_fk": {
|
"character_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "character_devilFruitId_devilFruit_id_fk",
|
"name": "character_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
@@ -206,17 +248,17 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"character_arcId_arc_id_fk": {
|
"character_arc_id_arc_id_fk": {
|
||||||
"name": "character_arcId_arc_id_fk",
|
"name": "character_arc_id_arc_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,8 +266,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterHistory": {
|
"character_history": {
|
||||||
"name": "characterHistory",
|
"name": "character_history",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -234,8 +276,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -243,9 +285,9 @@
|
|||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"type": "text",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"won": {
|
"won": {
|
||||||
@@ -256,34 +298,42 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": 0
|
"default": 0
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"created_at": {
|
||||||
"name": "createdAt",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updated_at": {
|
||||||
"name": "updatedAt",
|
"name": "updated_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {
|
||||||
|
"character_history_date_unique": {
|
||||||
|
"name": "character_history_date_unique",
|
||||||
|
"columns": [
|
||||||
|
"date"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterHistory_characterId_character_id_fk": {
|
"character_history_character_id_character_id_fk": {
|
||||||
"name": "characterHistory_characterId_character_id_fk",
|
"name": "character_history_character_id_character_id_fk",
|
||||||
"tableFrom": "characterHistory",
|
"tableFrom": "character_history",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -291,191 +341,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterOverride": {
|
"character_scrape_validation": {
|
||||||
"name": "characterOverride",
|
"name": "character_scrape_validation",
|
||||||
"columns": {
|
|
||||||
"characterId": {
|
|
||||||
"name": "characterId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "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
|
|
||||||
},
|
|
||||||
"affiliations": {
|
|
||||||
"name": "affiliations",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"devilFruitId": {
|
|
||||||
"name": "devilFruitId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiObservation": {
|
|
||||||
"name": "hakiObservation",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiArmament": {
|
|
||||||
"name": "hakiArmament",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiConqueror": {
|
|
||||||
"name": "hakiConqueror",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": 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
|
|
||||||
},
|
|
||||||
"firstAppearance": {
|
|
||||||
"name": "firstAppearance",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"pictureUrl": {
|
|
||||||
"name": "pictureUrl",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"epithets": {
|
|
||||||
"name": "epithets",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"name": "status",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"arcId": {
|
|
||||||
"name": "arcId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"name": "url",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"name": "notes",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"characterOverride_characterId_character_id_fk": {
|
|
||||||
"name": "characterOverride_characterId_character_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "character",
|
|
||||||
"columnsFrom": [
|
|
||||||
"characterId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"characterOverride_devilFruitId_devilFruit_id_fk": {
|
|
||||||
"name": "characterOverride_devilFruitId_devilFruit_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "devilFruit",
|
|
||||||
"columnsFrom": [
|
|
||||||
"devilFruitId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"characterOverride_arcId_arc_id_fk": {
|
|
||||||
"name": "characterOverride_arcId_arc_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "arc",
|
|
||||||
"columnsFrom": [
|
|
||||||
"arcId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"characterScrapeValidation": {
|
|
||||||
"name": "characterScrapeValidation",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -491,6 +358,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -512,31 +386,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliations": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliations",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -564,15 +445,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -585,6 +473,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -592,8 +487,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -605,34 +500,49 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": 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": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterScrapeValidation_devilFruitId_devilFruit_id_fk": {
|
"character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterScrapeValidation_devilFruitId_devilFruit_id_fk",
|
"name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterScrapeValidation_arcId_arc_id_fk": {
|
"character_scrape_validation_arc_id_arc_id_fk": {
|
||||||
"name": "characterScrapeValidation_arcId_arc_id_fk",
|
"name": "character_scrape_validation_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -664,8 +574,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"devilFruit": {
|
"devil_fruit": {
|
||||||
"name": "devilFruit",
|
"name": "devil_fruit",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -697,8 +607,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"devilFruit_name_unique": {
|
"devil_fruit_name_unique": {
|
||||||
"name": "devilFruit_name_unique",
|
"name": "devil_fruit_name_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
@@ -710,6 +620,183 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"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": {
|
"account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -947,6 +1034,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -995,6 +1089,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
"user_email_unique": {
|
"user_email_unique": {
|
||||||
"name": "user_email_unique",
|
"name": "user_email_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "8a8486cf-5e1c-4fcb-94ce-b6967ae10290",
|
"id": "736137e1-d840-4f5b-a1b4-d50648839073",
|
||||||
"prevId": "4fa96ce4-93c4-4d2d-9f9a-5badf47dfb05",
|
"prevId": "f3540f13-a6c4-4c52-ac29-6330ffce33fd",
|
||||||
"tables": {
|
"tables": {
|
||||||
"arc": {
|
"arc": {
|
||||||
"name": "arc",
|
"name": "arc",
|
||||||
@@ -21,15 +21,22 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"startChapter": {
|
"fr_name": {
|
||||||
"name": "startChapter",
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"start_chapter": {
|
||||||
|
"name": "start_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"endChapter": {
|
"end_chapter": {
|
||||||
"name": "endChapter",
|
"name": "end_chapter",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -66,6 +73,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -80,38 +94,45 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"affiliations": {
|
"affiliation": {
|
||||||
"name": "affiliations",
|
"name": "affiliation",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliation": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliation",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -140,15 +161,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -161,6 +189,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -168,8 +203,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -182,23 +217,30 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"isInDailyMode": {
|
"fr_url": {
|
||||||
"name": "isInDailyMode",
|
"name": "fr_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_in_daily_mode": {
|
||||||
|
"name": "is_in_daily_mode",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": true
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"character_devilFruitId_devilFruit_id_fk": {
|
"character_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "character_devilFruitId_devilFruit_id_fk",
|
"name": "character_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
@@ -206,17 +248,17 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"character_arcId_arc_id_fk": {
|
"character_arc_id_arc_id_fk": {
|
||||||
"name": "character_arcId_arc_id_fk",
|
"name": "character_arc_id_arc_id_fk",
|
||||||
"tableFrom": "character",
|
"tableFrom": "character",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -224,8 +266,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterHistory": {
|
"character_history": {
|
||||||
"name": "characterHistory",
|
"name": "character_history",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -234,8 +276,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"characterId": {
|
"character_id": {
|
||||||
"name": "characterId",
|
"name": "character_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -256,15 +298,15 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": 0
|
"default": 0
|
||||||
},
|
},
|
||||||
"createdAt": {
|
"created_at": {
|
||||||
"name": "createdAt",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"updatedAt": {
|
"updated_at": {
|
||||||
"name": "updatedAt",
|
"name": "updated_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -272,8 +314,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"characterHistory_date_unique": {
|
"character_history_date_unique": {
|
||||||
"name": "characterHistory_date_unique",
|
"name": "character_history_date_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"date"
|
"date"
|
||||||
],
|
],
|
||||||
@@ -281,17 +323,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterHistory_characterId_character_id_fk": {
|
"character_history_character_id_character_id_fk": {
|
||||||
"name": "characterHistory_characterId_character_id_fk",
|
"name": "character_history_character_id_character_id_fk",
|
||||||
"tableFrom": "characterHistory",
|
"tableFrom": "character_history",
|
||||||
"tableTo": "character",
|
"tableTo": "character",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"characterId"
|
"character_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -299,191 +341,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"characterOverride": {
|
"character_scrape_validation": {
|
||||||
"name": "characterOverride",
|
"name": "character_scrape_validation",
|
||||||
"columns": {
|
|
||||||
"characterId": {
|
|
||||||
"name": "characterId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"name": "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
|
|
||||||
},
|
|
||||||
"affiliations": {
|
|
||||||
"name": "affiliations",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"devilFruitId": {
|
|
||||||
"name": "devilFruitId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiObservation": {
|
|
||||||
"name": "hakiObservation",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiArmament": {
|
|
||||||
"name": "hakiArmament",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"hakiConqueror": {
|
|
||||||
"name": "hakiConqueror",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": 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
|
|
||||||
},
|
|
||||||
"firstAppearance": {
|
|
||||||
"name": "firstAppearance",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"pictureUrl": {
|
|
||||||
"name": "pictureUrl",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"epithets": {
|
|
||||||
"name": "epithets",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"name": "status",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"arcId": {
|
|
||||||
"name": "arcId",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"name": "url",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"name": "notes",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"characterOverride_characterId_character_id_fk": {
|
|
||||||
"name": "characterOverride_characterId_character_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "character",
|
|
||||||
"columnsFrom": [
|
|
||||||
"characterId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"characterOverride_devilFruitId_devilFruit_id_fk": {
|
|
||||||
"name": "characterOverride_devilFruitId_devilFruit_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "devilFruit",
|
|
||||||
"columnsFrom": [
|
|
||||||
"devilFruitId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
},
|
|
||||||
"characterOverride_arcId_arc_id_fk": {
|
|
||||||
"name": "characterOverride_arcId_arc_id_fk",
|
|
||||||
"tableFrom": "characterOverride",
|
|
||||||
"tableTo": "arc",
|
|
||||||
"columnsFrom": [
|
|
||||||
"arcId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
},
|
|
||||||
"characterScrapeValidation": {
|
|
||||||
"name": "characterScrapeValidation",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -499,6 +358,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_name": {
|
||||||
|
"name": "fr_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"gender": {
|
"gender": {
|
||||||
"name": "gender",
|
"name": "gender",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -513,38 +379,45 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"affiliations": {
|
"affiliation": {
|
||||||
"name": "affiliations",
|
"name": "affiliation",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"devilFruitId": {
|
"fr_affiliation": {
|
||||||
"name": "devilFruitId",
|
"name": "fr_affiliation",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"hakiObservation": {
|
"devil_fruit_id": {
|
||||||
"name": "hakiObservation",
|
"name": "devil_fruit_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"haki_observation": {
|
||||||
|
"name": "haki_observation",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiArmament": {
|
"haki_armament": {
|
||||||
"name": "hakiArmament",
|
"name": "haki_armament",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"hakiConqueror": {
|
"haki_conqueror": {
|
||||||
"name": "hakiConqueror",
|
"name": "haki_conqueror",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -572,15 +445,22 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"firstAppearance": {
|
"fr_origin": {
|
||||||
"name": "firstAppearance",
|
"name": "fr_origin",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_appearance": {
|
||||||
|
"name": "first_appearance",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"pictureUrl": {
|
"picture_url": {
|
||||||
"name": "pictureUrl",
|
"name": "picture_url",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -593,6 +473,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"fr_epithets": {
|
||||||
|
"name": "fr_epithets",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -600,8 +487,8 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"arcId": {
|
"arc_id": {
|
||||||
"name": "arcId",
|
"name": "arc_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
@@ -613,34 +500,49 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": 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": {},
|
"indexes": {},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"characterScrapeValidation_devilFruitId_devilFruit_id_fk": {
|
"character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": {
|
||||||
"name": "characterScrapeValidation_devilFruitId_devilFruit_id_fk",
|
"name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "devilFruit",
|
"tableTo": "devil_fruit",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"devilFruitId"
|
"devil_fruit_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"characterScrapeValidation_arcId_arc_id_fk": {
|
"character_scrape_validation_arc_id_arc_id_fk": {
|
||||||
"name": "characterScrapeValidation_arcId_arc_id_fk",
|
"name": "character_scrape_validation_arc_id_arc_id_fk",
|
||||||
"tableFrom": "characterScrapeValidation",
|
"tableFrom": "character_scrape_validation",
|
||||||
"tableTo": "arc",
|
"tableTo": "arc",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"arcId"
|
"arc_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "set null",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -672,8 +574,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"devilFruit": {
|
"devil_fruit": {
|
||||||
"name": "devilFruit",
|
"name": "devil_fruit",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -705,8 +607,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"devilFruit_name_unique": {
|
"devil_fruit_name_unique": {
|
||||||
"name": "devilFruit_name_unique",
|
"name": "devil_fruit_name_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
@@ -718,6 +620,183 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"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": {
|
"account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -955,6 +1034,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -1003,6 +1089,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
"user_email_unique": {
|
"user_email_unique": {
|
||||||
"name": "user_email_unique",
|
"name": "user_email_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,71 +5,29 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1772325597983,
|
"when": 1773602933375,
|
||||||
"tag": "0000_graceful_master_mold",
|
"tag": "0000_huge_doctor_octopus",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1772383366179,
|
"when": 1773697753818,
|
||||||
"tag": "0001_nostalgic_hercules",
|
"tag": "0001_fuzzy_talisman",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1772390182445,
|
"when": 1775950314114,
|
||||||
"tag": "0002_large_gwen_stacy",
|
"tag": "0002_old_earthquake",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1772449624450,
|
"when": 1776195681488,
|
||||||
"tag": "0003_wise_blonde_phantom",
|
"tag": "0003_mixed_ben_grimm",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 4,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772480377099,
|
|
||||||
"tag": "0004_unique_lorna_dane",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 5,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772562012631,
|
|
||||||
"tag": "0005_large_jane_foster",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 6,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772562364830,
|
|
||||||
"tag": "0006_premium_mesmero",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 7,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772735982970,
|
|
||||||
"tag": "0007_gray_shinko_yamashiro",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 8,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772821532270,
|
|
||||||
"tag": "0008_skinny_warpath",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 9,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1772822823122,
|
|
||||||
"tag": "0009_true_gravity",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,50 +1,65 @@
|
|||||||
[
|
[
|
||||||
"aladdin_aladdin",
|
"absalom_absalom",
|
||||||
|
"king_king",
|
||||||
"alvida_alvida",
|
"alvida_alvida",
|
||||||
"aramaki_aramaki",
|
"aramaki_aramaki",
|
||||||
"arlong_arlong",
|
"arlong_arlong",
|
||||||
"ashura_doji_ashura_doji",
|
"ashura_doji_ashura_doji",
|
||||||
|
"vegapunk/atlas_atlas",
|
||||||
|
"avalo_pizarro_avalo_pizarro",
|
||||||
"baby_5_baby_5",
|
"baby_5_baby_5",
|
||||||
"baggy_baggy",
|
"buggy_buggy",
|
||||||
"bartholomew_kuma_bartholomew_kuma",
|
"bartholomew_kuma_bartholomew_kuma",
|
||||||
"bartolomeo_bartolomeo",
|
"bartolomeo_bartolomeo",
|
||||||
"basil_hawkins_basil_hawkins",
|
"basil_hawkins_basil_hawkins",
|
||||||
"batman_batman",
|
"bell-mère_bell-mère",
|
||||||
"bellamy_bellamy",
|
"bellamy_bellamy",
|
||||||
"belo_betty_belo_betty",
|
"belo_betty_belo_betty",
|
||||||
"ben_beckman_ben_beckman",
|
"benn_beckman_ben_beckman",
|
||||||
"bentham_bentham",
|
"bentham_bentham",
|
||||||
"bepo_bepo",
|
"bepo_bepo",
|
||||||
"black_maria_black_maria",
|
"black_maria_black_maria",
|
||||||
|
"blueno_blueno",
|
||||||
"boa_hancock_boa_hancock",
|
"boa_hancock_boa_hancock",
|
||||||
"boa_marigold_boa_marigold",
|
"boa_marigold_boa_marigold",
|
||||||
"boa_sandersonia_boa_sandersonia",
|
"boa_sandersonia_boa_sandersonia",
|
||||||
"borsalino_borsalino",
|
"borsalino_borsalino",
|
||||||
"brogy_brogy",
|
"brogy_brogy",
|
||||||
"brook_brook",
|
"brook_brook",
|
||||||
|
"buckingham_stussy_buckingham_stussy",
|
||||||
|
"buffalo_buffalo",
|
||||||
"camie_camie",
|
"camie_camie",
|
||||||
"capone_bege_capone_bege",
|
"capone_bege_capone_bege",
|
||||||
|
"carmel_carmel",
|
||||||
"caribou_caribou",
|
"caribou_caribou",
|
||||||
"carrot_carrot",
|
"carrot_carrot",
|
||||||
"catarina_devon_catarina_devon",
|
"catarina_devon_catarina_devon",
|
||||||
"cavendish_cavendish",
|
"cavendish_cavendish",
|
||||||
"cesar_clown_cesar_clown",
|
"caesar_clown_caesar_clown",
|
||||||
|
"charlotte_brûlée_charlotte_brûlée",
|
||||||
|
"charlotte_cracker_charlotte_cracker",
|
||||||
|
"charlotte_katakuri_charlotte_katakuri",
|
||||||
|
"charlotte_linlin_charlotte_linlin",
|
||||||
|
"charlotte_mont-d'or_charlotte_mont-d'or",
|
||||||
|
"charlotte_oven_charlotte_oven",
|
||||||
|
"charlotte_perospero_charlotte_perospero",
|
||||||
|
"charlotte_pudding_charlotte_pudding",
|
||||||
|
"charlotte_smoothie_charlotte_smoothie",
|
||||||
"chinjao_chinjao",
|
"chinjao_chinjao",
|
||||||
"coby_coby",
|
"clou_d_clover_clou_d_clover",
|
||||||
"corazon_corazon",
|
|
||||||
"crocodile_crocodile",
|
"crocodile_crocodile",
|
||||||
"crocus_crocus",
|
"crocus_crocus",
|
||||||
"curly_dadan_curly_dadan",
|
"curly_dadan_curly_dadan",
|
||||||
"dalton_dalton",
|
"dalton_dalton",
|
||||||
"daz_bones_daz_bones",
|
"daz_bonez_daz_bonez",
|
||||||
"denjiro_denjiro",
|
"denjiro_denjiro",
|
||||||
"diamante_diamante",
|
"diamante_diamante",
|
||||||
"doc_q_doc_q",
|
"doc_q_doc_q",
|
||||||
"don_quichotte_doflamingo_don_quichotte_doflamingo",
|
"donquixote_doflamingo_donquixote_doflamingo",
|
||||||
"don_quichotte_rossinante_don_quichotte_rossinante",
|
"donquixote_rosinante_donquixote_rosinante",
|
||||||
"dorry_dorry",
|
"dorry_dorry",
|
||||||
"dracule_mihawk_dracule_mihawk",
|
"dracule_mihawk_dracule_mihawk",
|
||||||
"duval_duval",
|
"vegapunk/edison_edison",
|
||||||
"edward_newgate_edward_newgate",
|
"edward_newgate_edward_newgate",
|
||||||
"edward_weevil_edward_weevil",
|
"edward_weevil_edward_weevil",
|
||||||
"emporio_ivankov_emporio_ivankov",
|
"emporio_ivankov_emporio_ivankov",
|
||||||
@@ -53,30 +68,47 @@
|
|||||||
"fisher_tiger_fisher_tiger",
|
"fisher_tiger_fisher_tiger",
|
||||||
"foxy_foxy",
|
"foxy_foxy",
|
||||||
"franky_franky",
|
"franky_franky",
|
||||||
"fujitora_fujitora",
|
"fukaboshi_fukaboshi",
|
||||||
"gan_forr_gan_forr",
|
"fukurou_fukurou",
|
||||||
|
"galdino_galdino",
|
||||||
|
"gan_fall_gan_fall",
|
||||||
"gecko_moria_gecko_moria",
|
"gecko_moria_gecko_moria",
|
||||||
|
"gem_gem",
|
||||||
|
"genzo_genzo",
|
||||||
"gin_gin",
|
"gin_gin",
|
||||||
|
"ginny_ginny",
|
||||||
"gol_d_roger_gol_d_roger",
|
"gol_d_roger_gol_d_roger",
|
||||||
"haguar_d_sauro_haguar_d_sauro",
|
"guernika_guernika",
|
||||||
|
"hack_hack",
|
||||||
|
"jaguar_d_saul_jaguar_d_saul",
|
||||||
"hajrudin_hajrudin",
|
"hajrudin_hajrudin",
|
||||||
"hannyabal_hannyabal",
|
"hannyabal_hannyabal",
|
||||||
"hatchan_hatchan",
|
"harald_harald",
|
||||||
|
"haredas_haredas",
|
||||||
|
"heracles_heracles",
|
||||||
|
"helmeppo_helmeppo",
|
||||||
|
"hibari_hibari",
|
||||||
|
"hiriluk_hiriluk",
|
||||||
"hina_hina",
|
"hina_hina",
|
||||||
"hody_jones_hody_jones",
|
"hody_jones_hody_jones",
|
||||||
"hyogoro_hyogoro",
|
"hogback_hogback",
|
||||||
|
"hyougoro_hyougoro",
|
||||||
"iceburg_iceburg",
|
"iceburg_iceburg",
|
||||||
|
"igaram_igaram",
|
||||||
"imu_imu",
|
"imu_imu",
|
||||||
"inazuma_inazuma",
|
"inazuma_inazuma",
|
||||||
"inuarashi_inuarashi",
|
"inuarashi_inuarashi",
|
||||||
"issho_issho",
|
"issho_issho",
|
||||||
"izo_izo",
|
"izou_izou",
|
||||||
"jabra_jabra",
|
"jabra_jabra",
|
||||||
"jack_jack",
|
"jack_jack",
|
||||||
|
"jango_jango",
|
||||||
"jesus_burgess_jesus_burgess",
|
"jesus_burgess_jesus_burgess",
|
||||||
"jewelry_bonney_jewelry_bonney",
|
"jewelry_bonney_jewelry_bonney",
|
||||||
"jinbei_jinbei",
|
"jinbe_jinbe",
|
||||||
|
"giolla_giolla",
|
||||||
"joy_boy_joy_boy",
|
"joy_boy_joy_boy",
|
||||||
|
"jozu_jozu",
|
||||||
"kaidou_kaidou",
|
"kaidou_kaidou",
|
||||||
"kaku_kaku",
|
"kaku_kaku",
|
||||||
"kalgara_kalgara",
|
"kalgara_kalgara",
|
||||||
@@ -85,60 +117,139 @@
|
|||||||
"karoo_karoo",
|
"karoo_karoo",
|
||||||
"kawamatsu_kawamatsu",
|
"kawamatsu_kawamatsu",
|
||||||
"kaya_kaya",
|
"kaya_kaya",
|
||||||
|
"kelly_funk_kelly_funk",
|
||||||
|
"kikunojo_kikunojo",
|
||||||
"killer_killer",
|
"killer_killer",
|
||||||
"kinemon_kinemon",
|
"kin'emon_kin'emon",
|
||||||
"koala_koala",
|
"koala_koala",
|
||||||
"koby_koby",
|
"koby_koby",
|
||||||
"kong_kong",
|
"kokoro_kokoro",
|
||||||
"kozuki_hiyori_kozuki_hiyori",
|
"kouzuki_hiyori_kouzuki_hiyori",
|
||||||
"kozuki_momonosuke_kozuki_momonosuke",
|
"kouzuki_momonosuke_kouzuki_momonosuke",
|
||||||
"kozuki_oden_kozuki_oden",
|
"kouzuki_oden_kouzuki_oden",
|
||||||
|
"kouzuki_sukiyaki_kouzuki_sukiyaki",
|
||||||
|
"kouzuki_toki_kouzuki_toki",
|
||||||
"krieg_krieg",
|
"krieg_krieg",
|
||||||
|
"kumadori_kumadori",
|
||||||
"kureha_kureha",
|
"kureha_kureha",
|
||||||
"kuro_kuro",
|
"kuro_kuro",
|
||||||
|
"kurozumi_kanjuro_kurozumi_kanjuro",
|
||||||
"kurozumi_orochi_kurozumi_orochi",
|
"kurozumi_orochi_kurozumi_orochi",
|
||||||
|
"kurozumi_tama_kurozumi_tama",
|
||||||
"kuzan_kuzan",
|
"kuzan_kuzan",
|
||||||
"kyros_kyros",
|
"kyros_kyros",
|
||||||
"laboon_laboon",
|
"laboon_laboon",
|
||||||
"laffitte_laffitte",
|
"laffitte_laffitte",
|
||||||
"lao_g_lao_g",
|
"lao_g_lao_g",
|
||||||
"leo_leo",
|
"leo_leo",
|
||||||
|
"vegapunk/lilith_lilith",
|
||||||
"lindbergh_lindbergh",
|
"lindbergh_lindbergh",
|
||||||
"loki_loki",
|
"loki_loki",
|
||||||
"lucky_roux_lucky_roux",
|
"lucky_roux_lucky_roux",
|
||||||
"magellan_magellan",
|
"magellan_magellan",
|
||||||
"makino_makino",
|
"makino_makino",
|
||||||
|
"mansherry_mansherry",
|
||||||
"marco_marco",
|
"marco_marco",
|
||||||
"marshall_d_teach_marshall_d_teach",
|
"marshall_d_teach_marshall_d_teach",
|
||||||
|
"merry_merry",
|
||||||
|
"momoo_momoo",
|
||||||
|
"mocha_mocha",
|
||||||
|
"monet_monet",
|
||||||
"monkey_d_dragon_monkey_d_dragon",
|
"monkey_d_dragon_monkey_d_dragon",
|
||||||
"monkey_d_garp_monkey_d_garp",
|
"monkey_d_garp_monkey_d_garp",
|
||||||
"monkey_d_luffy_monkey_d_luffy",
|
"monkey_d_luffy_monkey_d_luffy",
|
||||||
"montblanc_norland_montblanc_norland",
|
"mont_blanc_cricket_mont_blanc_cricket",
|
||||||
|
"mont_blanc_noland_mont_blanc_noland",
|
||||||
"morgans_morgans",
|
"morgans_morgans",
|
||||||
|
"morgan_morgan",
|
||||||
"morley_morley",
|
"morley_morley",
|
||||||
"mr_3_mr_3",
|
|
||||||
"nami_nami",
|
"nami_nami",
|
||||||
"nefertari_cobra_nefertari_cobra",
|
"nefertari_cobra_nefertari_cobra",
|
||||||
"nefertari_vivi_nefertari_vivi",
|
"nefertari_vivi_nefertari_vivi",
|
||||||
"nekomamushi_nekomamushi",
|
"nekomamushi_nekomamushi",
|
||||||
"neptune_neptune",
|
"neptune_neptune",
|
||||||
|
"nico_olvia_nico_olvia",
|
||||||
"nico_robin_nico_robin",
|
"nico_robin_nico_robin",
|
||||||
"oars_oars",
|
"nojiko_nojiko",
|
||||||
|
"hatchan_hatchan",
|
||||||
"otohime_otohime",
|
"otohime_otohime",
|
||||||
|
"oars_oars",
|
||||||
"page_one_page_one",
|
"page_one_page_one",
|
||||||
"pandaman_pandaman",
|
"pandaman_pandaman",
|
||||||
|
"paulie_paulie",
|
||||||
|
"pedro_pedro",
|
||||||
"pekoms_pekoms",
|
"pekoms_pekoms",
|
||||||
"pell_pell",
|
"pell_pell",
|
||||||
"perona_perona",
|
"perona_perona",
|
||||||
"pica_pica",
|
"pica_pica",
|
||||||
"portgas_d_ace_portgas_d_ace",
|
"portgas_d_ace_portgas_d_ace",
|
||||||
|
"vegapunk/pythagoras_pythagoras",
|
||||||
"queen_queen",
|
"queen_queen",
|
||||||
"raizo_raizo",
|
"raizo_raizo",
|
||||||
"rebecca_rebecca",
|
"rebecca_rebecca",
|
||||||
|
"riku_doldo_iii_riku_doldo_iii",
|
||||||
"rob_lucci_rob_lucci",
|
"rob_lucci_rob_lucci",
|
||||||
"rocks_d_xebec_rocks_d_xebec",
|
"rocks_d_xebec_rocks_d_xebec",
|
||||||
"roronoa_zoro_roronoa_zoro",
|
"roronoa_zoro_roronoa_zoro",
|
||||||
|
"s-bear_s-bear",
|
||||||
|
"s-hawk_s-hawk",
|
||||||
|
"s-snake_s-snake",
|
||||||
"sabo_sabo",
|
"sabo_sabo",
|
||||||
|
"sadi_sadi",
|
||||||
|
"donquixote_mjosgard_donquixote_mjosgard",
|
||||||
|
"rimoshifu_killingham_rimoshifu_killingham",
|
||||||
|
"manmayer_gunko_manmayer_gunko",
|
||||||
|
"shepherd_sommers_shepherd_sommers",
|
||||||
|
"sakazuki_sakazuki",
|
||||||
|
"sanjuan_wolf_sanjuan_wolf",
|
||||||
|
"sasaki_sasaki",
|
||||||
|
"scratchmen_apoo_scratchmen_apoo",
|
||||||
|
"sengoku_sengoku",
|
||||||
|
"senor_pink_senor_pink",
|
||||||
|
"sentomaru_sentomaru",
|
||||||
|
"vegapunk/shaka_shaka",
|
||||||
|
"shakuyaku_shakuyaku",
|
||||||
|
"shanks_shanks",
|
||||||
|
"shiryu_shiryu",
|
||||||
|
"shimotsuki_kuina_shimotsuki_kuina",
|
||||||
|
"shimotsuki_yasuie_shimotsuki_yasuie",
|
||||||
|
"shinobu_shinobu",
|
||||||
|
"shirahoshi_shirahoshi",
|
||||||
|
"silvers_rayleigh_silvers_rayleigh",
|
||||||
|
"smoker_smoker",
|
||||||
|
"spandam_spandam",
|
||||||
|
"speed_speed",
|
||||||
|
"stussy_stussy",
|
||||||
|
"sugar_sugar",
|
||||||
|
"tamago_tamago",
|
||||||
|
"tashigi_tashigi",
|
||||||
|
"toko_toko",
|
||||||
|
"tom_tom",
|
||||||
|
"tony_tony_chopper_tony_tony_chopper",
|
||||||
|
"trafalgar_d_water_law_trafalgar_d_water_law",
|
||||||
|
"trebol_trebol",
|
||||||
|
"tsuru_tsuru",
|
||||||
|
"ulti_ulti",
|
||||||
|
"urouge_urouge",
|
||||||
|
"usopp_usopp",
|
||||||
|
"uta_uta",
|
||||||
|
"van_augur_van_augur",
|
||||||
|
"vander_decken_ix_vander_decken_ix",
|
||||||
"vegapunk_vegapunk",
|
"vegapunk_vegapunk",
|
||||||
"yamato_yamato"
|
"vergo_vergo",
|
||||||
|
"vinsmoke_ichiji_vinsmoke_ichiji",
|
||||||
|
"vinsmoke_judge_vinsmoke_judge",
|
||||||
|
"vinsmoke_niji_vinsmoke_niji",
|
||||||
|
"vinsmoke_reiju_vinsmoke_reiju",
|
||||||
|
"sanji_sanji",
|
||||||
|
"vinsmoke_yonji_vinsmoke_yonji",
|
||||||
|
"viola_viola",
|
||||||
|
"wadatsumi_wadatsumi",
|
||||||
|
"wapol_wapol",
|
||||||
|
"wyper_wyper",
|
||||||
|
"x_drake_x_drake",
|
||||||
|
"yamato_yamato",
|
||||||
|
"yasopp_yasopp",
|
||||||
|
"vegapunk/york_york",
|
||||||
|
"zeff_zeff"
|
||||||
]
|
]
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import { createClient } from '@libsql/client';
|
import { createClient } from '@libsql/client';
|
||||||
import { drizzle } from 'drizzle-orm/libsql';
|
import { drizzle } from 'drizzle-orm/libsql';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
import { sql, eq, inArray } from 'drizzle-orm';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { arc, character, devilFruit, characterScrapeValidation, type DevilFruitType } from '../src/lib/server/db/schema';
|
import { arc, character, devilFruit, characterScrapeValidation, type DevilFruitType } from '../src/lib/server/db/schema';
|
||||||
|
|
||||||
|
type Status = 'Alive' | 'Dead' | 'Unknown';
|
||||||
|
|
||||||
type ArcRecord = {
|
type ArcRecord = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
frName?: string | null;
|
||||||
startChapter: number;
|
startChapter: number;
|
||||||
endChapter?: number | null;
|
endChapter?: number | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -22,9 +25,11 @@ type DevilFruitRecord = {
|
|||||||
type CharacterRecord = {
|
type CharacterRecord = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
frName?: string | null;
|
||||||
gender?: string | null;
|
gender?: string | null;
|
||||||
age?: number | null;
|
age?: number | null;
|
||||||
affiliations?: string[] | string | null;
|
affiliation?: string | null;
|
||||||
|
frAffiliation?: string | null;
|
||||||
devilFruitId?: string | null;
|
devilFruitId?: string | null;
|
||||||
hakiObservation?: boolean;
|
hakiObservation?: boolean;
|
||||||
hakiArmament?: boolean;
|
hakiArmament?: boolean;
|
||||||
@@ -32,12 +37,15 @@ type CharacterRecord = {
|
|||||||
bounty?: number | null;
|
bounty?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
origin?: string | null;
|
origin?: string | null;
|
||||||
|
frOrigin?: string | null;
|
||||||
firstAppearance?: number;
|
firstAppearance?: number;
|
||||||
pictureUrl?: string | null;
|
pictureUrl?: string | null;
|
||||||
epithets?: string[] | string | null;
|
epithets?: string[] | string | null;
|
||||||
status?: string | null;
|
frEpithets?: string[] | string | null;
|
||||||
|
status?: Status | null;
|
||||||
arcId?: string | null;
|
arcId?: string | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
|
frUrl?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DATABASE_URL = process.env.DATABASE_URL || 'file:local.db';
|
const DATABASE_URL = process.env.DATABASE_URL || 'file:local.db';
|
||||||
@@ -86,7 +94,7 @@ function toJsonArray(value: string[] | string | null | undefined): string[] | nu
|
|||||||
|
|
||||||
function toDevilFruitType(value: DevilFruitType | string | null | undefined): DevilFruitType | null {
|
function toDevilFruitType(value: DevilFruitType | string | null | undefined): DevilFruitType | null {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
if (value === 'Paramecia' || value === 'Zoan' || value === 'Logia' || value === 'Unknown') {
|
if (value === 'Paramecia' || value === 'Zoan' || value === 'Logia' || value === 'Smile' || value === 'Unknown') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
@@ -112,62 +120,31 @@ function transformCharacterData(item: CharacterRecord) {
|
|||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
|
frName: toNullable(item.frName),
|
||||||
gender: toNullable(item.gender),
|
gender: toNullable(item.gender),
|
||||||
age: toNullable(item.age),
|
age: toNullable(item.age),
|
||||||
affiliations: toJsonArray(item.affiliations),
|
affiliation: toNullable(item.affiliation),
|
||||||
|
frAffiliation: toNullable(item.frAffiliation),
|
||||||
devilFruitId: toNullable(item.devilFruitId),
|
devilFruitId: toNullable(item.devilFruitId),
|
||||||
hakiObservation: !!item.hakiObservation,
|
hakiObservation: !!item.hakiObservation,
|
||||||
hakiArmament: !!item.hakiArmament,
|
hakiArmament: !!item.hakiArmament,
|
||||||
hakiConqueror: !!item.hakiConqueror,
|
hakiConqueror: !!item.hakiConqueror,
|
||||||
bounty: item.bounty ?? 0,
|
bounty: item.bounty ?? 0,
|
||||||
height: toNumber(item.height as any),
|
height: toNumber(item.height as string | number | null),
|
||||||
origin: toNullable(item.origin),
|
origin: toNullable(item.origin),
|
||||||
|
frOrigin: toNullable(item.frOrigin),
|
||||||
firstAppearance: item.firstAppearance ?? 0,
|
firstAppearance: item.firstAppearance ?? 0,
|
||||||
pictureUrl: toNullable(item.pictureUrl),
|
pictureUrl: toNullable(item.pictureUrl),
|
||||||
epithets: toJsonArray(item.epithets),
|
epithets: toJsonArray(item.epithets),
|
||||||
|
frEpithets: toJsonArray(item.frEpithets),
|
||||||
status: toNullable(item.status),
|
status: toNullable(item.status),
|
||||||
arcId: toNullable(item.arcId),
|
arcId: toNullable(item.arcId),
|
||||||
url: toNullable(item.url)
|
url: toNullable(item.url),
|
||||||
|
frUrl: toNullable(item.frUrl),
|
||||||
|
isDeleted: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasChanged(jsonData: any, dbData: any): boolean {
|
|
||||||
if (!dbData) return true;
|
|
||||||
|
|
||||||
// Print any differences for debugging
|
|
||||||
for (const key in jsonData) {
|
|
||||||
const jsonValue = jsonData[key];
|
|
||||||
const dbValue = dbData[key];
|
|
||||||
const jsonString = typeof jsonValue === 'object' ? JSON.stringify(jsonValue) : String(jsonValue);
|
|
||||||
const dbString = typeof dbValue === 'object' ? JSON.stringify(dbValue) : String(dbValue);
|
|
||||||
if (jsonString !== dbString) {
|
|
||||||
console.log(`\nField "${key}" changed for character ID ${jsonData.id}:`);
|
|
||||||
console.log(` JSON: ${jsonString}`);
|
|
||||||
console.log(` DB: ${dbString}`);
|
|
||||||
} }
|
|
||||||
|
|
||||||
// Compare each field
|
|
||||||
return (
|
|
||||||
jsonData.name != dbData.name ||
|
|
||||||
jsonData.gender != dbData.gender ||
|
|
||||||
jsonData.age != dbData.age ||
|
|
||||||
JSON.stringify(jsonData.affiliations) != JSON.stringify(dbData.affiliations) ||
|
|
||||||
jsonData.devilFruitId != dbData.devilFruitId ||
|
|
||||||
jsonData.hakiObservation != dbData.hakiObservation ||
|
|
||||||
jsonData.hakiArmament != dbData.hakiArmament ||
|
|
||||||
jsonData.hakiConqueror != dbData.hakiConqueror ||
|
|
||||||
jsonData.bounty != dbData.bounty ||
|
|
||||||
jsonData.height != dbData.height ||
|
|
||||||
jsonData.origin != dbData.origin ||
|
|
||||||
jsonData.firstAppearance != dbData.firstAppearance ||
|
|
||||||
jsonData.pictureUrl != dbData.pictureUrl ||
|
|
||||||
JSON.stringify(jsonData.epithets) != JSON.stringify(dbData.epithets) ||
|
|
||||||
jsonData.status != dbData.status ||
|
|
||||||
jsonData.arcId != dbData.arcId ||
|
|
||||||
jsonData.url != dbData.url
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isCharacterTableEmpty(): Promise<boolean> {
|
async function isCharacterTableEmpty(): Promise<boolean> {
|
||||||
const result = await db.select({ count: sql<number>`COUNT(*)` }).from(character);
|
const result = await db.select({ count: sql<number>`COUNT(*)` }).from(character);
|
||||||
return result[0]?.count === 0;
|
return result[0]?.count === 0;
|
||||||
@@ -195,6 +172,7 @@ async function importFromJson(): Promise<void> {
|
|||||||
.values({
|
.values({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
|
frName: toNullable(item.frName),
|
||||||
startChapter: item.startChapter,
|
startChapter: item.startChapter,
|
||||||
endChapter: toNullable(item.endChapter),
|
endChapter: toNullable(item.endChapter),
|
||||||
url: toNullable(item.url)
|
url: toNullable(item.url)
|
||||||
@@ -203,6 +181,7 @@ async function importFromJson(): Promise<void> {
|
|||||||
target: arc.id,
|
target: arc.id,
|
||||||
set: {
|
set: {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
|
frName: toNullable(item.frName),
|
||||||
startChapter: item.startChapter,
|
startChapter: item.startChapter,
|
||||||
endChapter: toNullable(item.endChapter),
|
endChapter: toNullable(item.endChapter),
|
||||||
url: toNullable(item.url)
|
url: toNullable(item.url)
|
||||||
@@ -329,6 +308,7 @@ async function importFromJson(): Promise<void> {
|
|||||||
} else {
|
} else {
|
||||||
// Update scrapeValidation table
|
// Update scrapeValidation table
|
||||||
console.log('Characters table not empty, updating scrapeValidation table for changes...\n');
|
console.log('Characters table not empty, updating scrapeValidation table for changes...\n');
|
||||||
|
const scrapedCharacterIds: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < characters.length; i++) {
|
for (let i = 0; i < characters.length; i++) {
|
||||||
const item = characters[i];
|
const item = characters[i];
|
||||||
@@ -341,6 +321,7 @@ async function importFromJson(): Promise<void> {
|
|||||||
|
|
||||||
lastSql = selectQuery.toSQL();
|
lastSql = selectQuery.toSQL();
|
||||||
|
|
||||||
|
scrapedCharacterIds.push(item.id);
|
||||||
const jsonData = transformCharacterData(item);
|
const jsonData = transformCharacterData(item);
|
||||||
|
|
||||||
const upsertQuery = db
|
const upsertQuery = db
|
||||||
@@ -363,6 +344,57 @@ async function importFromJson(): Promise<void> {
|
|||||||
logSqlOnError(lastSql);
|
logSqlOnError(lastSql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch all characters from the character table and mark those absent from the
|
||||||
|
// scrape as deleted in scrape validation.
|
||||||
|
const allCharacters = await db.select({ id: character.id }).from(character);
|
||||||
|
const scrapedSet = new Set(scrapedCharacterIds);
|
||||||
|
const idsToMarkDeleted = allCharacters
|
||||||
|
.map((c) => c.id)
|
||||||
|
.filter((id) => !scrapedSet.has(id));
|
||||||
|
|
||||||
|
if (idsToMarkDeleted.length > 0) {
|
||||||
|
console.log(`\n⚠️ Marking ${idsToMarkDeleted.length} character(s) as deleted in scrape validation...`);
|
||||||
|
const deletedCharacterRows = await db
|
||||||
|
.select()
|
||||||
|
.from(character)
|
||||||
|
.where(inArray(character.id, idsToMarkDeleted));
|
||||||
|
|
||||||
|
for (const row of deletedCharacterRows) {
|
||||||
|
await db
|
||||||
|
.insert(characterScrapeValidation)
|
||||||
|
.values({
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
frName: row.frName,
|
||||||
|
gender: row.gender,
|
||||||
|
age: row.age,
|
||||||
|
affiliation: row.affiliation,
|
||||||
|
frAffiliation: row.frAffiliation,
|
||||||
|
devilFruitId: row.devilFruitId,
|
||||||
|
hakiObservation: row.hakiObservation,
|
||||||
|
hakiArmament: row.hakiArmament,
|
||||||
|
hakiConqueror: row.hakiConqueror,
|
||||||
|
bounty: row.bounty,
|
||||||
|
height: row.height,
|
||||||
|
origin: row.origin,
|
||||||
|
frOrigin: row.frOrigin,
|
||||||
|
firstAppearance: row.firstAppearance,
|
||||||
|
pictureUrl: row.pictureUrl,
|
||||||
|
epithets: row.epithets,
|
||||||
|
frEpithets: row.frEpithets,
|
||||||
|
status: row.status,
|
||||||
|
arcId: row.arcId,
|
||||||
|
url: row.url,
|
||||||
|
frUrl: row.frUrl,
|
||||||
|
isDeleted: true
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: characterScrapeValidation.id,
|
||||||
|
set: { isDeleted: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n\n✓ Characters imported!`);
|
console.log(`\n\n✓ Characters imported!`);
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ const columns = [
|
|||||||
'origin',
|
'origin',
|
||||||
'devilFruitType',
|
'devilFruitType',
|
||||||
'arc',
|
'arc',
|
||||||
'status'
|
'status',
|
||||||
|
'age'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
async function initColumnConfig(): Promise<void> {
|
async function initColumnConfig(): Promise<void> {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
import { createClient } from '@libsql/client';
|
import { createClient } from '@libsql/client';
|
||||||
import { drizzle } from 'drizzle-orm/libsql';
|
import { drizzle } from 'drizzle-orm/libsql';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { character, characterHistory } from '../src/lib/server/db/schema';
|
import { character, characterHistory } from '../src/lib/server/db/schema';
|
||||||
|
|
||||||
@@ -24,13 +24,14 @@ function getErrorMessage(error: unknown): string {
|
|||||||
|
|
||||||
async function setDailyCharacters(): Promise<void> {
|
async function setDailyCharacters(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const dailyCharacterIds = readJsonFile('./scripts/daily-characters.json');
|
const dailyCharacterIdsRaw = readJsonFile('./scripts/daily-characters.json');
|
||||||
|
|
||||||
if (!dailyCharacterIds || dailyCharacterIds.length === 0) {
|
if (!dailyCharacterIdsRaw || dailyCharacterIdsRaw.length === 0) {
|
||||||
console.error('❌ No daily characters found in daily-characters.json');
|
throw new Error('No daily characters found in daily-characters.json');
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dailyCharacterIds = dailyCharacterIdsRaw;
|
||||||
|
|
||||||
console.log(`\n=== Setting Daily Mode Characters ===\n`);
|
console.log(`\n=== Setting Daily Mode Characters ===\n`);
|
||||||
console.log(`Found ${dailyCharacterIds.length} characters to set as daily\n`);
|
console.log(`Found ${dailyCharacterIds.length} characters to set as daily\n`);
|
||||||
|
|
||||||
@@ -45,16 +46,36 @@ async function setDailyCharacters(): Promise<void> {
|
|||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
||||||
|
const existingCharacters = await db
|
||||||
|
.select({ id: character.id })
|
||||||
|
.from(character)
|
||||||
|
.where(inArray(character.id, dailyCharacterIds));
|
||||||
|
|
||||||
|
const existingIdSet = new Set(existingCharacters.map((c) => c.id));
|
||||||
|
const missingIds = dailyCharacterIds.filter((id) => !existingIdSet.has(id));
|
||||||
|
|
||||||
|
if (missingIds.length > 0) {
|
||||||
|
errorCount += missingIds.length;
|
||||||
|
console.error(`✗ ${missingIds.length} character ID(s) were not found in database:`);
|
||||||
|
for (const missingId of missingIds) {
|
||||||
|
console.error(` - ${missingId}`);
|
||||||
|
}
|
||||||
|
console.error('');
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < dailyCharacterIds.length; i++) {
|
for (let i = 0; i < dailyCharacterIds.length; i++) {
|
||||||
const charId = dailyCharacterIds[i];
|
const charId = dailyCharacterIds[i];
|
||||||
|
if (!existingIdSet.has(charId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db
|
await db
|
||||||
.update(character)
|
.update(character)
|
||||||
.set({ isInDailyMode: true })
|
.set({ isInDailyMode: true })
|
||||||
.where(eq(character.id, charId));
|
.where(eq(character.id, charId));
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
process.stdout.write(`\rUpdated: ${successCount}/${dailyCharacterIds.length}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorCount++;
|
errorCount++;
|
||||||
console.error(`\n✗ Error updating character ${i + 1}:`);
|
console.error(`\n✗ Error updating character ${i + 1}:`);
|
||||||
|
|||||||
@@ -1,16 +1,64 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
export let characters: any[];
|
let {
|
||||||
export let selectedCharacters: any[];
|
characters,
|
||||||
|
selectedCharacters,
|
||||||
|
onSelect
|
||||||
|
}: {
|
||||||
|
characters: CharacterWithRelations[];
|
||||||
|
selectedCharacters: CharacterWithRelations[];
|
||||||
|
onSelect: (character: CharacterWithRelations) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const state = $state({
|
||||||
|
searchInput: '',
|
||||||
|
highlightedIndex: 0,
|
||||||
|
dropdownContainer: null as HTMLDivElement | null,
|
||||||
|
searchContainer: null as HTMLDivElement | null
|
||||||
|
});
|
||||||
|
|
||||||
let searchInput = '';
|
const isFrench = $derived($language === 'fr');
|
||||||
let highlightedIndex = 0;
|
|
||||||
let dropdownContainer: HTMLDivElement;
|
function parseEpithets(value: unknown): string[] {
|
||||||
let searchContainer: HTMLDivElement;
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (value.length > 0) {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayName(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
|
||||||
|
return character.frName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayEpithets(character: CharacterWithRelations): string[] {
|
||||||
|
const frenchEpithets = parseEpithets(character.frEpithets);
|
||||||
|
if (isFrench && frenchEpithets.length > 0) {
|
||||||
|
return frenchEpithets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseEpithets(character.epithets);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeSearchText(value: string): string {
|
function normalizeSearchText(value: string): string {
|
||||||
return value
|
return value
|
||||||
@@ -28,52 +76,54 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
$: filteredCharacters = characters.filter(char => {
|
const filteredCharacters = $derived.by(() => {
|
||||||
const searchTerm = normalizeSearchText(searchInput);
|
const searchTerm = normalizeSearchText(state.searchInput);
|
||||||
const nameMatches = normalizeSearchText(char.name).includes(searchTerm);
|
|
||||||
|
|
||||||
let epithetsMatches = false;
|
return characters.filter((char) => {
|
||||||
if (char.epithets) {
|
const displayName = getDisplayName(char);
|
||||||
try {
|
const displayEpithets = getDisplayEpithets(char);
|
||||||
const parsedEpithets = typeof char.epithets === 'string'
|
const nameMatches = normalizeSearchText(displayName).includes(searchTerm);
|
||||||
? JSON.parse(char.epithets)
|
const epithetsMatches = displayEpithets.some((epithet) =>
|
||||||
: char.epithets;
|
|
||||||
|
|
||||||
if (Array.isArray(parsedEpithets)) {
|
|
||||||
epithetsMatches = parsedEpithets.some((epithet: string) =>
|
|
||||||
normalizeSearchText(epithet).includes(searchTerm)
|
normalizeSearchText(epithet).includes(searchTerm)
|
||||||
);
|
);
|
||||||
} else if (typeof parsedEpithets === 'string') {
|
|
||||||
epithetsMatches = normalizeSearchText(parsedEpithets).includes(searchTerm);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
epithetsMatches = normalizeSearchText(String(char.epithets)).includes(searchTerm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (nameMatches || epithetsMatches) &&
|
return (nameMatches || epithetsMatches) &&
|
||||||
!selectedCharacters.some(selected => selected.id === char.id);
|
!selectedCharacters.some((selected) => selected.id === char.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset highlighted index when filtered list changes
|
// Reset highlighted index when filtered list changes.
|
||||||
$: if (filteredCharacters) {
|
$effect(() => {
|
||||||
highlightedIndex = 0;
|
const nextFilteredCharacters = filteredCharacters;
|
||||||
|
if (!nextFilteredCharacters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.highlightedIndex = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scroll highlighted item into view.
|
||||||
|
$effect(() => {
|
||||||
|
const nextFilteredCharacters = filteredCharacters;
|
||||||
|
|
||||||
|
if (!state.dropdownContainer || state.highlightedIndex < 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll highlighted item into view
|
if (state.highlightedIndex >= nextFilteredCharacters.length) {
|
||||||
$: if (dropdownContainer && highlightedIndex >= 0) {
|
return;
|
||||||
const highlightedButton = dropdownContainer.querySelector(
|
|
||||||
`button:nth-child(${highlightedIndex + 1})`
|
|
||||||
) as HTMLElement;
|
|
||||||
if (highlightedButton) {
|
|
||||||
highlightedButton.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectCharacter(character: any) {
|
const highlightedButton = state.dropdownContainer.querySelector(
|
||||||
dispatch('select', character);
|
`button:nth-child(${state.highlightedIndex + 1})`
|
||||||
searchInput = '';
|
) as HTMLElement | null;
|
||||||
highlightedIndex = 0;
|
|
||||||
|
highlightedButton?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectCharacter(character: CharacterWithRelations) {
|
||||||
|
onSelect(character);
|
||||||
|
state.searchInput = '';
|
||||||
|
state.highlightedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
@@ -82,16 +132,19 @@
|
|||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
highlightedIndex = Math.min(highlightedIndex + 1, filteredCharacters.length - 1);
|
state.highlightedIndex = Math.min(
|
||||||
|
state.highlightedIndex + 1,
|
||||||
|
filteredCharacters.length - 1
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
highlightedIndex = Math.max(highlightedIndex - 1, 0);
|
state.highlightedIndex = Math.max(state.highlightedIndex - 1, 0);
|
||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (filteredCharacters[highlightedIndex]) {
|
if (filteredCharacters[state.highlightedIndex]) {
|
||||||
selectCharacter(filteredCharacters[highlightedIndex]);
|
selectCharacter(filteredCharacters[state.highlightedIndex]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -99,44 +152,43 @@
|
|||||||
|
|
||||||
function submitGuess() {
|
function submitGuess() {
|
||||||
if (filteredCharacters.length === 0) return;
|
if (filteredCharacters.length === 0) return;
|
||||||
const characterToSelect =
|
const characterToSelect = filteredCharacters[state.highlightedIndex] ?? filteredCharacters[0];
|
||||||
filteredCharacters[highlightedIndex] ?? filteredCharacters[0];
|
|
||||||
if (characterToSelect) {
|
if (characterToSelect) {
|
||||||
selectCharacter(characterToSelect);
|
selectCharacter(characterToSelect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
if (searchContainer && !searchContainer.contains(event.target as Node)) {
|
if (state.searchContainer && !state.searchContainer.contains(event.target as Node)) {
|
||||||
searchInput = '';
|
state.searchInput = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur z-10">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur z-10">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Entrer une supposition</h2>
|
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.components.searchInput.title}</h2>
|
||||||
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
|
<div class="mt-4 flex flex-col gap-3 sm:flex-row">
|
||||||
<div bind:this={searchContainer} class="relative w-full">
|
<div bind:this={state.searchContainer} class="relative w-full">
|
||||||
<input
|
<input
|
||||||
bind:value={searchInput}
|
bind:value={state.searchInput}
|
||||||
class="w-full rounded-full border border-amber-200/30 bg-slate-900/60 px-5 py-3 text-sm text-slate-100 placeholder:text-slate-400 focus:border-amber-200/70 focus:outline-none"
|
class="w-full rounded-full border border-amber-200/30 bg-slate-900/60 px-5 py-3 text-sm text-slate-100 placeholder:text-slate-400 focus:border-amber-200/70 focus:outline-none"
|
||||||
placeholder="Nom du personnage"
|
placeholder={$t.game.components.searchInput.placeholder}
|
||||||
type="text"
|
type="text"
|
||||||
onkeydown={handleKeydown}
|
onkeydown={handleKeydown}
|
||||||
/>
|
/>
|
||||||
{#if searchInput.length > 0 && filteredCharacters.length > 0}
|
{#if state.searchInput.length > 0 && filteredCharacters.length > 0}
|
||||||
<div bind:this={dropdownContainer} class="absolute top-full left-0 right-0 mt-2 max-h-96 overflow-y-auto rounded-2xl border border-amber-200/30 bg-slate-900/90 backdrop-blur z-10">
|
<div bind:this={state.dropdownContainer} class="absolute top-full left-0 right-0 mt-2 max-h-96 overflow-y-auto rounded-2xl border border-amber-200/30 bg-slate-900/90 backdrop-blur z-10">
|
||||||
{#each filteredCharacters as character, index (character.id)}
|
{#each filteredCharacters as character, index (character.id)}
|
||||||
<button
|
<button
|
||||||
class="w-full px-5 py-4 text-left text-base text-slate-100 transition border-b border-slate-800/50 last:border-b-0 flex items-center gap-4 {index === highlightedIndex ? 'bg-slate-700' : 'hover:bg-slate-800/70'}"
|
class="w-full px-5 py-4 text-left text-base text-slate-100 transition border-b border-slate-800/50 last:border-b-0 flex items-center gap-4 {index === state.highlightedIndex ? 'bg-slate-700' : 'hover:bg-slate-800/70'}"
|
||||||
type="button"
|
type="button"
|
||||||
onmouseenter={() => highlightedIndex = index}
|
onmouseenter={() => (state.highlightedIndex = index)}
|
||||||
onclick={() => selectCharacter(character)}
|
onclick={() => selectCharacter(character)}
|
||||||
>
|
>
|
||||||
{#if character.pictureUrl}
|
{#if character.pictureUrl}
|
||||||
<img
|
<img
|
||||||
src={character.pictureUrl}
|
src={character.pictureUrl}
|
||||||
alt={character.name}
|
alt={getDisplayName(character)}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="w-12 h-12 rounded-full object-cover border border-amber-200/30"
|
class="w-12 h-12 rounded-full object-cover border border-amber-200/30"
|
||||||
/>
|
/>
|
||||||
@@ -146,17 +198,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<span class="font-semibold text-amber-100">{character.name}</span>
|
<span class="font-semibold text-amber-100">{getDisplayName(character)}</span>
|
||||||
{#if character.epithets}
|
{#if getDisplayEpithets(character).length > 0}
|
||||||
{@const parsedEpithets = typeof character.epithets === 'string'
|
|
||||||
? JSON.parse(character.epithets)
|
|
||||||
: character.epithets}
|
|
||||||
{#if Array.isArray(parsedEpithets) && parsedEpithets.length > 0}
|
|
||||||
<span class="ml-2 text-xs text-slate-400">
|
<span class="ml-2 text-xs text-slate-400">
|
||||||
• {parsedEpithets.join(', ')}
|
• {getDisplayEpithets(character).join(', ')}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -166,10 +213,10 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={submitGuess}
|
onclick={submitGuess}
|
||||||
disabled={searchInput.length === 0 || filteredCharacters.length === 0}
|
disabled={state.searchInput.length === 0 || filteredCharacters.length === 0}
|
||||||
class="rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Valider
|
{$t.game.components.searchInput.submit}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
73
src/lib/components/FriendsTodaySection.svelte
Normal file
73
src/lib/components/FriendsTodaySection.svelte
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from '$lib/i18n';
|
||||||
|
|
||||||
|
type TriedCharacter = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
pictureUrl: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FriendTodayResult = {
|
||||||
|
userId: string;
|
||||||
|
name: string;
|
||||||
|
image: string | null;
|
||||||
|
tryCount: number;
|
||||||
|
triedCharacters: TriedCharacter[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export let friendsTodayResults: FriendTodayResult[] = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if friendsTodayResults.length > 0}
|
||||||
|
<section class="mt-6 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100 text-center">{$t.game.daily.friendsToday}</p>
|
||||||
|
<div class="mt-4 space-y-2">
|
||||||
|
{#each friendsTodayResults as friendResult (friendResult.userId)}
|
||||||
|
<div class="rounded-lg border border-white/10 bg-slate-950/50 px-4 py-3">
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
{#if friendResult.image}
|
||||||
|
<img
|
||||||
|
src={friendResult.image}
|
||||||
|
alt={friendResult.name}
|
||||||
|
class="h-8 w-8 rounded-full border border-white/20 object-cover"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-amber-300/20 text-xs font-semibold text-amber-100">
|
||||||
|
{friendResult.name?.charAt(0).toUpperCase() || 'U'}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="text-sm font-semibold text-slate-100">{friendResult.name}</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-amber-300">
|
||||||
|
{friendResult.tryCount} {friendResult.tryCount > 1 ? $t.game.daily.friendTryPlural : $t.game.daily.friendTrySingular}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 border-t border-white/10 pt-2">
|
||||||
|
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
|
{$t.game.daily.friendsTriedCharacters}
|
||||||
|
</p>
|
||||||
|
{#if friendResult.triedCharacters && friendResult.triedCharacters.length > 0}
|
||||||
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
|
{#each friendResult.triedCharacters as triedCharacter (triedCharacter.id)}
|
||||||
|
<span class="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-2.5 py-1 text-xs text-slate-200">
|
||||||
|
{#if triedCharacter.pictureUrl}
|
||||||
|
<img
|
||||||
|
src={triedCharacter.pictureUrl}
|
||||||
|
alt={triedCharacter.name}
|
||||||
|
class="h-4 w-4 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{triedCharacter.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="mt-1 text-xs text-slate-500">{$t.game.daily.friendsNoTriedCharacters}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
@@ -1,192 +1,344 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { formatBounty } from '$lib';
|
import { formatBounty } from '$lib';
|
||||||
|
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
||||||
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
export let selectedCharacters: any[];
|
export let selectedCharacters: CharacterWithRelations[];
|
||||||
export let dailyCharacter: any;
|
export let dailyCharacter: CharacterWithRelations;
|
||||||
export let columnVisibility: any;
|
export let columnVisibility: {
|
||||||
|
status?: boolean;
|
||||||
|
gender?: boolean;
|
||||||
|
affiliation?: boolean;
|
||||||
|
devilFruitType?: boolean;
|
||||||
|
haki?: boolean;
|
||||||
|
bounty?: boolean;
|
||||||
|
height?: boolean;
|
||||||
|
age?: boolean;
|
||||||
|
origin?: boolean;
|
||||||
|
arc?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
$: isFrench = $language === 'fr';
|
||||||
|
|
||||||
|
function getDisplayName(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
|
||||||
|
return character.frName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiUrl(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
|
||||||
|
return character.frUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.url || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiBaseUrl(): string {
|
||||||
|
return isFrench ? 'https://onepiece.fandom.com/fr/wiki/' : 'https://onepiece.fandom.com/wiki/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayOrigin(character: CharacterWithRelations): string | null {
|
||||||
|
if (isFrench && typeof character.frOrigin === 'string' && character.frOrigin.length > 0) {
|
||||||
|
return character.frOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMatchingOrigin(characterEntry: CharacterWithRelations, dailyEntry: CharacterWithRelations): boolean {
|
||||||
|
return getDisplayOrigin(characterEntry) === getDisplayOrigin(dailyEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayArcName(character: CharacterWithRelations): string | null {
|
||||||
|
if (isFrench && typeof character.frArcName === 'string' && character.frArcName.length > 0) {
|
||||||
|
return character.frArcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<section
|
||||||
|
class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col items-center gap-4 text-center">
|
<div class="flex flex-col items-center gap-4 text-center">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Historique</p>
|
<p class="text-xs font-semibold tracking-[0.28em] text-amber-100 uppercase">{$t.game.components.guessHistory.title}</p>
|
||||||
</div>
|
</div>
|
||||||
{#if selectedCharacters.length === 0}
|
{#if selectedCharacters.length === 0}
|
||||||
<p class="text-sm text-slate-200 text-center">Aucune tentative pour le moment.</p>
|
<p class="text-center text-sm text-slate-200">{$t.game.components.guessHistory.empty}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="overflow-x-auto pb-2 -mx-6 px-6 sm:mx-0 sm:px-0">
|
<div class="-mx-6 overflow-x-auto px-6 pb-2 sm:mx-0 sm:px-0">
|
||||||
<div class="w-max min-w-max mx-auto">
|
<div class="mx-auto w-max min-w-max">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex gap-1 sm:gap-2 mb-2">
|
<div class="mb-2 flex gap-1 sm:gap-2">
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Personnage</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.character}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if columnVisibility.status !== false}
|
{#if columnVisibility.status !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Statut</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.status}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.gender !== false}
|
{#if columnVisibility.gender !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Genre</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.gender}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.affiliations !== false}
|
{#if columnVisibility.affiliation !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Affiliations</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.affiliations}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.devilFruitType !== false}
|
{#if columnVisibility.devilFruitType !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Fruit</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.fruit}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.haki !== false}
|
{#if columnVisibility.haki !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Haki</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.haki}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.bounty !== false}
|
{#if columnVisibility.bounty !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Prime</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.bounty}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.height !== false}
|
{#if columnVisibility.height !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Taille</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.height}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if columnVisibility.age !== false}
|
||||||
|
<div
|
||||||
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.age}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.origin !== false}
|
{#if columnVisibility.origin !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Origine</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.origin}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if columnVisibility.arc !== false}
|
{#if columnVisibility.arc !== false}
|
||||||
<div class="w-16 sm:w-20 md:w-24 shrink-0 rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 sm:p-2 text-center flex items-center justify-center">
|
<div
|
||||||
<p class="text-[9px] sm:text-xs font-semibold uppercase tracking-wider text-amber-100">Arc</p>
|
class="flex w-16 shrink-0 items-center justify-center rounded-lg border border-amber-200/30 bg-amber-900/30 p-1 text-center sm:w-20 sm:p-2 md:w-24"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="text-[9px] font-semibold tracking-wider text-amber-100 uppercase sm:text-xs"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.arc}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rows -->
|
<!-- Rows -->
|
||||||
{#each selectedCharacters as character (character.id)}
|
{#each selectedCharacters as character (character.id)}
|
||||||
<div class="flex gap-1 sm:gap-2 mb-2">
|
<div class="mb-2 flex gap-1 sm:gap-2">
|
||||||
<!-- Personnage -->
|
<!-- Personnage -->
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 bg-slate-950/60 overflow-hidden">
|
<div
|
||||||
|
class="h-16 w-16 shrink-0 overflow-hidden rounded-lg border border-white/10 bg-slate-950/60 sm:h-20 sm:w-20 md:h-24 md:w-24"
|
||||||
|
>
|
||||||
{#if character.pictureUrl}
|
{#if character.pictureUrl}
|
||||||
<a
|
<a
|
||||||
href={"https://onepiece.fandom.com/fr/wiki/" + character.url}
|
href={getWikiBaseUrl() + getWikiUrl(character)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="block w-full h-full"
|
class="block h-full w-full"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={character.pictureUrl}
|
src={character.pictureUrl}
|
||||||
alt={character.name}
|
alt={getDisplayName(character)}
|
||||||
class="w-full h-full object-cover hover:opacity-80 transition-opacity cursor-pointer"
|
class="h-full w-full cursor-pointer object-cover transition-opacity hover:opacity-80"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="w-full h-full bg-slate-800 flex items-center justify-center p-1 sm:p-2">
|
<div
|
||||||
<span class="text-xs sm:text-sm md:text-xl text-center font-semibold line-clamp-3">{character.name}</span>
|
class="flex h-full w-full items-center justify-center bg-slate-800 p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="line-clamp-3 text-center text-xs font-semibold sm:text-sm md:text-xl"
|
||||||
|
>{getDisplayName(character)}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vivant / Mort -->
|
<!-- Vivant / Mort -->
|
||||||
{#if columnVisibility.status !== false}
|
{#if columnVisibility.status !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.status === dailyCharacter.status ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
<div
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center">
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.status ===
|
||||||
|
dailyCharacter.status
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||||
{character.status === 'Alive'
|
{character.status === 'Alive'
|
||||||
? 'Vivant'
|
? $t.game.components.guessHistory.alive
|
||||||
: character.status === 'Deceased' || character.status === 'Dead'
|
: character.status === 'Dead'
|
||||||
? 'Mort'
|
? $t.game.components.guessHistory.dead
|
||||||
: character.status === 'Unknown'
|
: character.status === 'Unknown'
|
||||||
? 'Inconnu'
|
? $t.game.components.guessHistory.unknown
|
||||||
: character.status === null
|
: character.status === null
|
||||||
? '-'
|
? '-'
|
||||||
: character.status || 'Inconnu'}
|
: character.status || $t.game.components.guessHistory.unknown}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Genre -->
|
<!-- Genre -->
|
||||||
{#if columnVisibility.gender !== false}
|
{#if columnVisibility.gender !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.gender === dailyCharacter.gender ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
<div
|
||||||
<p class="text-xs sm:text-sm md:text-base font-bold text-white text-center">
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.gender ===
|
||||||
{character.gender === 'Male' ? 'Homme' : character.gender === 'Female' ? 'Femme' : character.gender || 'Inconnu'}
|
dailyCharacter.gender
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
<p class="text-center text-xs font-bold text-white sm:text-sm md:text-base">
|
||||||
|
{character.gender === 'Male'
|
||||||
|
? $t.game.components.guessHistory.male
|
||||||
|
: character.gender === 'Female'
|
||||||
|
? $t.game.components.guessHistory.female
|
||||||
|
: character.gender || $t.game.components.guessHistory.unknown}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Affiliations -->
|
<!-- Affiliations -->
|
||||||
{#if columnVisibility.affiliations !== false}
|
{#if columnVisibility.affiliation !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {(() => {
|
<div
|
||||||
try {
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {getDislayAffiliation(character) === getDislayAffiliation(dailyCharacter)
|
||||||
const charAff = typeof character.affiliations === 'string'
|
? 'bg-emerald-600/90'
|
||||||
? ((character.affiliations as string).includes('[') ? JSON.parse(character.affiliations) : (character.affiliations as string).split(',').map((a: string) => a.trim()))
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||||
: character.affiliations;
|
>
|
||||||
const dailyAff = typeof dailyCharacter.affiliations === 'string'
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||||
? ((dailyCharacter.affiliations as string).includes('[') ? JSON.parse(dailyCharacter.affiliations) : (dailyCharacter.affiliations as string).split(',').map((a: string) => a.trim()))
|
{getDislayAffiliation(character) || $t.game.components.guessHistory.unknown}
|
||||||
: dailyCharacter.affiliations;
|
</p>
|
||||||
const charFirstAff = Array.isArray(charAff) ? charAff[0] : charAff;
|
|
||||||
const dailyFirstAff = Array.isArray(dailyAff) ? dailyAff[0] : dailyAff;
|
|
||||||
|
|
||||||
const charHasAff = charFirstAff && charFirstAff.trim() !== '';
|
|
||||||
const dailyHasAff = dailyFirstAff && dailyFirstAff.trim() !== '';
|
|
||||||
|
|
||||||
// If both have the same affiliation status and value
|
|
||||||
if (charHasAff === dailyHasAff && ((!charHasAff && !dailyHasAff) || charFirstAff === dailyFirstAff)) {
|
|
||||||
return 'bg-emerald-600/90';
|
|
||||||
}
|
|
||||||
return 'bg-red-900/60';
|
|
||||||
} catch (e) {
|
|
||||||
return 'bg-slate-950/60';
|
|
||||||
}
|
|
||||||
})()} p-1 sm:p-2 flex items-center justify-center overflow-hidden">
|
|
||||||
{#if character.affiliations}
|
|
||||||
{@const parsedAffiliations = typeof character.affiliations === 'string'
|
|
||||||
? (character.affiliations.includes('[') ? JSON.parse(character.affiliations) : character.affiliations.split(',').map((a: string) => a.trim()))
|
|
||||||
: character.affiliations}
|
|
||||||
{#if Array.isArray(parsedAffiliations) && parsedAffiliations.length > 0}
|
|
||||||
<p class="w-full text-[10px] sm:text-xs md:text-sm font-bold text-white text-center whitespace-normal break-words leading-tight">{parsedAffiliations[0]}</p>
|
|
||||||
{:else}
|
|
||||||
<p class="w-full text-[10px] sm:text-xs md:text-sm font-bold text-white text-center whitespace-normal break-words leading-tight">{parsedAffiliations}</p>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<p class="text-xs sm:text-sm md:text-base font-bold text-slate-400 text-center">-</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Fruit -->
|
<!-- Fruit -->
|
||||||
{#if columnVisibility.devilFruitType !== false}
|
{#if columnVisibility.devilFruitType !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.devilFruitType === dailyCharacter.devilFruitType ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
<div
|
||||||
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.devilFruitType ===
|
||||||
|
dailyCharacter.devilFruitType
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||||
|
>
|
||||||
{#if character.devilFruitType}
|
{#if character.devilFruitType}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center">{character.devilFruitType}</p>
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||||
|
{character.devilFruitType}
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-2xl sm:text-3xl md:text-5xl font-bold text-white text-center">✕</p>
|
<p class="text-center text-2xl font-bold text-white sm:text-3xl md:text-5xl">
|
||||||
|
✕
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Haki -->
|
<!-- Haki -->
|
||||||
{#if columnVisibility.haki !== false}
|
{#if columnVisibility.haki !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {(() => {
|
<div
|
||||||
if (character.hakiObservation === dailyCharacter.hakiObservation && character.hakiArmament === dailyCharacter.hakiArmament && character.hakiConqueror === dailyCharacter.hakiConqueror) {
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {(() => {
|
||||||
|
if (
|
||||||
|
character.hakiObservation === dailyCharacter.hakiObservation &&
|
||||||
|
character.hakiArmament === dailyCharacter.hakiArmament &&
|
||||||
|
character.hakiConqueror === dailyCharacter.hakiConqueror
|
||||||
|
) {
|
||||||
return 'bg-emerald-600/90';
|
return 'bg-emerald-600/90';
|
||||||
} else if ((character.hakiObservation && dailyCharacter.hakiObservation) ||
|
} else if (
|
||||||
|
(character.hakiObservation && dailyCharacter.hakiObservation) ||
|
||||||
(character.hakiArmament && dailyCharacter.hakiArmament) ||
|
(character.hakiArmament && dailyCharacter.hakiArmament) ||
|
||||||
(character.hakiConqueror && dailyCharacter.hakiConqueror)) {
|
(character.hakiConqueror && dailyCharacter.hakiConqueror)
|
||||||
|
) {
|
||||||
return 'bg-yellow-600/80';
|
return 'bg-yellow-600/80';
|
||||||
} else {
|
} else {
|
||||||
return 'bg-red-900/60';
|
return 'bg-red-900/60';
|
||||||
}
|
}
|
||||||
})()} p-1 sm:p-2 flex items-center justify-center">
|
})()} flex items-center justify-center p-1 sm:p-2"
|
||||||
<p class="text-sm sm:text-lg md:text-2xl font-bold text-white text-center">
|
>
|
||||||
{#if character.hakiObservation}<span title="Haki de l'Observation">👁️</span>{/if}
|
<p class="text-center text-sm font-bold text-white sm:text-lg md:text-2xl">
|
||||||
{#if character.hakiArmament}<span title="Haki de l'Armement">🦾</span>{/if}
|
{#if character.hakiObservation}<span title={$t.game.components.guessHistory.obsHakiTitle}>👁️</span
|
||||||
{#if character.hakiConqueror}<span title="Haki des Rois">👑</span>{/if}
|
>{/if}
|
||||||
|
{#if character.hakiArmament}<span title={$t.game.components.guessHistory.armHakiTitle}>🦾</span>{/if}
|
||||||
|
{#if character.hakiConqueror}<span title={$t.game.components.guessHistory.kingHakiTitle}>👑</span>{/if}
|
||||||
{#if !character.hakiObservation && !character.hakiArmament && !character.hakiConqueror}
|
{#if !character.hakiObservation && !character.hakiArmament && !character.hakiConqueror}
|
||||||
<span class="text-2xl sm:text-3xl md:text-5xl">✕</span>
|
<span class="text-2xl sm:text-3xl md:text-5xl">✕</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -196,61 +348,145 @@
|
|||||||
|
|
||||||
<!-- Prime -->
|
<!-- Prime -->
|
||||||
{#if columnVisibility.bounty !== false}
|
{#if columnVisibility.bounty !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.bounty === dailyCharacter.bounty ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center relative overflow-hidden">
|
<div
|
||||||
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.bounty ===
|
||||||
|
dailyCharacter.bounty
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
||||||
|
>
|
||||||
{#if character.bounty != null && dailyCharacter.bounty != null && character.bounty !== dailyCharacter.bounty}
|
{#if character.bounty != null && dailyCharacter.bounty != null && character.bounty !== dailyCharacter.bounty}
|
||||||
<div class="absolute w-full h-full opacity-30 pointer-events-none" style="
|
<div
|
||||||
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
||||||
|
style="
|
||||||
background-color: rgb(203, 213, 225);
|
background-color: rgb(203, 213, 225);
|
||||||
clip-path: {character.bounty > dailyCharacter.bounty
|
clip-path: {character.bounty > dailyCharacter.bounty
|
||||||
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
||||||
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
||||||
"></div>
|
"
|
||||||
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if character.bounty != null}
|
{#if character.bounty != null}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center relative z-10">{formatBounty(character.bounty)} ฿</p>
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{formatBounty(character.bounty)} ฿
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center relative z-10">Inconnue</p>
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.unknown}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Taille -->
|
<!-- Taille -->
|
||||||
{#if columnVisibility.height !== false}
|
{#if columnVisibility.height !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.height === dailyCharacter.height ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center relative overflow-hidden">
|
<div
|
||||||
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.height ===
|
||||||
|
dailyCharacter.height
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
||||||
|
>
|
||||||
{#if character.height && dailyCharacter.height && character.height !== dailyCharacter.height}
|
{#if character.height && dailyCharacter.height && character.height !== dailyCharacter.height}
|
||||||
<div class="absolute w-full h-full opacity-30 pointer-events-none" style="
|
<div
|
||||||
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
||||||
|
style="
|
||||||
background-color: rgb(203, 213, 225);
|
background-color: rgb(203, 213, 225);
|
||||||
clip-path: {character.height > dailyCharacter.height
|
clip-path: {character.height > dailyCharacter.height
|
||||||
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
||||||
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
||||||
"></div>
|
"
|
||||||
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if character.height}
|
{#if character.height}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center relative z-10">{character.height} m</p>
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{character.height} m
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center relative z-10">Inconnue</p>
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.unknown}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Age -->
|
||||||
|
{#if columnVisibility.age !== false}
|
||||||
|
<div
|
||||||
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {character.age ===
|
||||||
|
dailyCharacter.age
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
{#if character.age != null && dailyCharacter.age != null && character.age !== dailyCharacter.age}
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
||||||
|
style="
|
||||||
|
background-color: rgb(203, 213, 225);
|
||||||
|
clip-path: {character.age > dailyCharacter.age
|
||||||
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
||||||
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
{/if}
|
||||||
|
{#if character.age != null}
|
||||||
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{character.age}
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{$t.game.components.guessHistory.unknown}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Origine -->
|
<!-- Origine -->
|
||||||
{#if columnVisibility.origin !== false}
|
{#if columnVisibility.origin !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.origin === dailyCharacter.origin ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center">
|
<div
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center">{character.origin || 'Inconnue'}</p>
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingOrigin(character, dailyCharacter)
|
||||||
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} flex items-center justify-center p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
<p class="text-center text-[10px] font-bold text-white sm:text-xs md:text-sm">
|
||||||
|
{getDisplayOrigin(character) || $t.game.components.guessHistory.unknown}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Arc -->
|
<!-- Arc -->
|
||||||
{#if columnVisibility.arc !== false}
|
{#if columnVisibility.arc !== false}
|
||||||
<div class="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 shrink-0 rounded-lg border border-white/10 {character.arcName === dailyCharacter.arcName ? 'bg-emerald-600/90' : 'bg-red-900/60'} p-1 sm:p-2 flex items-center justify-center relative overflow-hidden">
|
<div
|
||||||
{#if character.arcName !== dailyCharacter.arcName && character.firstAppearance && dailyCharacter.firstAppearance && character.firstAppearance !== dailyCharacter.firstAppearance}
|
class="h-16 w-16 shrink-0 rounded-lg border border-white/10 sm:h-20 sm:w-20 md:h-24 md:w-24 {hasMatchingArc(character, dailyCharacter)
|
||||||
<div class="absolute w-full h-full opacity-30 pointer-events-none" style="
|
? 'bg-emerald-600/90'
|
||||||
|
: 'bg-red-900/60'} relative flex items-center justify-center overflow-hidden p-1 sm:p-2"
|
||||||
|
>
|
||||||
|
{#if !hasMatchingArc(character, dailyCharacter) && character.firstAppearance && dailyCharacter.firstAppearance && character.firstAppearance !== dailyCharacter.firstAppearance}
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute h-full w-full opacity-30"
|
||||||
|
style="
|
||||||
background-color: rgb(203, 213, 225);
|
background-color: rgb(203, 213, 225);
|
||||||
clip-path: {character.firstAppearance > dailyCharacter.firstAppearance
|
clip-path: {character.firstAppearance > dailyCharacter.firstAppearance
|
||||||
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
? 'polygon(97% 60%,80% 60%,80% 5%,20% 5%,20% 60%,3% 60%,50% 95%)'
|
||||||
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
: 'polygon(97% 40%,80% 40%,80% 95%,20% 95%,20% 40%,3% 40%,50% 5%)'};
|
||||||
"></div>
|
"
|
||||||
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="text-[10px] sm:text-xs md:text-sm font-bold text-white text-center relative z-10">{character.arcName || 'Inconnu'}</p>
|
<p
|
||||||
|
class="relative z-10 text-center text-[10px] font-bold text-white sm:text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{getDisplayArcName(character) || $t.game.components.guessHistory.unknown}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let dailyCharacter: any;
|
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||||
export let selectedCharacters: any[];
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
|
export let dailyCharacter: CharacterWithRelations;
|
||||||
|
export let selectedCharacters: CharacterWithRelations[];
|
||||||
export let showOriginUnlock: boolean = false;
|
export let showOriginUnlock: boolean = false;
|
||||||
export let showFruitUnlock: boolean = false;
|
export let showFruitUnlock: boolean = false;
|
||||||
export let showAffiliationUnlock: boolean = false;
|
export let showAffiliationUnlock: boolean = false;
|
||||||
@@ -13,6 +16,15 @@
|
|||||||
$: isOriginAvailable = selectedCharacters.length >= 5;
|
$: isOriginAvailable = selectedCharacters.length >= 5;
|
||||||
$: isFruitAvailable = selectedCharacters.length >= 10;
|
$: isFruitAvailable = selectedCharacters.length >= 10;
|
||||||
$: isAffiliationAvailable = selectedCharacters.length >= 15;
|
$: isAffiliationAvailable = selectedCharacters.length >= 15;
|
||||||
|
$: isFrench = $language === 'fr';
|
||||||
|
|
||||||
|
function getDisplayOrigin(character: CharacterWithRelations): string | null {
|
||||||
|
if (isFrench && typeof character.frOrigin === 'string' && character.frOrigin.length > 0) {
|
||||||
|
return character.frOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.origin;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -42,13 +54,13 @@
|
|||||||
disabled={!isOriginAvailable}
|
disabled={!isOriginAvailable}
|
||||||
onclick={() => showHintOrigin = !showHintOrigin}
|
onclick={() => showHintOrigin = !showHintOrigin}
|
||||||
>
|
>
|
||||||
<p class="text-sm font-medium text-amber-100">Origine</p>
|
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.origin}</p>
|
||||||
{#if showHintOrigin}
|
{#if showHintOrigin}
|
||||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.origin || 'Inconnue'}</p>
|
<p class="mt-2 text-xs text-white font-semibold">{getDisplayOrigin(dailyCharacter) || $t.game.components.hints.unknown}</p>
|
||||||
{:else if Math.max(0, 5 - selectedCharacters.length) > 0}
|
{:else if Math.max(0, 5 - selectedCharacters.length) > 0}
|
||||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 5 - selectedCharacters.length)} essais avant déblocage</p>
|
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 5 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -57,13 +69,13 @@
|
|||||||
disabled={!isFruitAvailable}
|
disabled={!isFruitAvailable}
|
||||||
onclick={() => showHintFruit = !showHintFruit}
|
onclick={() => showHintFruit = !showHintFruit}
|
||||||
>
|
>
|
||||||
<p class="text-sm font-medium text-amber-100">Fruit du démon</p>
|
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.devilFruit}</p>
|
||||||
{#if showHintFruit}
|
{#if showHintFruit}
|
||||||
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || 'Aucun'}</p>
|
<p class="mt-2 text-xs text-white font-semibold">{dailyCharacter.devilFruitName || $t.game.components.hints.none}</p>
|
||||||
{:else if Math.max(0, 10 - selectedCharacters.length) > 0}
|
{:else if Math.max(0, 10 - selectedCharacters.length) > 0}
|
||||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 10 - selectedCharacters.length)} essais avant déblocage</p>
|
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 10 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -72,16 +84,13 @@
|
|||||||
disabled={!isAffiliationAvailable}
|
disabled={!isAffiliationAvailable}
|
||||||
onclick={() => showHintAffiliation = !showHintAffiliation}
|
onclick={() => showHintAffiliation = !showHintAffiliation}
|
||||||
>
|
>
|
||||||
<p class="text-sm font-medium text-amber-100">Affiliation</p>
|
<p class="text-sm font-medium text-amber-100">{$t.game.components.hints.affiliation}</p>
|
||||||
{#if showHintAffiliation}
|
{#if showHintAffiliation}
|
||||||
{@const affiliations = typeof dailyCharacter.affiliations === 'string'
|
<p class="mt-2 text-xs text-white font-semibold">{isFrench && dailyCharacter.frAffiliation ? dailyCharacter.frAffiliation : dailyCharacter.affiliation || $t.game.components.hints.unknown}</p>
|
||||||
? ((dailyCharacter.affiliations as string).includes('[') ? JSON.parse(dailyCharacter.affiliations) : (dailyCharacter.affiliations as string).split(',').map((a: string) => a.trim()))
|
|
||||||
: dailyCharacter.affiliations}
|
|
||||||
<p class="mt-2 text-xs text-white font-semibold">{Array.isArray(affiliations) ? affiliations[0] : affiliations || 'Inconnue'}</p>
|
|
||||||
{:else if Math.max(0, 15 - selectedCharacters.length) > 0}
|
{:else if Math.max(0, 15 - selectedCharacters.length) > 0}
|
||||||
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 15 - selectedCharacters.length)} essais avant déblocage</p>
|
<p class="mt-2 text-xs text-slate-400">{Math.max(0, 15 - selectedCharacters.length)} {$t.game.components.hints.beforeUnlock}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mt-2 text-xs text-slate-400">Indice disponible !</p>
|
<p class="mt-2 text-xs text-slate-400">{$t.game.components.hints.available}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
104
src/lib/components/LanguageSwitcher.svelte
Normal file
104
src/lib/components/LanguageSwitcher.svelte
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { availableLanguages, language, setLanguage } from '$lib/i18n';
|
||||||
|
|
||||||
|
let isOpen = false;
|
||||||
|
let rootElement: HTMLDivElement | undefined;
|
||||||
|
|
||||||
|
const languageLabels: Record<string, string> = {
|
||||||
|
en: 'English',
|
||||||
|
fr: 'Francais'
|
||||||
|
};
|
||||||
|
|
||||||
|
const languageFlags: Record<string, string> = {
|
||||||
|
en: 'GB',
|
||||||
|
fr: 'FR'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLanguageLabel(lang: string): string {
|
||||||
|
return languageLabels[lang] || lang.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlagCode(lang: string): string {
|
||||||
|
return languageFlags[lang] || 'UN';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFlagEmoji(code: string): string {
|
||||||
|
const normalized = code.toUpperCase();
|
||||||
|
if (normalized.length !== 2) {
|
||||||
|
return 'UN';
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = normalized.codePointAt(0);
|
||||||
|
const second = normalized.codePointAt(1);
|
||||||
|
if (!first || !second) {
|
||||||
|
return 'UN';
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.fromCodePoint(127397 + first, 127397 + second);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenu() {
|
||||||
|
isOpen = !isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectLanguage(lang: string) {
|
||||||
|
setLanguage(lang);
|
||||||
|
isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const onDocumentClick = (event: MouseEvent) => {
|
||||||
|
if (!rootElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rootElement.contains(event.target as Node)) {
|
||||||
|
isOpen = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('click', onDocumentClick);
|
||||||
|
return () => document.removeEventListener('click', onDocumentClick);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={rootElement} class="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={toggleMenu}
|
||||||
|
class="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-2 text-sm font-semibold text-slate-100 transition hover:border-amber-300/50 hover:bg-white/10"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
aria-label="Change language"
|
||||||
|
>
|
||||||
|
<span class="text-base" aria-hidden="true">{toFlagEmoji(getFlagCode($language))}</span>
|
||||||
|
<span class="uppercase text-xs tracking-wider">{$language}</span>
|
||||||
|
<svg
|
||||||
|
class="h-3.5 w-3.5 transition-transform {isOpen ? 'rotate-180' : ''}"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isOpen}
|
||||||
|
<div class="absolute right-0 top-full z-20 mt-2 w-44 rounded-xl border border-white/10 bg-slate-900/95 p-1 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||||
|
{#each availableLanguages as lang (lang)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => selectLanguage(lang)}
|
||||||
|
class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm transition {lang === $language ? 'bg-amber-300 text-slate-900' : 'text-slate-100 hover:bg-white/5'}"
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="text-base" aria-hidden="true">{toFlagEmoji(getFlagCode(lang))}</span>
|
||||||
|
<span>{getLanguageLabel(lang)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="text-xs uppercase tracking-wide opacity-70">{lang}</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { User } from 'better-auth/types';
|
import type { User } from 'better-auth/types';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: (User & { isAdmin?: boolean }) | null;
|
user: (User & { isAdmin?: boolean }) | null;
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
{user.name?.charAt(0).toUpperCase() || 'U'}
|
{user.name?.charAt(0).toUpperCase() || 'U'}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="max-w-[150px] truncate text-sm font-semibold text-slate-100">
|
<span class="max-w-37.5 truncate text-sm font-semibold text-slate-100">
|
||||||
{user.name || 'Utilisateur'}
|
{user.name || 'Utilisateur'}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
@@ -77,15 +78,15 @@
|
|||||||
class="absolute right-0 top-full mt-2 w-48 rounded-xl border border-white/10 bg-slate-900/95 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
class="absolute right-0 top-full mt-2 w-48 rounded-xl border border-white/10 bg-slate-900/95 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="/profile"
|
href={resolve("/profile")}
|
||||||
onclick={closeMenu}
|
onclick={closeMenu}
|
||||||
class="block border-b border-white/5 px-4 py-3 text-sm font-semibold text-slate-100 transition hover:bg-white/5 hover:text-amber-100 first:rounded-t-xl"
|
class="block border-b border-white/5 px-4 py-3 text-sm font-semibold text-slate-100 transition hover:bg-white/5 hover:text-amber-100 first:rounded-t-xl"
|
||||||
>
|
>
|
||||||
Voir mon profil
|
Voir mon profil
|
||||||
</a>
|
</a>
|
||||||
{#if (user as any).isAdmin}
|
{#if (user).isAdmin}
|
||||||
<a
|
<a
|
||||||
href="/admin"
|
href={resolve("/admin")}
|
||||||
onclick={closeMenu}
|
onclick={closeMenu}
|
||||||
class="block border-b border-white/5 px-4 py-3 text-sm font-semibold text-amber-300 transition hover:bg-white/5 hover:text-amber-200"
|
class="block border-b border-white/5 px-4 py-3 text-sm font-semibold text-amber-300 transition hover:bg-white/5 hover:text-amber-200"
|
||||||
>
|
>
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href="/login"
|
href={resolve("/login")}
|
||||||
class="rounded-full bg-amber-300 px-5 py-2.5 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
|
class="rounded-full bg-amber-300 px-5 py-2.5 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
Se connecter
|
Se connecter
|
||||||
|
|||||||
@@ -1,26 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let selectedCharacter: any;
|
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||||
export let selectedCharacters: any[];
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
|
export let selectedCharacter: CharacterWithRelations;
|
||||||
|
export let selectedCharacters: CharacterWithRelations[];
|
||||||
export let isGeckoMoriaWin: boolean = false;
|
export let isGeckoMoriaWin: boolean = false;
|
||||||
|
|
||||||
const oneTryMessages = ['Tricheur 👀', '1 essai ? Avoue, tu avais la réponse 😏', 'Premier coup direct... suspect 🤨'];
|
$: isFrench = $language === 'fr';
|
||||||
const twoTryMessages = ['Bien joué ! ⚡', 'Deux essais, propre ! 👏', 'Tu chauffes vite, bien joué 🔥'];
|
|
||||||
const tenPlusMessages = [
|
|
||||||
'${attempts} essais... même un escargophone aurait trouvé plus vite 📞',
|
|
||||||
'${attempts} tentatives ? Le Grand Line est moins long que ça 😵',
|
|
||||||
'${attempts} essais : performance légendaire... dans le mauvais sens 🫠'
|
|
||||||
];
|
|
||||||
const fivePlusMessages = [
|
|
||||||
"${attempts} essais ? On va dire que c'était pour le suspense 😅",
|
|
||||||
'Ça en fait des essais... mais au moins tu y es arrivé 😬',
|
|
||||||
'Tu ne lâches rien, même après plusieurs essais 😂'
|
|
||||||
];
|
|
||||||
const defaultMessages = ['Pas mal du tout !', 'Bien tenté, bon rythme 👍', 'Ça se passe bien, continue comme ça ✨'];
|
|
||||||
|
|
||||||
const pickMessage = (messages: string[]) => messages[Math.floor(Math.random() * messages.length)];
|
function getDisplayName(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
|
||||||
|
return character.frName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiUrl(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
|
||||||
|
return character.frUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.url || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiBaseUrl(): string {
|
||||||
|
return isFrench ? 'https://onepiece.fandom.com/fr/wiki/' : 'https://onepiece.fandom.com/wiki/';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickMessage = (messages: readonly string[]) => messages[Math.floor(Math.random() * messages.length)];
|
||||||
|
|
||||||
const getAttemptMessage = (attempts: number): string => {
|
const getAttemptMessage = (attempts: number): string => {
|
||||||
if (attempts <= 0) return '';
|
if (attempts <= 0) return '';
|
||||||
|
const oneTryMessages = $t.game.components.winPanel.oneTryMessages;
|
||||||
|
const twoTryMessages = $t.game.components.winPanel.twoTryMessages;
|
||||||
|
const tenPlusMessages = $t.game.components.winPanel.tenPlusMessages;
|
||||||
|
const fivePlusMessages = $t.game.components.winPanel.fivePlusMessages;
|
||||||
|
const defaultMessages = $t.game.components.winPanel.defaultMessages;
|
||||||
if (attempts === 1) {
|
if (attempts === 1) {
|
||||||
return pickMessage(oneTryMessages);
|
return pickMessage(oneTryMessages);
|
||||||
}
|
}
|
||||||
@@ -39,31 +55,34 @@
|
|||||||
|
|
||||||
$: attempts = selectedCharacters.length;
|
$: attempts = selectedCharacters.length;
|
||||||
$: attemptMessage = getAttemptMessage(attempts);
|
$: attemptMessage = getAttemptMessage(attempts);
|
||||||
|
$: attemptWord = selectedCharacters.length > 1
|
||||||
|
? $t.game.components.winPanel.attemptPlural
|
||||||
|
: $t.game.components.winPanel.attemptSingular;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isGeckoMoriaWin}
|
{#if isGeckoMoriaWin}
|
||||||
<div class="rounded-3xl border border-slate-700/80 bg-slate-950/80 p-4 shadow-[0_24px_60px_rgba(0,0,0,0.8)] backdrop-blur gecko-moria-effect">
|
<div class="rounded-3xl border border-slate-700/80 bg-slate-950/80 p-4 shadow-[0_24px_60px_rgba(0,0,0,0.8)] backdrop-blur gecko-moria-effect">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-3xl mb-2">🌑</div>
|
<div class="text-3xl mb-2">🌑</div>
|
||||||
<h2 class="text-xl font-bold text-slate-300 mb-1">Moria vous contrôle...</h2>
|
<h2 class="text-xl font-bold text-slate-300 mb-1">{$t.game.components.winPanel.moriaTitle}</h2>
|
||||||
<p class="text-sm text-slate-400">Vous avez succombé à l'ombre en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
|
<p class="text-sm text-slate-400">{$t.game.components.winPanel.moriaPrefix} {selectedCharacters.length} {attemptWord} !</p>
|
||||||
<p class="text-xs text-slate-300 mt-1">{attemptMessage}</p>
|
<p class="text-xs text-slate-300 mt-1">{attemptMessage}</p>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
{#if selectedCharacter.pictureUrl}
|
{#if selectedCharacter.pictureUrl}
|
||||||
<a
|
<a
|
||||||
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
|
href={getWikiBaseUrl() + getWikiUrl(selectedCharacter)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="inline-block"
|
class="inline-block"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={selectedCharacter.pictureUrl}
|
src={selectedCharacter.pictureUrl}
|
||||||
alt={selectedCharacter.name}
|
alt={getDisplayName(selectedCharacter)}
|
||||||
class="w-20 h-20 mx-auto rounded-full border-2 border-slate-600 shadow-lg object-cover hover:border-slate-500 transition-colors cursor-pointer opacity-80"
|
class="w-20 h-20 mx-auto rounded-full border-2 border-slate-600 shadow-lg object-cover hover:border-slate-500 transition-colors cursor-pointer opacity-80"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="mt-2 text-lg font-bold text-slate-200">{selectedCharacter.name}</p>
|
<p class="mt-2 text-lg font-bold text-slate-200">{getDisplayName(selectedCharacter)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,25 +90,25 @@
|
|||||||
<div class="rounded-3xl border border-emerald-500/50 bg-emerald-500/10 p-4 shadow-[0_24px_60px_rgba(16,185,129,0.3)] backdrop-blur">
|
<div class="rounded-3xl border border-emerald-500/50 bg-emerald-500/10 p-4 shadow-[0_24px_60px_rgba(16,185,129,0.3)] backdrop-blur">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-3xl mb-2">🎉</div>
|
<div class="text-3xl mb-2">🎉</div>
|
||||||
<h2 class="text-xl font-bold text-emerald-400 mb-1">Félicitations !</h2>
|
<h2 class="text-xl font-bold text-emerald-400 mb-1">{$t.game.components.winPanel.winTitle}</h2>
|
||||||
<p class="text-sm text-emerald-300">Vous avez trouvé le personnage en {selectedCharacters.length} {selectedCharacters.length > 1 ? 'tentatives' : 'tentative'} !</p>
|
<p class="text-sm text-emerald-300">{$t.game.components.winPanel.winPrefix} {selectedCharacters.length} {attemptWord} !</p>
|
||||||
<p class="text-xs text-emerald-200 mt-1">{attemptMessage}</p>
|
<p class="text-xs text-emerald-200 mt-1">{attemptMessage}</p>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
{#if selectedCharacter.pictureUrl}
|
{#if selectedCharacter.pictureUrl}
|
||||||
<a
|
<a
|
||||||
href={"https://onepiece.fandom.com/fr/wiki/" + selectedCharacter.url}
|
href={getWikiBaseUrl() + getWikiUrl(selectedCharacter)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="inline-block"
|
class="inline-block"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={selectedCharacter.pictureUrl}
|
src={selectedCharacter.pictureUrl}
|
||||||
alt={selectedCharacter.name}
|
alt={getDisplayName(selectedCharacter)}
|
||||||
class="w-20 h-20 mx-auto rounded-full border-2 border-emerald-400 shadow-lg object-cover hover:border-emerald-300 transition-colors cursor-pointer"
|
class="w-20 h-20 mx-auto rounded-full border-2 border-emerald-400 shadow-lg object-cover hover:border-emerald-300 transition-colors cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="mt-2 text-lg font-bold text-white">{selectedCharacter.name}</p>
|
<p class="mt-2 text-lg font-bold text-white">{getDisplayName(selectedCharacter)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,57 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let yesterdayCharacter: any;
|
import type { CharacterWithRelations } from "$lib/server/daily-character";
|
||||||
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
|
export let yesterdayCharacter: CharacterWithRelations | null;
|
||||||
|
|
||||||
|
$: isFrench = $language === 'fr';
|
||||||
|
|
||||||
|
function parseEpithets(value: unknown): string[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (value.length > 0) {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayName(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frName === 'string' && character.frName.length > 0) {
|
||||||
|
return character.frName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayEpithets(character: CharacterWithRelations): string[] {
|
||||||
|
const frenchEpithets = parseEpithets(character.frEpithets);
|
||||||
|
if (isFrench && frenchEpithets.length > 0) {
|
||||||
|
return frenchEpithets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseEpithets(character.epithets);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiUrl(character: CharacterWithRelations): string {
|
||||||
|
if (isFrench && typeof character.frUrl === 'string' && character.frUrl.length > 0) {
|
||||||
|
return character.frUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.url || '';
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<section class="mt-8 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||||
@@ -8,43 +60,52 @@
|
|||||||
{#if yesterdayCharacter.pictureUrl}
|
{#if yesterdayCharacter.pictureUrl}
|
||||||
<img
|
<img
|
||||||
src={yesterdayCharacter.pictureUrl}
|
src={yesterdayCharacter.pictureUrl}
|
||||||
alt={yesterdayCharacter.name}
|
alt={getDisplayName(yesterdayCharacter)}
|
||||||
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
|
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||||
Photo
|
{$t.game.components.yesterdayCharacter.photo}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.components.yesterdayCharacter.title}</p>
|
||||||
<p class="mt-2 text-lg font-semibold text-white">{yesterdayCharacter.name}</p>
|
<p class="mt-2 text-lg font-semibold text-white">{getDisplayName(yesterdayCharacter)}</p>
|
||||||
{#if yesterdayCharacter.epithets}
|
{#if getDisplayEpithets(yesterdayCharacter).length > 0}
|
||||||
<p class="mt-1 text-sm text-slate-400">
|
<p class="mt-1 text-sm text-slate-400">
|
||||||
{typeof yesterdayCharacter.epithets === 'string'
|
{getDisplayEpithets(yesterdayCharacter).join(', ')}
|
||||||
? JSON.parse(yesterdayCharacter.epithets).join(', ')
|
|
||||||
: (yesterdayCharacter.epithets as string[]).join(', ')}
|
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if isFrench}
|
||||||
<a
|
<a
|
||||||
href={"https://onepiece.fandom.com/fr/wiki/" + yesterdayCharacter.url}
|
href="https://onepiece.fandom.com/fr/wiki/{getWikiUrl(yesterdayCharacter)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
||||||
>
|
>
|
||||||
Voir la page
|
{$t.game.components.yesterdayCharacter.openPage}
|
||||||
</a>
|
</a>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href="https://onepiece.fandom.com/wiki/{getWikiUrl(yesterdayCharacter)}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
||||||
|
>
|
||||||
|
{$t.game.components.yesterdayCharacter.openPage}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
||||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||||
Photo
|
{$t.game.components.yesterdayCharacter.photo}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.components.yesterdayCharacter.title}</p>
|
||||||
<p class="mt-2 text-lg font-semibold text-white">Aucun personnage</p>
|
<p class="mt-2 text-lg font-semibold text-white">{$t.game.components.yesterdayCharacter.none}</p>
|
||||||
<p class="mt-1 text-sm text-slate-200">Aucun personnage d'hier disponible</p>
|
<p class="mt-1 text-sm text-slate-200">{$t.game.components.yesterdayCharacter.noneAvailable}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
233
src/lib/i18n/en.json
Normal file
233
src/lib/i18n/en.json
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"language": "Language",
|
||||||
|
"selectLanguage": "Select Language",
|
||||||
|
"english": "English",
|
||||||
|
"french": "Français",
|
||||||
|
"german": "Deutsch",
|
||||||
|
"spanish": "Español"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"home": {
|
||||||
|
"heroDescription": "Guess the character from pirate crews, marines, or the wider world. Every hint brings you closer to the treasure.",
|
||||||
|
"dailyTitle": "Daily Character",
|
||||||
|
"dailySubtitle": "A new mystery every 24 hours",
|
||||||
|
"dailyDescription": "Compare your guesses, unlock hints, and keep your streak alive.",
|
||||||
|
"dailyCta": "Start",
|
||||||
|
"infiniteTitle": "Infinite Mode",
|
||||||
|
"infiniteSubtitle": "Endless challenges",
|
||||||
|
"infiniteDescription": "Chain characters and chase your score. No limits, only fun.",
|
||||||
|
"infiniteCta": "Play",
|
||||||
|
"photoFallback": "Photo",
|
||||||
|
"yesterdayCharacter": "Yesterday's character",
|
||||||
|
"openPage": "Open page",
|
||||||
|
"noCharacter": "No character",
|
||||||
|
"noYesterdayCharacter": "No character from yesterday available"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"titleSignUp": "Sign Up",
|
||||||
|
"titleSignIn": "Sign In",
|
||||||
|
"headerSignUp": "Create your account",
|
||||||
|
"headerSignIn": "Welcome, pirate",
|
||||||
|
"nameLabel": "Name",
|
||||||
|
"namePlaceholder": "Your name",
|
||||||
|
"usernameLabel": "Username",
|
||||||
|
"usernamePlaceholder": "e.g. luffy_gear5",
|
||||||
|
"identifierLabelSignUp": "Email",
|
||||||
|
"identifierLabelSignIn": "Email or username",
|
||||||
|
"identifierPlaceholderSignUp": "yourmail@email.com",
|
||||||
|
"identifierPlaceholderSignIn": "yourmail@email.com or luffy_gear5",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"confirmPasswordLabel": "Confirm password",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"submitSignUp": "Create an account",
|
||||||
|
"submitSignIn": "Log in",
|
||||||
|
"togglePromptSignUp": "Already have an account?",
|
||||||
|
"togglePromptSignIn": "Don't have an account?",
|
||||||
|
"toggleActionSignUp": "Log in",
|
||||||
|
"toggleActionSignIn": "Sign up",
|
||||||
|
"backHome": "Back to home"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"pageTitle": "My Profile",
|
||||||
|
"headerTitle": "My Profile",
|
||||||
|
"headerSubtitle": "Edit your profile information",
|
||||||
|
"tabProfile": "Profile",
|
||||||
|
"tabPassword": "Password",
|
||||||
|
"tabDaily": "Daily History",
|
||||||
|
"tabSessions": "Sessions",
|
||||||
|
"tabFriends": "Friends",
|
||||||
|
"avatarFallbackAlt": "Profile",
|
||||||
|
"email": "Email",
|
||||||
|
"displayName": "Display name",
|
||||||
|
"displayNamePlaceholder": "Your name",
|
||||||
|
"profileUpdateSuccess": "Profile updated successfully!",
|
||||||
|
"updating": "Updating...",
|
||||||
|
"saveChanges": "Save changes",
|
||||||
|
"friendsTitle": "Friends System",
|
||||||
|
"addFriendByUsername": "Add a friend by username",
|
||||||
|
"friendUsernamePlaceholder": "e.g. luffy_gear5",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"send": "Send",
|
||||||
|
"incomingRequests": "Incoming requests",
|
||||||
|
"noIncomingRequests": "No incoming requests.",
|
||||||
|
"accept": "Accept",
|
||||||
|
"decline": "Decline",
|
||||||
|
"outgoingRequests": "Outgoing requests",
|
||||||
|
"noOutgoingRequests": "No outgoing requests.",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"myFriends": "My friends",
|
||||||
|
"noFriends": "You don't have any friends yet.",
|
||||||
|
"remove": "Remove",
|
||||||
|
"changePasswordTitle": "Change password",
|
||||||
|
"currentPassword": "Current password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmPassword": "Confirm password",
|
||||||
|
"passwordChangeSuccess": "Password changed successfully!",
|
||||||
|
"changing": "Changing...",
|
||||||
|
"changePassword": "Change password",
|
||||||
|
"dailyHistoryTitle": "Daily history",
|
||||||
|
"noDailyHistory": "No history available",
|
||||||
|
"triedCharactersTitle": "Tried characters",
|
||||||
|
"noTriedCharacters": "No characters recorded",
|
||||||
|
"noImage": "N/A",
|
||||||
|
"trySingular": "try",
|
||||||
|
"tryPlural": "tries",
|
||||||
|
"activeSessionsTitle": "Active sessions",
|
||||||
|
"noActiveSessions": "No active session",
|
||||||
|
"unknownDevice": "Unknown device",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"ip": "IP",
|
||||||
|
"created": "Created",
|
||||||
|
"terminate": "Terminate",
|
||||||
|
"backHome": "Back to home"
|
||||||
|
},
|
||||||
|
"daily": {
|
||||||
|
"metaTitle": "OnePieceDle - Daily Mode",
|
||||||
|
"title": "Daily Character",
|
||||||
|
"winsPeopleSingular": "person",
|
||||||
|
"winsPeoplePlural": "people",
|
||||||
|
"winsVerbSingular": "has",
|
||||||
|
"winsVerbPlural": "have",
|
||||||
|
"winsSuffix": "found it today 🎉",
|
||||||
|
"reset": "Play again",
|
||||||
|
"description": "Guess the character. Each hint unlocks after a certain number of guesses. Good luck!",
|
||||||
|
"friendsToday": "Your friends today",
|
||||||
|
"friendsTriedCharacters": "Tried characters",
|
||||||
|
"friendsNoTriedCharacters": "No characters recorded",
|
||||||
|
"friendTrySingular": "try",
|
||||||
|
"friendTryPlural": "tries"
|
||||||
|
},
|
||||||
|
"infinite": {
|
||||||
|
"metaTitle": "OnePieceDle - Infinite Mode",
|
||||||
|
"title": "Infinite Mode",
|
||||||
|
"score": "Score",
|
||||||
|
"resetScore": "Reset",
|
||||||
|
"description": "Guess characters endlessly. Each hint unlocks after a certain number of guesses. Good luck!",
|
||||||
|
"nextCharacter": "Play again",
|
||||||
|
"revealAnswer": "Reveal answer",
|
||||||
|
"loadingCharacter": "Loading character...",
|
||||||
|
"filtersTitle": "Character filters",
|
||||||
|
"clearFilters": "Reset",
|
||||||
|
"filterGender": "Gender",
|
||||||
|
"filterStatus": "Status",
|
||||||
|
"filterAbilities": "Abilities",
|
||||||
|
"filterInformation": "Information",
|
||||||
|
"filterArcs": "Arcs",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"alive": "Alive",
|
||||||
|
"dead": "Dead",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"hasHaki": "Has Haki",
|
||||||
|
"fruitAll": "Fruit (All)",
|
||||||
|
"withFruit": "With Fruit",
|
||||||
|
"withoutFruit": "Without Fruit",
|
||||||
|
"heightDefined": "Height defined",
|
||||||
|
"ageDefined": "Age defined",
|
||||||
|
"originDefined": "Origin defined",
|
||||||
|
"availableCharactersSingular": "character available",
|
||||||
|
"availableCharactersPlural": "characters available",
|
||||||
|
"columnsTitle": "Columns"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"searchInput": {
|
||||||
|
"title": "Enter a guess",
|
||||||
|
"placeholder": "Character name",
|
||||||
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"origin": "Origin",
|
||||||
|
"devilFruit": "Devil fruit",
|
||||||
|
"affiliation": "Affiliation",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"none": "None",
|
||||||
|
"beforeUnlock": "guesses before unlock",
|
||||||
|
"available": "Hint available!"
|
||||||
|
},
|
||||||
|
"guessHistory": {
|
||||||
|
"title": "History",
|
||||||
|
"empty": "No guesses yet.",
|
||||||
|
"character": "Character",
|
||||||
|
"status": "Status",
|
||||||
|
"gender": "Gender",
|
||||||
|
"affiliations": "Affiliations",
|
||||||
|
"fruit": "Fruit",
|
||||||
|
"haki": "Haki",
|
||||||
|
"bounty": "Bounty",
|
||||||
|
"height": "Height",
|
||||||
|
"age": "Age",
|
||||||
|
"origin": "Origin",
|
||||||
|
"arc": "Arc",
|
||||||
|
"alive": "Alive",
|
||||||
|
"dead": "Dead",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"obsHakiTitle": "Observation Haki",
|
||||||
|
"armHakiTitle": "Armament Haki",
|
||||||
|
"kingHakiTitle": "Conqueror's Haki"
|
||||||
|
},
|
||||||
|
"winPanel": {
|
||||||
|
"attemptSingular": "attempt",
|
||||||
|
"attemptPlural": "attempts",
|
||||||
|
"moriaTitle": "Moria controls you...",
|
||||||
|
"moriaPrefix": "You succumbed to the shadows in",
|
||||||
|
"winTitle": "Congratulations!",
|
||||||
|
"winPrefix": "You found the character in",
|
||||||
|
"oneTryMessages": [
|
||||||
|
"Cheater 👀",
|
||||||
|
"1 guess? Admit it, you already knew 😏",
|
||||||
|
"First try... suspicious 🤨"
|
||||||
|
],
|
||||||
|
"twoTryMessages": [
|
||||||
|
"Well played! ⚡",
|
||||||
|
"Two guesses, clean! 👏",
|
||||||
|
"You warmed up fast, nice 🔥"
|
||||||
|
],
|
||||||
|
"tenPlusMessages": [
|
||||||
|
"${attempts} guesses... even a transponder snail would be faster 📞",
|
||||||
|
"${attempts} attempts? The Grand Line is shorter than that 😵",
|
||||||
|
"${attempts} guesses: legendary performance... in the wrong direction 🫠"
|
||||||
|
],
|
||||||
|
"fivePlusMessages": [
|
||||||
|
"${attempts} guesses? Let's say it was for suspense 😅",
|
||||||
|
"That is a lot of guesses... but you made it 😬",
|
||||||
|
"You never give up, even after several guesses 😂"
|
||||||
|
],
|
||||||
|
"defaultMessages": [
|
||||||
|
"Not bad at all!",
|
||||||
|
"Nice try, good pace 👍",
|
||||||
|
"Things are going well, keep it up ✨"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"yesterdayCharacter": {
|
||||||
|
"photo": "Photo",
|
||||||
|
"title": "Yesterday's character",
|
||||||
|
"openPage": "Open page",
|
||||||
|
"none": "No character",
|
||||||
|
"noneAvailable": "No character from yesterday available"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
233
src/lib/i18n/fr.json
Normal file
233
src/lib/i18n/fr.json
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"language": "Langue",
|
||||||
|
"selectLanguage": "Sélectionnez la Langue",
|
||||||
|
"english": "English",
|
||||||
|
"french": "Français",
|
||||||
|
"german": "Deutsch",
|
||||||
|
"spanish": "Español"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"home": {
|
||||||
|
"heroDescription": "Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.",
|
||||||
|
"dailyTitle": "Personnage du jour",
|
||||||
|
"dailySubtitle": "Nouveau mystere toutes les 24 heures",
|
||||||
|
"dailyDescription": "Compare tes essais, debloque des indices et garde ta serie.",
|
||||||
|
"dailyCta": "Commencer",
|
||||||
|
"infiniteTitle": "Mode Infini",
|
||||||
|
"infiniteSubtitle": "Des defis sans fin",
|
||||||
|
"infiniteDescription": "Enchaine les personnages et croise ton score. Pas de limite, que du plaisir.",
|
||||||
|
"infiniteCta": "Jouer",
|
||||||
|
"photoFallback": "Photo",
|
||||||
|
"yesterdayCharacter": "Personnage d'hier",
|
||||||
|
"openPage": "Voir la page",
|
||||||
|
"noCharacter": "Aucun personnage",
|
||||||
|
"noYesterdayCharacter": "Aucun personnage d'hier disponible"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"titleSignUp": "Inscription",
|
||||||
|
"titleSignIn": "Connexion",
|
||||||
|
"headerSignUp": "Creer votre compte",
|
||||||
|
"headerSignIn": "Bienvenue, pirate",
|
||||||
|
"nameLabel": "Nom",
|
||||||
|
"namePlaceholder": "Votre nom",
|
||||||
|
"usernameLabel": "Nom d'utilisateur",
|
||||||
|
"usernamePlaceholder": "ex: luffy_gear5",
|
||||||
|
"identifierLabelSignUp": "E-mail",
|
||||||
|
"identifierLabelSignIn": "E-mail ou nom d'utilisateur",
|
||||||
|
"identifierPlaceholderSignUp": "votremail@email.com",
|
||||||
|
"identifierPlaceholderSignIn": "votremail@email.com ou luffy_gear5",
|
||||||
|
"passwordLabel": "Mot de passe",
|
||||||
|
"confirmPasswordLabel": "Confirmer le mot de passe",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"submitSignUp": "Creer un compte",
|
||||||
|
"submitSignIn": "Se connecter",
|
||||||
|
"togglePromptSignUp": "Vous avez deja un compte ?",
|
||||||
|
"togglePromptSignIn": "Vous n'avez pas de compte ?",
|
||||||
|
"toggleActionSignUp": "Se connecter",
|
||||||
|
"toggleActionSignIn": "S'inscrire",
|
||||||
|
"backHome": "Retour a l'accueil"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"pageTitle": "Mon Profil",
|
||||||
|
"headerTitle": "Mon Profil",
|
||||||
|
"headerSubtitle": "Modifie les informations de ton profil",
|
||||||
|
"tabProfile": "Profil",
|
||||||
|
"tabPassword": "Mot de passe",
|
||||||
|
"tabDaily": "Historique Daily",
|
||||||
|
"tabSessions": "Sessions",
|
||||||
|
"tabFriends": "Amis",
|
||||||
|
"avatarFallbackAlt": "Profil",
|
||||||
|
"email": "Email",
|
||||||
|
"displayName": "Nom d'affichage",
|
||||||
|
"displayNamePlaceholder": "Ton nom",
|
||||||
|
"profileUpdateSuccess": "Profil mis a jour avec succes !",
|
||||||
|
"updating": "Mise a jour...",
|
||||||
|
"saveChanges": "Enregistrer les modifications",
|
||||||
|
"friendsTitle": "Systeme d'amis",
|
||||||
|
"addFriendByUsername": "Ajouter un ami par nom d'utilisateur",
|
||||||
|
"friendUsernamePlaceholder": "ex: luffy_gear5",
|
||||||
|
"sending": "Envoi...",
|
||||||
|
"send": "Envoyer",
|
||||||
|
"incomingRequests": "Demandes recues",
|
||||||
|
"noIncomingRequests": "Aucune demande recue.",
|
||||||
|
"accept": "Accepter",
|
||||||
|
"decline": "Refuser",
|
||||||
|
"outgoingRequests": "Demandes envoyees",
|
||||||
|
"noOutgoingRequests": "Aucune demande envoyee.",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"myFriends": "Mes amis",
|
||||||
|
"noFriends": "Tu n'as pas encore d'amis.",
|
||||||
|
"remove": "Supprimer",
|
||||||
|
"changePasswordTitle": "Changer le mot de passe",
|
||||||
|
"currentPassword": "Mot de passe actuel",
|
||||||
|
"newPassword": "Nouveau mot de passe",
|
||||||
|
"confirmPassword": "Confirmer le mot de passe",
|
||||||
|
"passwordChangeSuccess": "Mot de passe change avec succes !",
|
||||||
|
"changing": "Changement en cours...",
|
||||||
|
"changePassword": "Changer le mot de passe",
|
||||||
|
"dailyHistoryTitle": "Historique des Daily",
|
||||||
|
"noDailyHistory": "Aucun historique disponible",
|
||||||
|
"triedCharactersTitle": "Personnages essayes",
|
||||||
|
"noTriedCharacters": "Aucun personnage enregistre",
|
||||||
|
"noImage": "N/A",
|
||||||
|
"trySingular": "tentative",
|
||||||
|
"tryPlural": "tentatives",
|
||||||
|
"activeSessionsTitle": "Sessions actives",
|
||||||
|
"noActiveSessions": "Aucune session active",
|
||||||
|
"unknownDevice": "Appareil inconnu",
|
||||||
|
"unknown": "Inconnue",
|
||||||
|
"ip": "IP",
|
||||||
|
"created": "Creee",
|
||||||
|
"terminate": "Terminer",
|
||||||
|
"backHome": "Retour a l'accueil"
|
||||||
|
},
|
||||||
|
"daily": {
|
||||||
|
"metaTitle": "OnePieceDle - Mode du jour",
|
||||||
|
"title": "Personnage du jour",
|
||||||
|
"winsPeopleSingular": "personne",
|
||||||
|
"winsPeoplePlural": "personnes",
|
||||||
|
"winsVerbSingular": "a",
|
||||||
|
"winsVerbPlural": "ont",
|
||||||
|
"winsSuffix": "trouve aujourd'hui 🎉",
|
||||||
|
"reset": "Recommencer",
|
||||||
|
"description": "Devine le personnage. Chaque indice se debloque apres un certain nombre de tentatives. Bonne chance !",
|
||||||
|
"friendsToday": "Tes amis aujourd'hui",
|
||||||
|
"friendsTriedCharacters": "Personnages essayes",
|
||||||
|
"friendsNoTriedCharacters": "Aucun personnage enregistre",
|
||||||
|
"friendTrySingular": "coup",
|
||||||
|
"friendTryPlural": "coups"
|
||||||
|
},
|
||||||
|
"infinite": {
|
||||||
|
"metaTitle": "OnePieceDle - Mode Infini",
|
||||||
|
"title": "Mode Infini",
|
||||||
|
"score": "Score",
|
||||||
|
"resetScore": "Reinitialiser",
|
||||||
|
"description": "Devine des personnages a l'infini ! Chaque indice se debloque apres un certain nombre de tentatives. Bonne chance !",
|
||||||
|
"nextCharacter": "Recommencer",
|
||||||
|
"revealAnswer": "Reveler la reponse",
|
||||||
|
"loadingCharacter": "Chargement du personnage...",
|
||||||
|
"filtersTitle": "Filtres de personnages",
|
||||||
|
"clearFilters": "Reinitialiser",
|
||||||
|
"filterGender": "Genre",
|
||||||
|
"filterStatus": "Statut",
|
||||||
|
"filterAbilities": "Capacites",
|
||||||
|
"filterInformation": "Informations",
|
||||||
|
"filterArcs": "Arcs",
|
||||||
|
"male": "Homme",
|
||||||
|
"female": "Femme",
|
||||||
|
"alive": "Vivant",
|
||||||
|
"dead": "Mort",
|
||||||
|
"unknown": "Inconnu",
|
||||||
|
"hasHaki": "A du Haki",
|
||||||
|
"fruitAll": "Fruit (Tous)",
|
||||||
|
"withFruit": "Avec Fruit",
|
||||||
|
"withoutFruit": "Sans Fruit",
|
||||||
|
"heightDefined": "Taille definie",
|
||||||
|
"ageDefined": "Age defini",
|
||||||
|
"originDefined": "Origine definie",
|
||||||
|
"availableCharactersSingular": "personnage disponible",
|
||||||
|
"availableCharactersPlural": "personnages disponibles",
|
||||||
|
"columnsTitle": "Colonnes"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"searchInput": {
|
||||||
|
"title": "Entrer une supposition",
|
||||||
|
"placeholder": "Nom du personnage",
|
||||||
|
"submit": "Valider"
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"origin": "Origine",
|
||||||
|
"devilFruit": "Fruit du demon",
|
||||||
|
"affiliation": "Affiliation",
|
||||||
|
"unknown": "Inconnue",
|
||||||
|
"none": "Aucun",
|
||||||
|
"beforeUnlock": "essais avant deblocage",
|
||||||
|
"available": "Indice disponible !"
|
||||||
|
},
|
||||||
|
"guessHistory": {
|
||||||
|
"title": "Historique",
|
||||||
|
"empty": "Aucune tentative pour le moment.",
|
||||||
|
"character": "Personnage",
|
||||||
|
"status": "Statut",
|
||||||
|
"gender": "Genre",
|
||||||
|
"affiliations": "Affiliations",
|
||||||
|
"fruit": "Fruit",
|
||||||
|
"haki": "Haki",
|
||||||
|
"bounty": "Prime",
|
||||||
|
"height": "Taille",
|
||||||
|
"age": "Age",
|
||||||
|
"origin": "Origine",
|
||||||
|
"arc": "Arc",
|
||||||
|
"alive": "Vivant",
|
||||||
|
"dead": "Mort",
|
||||||
|
"unknown": "Inconnu",
|
||||||
|
"male": "Homme",
|
||||||
|
"female": "Femme",
|
||||||
|
"obsHakiTitle": "Haki de l'Observation",
|
||||||
|
"armHakiTitle": "Haki de l'Armement",
|
||||||
|
"kingHakiTitle": "Haki des Rois"
|
||||||
|
},
|
||||||
|
"winPanel": {
|
||||||
|
"attemptSingular": "tentative",
|
||||||
|
"attemptPlural": "tentatives",
|
||||||
|
"moriaTitle": "Moria vous controle...",
|
||||||
|
"moriaPrefix": "Vous avez succombe a l'ombre en",
|
||||||
|
"winTitle": "Felicitations !",
|
||||||
|
"winPrefix": "Vous avez trouve le personnage en",
|
||||||
|
"oneTryMessages": [
|
||||||
|
"Tricheur 👀",
|
||||||
|
"1 essai ? Avoue, tu avais la reponse 😏",
|
||||||
|
"Premier coup direct... suspect 🤨"
|
||||||
|
],
|
||||||
|
"twoTryMessages": [
|
||||||
|
"Bien joue ! ⚡",
|
||||||
|
"Deux essais, propre ! 👏",
|
||||||
|
"Tu chauffes vite, bien joue 🔥"
|
||||||
|
],
|
||||||
|
"tenPlusMessages": [
|
||||||
|
"${attempts} essais... meme un escargophone aurait trouve plus vite 📞",
|
||||||
|
"${attempts} tentatives ? Le Grand Line est moins long que ca 😵",
|
||||||
|
"${attempts} essais : performance legendaire... dans le mauvais sens 🫠"
|
||||||
|
],
|
||||||
|
"fivePlusMessages": [
|
||||||
|
"${attempts} essais ? On va dire que c'etait pour le suspense 😅",
|
||||||
|
"Ca en fait des essais... mais au moins tu y es arrive 😬",
|
||||||
|
"Tu ne laches rien, meme apres plusieurs essais 😂"
|
||||||
|
],
|
||||||
|
"defaultMessages": [
|
||||||
|
"Pas mal du tout !",
|
||||||
|
"Bien tente, bon rythme 👍",
|
||||||
|
"Ca se passe bien, continue comme ca ✨"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"yesterdayCharacter": {
|
||||||
|
"photo": "Photo",
|
||||||
|
"title": "Personnage d'hier",
|
||||||
|
"openPage": "Voir la page",
|
||||||
|
"none": "Aucun personnage",
|
||||||
|
"noneAvailable": "Aucun personnage d'hier disponible"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/lib/i18n/index.ts
Normal file
51
src/lib/i18n/index.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { writable, derived } from 'svelte/store';
|
||||||
|
import type { Writable, Readable } from 'svelte/store';
|
||||||
|
|
||||||
|
import en from './en.json';
|
||||||
|
import fr from './fr.json';
|
||||||
|
|
||||||
|
type Messages = typeof en;
|
||||||
|
|
||||||
|
const translations: Record<string, Messages> = { en, fr };
|
||||||
|
|
||||||
|
// Get initial language
|
||||||
|
function getInitialLanguage(): string {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const stored = localStorage.getItem('language');
|
||||||
|
if (stored && stored in translations) {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
const browserLang = navigator.language.split('-')[0];
|
||||||
|
if (browserLang in translations) {
|
||||||
|
return browserLang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create writable store for the current language
|
||||||
|
export const language: Writable<string> = writable(getInitialLanguage());
|
||||||
|
|
||||||
|
// Create derived store for the current messages
|
||||||
|
export const t: Readable<Messages> = derived(language, ($language) => {
|
||||||
|
return translations[$language] || translations['en'];
|
||||||
|
});
|
||||||
|
|
||||||
|
export function setLanguage(lang: string) {
|
||||||
|
if (lang in translations) {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('language', lang);
|
||||||
|
}
|
||||||
|
language.set(lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLanguage(): string {
|
||||||
|
let currentLang = 'en';
|
||||||
|
language.subscribe((lang) => {
|
||||||
|
currentLang = lang;
|
||||||
|
})();
|
||||||
|
return currentLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const availableLanguages = Object.keys(translations);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { arc, character, characterHistory, characterOverride, devilFruit } from '$lib/server/db/schema';
|
import { arc, character, characterHistory, devilFruit, type Character } from '$lib/server/db/schema';
|
||||||
import { desc, eq, inArray, and } from 'drizzle-orm';
|
import { desc, eq, and } from 'drizzle-orm';
|
||||||
|
|
||||||
// Generate or get random seed for daily character selection
|
// Generate or get random seed for daily character selection
|
||||||
const RANDOM_SEED = Math.random();
|
const RANDOM_SEED = Math.random();
|
||||||
@@ -8,9 +8,11 @@ const RANDOM_SEED = Math.random();
|
|||||||
const characterWithRelationsSelect = {
|
const characterWithRelationsSelect = {
|
||||||
id: character.id,
|
id: character.id,
|
||||||
name: character.name,
|
name: character.name,
|
||||||
|
frName: character.frName,
|
||||||
gender: character.gender,
|
gender: character.gender,
|
||||||
age: character.age,
|
age: character.age,
|
||||||
affiliations: character.affiliations,
|
affiliation: character.affiliation,
|
||||||
|
frAffiliation: character.frAffiliation,
|
||||||
devilFruitId: character.devilFruitId,
|
devilFruitId: character.devilFruitId,
|
||||||
devilFruitName: devilFruit.name,
|
devilFruitName: devilFruit.name,
|
||||||
devilFruitType: devilFruit.type,
|
devilFruitType: devilFruit.type,
|
||||||
@@ -20,23 +22,26 @@ const characterWithRelationsSelect = {
|
|||||||
bounty: character.bounty,
|
bounty: character.bounty,
|
||||||
height: character.height,
|
height: character.height,
|
||||||
origin: character.origin,
|
origin: character.origin,
|
||||||
|
frOrigin: character.frOrigin,
|
||||||
firstAppearance: character.firstAppearance,
|
firstAppearance: character.firstAppearance,
|
||||||
pictureUrl: character.pictureUrl,
|
pictureUrl: character.pictureUrl,
|
||||||
epithets: character.epithets,
|
epithets: character.epithets,
|
||||||
|
frEpithets: character.frEpithets,
|
||||||
status: character.status,
|
status: character.status,
|
||||||
url: character.url,
|
url: character.url,
|
||||||
|
frUrl: character.frUrl,
|
||||||
arcId: character.arcId,
|
arcId: character.arcId,
|
||||||
arcName: arc.name
|
arcName: arc.name,
|
||||||
|
frArcName: arc.frName,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CharacterWithRelations = typeof character.$inferSelect & {
|
export type CharacterWithRelations = Character & {
|
||||||
devilFruitName: string | null;
|
devilFruitName: string | null;
|
||||||
devilFruitType: string | null;
|
devilFruitType: string | null;
|
||||||
arcName: string | null;
|
arcName: string | null;
|
||||||
|
frArcName: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CharacterOverrideRow = typeof characterOverride.$inferSelect;
|
|
||||||
|
|
||||||
type RelationMaps = {
|
type RelationMaps = {
|
||||||
arcNameById: Map<string, string | null>;
|
arcNameById: Map<string, string | null>;
|
||||||
devilFruitById: Map<string, { name: string | null; type: string | null }>;
|
devilFruitById: Map<string, { name: string | null; type: string | null }>;
|
||||||
@@ -46,102 +51,6 @@ function isNotNullish<T>(value: T | null | undefined): value is T {
|
|||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeCharacterWithOverride(
|
|
||||||
baseCharacter: CharacterWithRelations,
|
|
||||||
overrideRow?: CharacterOverrideRow,
|
|
||||||
relationMaps?: RelationMaps
|
|
||||||
): CharacterWithRelations {
|
|
||||||
if (!overrideRow) {
|
|
||||||
return baseCharacter;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedCharacter = { ...baseCharacter } as CharacterWithRelations;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(overrideRow)) {
|
|
||||||
if (key === 'characterId' || key === 'notes') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotNullish(value)) {
|
|
||||||
(mergedCharacter as Record<string, unknown>)[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relationMaps) {
|
|
||||||
if (mergedCharacter.arcId) {
|
|
||||||
mergedCharacter.arcName = relationMaps.arcNameById.get(mergedCharacter.arcId) ?? null;
|
|
||||||
} else {
|
|
||||||
mergedCharacter.arcName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedCharacter.devilFruitId) {
|
|
||||||
const devilFruitData = relationMaps.devilFruitById.get(mergedCharacter.devilFruitId);
|
|
||||||
mergedCharacter.devilFruitName = devilFruitData?.name ?? null;
|
|
||||||
mergedCharacter.devilFruitType = devilFruitData?.type ?? null;
|
|
||||||
} else {
|
|
||||||
mergedCharacter.devilFruitName = null;
|
|
||||||
mergedCharacter.devilFruitType = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergedCharacter;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function applyCharacterOverrides(
|
|
||||||
characters: CharacterWithRelations[]
|
|
||||||
): Promise<CharacterWithRelations[]> {
|
|
||||||
if (characters.length === 0) {
|
|
||||||
return characters;
|
|
||||||
}
|
|
||||||
|
|
||||||
const characterIds = characters.map((currentCharacter) => currentCharacter.id);
|
|
||||||
const overrideRows = await db
|
|
||||||
.select()
|
|
||||||
.from(characterOverride)
|
|
||||||
.where(inArray(characterOverride.characterId, characterIds));
|
|
||||||
|
|
||||||
if (overrideRows.length === 0) {
|
|
||||||
return characters;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overrideByCharacterId = new Map<string, CharacterOverrideRow>(
|
|
||||||
overrideRows.map((overrideRow) => [overrideRow.characterId, overrideRow])
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldRefreshRelations = overrideRows.some(
|
|
||||||
(overrideRow) => isNotNullish(overrideRow.arcId) || isNotNullish(overrideRow.devilFruitId)
|
|
||||||
);
|
|
||||||
|
|
||||||
let relationMaps: RelationMaps | undefined;
|
|
||||||
|
|
||||||
if (shouldRefreshRelations) {
|
|
||||||
const [allArcs, allDevilFruits] = await Promise.all([
|
|
||||||
db.select({ id: arc.id, name: arc.name }).from(arc),
|
|
||||||
db
|
|
||||||
.select({ id: devilFruit.id, name: devilFruit.name, type: devilFruit.type })
|
|
||||||
.from(devilFruit)
|
|
||||||
]);
|
|
||||||
|
|
||||||
relationMaps = {
|
|
||||||
arcNameById: new Map(allArcs.map((currentArc) => [currentArc.id, currentArc.name])),
|
|
||||||
devilFruitById: new Map(
|
|
||||||
allDevilFruits.map((currentDevilFruit) => [
|
|
||||||
currentDevilFruit.id,
|
|
||||||
{ name: currentDevilFruit.name, type: currentDevilFruit.type }
|
|
||||||
])
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return characters.map((currentCharacter) =>
|
|
||||||
mergeCharacterWithOverride(
|
|
||||||
currentCharacter,
|
|
||||||
overrideByCharacterId.get(currentCharacter.id),
|
|
||||||
relationMaps
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDateKey(date: Date): number {
|
export function getDateKey(date: Date): number {
|
||||||
return normalizeDay(date).getTime();
|
return normalizeDay(date).getTime();
|
||||||
}
|
}
|
||||||
@@ -161,26 +70,22 @@ function pickDailyCharacter(characters: CharacterWithRelations[], date: Date): C
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getDailyModeCharacters(): Promise<CharacterWithRelations[]> {
|
export async function getDailyModeCharacters(): Promise<CharacterWithRelations[]> {
|
||||||
const characters = (await db
|
return (await db
|
||||||
.select(characterWithRelationsSelect)
|
.select(characterWithRelationsSelect)
|
||||||
.from(character)
|
.from(character)
|
||||||
.leftJoin(arc, eq(character.arcId, arc.id))
|
.leftJoin(arc, eq(character.arcId, arc.id))
|
||||||
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
||||||
.where(eq(character.isInDailyMode, true))
|
.where(eq(character.isInDailyMode, true))
|
||||||
.all()) as CharacterWithRelations[];
|
.all()) as CharacterWithRelations[];
|
||||||
|
|
||||||
return applyCharacterOverrides(characters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllCharacters(): Promise<CharacterWithRelations[]> {
|
export async function getAllCharacters(): Promise<CharacterWithRelations[]> {
|
||||||
const characters = (await db
|
return (await db
|
||||||
.select(characterWithRelationsSelect)
|
.select(characterWithRelationsSelect)
|
||||||
.from(character)
|
.from(character)
|
||||||
.leftJoin(arc, eq(character.arcId, arc.id))
|
.leftJoin(arc, eq(character.arcId, arc.id))
|
||||||
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
||||||
.all()) as CharacterWithRelations[];
|
.all()) as CharacterWithRelations[];
|
||||||
|
|
||||||
return applyCharacterOverrides(characters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCharacterById(characterId: string): Promise<CharacterWithRelations | null> {
|
export async function getCharacterById(characterId: string): Promise<CharacterWithRelations | null> {
|
||||||
@@ -196,8 +101,7 @@ export async function getCharacterById(characterId: string): Promise<CharacterWi
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [overriddenCharacter] = await applyCharacterOverrides([found as CharacterWithRelations]);
|
return found as CharacterWithRelations
|
||||||
return overriddenCharacter ?? null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrCreateTodayCharacter(
|
export async function getOrCreateTodayCharacter(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { integer, sqliteTable, text, real, unique } from 'drizzle-orm/sqlite-core';
|
import { integer, sqliteTable, text, real, unique } from 'drizzle-orm/sqlite-core';
|
||||||
import { user } from './auth.schema';
|
import { user } from './auth.schema';
|
||||||
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
|
|
||||||
// Define devil fruit types
|
// Define devil fruit types
|
||||||
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Smile' | 'Unknown';
|
export type DevilFruitType = 'Paramecia' | 'Zoan' | 'Logia' | 'Smile' | 'Unknown';
|
||||||
@@ -17,129 +18,132 @@ export const config = sqliteTable('config', {
|
|||||||
export const arc = sqliteTable('arc', {
|
export const arc = sqliteTable('arc', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
startChapter: integer('startChapter').notNull(),
|
frName: text('fr_name'),
|
||||||
endChapter: integer('endChapter'),
|
startChapter: integer('start_chapter').notNull(),
|
||||||
|
endChapter: integer('end_chapter'),
|
||||||
url: text('url')
|
url: text('url')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type Arc = InferSelectModel<typeof arc>;
|
||||||
|
|
||||||
// Define the devil fruit table schema
|
// Define the devil fruit table schema
|
||||||
export const devilFruit = sqliteTable('devilFruit', {
|
export const devilFruit = sqliteTable('devil_fruit', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
name: text('name').notNull().unique(),
|
name: text('name').notNull().unique(),
|
||||||
type: text('type').$type<DevilFruitType>(),
|
type: text('type').$type<DevilFruitType>(),
|
||||||
url: text('url')
|
url: text('url')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type DevilFruit = InferSelectModel<typeof devilFruit>;
|
||||||
|
|
||||||
// Define the character table schema
|
// Define the character table schema
|
||||||
export const character = sqliteTable('character', {
|
export const character = sqliteTable('character', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
|
frName: text('fr_name'),
|
||||||
gender: text('gender'),
|
gender: text('gender'),
|
||||||
age: integer('age'),
|
age: integer('age'),
|
||||||
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
|
affiliation: text('affiliation'),
|
||||||
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
|
frAffiliation: text('fr_affiliation'),
|
||||||
hakiObservation: integer('hakiObservation', { mode: 'boolean' }).default(false),
|
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id),
|
||||||
hakiArmament: integer('hakiArmament', { mode: 'boolean' }).default(false),
|
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
|
||||||
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }).default(false),
|
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
|
||||||
|
hakiConqueror: integer('haki_conqueror', { mode: 'boolean' }).default(false),
|
||||||
bounty: integer('bounty').default(0),
|
bounty: integer('bounty').default(0),
|
||||||
height: real('height'),
|
height: real('height'),
|
||||||
origin: text('origin'),
|
origin: text('origin'),
|
||||||
firstAppearance: integer('firstAppearance').notNull(),
|
frOrigin: text('fr_origin'),
|
||||||
pictureUrl: text('pictureUrl'),
|
firstAppearance: integer('first_appearance').notNull(),
|
||||||
|
pictureUrl: text('picture_url'),
|
||||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
||||||
|
frEpithets: text('fr_epithets', { mode: 'json' }).$type<string[]>(),
|
||||||
status: text('status').$type<Status | null>(),
|
status: text('status').$type<Status | null>(),
|
||||||
arcId: text('arcId').references(() => arc.id),
|
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
|
||||||
url: text('url'),
|
url: text('url'),
|
||||||
isInDailyMode: integer('isInDailyMode', { mode: 'boolean' }).default(false)
|
frUrl: text('fr_url'),
|
||||||
|
isInDailyMode: integer('is_in_daily_mode', { mode: 'boolean' }).default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the character override table schema
|
export type Character = InferSelectModel<typeof character>;
|
||||||
export const characterOverride = sqliteTable('characterOverride', {
|
|
||||||
characterId: text('characterId').primaryKey().references(() => character.id),
|
|
||||||
name: text('name'),
|
|
||||||
gender: text('gender'),
|
|
||||||
age: integer('age'),
|
|
||||||
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
|
|
||||||
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
|
|
||||||
hakiObservation: integer('hakiObservation', { mode: 'boolean' }),
|
|
||||||
hakiArmament: integer('hakiArmament', { mode: 'boolean' }),
|
|
||||||
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }),
|
|
||||||
bounty: integer('bounty'),
|
|
||||||
height: real('height'),
|
|
||||||
origin: text('origin'),
|
|
||||||
firstAppearance: integer('firstAppearance'),
|
|
||||||
pictureUrl: text('pictureUrl'),
|
|
||||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
|
||||||
status: text('status').$type<Status | null>(),
|
|
||||||
arcId: text('arcId').references(() => arc.id),
|
|
||||||
url: text('url'),
|
|
||||||
notes: text('notes')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Define the character scrape validation table schema
|
// Define the character scrape validation table schema
|
||||||
export const characterScrapeValidation = sqliteTable('characterScrapeValidation', {
|
export const characterScrapeValidation = sqliteTable('character_scrape_validation', {
|
||||||
id: text('id').primaryKey(),
|
id: text('id').primaryKey(),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
|
frName: text('fr_name'),
|
||||||
gender: text('gender'),
|
gender: text('gender'),
|
||||||
age: integer('age'),
|
age: integer('age'),
|
||||||
affiliations: text('affiliations', { mode: 'json' }).$type<string[]>(),
|
affiliation: text('affiliation'),
|
||||||
devilFruitId: text('devilFruitId').references(() => devilFruit.id),
|
frAffiliation: text('fr_affiliation'),
|
||||||
hakiObservation: integer('hakiObservation', { mode: 'boolean' }).default(false),
|
devilFruitId: text('devil_fruit_id').references(() => devilFruit.id, { onDelete: 'set null' }),
|
||||||
hakiArmament: integer('hakiArmament', { mode: 'boolean' }).default(false),
|
hakiObservation: integer('haki_observation', { mode: 'boolean' }).default(false),
|
||||||
hakiConqueror: integer('hakiConqueror', { mode: 'boolean' }).default(false),
|
hakiArmament: integer('haki_armament', { mode: 'boolean' }).default(false),
|
||||||
|
hakiConqueror: integer('haki_conqueror', { mode: 'boolean' }).default(false),
|
||||||
bounty: integer('bounty'),
|
bounty: integer('bounty'),
|
||||||
height: real('height'),
|
height: real('height'),
|
||||||
origin: text('origin'),
|
origin: text('origin'),
|
||||||
firstAppearance: integer('firstAppearance').notNull(),
|
frOrigin: text('fr_origin'),
|
||||||
pictureUrl: text('pictureUrl'),
|
firstAppearance: integer('first_appearance').notNull(),
|
||||||
|
pictureUrl: text('picture_url'),
|
||||||
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
|
||||||
|
frEpithets: text('fr_epithets', { mode: 'json' }).$type<string[]>(),
|
||||||
status: text('status').$type<Status | null>(),
|
status: text('status').$type<Status | null>(),
|
||||||
arcId: text('arcId').references(() => arc.id),
|
arcId: text('arc_id').references(() => arc.id, { onDelete: 'set null' }),
|
||||||
url: text('url')
|
url: text('url'),
|
||||||
|
frUrl: text('fr_url'),
|
||||||
|
isDeleted: integer('is_deleted', { mode: 'boolean' }).default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define the caracter history table schema
|
export type CharacterScrapeValidation = InferSelectModel<typeof characterScrapeValidation>;
|
||||||
export const characterHistory = sqliteTable('characterHistory', {
|
|
||||||
|
// Define the character history table schema
|
||||||
|
export const characterHistory = sqliteTable('character_history', {
|
||||||
id: text('id')
|
id: text('id')
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => crypto.randomUUID()),
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
characterId: text('characterId').references(() => character.id),
|
characterId: text('character_id').references(() => character.id, { onDelete: 'cascade' }),
|
||||||
date: integer('date').notNull().unique(),
|
date: integer('date').notNull().unique(),
|
||||||
won: integer('won').notNull().default(0),
|
won: integer('won').notNull().default(0),
|
||||||
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
|
createdAt: integer('created_at').notNull().$default(() => Date.now()),
|
||||||
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
|
updatedAt: integer('updated_at').notNull().$default(() => Date.now()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type CharacterHistory = InferSelectModel<typeof characterHistory>;
|
||||||
|
|
||||||
// Define the user character history table schema
|
// Define the user character history table schema
|
||||||
export const userCharacterHistory = sqliteTable('userCharacterHistory', {
|
export const userCharacterHistory = sqliteTable('user_character_history', {
|
||||||
id: text('id')
|
id: text('id')
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => crypto.randomUUID()),
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
userId: text('userId').references(() => user.id),
|
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
|
||||||
characterHistoryId: text('characterHistoryId').references(() => characterHistory.id),
|
characterHistoryId: text('character_history_id').references(() => characterHistory.id, { onDelete: 'cascade' }),
|
||||||
tryCount: integer('tryCount').notNull(),
|
tryCount: integer('try_count').notNull(),
|
||||||
createdAt: integer('createdAt').notNull().$default(() => Date.now())
|
triedCharacterIds: text('tried_character_ids', { mode: 'json' }).$type<string[]>(),
|
||||||
|
createdAt: integer('created_at').notNull().$default(() => Date.now())
|
||||||
}, (table) => [
|
}, (table) => [
|
||||||
unique().on(table.userId, table.characterHistoryId)
|
unique().on(table.userId, table.characterHistoryId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export type UserCharacterHistory = InferSelectModel<typeof userCharacterHistory>;
|
||||||
|
|
||||||
// Define the friendship table schema (friend requests + accepted friends)
|
// Define the friendship table schema (friend requests + accepted friends)
|
||||||
export const friendship = sqliteTable('friendship', {
|
export const friendship = sqliteTable('friendship', {
|
||||||
id: text('id')
|
id: text('id')
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => crypto.randomUUID()),
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
requesterId: text('requesterId')
|
requesterId: text('requester_id')
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, { onDelete: 'cascade' }),
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
addresseeId: text('addresseeId')
|
addresseeId: text('addressee_id')
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, { onDelete: 'cascade' }),
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
status: text('status').$type<FriendshipStatus>().notNull().default('pending'),
|
status: text('status').$type<FriendshipStatus>().notNull().default('pending'),
|
||||||
createdAt: integer('createdAt').notNull().$default(() => Date.now()),
|
createdAt: integer('created_at').notNull().$default(() => Date.now()),
|
||||||
updatedAt: integer('updatedAt').notNull().$default(() => Date.now()),
|
updatedAt: integer('updated_at').notNull().$default(() => Date.now()),
|
||||||
}, (table) => [
|
}, (table) => [
|
||||||
unique().on(table.requesterId, table.addresseeId)
|
unique().on(table.requesterId, table.addresseeId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export type Friendship = InferSelectModel<typeof friendship>;
|
||||||
|
|
||||||
export * from './auth.schema';
|
export * from './auth.schema';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import ProfileButton from '$lib/components/ProfileButton.svelte';
|
import ProfileButton from '$lib/components/ProfileButton.svelte';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
|
|
||||||
@@ -29,9 +30,9 @@
|
|||||||
<h2 class="text-lg font-black uppercase tracking-[0.15em] text-amber-50">Admin</h2>
|
<h2 class="text-lg font-black uppercase tracking-[0.15em] text-amber-50">Admin</h2>
|
||||||
</div>
|
</div>
|
||||||
<nav class="flex-1 space-y-2 px-3">
|
<nav class="flex-1 space-y-2 px-3">
|
||||||
{#each navItems as item}
|
{#each navItems as item (item.label)}
|
||||||
<a
|
<a
|
||||||
href={item.href}
|
href={resolve(item.href)}
|
||||||
class={`flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium transition-colors ${
|
class={`flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium transition-colors ${
|
||||||
isActive(item.href, $page.url.pathname)
|
isActive(item.href, $page.url.pathname)
|
||||||
? 'bg-amber-600 text-white'
|
? 'bg-amber-600 text-white'
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="border-t border-white/5 p-3">
|
<div class="border-t border-white/5 p-3">
|
||||||
<a
|
<a
|
||||||
href="/"
|
href={resolve('/')}
|
||||||
class="flex items-center gap-2 rounded-lg px-4 py-3 text-sm font-medium text-gray-300 transition-colors hover:bg-slate-800 hover:text-white"
|
class="flex items-center gap-2 rounded-lg px-4 py-3 text-sm font-medium text-gray-300 transition-colors hover:bg-slate-800 hover:text-white"
|
||||||
title="Return to site"
|
title="Return to site"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { db } from '$lib/server/db';
|
|||||||
import { character, devilFruit, arc, user } from '$lib/server/db/schema';
|
import { character, devilFruit, arc, user } from '$lib/server/db/schema';
|
||||||
import { getOrCreateTodayCharacter, getTodayCharacterWinsCount } from '$lib/server/daily-character';
|
import { getOrCreateTodayCharacter, getTodayCharacterWinsCount } from '$lib/server/daily-character';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { count, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const [characters, devilFruits, arcs, users] = await Promise.all([
|
const [totalCharacters, totalDevilFruits, totalArcs, totalUsers, adminUsers, charactersInDaily] = await Promise.all([
|
||||||
db.select().from(character),
|
db.select({ count: count() }).from(character),
|
||||||
db.select().from(devilFruit),
|
db.select({ count: count() }).from(devilFruit),
|
||||||
db.select().from(arc),
|
db.select({ count: count() }).from(arc),
|
||||||
db.select().from(user)
|
db.select({ count: count() }).from(user),
|
||||||
|
db.select({ count: count() }).from(user).where(eq(user.isAdmin, true)),
|
||||||
|
db.select({ count: count() }).from(character).where(eq(character.isInDailyMode, true))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Get today's daily character and count wins
|
// Get today's daily character and count wins
|
||||||
@@ -21,12 +24,12 @@ export const load: PageServerLoad = async () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
stats: {
|
stats: {
|
||||||
totalCharacters: characters.length,
|
totalCharacters: totalCharacters[0].count,
|
||||||
charactersInDaily: characters.filter((c) => c.isInDailyMode).length,
|
charactersInDaily: charactersInDaily[0].count,
|
||||||
totalDevilFruits: devilFruits.length,
|
totalDevilFruits: totalDevilFruits[0].count,
|
||||||
totalArcs: arcs.length,
|
totalArcs: totalArcs[0].count,
|
||||||
totalUsers: users.length,
|
totalUsers: totalUsers[0].count,
|
||||||
adminUsers: users.filter((u) => u.isAdmin).length,
|
adminUsers: adminUsers[0].count,
|
||||||
dailyCharacterWins
|
dailyCharacterWins
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const EXEC_OPTIONS = {
|
|||||||
maxBuffer: 50 * 1024 * 1024
|
maxBuffer: 50 * 1024 * 1024
|
||||||
};
|
};
|
||||||
|
|
||||||
async function upsertCharacterFromScrapeValidation(characterId: string): Promise<boolean> {
|
async function applyCharacterChangeFromScrapeValidation(characterId: string): Promise<boolean> {
|
||||||
const [scraped] = await db
|
const [scraped] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(characterScrapeValidation)
|
.from(characterScrapeValidation)
|
||||||
@@ -21,14 +21,21 @@ async function upsertCharacterFromScrapeValidation(characterId: string): Promise
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scraped.isDeleted) {
|
||||||
|
await db.delete(character).where(eq(character.id, characterId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.insert(character)
|
.insert(character)
|
||||||
.values({
|
.values({
|
||||||
id: scraped.id,
|
id: scraped.id,
|
||||||
name: scraped.name,
|
name: scraped.name,
|
||||||
|
frName: scraped.frName,
|
||||||
gender: scraped.gender,
|
gender: scraped.gender,
|
||||||
age: scraped.age,
|
age: scraped.age,
|
||||||
affiliations: scraped.affiliations,
|
affiliation: scraped.affiliation,
|
||||||
|
frAffiliation: scraped.frAffiliation,
|
||||||
devilFruitId: scraped.devilFruitId,
|
devilFruitId: scraped.devilFruitId,
|
||||||
hakiObservation: scraped.hakiObservation,
|
hakiObservation: scraped.hakiObservation,
|
||||||
hakiArmament: scraped.hakiArmament,
|
hakiArmament: scraped.hakiArmament,
|
||||||
@@ -36,20 +43,25 @@ async function upsertCharacterFromScrapeValidation(characterId: string): Promise
|
|||||||
bounty: scraped.bounty,
|
bounty: scraped.bounty,
|
||||||
height: scraped.height,
|
height: scraped.height,
|
||||||
origin: scraped.origin,
|
origin: scraped.origin,
|
||||||
|
frOrigin: scraped.frOrigin,
|
||||||
firstAppearance: scraped.firstAppearance,
|
firstAppearance: scraped.firstAppearance,
|
||||||
pictureUrl: scraped.pictureUrl,
|
pictureUrl: scraped.pictureUrl,
|
||||||
epithets: scraped.epithets,
|
epithets: scraped.epithets,
|
||||||
|
frEpithets: scraped.frEpithets,
|
||||||
status: scraped.status,
|
status: scraped.status,
|
||||||
arcId: scraped.arcId,
|
arcId: scraped.arcId,
|
||||||
url: scraped.url
|
url: scraped.url,
|
||||||
|
frUrl: scraped.frUrl,
|
||||||
})
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: character.id,
|
target: character.id,
|
||||||
set: {
|
set: {
|
||||||
name: scraped.name,
|
name: scraped.name,
|
||||||
|
frName: scraped.frName,
|
||||||
gender: scraped.gender,
|
gender: scraped.gender,
|
||||||
age: scraped.age,
|
age: scraped.age,
|
||||||
affiliations: scraped.affiliations,
|
affiliation: scraped.affiliation,
|
||||||
|
frAffiliation: scraped.frAffiliation,
|
||||||
devilFruitId: scraped.devilFruitId,
|
devilFruitId: scraped.devilFruitId,
|
||||||
hakiObservation: scraped.hakiObservation,
|
hakiObservation: scraped.hakiObservation,
|
||||||
hakiArmament: scraped.hakiArmament,
|
hakiArmament: scraped.hakiArmament,
|
||||||
@@ -57,12 +69,15 @@ async function upsertCharacterFromScrapeValidation(characterId: string): Promise
|
|||||||
bounty: scraped.bounty,
|
bounty: scraped.bounty,
|
||||||
height: scraped.height,
|
height: scraped.height,
|
||||||
origin: scraped.origin,
|
origin: scraped.origin,
|
||||||
|
frOrigin: scraped.frOrigin,
|
||||||
firstAppearance: scraped.firstAppearance,
|
firstAppearance: scraped.firstAppearance,
|
||||||
pictureUrl: scraped.pictureUrl,
|
pictureUrl: scraped.pictureUrl,
|
||||||
epithets: scraped.epithets,
|
epithets: scraped.epithets,
|
||||||
|
frEpithets: scraped.frEpithets,
|
||||||
status: scraped.status,
|
status: scraped.status,
|
||||||
arcId: scraped.arcId,
|
arcId: scraped.arcId,
|
||||||
url: scraped.url
|
url: scraped.url,
|
||||||
|
frUrl: scraped.frUrl
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +94,7 @@ export async function load() {
|
|||||||
|
|
||||||
// Compare and categorize changes
|
// Compare and categorize changes
|
||||||
const changes: {
|
const changes: {
|
||||||
type: 'new' | 'modified';
|
type: 'new' | 'modified' | 'deleted';
|
||||||
id: string;
|
id: string;
|
||||||
scraped: (typeof scrapedCharacters)[0];
|
scraped: (typeof scrapedCharacters)[0];
|
||||||
current?: (typeof currentCharacters)[0];
|
current?: (typeof currentCharacters)[0];
|
||||||
@@ -89,6 +104,18 @@ export async function load() {
|
|||||||
for (const scraped of scrapedCharacters) {
|
for (const scraped of scrapedCharacters) {
|
||||||
const current = currentCharMap.get(scraped.id);
|
const current = currentCharMap.get(scraped.id);
|
||||||
|
|
||||||
|
if (scraped.isDeleted) {
|
||||||
|
if (current) {
|
||||||
|
changes.push({
|
||||||
|
type: 'deleted',
|
||||||
|
id: scraped.id,
|
||||||
|
scraped,
|
||||||
|
current
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
// New character
|
// New character
|
||||||
changes.push({
|
changes.push({
|
||||||
@@ -101,9 +128,11 @@ export async function load() {
|
|||||||
const differences: Record<string, { current: any; scraped: any }> = {};
|
const differences: Record<string, { current: any; scraped: any }> = {};
|
||||||
const fieldsToCompare = [
|
const fieldsToCompare = [
|
||||||
'name',
|
'name',
|
||||||
|
'frName',
|
||||||
'gender',
|
'gender',
|
||||||
'age',
|
'age',
|
||||||
'affiliations',
|
'affiliation',
|
||||||
|
'frAffiliation',
|
||||||
'devilFruitId',
|
'devilFruitId',
|
||||||
'hakiObservation',
|
'hakiObservation',
|
||||||
'hakiArmament',
|
'hakiArmament',
|
||||||
@@ -111,12 +140,15 @@ export async function load() {
|
|||||||
'bounty',
|
'bounty',
|
||||||
'height',
|
'height',
|
||||||
'origin',
|
'origin',
|
||||||
|
'frOrigin',
|
||||||
'firstAppearance',
|
'firstAppearance',
|
||||||
'pictureUrl',
|
'pictureUrl',
|
||||||
'epithets',
|
'epithets',
|
||||||
|
'frEpithets',
|
||||||
'status',
|
'status',
|
||||||
'arcId',
|
'arcId',
|
||||||
'url'
|
'url',
|
||||||
|
'frUrl'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const field of fieldsToCompare) {
|
for (const field of fieldsToCompare) {
|
||||||
@@ -144,11 +176,16 @@ export async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typeOrder: Record<'new' | 'modified' | 'deleted', number> = {
|
||||||
|
new: 0,
|
||||||
|
modified: 1,
|
||||||
|
deleted: 2
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
changes: changes.sort((a, b) => {
|
changes: changes.sort((a, b) => {
|
||||||
// Show 'new' first, then 'modified'
|
|
||||||
if (a.type !== b.type) {
|
if (a.type !== b.type) {
|
||||||
return a.type === 'new' ? -1 : 1;
|
return typeOrder[a.type] - typeOrder[b.type];
|
||||||
}
|
}
|
||||||
return a.id.localeCompare(b.id);
|
return a.id.localeCompare(b.id);
|
||||||
})
|
})
|
||||||
@@ -209,10 +246,10 @@ export const actions = {
|
|||||||
return { success: false, message: 'characterId is required' };
|
return { success: false, message: 'characterId is required' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const applied = await upsertCharacterFromScrapeValidation(characterId);
|
const applied = await applyCharacterChangeFromScrapeValidation(characterId);
|
||||||
return {
|
return {
|
||||||
success: applied,
|
success: applied,
|
||||||
message: applied ? 'Character applied successfully' : 'Character not found in scrape validation table'
|
message: applied ? 'Character change applied successfully' : 'Character not found in scrape validation table'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -221,7 +258,7 @@ export const actions = {
|
|||||||
let appliedCount = 0;
|
let appliedCount = 0;
|
||||||
|
|
||||||
for (const scraped of scrapedCharacters) {
|
for (const scraped of scrapedCharacters) {
|
||||||
const applied = await upsertCharacterFromScrapeValidation(scraped.id);
|
const applied = await applyCharacterChangeFromScrapeValidation(scraped.id);
|
||||||
if (applied) {
|
if (applied) {
|
||||||
appliedCount++;
|
appliedCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
type CharacterLike = {
|
||||||
|
name: string;
|
||||||
|
pictureUrl?: string | null;
|
||||||
|
url?: string | null;
|
||||||
|
status?: string | null;
|
||||||
|
gender?: string | null;
|
||||||
|
age?: number | null;
|
||||||
|
bounty?: number | null;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CharacterChange = {
|
||||||
|
type: 'new' | 'modified' | 'deleted';
|
||||||
|
id: string;
|
||||||
|
scraped: CharacterLike;
|
||||||
|
current?: CharacterLike;
|
||||||
|
differences?: Record<string, { current: unknown; scraped: unknown }>;
|
||||||
|
};
|
||||||
|
|
||||||
let { data, form } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
const newCharacters = $derived(data.changes.filter((c: any) => c.type === 'new'));
|
const newCharacters = $derived((data.changes as CharacterChange[]).filter((c) => c.type === 'new'));
|
||||||
const modifiedCharacters = $derived(data.changes.filter((c: any) => c.type === 'modified'));
|
const modifiedCharacters = $derived((data.changes as CharacterChange[]).filter((c) => c.type === 'modified'));
|
||||||
|
const deletedCharacters = $derived((data.changes as CharacterChange[]).filter((c) => c.type === 'deleted'));
|
||||||
|
|
||||||
function fandomUrl(path: string | null | undefined): string {
|
function formatValue(value: unknown): 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) {
|
if (value === null || value === undefined) {
|
||||||
return '—';
|
return '—';
|
||||||
}
|
}
|
||||||
@@ -23,13 +36,6 @@
|
|||||||
}
|
}
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDifferenceColor(current: any, scraped: any): string {
|
|
||||||
if (JSON.stringify(current) === JSON.stringify(scraped)) {
|
|
||||||
return 'text-gray-400';
|
|
||||||
}
|
|
||||||
return 'text-amber-300';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -39,7 +45,7 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 mb-2">Character Changes</h1>
|
<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>
|
<p class="text-gray-400">Total changes: {newCharacters.length} new, {modifiedCharacters.length} modified, {deletedCharacters.length} deleted</p>
|
||||||
<form method="POST" action="?/runScrapeImport" class="mt-4">
|
<form method="POST" action="?/runScrapeImport" class="mt-4">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -56,7 +62,7 @@
|
|||||||
{#if form?.logs}
|
{#if form?.logs}
|
||||||
<pre class="mt-3 max-h-72 overflow-auto rounded-lg border border-white/10 bg-slate-900/70 p-3 text-xs text-slate-200 whitespace-pre-wrap">{form.logs}</pre>
|
<pre class="mt-3 max-h-72 overflow-auto rounded-lg border border-white/10 bg-slate-900/70 p-3 text-xs text-slate-200 whitespace-pre-wrap">{form.logs}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
{#if newCharacters.length + modifiedCharacters.length > 0}
|
{#if newCharacters.length + modifiedCharacters.length + deletedCharacters.length > 0}
|
||||||
<form method="POST" action="?/acceptAll" class="mt-4">
|
<form method="POST" action="?/acceptAll" class="mt-4">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -80,9 +86,9 @@
|
|||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
{#if change.scraped.pictureUrl}
|
{#if change.scraped.pictureUrl}
|
||||||
<a href={fandomUrl(change.scraped.url)} target="_blank" rel="noopener noreferrer">
|
<a href="https://onepiece.fandom.com/fr/wiki/{change.scraped.url}" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
src={change.scraped.pictureUrl}
|
src={change.scraped.pictureUrl ?? undefined}
|
||||||
alt={change.scraped.name}
|
alt={change.scraped.name}
|
||||||
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
||||||
/>
|
/>
|
||||||
@@ -139,10 +145,10 @@
|
|||||||
<div class="flex items-center justify-between gap-3 pb-4 border-b border-amber-500/20">
|
<div class="flex items-center justify-between gap-3 pb-4 border-b border-amber-500/20">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
{#if change.current?.pictureUrl}
|
{#if change.current?.pictureUrl}
|
||||||
<a href={fandomUrl(change.current?.url ?? change.scraped.url)} target="_blank" rel="noopener noreferrer">
|
<a href="https://onepiece.fandom.com/fr/wiki/{change.current?.url ?? change.scraped.url}" target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
src={change.current.pictureUrl}
|
src={change.current?.pictureUrl ?? undefined}
|
||||||
alt={change.current.name}
|
alt={change.current?.name ?? change.scraped.name}
|
||||||
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
@@ -165,7 +171,7 @@
|
|||||||
|
|
||||||
{#if change.differences}
|
{#if change.differences}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#each Object.entries(change.differences) as [field, diff]}
|
{#each Object.entries(change.differences) as [field, diff] (field)}
|
||||||
<div class="bg-slate-900/50 rounded p-3 space-y-1">
|
<div class="bg-slate-900/50 rounded p-3 space-y-1">
|
||||||
<h4 class="text-sm font-semibold text-amber-100 uppercase tracking-widest">{field}</h4>
|
<h4 class="text-sm font-semibold text-amber-100 uppercase tracking-widest">{field}</h4>
|
||||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||||
@@ -188,7 +194,49 @@
|
|||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if newCharacters.length === 0 && modifiedCharacters.length === 0}
|
<!-- Deleted Characters Section -->
|
||||||
|
{#if deletedCharacters.length > 0}
|
||||||
|
<section class="space-y-4">
|
||||||
|
<h2 class="text-xl font-bold text-rose-400 uppercase tracking-[0.15em]">
|
||||||
|
🗑️ Deleted Characters ({deletedCharacters.length})
|
||||||
|
</h2>
|
||||||
|
<div class="grid gap-4">
|
||||||
|
{#each deletedCharacters as change (change.id)}
|
||||||
|
<div class="rounded-lg border border-rose-500/30 bg-rose-500/5 p-4 space-y-3">
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
{#if change.current?.pictureUrl}
|
||||||
|
<a href="https://onepiece.fandom.com/fr/wiki/{change.current?.url ?? change.scraped.url}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
src={change.current?.pictureUrl ?? undefined}
|
||||||
|
alt={change.current?.name ?? change.scraped.name}
|
||||||
|
class="w-12 h-12 rounded object-cover hover:opacity-80 transition"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-rose-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-rose-300/40 bg-rose-500/20 px-3 py-1 text-xs font-semibold text-rose-100 transition hover:bg-rose-500/30"
|
||||||
|
>
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-rose-200/80">This character is no longer present in the latest scrape and will be removed if accepted.</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if newCharacters.length === 0 && modifiedCharacters.length === 0 && deletedCharacters.length === 0}
|
||||||
<div class="rounded-lg border border-white/10 bg-white/5 p-8 text-center">
|
<div class="rounded-lg border border-white/10 bg-white/5 p-8 text-center">
|
||||||
<p class="text-gray-400">Aucun changement détecté. Les tables character et characterScrapeValidation sont synchronisées.</p>
|
<p class="text-gray-400">Aucun changement détecté. Les tables character et characterScrapeValidation sont synchronisées.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,61 +1,8 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { character, devilFruit, arc, characterOverride } from '$lib/server/db/schema';
|
import { character, devilFruit, arc, type Status } from '$lib/server/db/schema';
|
||||||
import { eq, sql } from 'drizzle-orm';
|
import { eq, sql } from 'drizzle-orm';
|
||||||
import { fail } from '@sveltejs/kit';
|
import { fail } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { writeFile } from 'fs/promises';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { existsSync, mkdirSync } from 'fs';
|
|
||||||
import { env } from '$env/dynamic/private';
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
|
||||||
const [charactersData, devilFruits, arcs, overrides, statusesData, gendersData] = await Promise.all([
|
|
||||||
db
|
|
||||||
.select({
|
|
||||||
id: character.id,
|
|
||||||
name: character.name,
|
|
||||||
gender: character.gender,
|
|
||||||
age: character.age,
|
|
||||||
affiliations: character.affiliations,
|
|
||||||
devilFruitId: character.devilFruitId,
|
|
||||||
hakiObservation: character.hakiObservation,
|
|
||||||
hakiArmament: character.hakiArmament,
|
|
||||||
hakiConqueror: character.hakiConqueror,
|
|
||||||
bounty: character.bounty,
|
|
||||||
height: character.height,
|
|
||||||
origin: character.origin,
|
|
||||||
firstAppearance: character.firstAppearance,
|
|
||||||
pictureUrl: character.pictureUrl,
|
|
||||||
epithets: character.epithets,
|
|
||||||
status: character.status,
|
|
||||||
url: character.url,
|
|
||||||
arcId: character.arcId,
|
|
||||||
isInDailyMode: character.isInDailyMode,
|
|
||||||
arcName: arc.name,
|
|
||||||
devilFruitName: devilFruit.name,
|
|
||||||
devilFruitType: devilFruit.type
|
|
||||||
})
|
|
||||||
.from(character)
|
|
||||||
.leftJoin(arc, eq(character.arcId, arc.id))
|
|
||||||
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
|
||||||
.orderBy(character.name),
|
|
||||||
db.select().from(devilFruit).orderBy(devilFruit.name),
|
|
||||||
db.select().from(arc).orderBy(arc.name),
|
|
||||||
db.select().from(characterOverride),
|
|
||||||
db.selectDistinct({ status: character.status })
|
|
||||||
.from(character)
|
|
||||||
.where(sql`${character.status} IS NOT NULL AND ${character.status} != ''`),
|
|
||||||
db.selectDistinct({ gender: character.gender })
|
|
||||||
.from(character)
|
|
||||||
.where(sql`${character.gender} IS NOT NULL AND ${character.gender} != ''`)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create a map of overrides by characterId for easy lookup
|
|
||||||
const overridesMap = new Map(overrides.map((o) => [o.characterId, o]));
|
|
||||||
|
|
||||||
// Create maps for arcs and devil fruits to lookup names by ID
|
|
||||||
const arcMap = new Map(arcs.map((a) => [a.id, a.name]));
|
|
||||||
const devilFruitMap = new Map(devilFruits.map((f) => [f.id, { name: f.name, type: f.type }]));
|
|
||||||
|
|
||||||
// Helper function to normalize data (parse JSON arrays)
|
// Helper function to normalize data (parse JSON arrays)
|
||||||
const normalizeArray = (value: any): any => {
|
const normalizeArray = (value: any): any => {
|
||||||
@@ -71,55 +18,54 @@ export const load: PageServerLoad = async () => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge character data with overrides
|
export const load: PageServerLoad = async () => {
|
||||||
const charactersWithOverrides = charactersData.map((char) => {
|
let [characters, devilFruits, arcs, statusesData, gendersData] = await Promise.all([
|
||||||
const override = overridesMap.get(char.id);
|
db
|
||||||
|
.select({
|
||||||
// Build displayValues by only applying non-null override fields
|
id: character.id,
|
||||||
const displayValues = { ...char } as any;
|
name: character.name,
|
||||||
if (override) {
|
gender: character.gender,
|
||||||
Object.keys(override).forEach((key) => {
|
age: character.age,
|
||||||
if (override[key as keyof typeof override] !== null && key !== 'characterId') {
|
affiliation: character.affiliation,
|
||||||
displayValues[key as keyof typeof displayValues] = override[key as keyof typeof override];
|
devilFruitId: character.devilFruitId,
|
||||||
}
|
hakiObservation: character.hakiObservation,
|
||||||
});
|
hakiArmament: character.hakiArmament,
|
||||||
|
hakiConqueror: character.hakiConqueror,
|
||||||
// Update arcName if arcId was overridden
|
bounty: character.bounty,
|
||||||
if (override.arcId !== null && override.arcId !== undefined) {
|
height: character.height,
|
||||||
displayValues.arcName = arcMap.get(override.arcId) || null;
|
origin: character.origin,
|
||||||
}
|
firstAppearance: character.firstAppearance,
|
||||||
|
pictureUrl: character.pictureUrl,
|
||||||
// Update devilFruitName and devilFruitType if devilFruitId was overridden
|
epithets: normalizeArray(character.epithets),
|
||||||
if (override.devilFruitId !== null && override.devilFruitId !== undefined) {
|
status: character.status,
|
||||||
const fruit = devilFruitMap.get(override.devilFruitId);
|
url: character.url,
|
||||||
displayValues.devilFruitName = fruit?.name || null;
|
arcId: character.arcId,
|
||||||
displayValues.devilFruitType = fruit?.type || null;
|
isInDailyMode: character.isInDailyMode,
|
||||||
}
|
arcName: arc.name,
|
||||||
}
|
devilFruitName: devilFruit.name,
|
||||||
|
devilFruitType: devilFruit.type
|
||||||
// Pre-normalize arrays (epithets, affiliations) for performance
|
})
|
||||||
displayValues.epithets = normalizeArray(displayValues.epithets);
|
.from(character)
|
||||||
displayValues.affiliations = normalizeArray(displayValues.affiliations);
|
.leftJoin(arc, eq(character.arcId, arc.id))
|
||||||
|
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
|
||||||
// Create search text for epithets
|
.orderBy(character.name),
|
||||||
displayValues.epithetsSearchText = Array.isArray(displayValues.epithets)
|
db.select().from(devilFruit).orderBy(devilFruit.name),
|
||||||
? displayValues.epithets.join(' ').toLowerCase()
|
db.select().from(arc).orderBy(arc.name),
|
||||||
: (displayValues.epithets || '').toLowerCase();
|
db.selectDistinct({ status: character.status })
|
||||||
|
.from(character)
|
||||||
|
.where(sql`${character.status} IS NOT NULL AND ${character.status} != ''`),
|
||||||
|
db.selectDistinct({ gender: character.gender })
|
||||||
|
.from(character)
|
||||||
|
.where(sql`${character.gender} IS NOT NULL AND ${character.gender} != ''`)
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...char,
|
characters,
|
||||||
override,
|
|
||||||
displayValues
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
characters: charactersWithOverrides,
|
|
||||||
devilFruits,
|
devilFruits,
|
||||||
arcs,
|
arcs,
|
||||||
availableStatuses: statusesData
|
availableStatuses: statusesData
|
||||||
.map(s => s.status)
|
.map(s => s.status)
|
||||||
.filter((s): s is string => !!s)
|
.filter((s): s is Status => !!s)
|
||||||
.sort((a, b) => a.localeCompare(b)),
|
.sort((a, b) => a.localeCompare(b)),
|
||||||
availableGenders: gendersData
|
availableGenders: gendersData
|
||||||
.map(g => g.gender)
|
.map(g => g.gender)
|
||||||
@@ -129,112 +75,6 @@ export const load: PageServerLoad = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
update: async ({ request, locals }) => {
|
|
||||||
if (!locals.user?.isAdmin) {
|
|
||||||
return fail(401, { error: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = await request.formData();
|
|
||||||
const id = formData.get('id') as string;
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return fail(400, { error: 'Character ID is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [originalCharacter] = await db
|
|
||||||
.select({
|
|
||||||
hakiObservation: character.hakiObservation,
|
|
||||||
hakiArmament: character.hakiArmament,
|
|
||||||
hakiConqueror: character.hakiConqueror
|
|
||||||
})
|
|
||||||
.from(character)
|
|
||||||
.where(eq(character.id, id))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!originalCharacter) {
|
|
||||||
return fail(404, { error: 'Character not found' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const updates: Record<string, any> = {};
|
|
||||||
|
|
||||||
// Handle file upload
|
|
||||||
const pictureFile = formData.get('pictureFile') as File;
|
|
||||||
const hasUploadedPicture = !!pictureFile && pictureFile.size > 0;
|
|
||||||
if (hasUploadedPicture) {
|
|
||||||
try {
|
|
||||||
const uploadsDir = env.UPLOADS_DIR || join(process.cwd(),'uploads');
|
|
||||||
if (!existsSync(uploadsDir)) {
|
|
||||||
mkdirSync(uploadsDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get file extension
|
|
||||||
const extension = pictureFile.name.split('.').pop();
|
|
||||||
const filename = `${id}.${extension}`;
|
|
||||||
const filepath = join(uploadsDir, filename);
|
|
||||||
|
|
||||||
// Convert file to buffer and save
|
|
||||||
const buffer = Buffer.from(await pictureFile.arrayBuffer());
|
|
||||||
await writeFile(filepath, buffer);
|
|
||||||
|
|
||||||
// Update pictureUrl to point to the handler route
|
|
||||||
updates.pictureUrl = `/uploads/${filename}`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('File upload error:', error);
|
|
||||||
return fail(500, { error: 'Failed to upload file' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formData.forEach((value, key) => {
|
|
||||||
if (key !== 'id' && key !== 'pictureFile') {
|
|
||||||
if (hasUploadedPicture && key === 'pictureUrl') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle integers (age, bounty, height)
|
|
||||||
if (key === 'age' || key === 'bounty' || key === 'height') {
|
|
||||||
const strValue = value as string;
|
|
||||||
updates[key] = strValue && strValue !== '' ? parseInt(strValue) : null;
|
|
||||||
}
|
|
||||||
// Handle text IDs (devilFruitId, arcId)
|
|
||||||
else if (key === 'devilFruitId' || key === 'arcId') {
|
|
||||||
const strValue = value as string;
|
|
||||||
updates[key] = strValue && strValue !== '' ? strValue : null;
|
|
||||||
}
|
|
||||||
// Handle checkboxes (haki fields) after parsing all form data
|
|
||||||
else if (key === 'hakiObservation' || key === 'hakiArmament' || key === 'hakiConqueror') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle strings (name, gender, status, origin, affiliations, epithets, pictureUrl, url, firstAppearance)
|
|
||||||
else {
|
|
||||||
updates[key] = value || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const submittedHakiObservation = formData.has('hakiObservation');
|
|
||||||
const submittedHakiArmament = formData.has('hakiArmament');
|
|
||||||
const submittedHakiConqueror = formData.has('hakiConqueror');
|
|
||||||
|
|
||||||
updates.hakiObservation =
|
|
||||||
submittedHakiObservation === originalCharacter.hakiObservation ? null : submittedHakiObservation;
|
|
||||||
updates.hakiArmament =
|
|
||||||
submittedHakiArmament === originalCharacter.hakiArmament ? null : submittedHakiArmament;
|
|
||||||
updates.hakiConqueror =
|
|
||||||
submittedHakiConqueror === originalCharacter.hakiConqueror ? null : submittedHakiConqueror;
|
|
||||||
|
|
||||||
// Update or insert into characterOverride table
|
|
||||||
await db
|
|
||||||
.insert(characterOverride)
|
|
||||||
.values({ characterId: id, ...updates })
|
|
||||||
.onConflictDoUpdate({ target: characterOverride.characterId, set: updates });
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Character update error:', error);
|
|
||||||
return fail(500, { error: 'Failed to update character' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
delete: async ({ request, locals }) => {
|
delete: async ({ request, locals }) => {
|
||||||
if (!locals.user?.isAdmin) {
|
if (!locals.user?.isAdmin) {
|
||||||
return fail(401, { error: 'Unauthorized' });
|
return fail(401, { error: 'Unauthorized' });
|
||||||
|
|||||||
@@ -15,13 +15,11 @@
|
|||||||
let filterGender = $state('all');
|
let filterGender = $state('all');
|
||||||
let filterArc = $state('all');
|
let filterArc = $state('all');
|
||||||
let filterHaki = $state<'all' | 'observation' | 'armament' | 'conqueror' | 'none'>('all');
|
let filterHaki = $state<'all' | 'observation' | 'armament' | 'conqueror' | 'none'>('all');
|
||||||
let selectedCharacterId = $state<string | null>(null);
|
|
||||||
let isEditModalOpen = $state(false);
|
let isEditModalOpen = $state(false);
|
||||||
let isSaving = $state(false);
|
let isSaving = $state(false);
|
||||||
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
let dailyModeToast = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
let dailyModeToast = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
let selectedChar = $state<any>(null);
|
let selectedChar = $state<any>(null);
|
||||||
let showOriginalValue = $state<Record<string, boolean>>({});
|
|
||||||
|
|
||||||
const showDailyModeToast = (type: 'success' | 'error', text: string) => {
|
const showDailyModeToast = (type: 'success' | 'error', text: string) => {
|
||||||
dailyModeToast = { type, text };
|
dailyModeToast = { type, text };
|
||||||
@@ -38,12 +36,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFandomUrl = (url: string | null | undefined) => {
|
|
||||||
if (!url) return null;
|
|
||||||
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
|
||||||
return `https://onepiece.fandom.com/fr/wiki/${url}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
let editForm = $state<any>({
|
let editForm = $state<any>({
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -52,7 +44,7 @@
|
|||||||
bounty: 0,
|
bounty: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
origin: '',
|
origin: '',
|
||||||
affiliations: '',
|
affiliation: '',
|
||||||
epithets: '',
|
epithets: '',
|
||||||
pictureUrl: '',
|
pictureUrl: '',
|
||||||
url: '',
|
url: '',
|
||||||
@@ -71,23 +63,22 @@
|
|||||||
|
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
normalizedQuery === '' ||
|
normalizedQuery === '' ||
|
||||||
char.displayValues.name.toLowerCase().includes(normalizedQuery) ||
|
char.name.toLowerCase().includes(normalizedQuery);
|
||||||
char.displayValues.epithetsSearchText.includes(normalizedQuery);
|
|
||||||
const matchesDaily =
|
const matchesDaily =
|
||||||
filterDaily === 'all' ||
|
filterDaily === 'all' ||
|
||||||
(filterDaily === 'daily' && char.displayValues.isInDailyMode) ||
|
(filterDaily === 'daily' && char.isInDailyMode) ||
|
||||||
(filterDaily === 'not-daily' && !char.displayValues.isInDailyMode);
|
(filterDaily === 'not-daily' && !char.isInDailyMode);
|
||||||
const matchesStatus = filterStatus === 'all' || (char.displayValues.status || '') === filterStatus;
|
const matchesStatus = filterStatus === 'all' || (char.status || '') === filterStatus;
|
||||||
const matchesGender = filterGender === 'all' || (char.displayValues.gender || '') === filterGender;
|
const matchesGender = filterGender === 'all' || (char.gender || '') === filterGender;
|
||||||
const matchesArc =
|
const matchesArc =
|
||||||
filterArc === 'all' ||
|
filterArc === 'all' ||
|
||||||
String(char.displayValues.arcId ?? '') === filterArc;
|
String(char.arcId ?? '') === filterArc;
|
||||||
const matchesHaki =
|
const matchesHaki =
|
||||||
filterHaki === 'all' ||
|
filterHaki === 'all' ||
|
||||||
(filterHaki === 'observation' && !!char.displayValues.hakiObservation) ||
|
(filterHaki === 'observation' && !!char.hakiObservation) ||
|
||||||
(filterHaki === 'armament' && !!char.displayValues.hakiArmament) ||
|
(filterHaki === 'armament' && !!char.hakiArmament) ||
|
||||||
(filterHaki === 'conqueror' && !!char.displayValues.hakiConqueror) ||
|
(filterHaki === 'conqueror' && !!char.hakiConqueror) ||
|
||||||
(filterHaki === 'none' && !char.displayValues.hakiObservation && !char.displayValues.hakiArmament && !char.displayValues.hakiConqueror);
|
(filterHaki === 'none' && !char.hakiObservation && !char.hakiArmament && !char.hakiConqueror);
|
||||||
|
|
||||||
return matchesSearch && matchesDaily && matchesStatus && matchesGender && matchesArc && matchesHaki;
|
return matchesSearch && matchesDaily && matchesStatus && matchesGender && matchesArc && matchesHaki;
|
||||||
});
|
});
|
||||||
@@ -98,7 +89,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openEditModal = (char: any) => {
|
const openEditModal = (char: any) => {
|
||||||
selectedCharacterId = char.id;
|
|
||||||
selectedChar = char;
|
selectedChar = char;
|
||||||
|
|
||||||
const override = char.override || {};
|
const override = char.override || {};
|
||||||
@@ -111,7 +101,7 @@
|
|||||||
bounty: override.bounty ?? null,
|
bounty: override.bounty ?? null,
|
||||||
height: override.height ?? null,
|
height: override.height ?? null,
|
||||||
origin: override.origin ?? '',
|
origin: override.origin ?? '',
|
||||||
affiliations: override.affiliations ?? '',
|
affiliation: override.affiliation ?? '',
|
||||||
epithets: override.epithets ?? '',
|
epithets: override.epithets ?? '',
|
||||||
pictureUrl: override.pictureUrl ?? '',
|
pictureUrl: override.pictureUrl ?? '',
|
||||||
url: override.url ?? '',
|
url: override.url ?? '',
|
||||||
@@ -123,13 +113,11 @@
|
|||||||
arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : (char.arcId || ''),
|
arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : (char.arcId || ''),
|
||||||
status: override.status ?? ''
|
status: override.status ?? ''
|
||||||
};
|
};
|
||||||
showOriginalValue = {};
|
|
||||||
isEditModalOpen = true;
|
isEditModalOpen = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
isEditModalOpen = false;
|
isEditModalOpen = false;
|
||||||
selectedCharacterId = null;
|
|
||||||
selectedChar = null;
|
selectedChar = null;
|
||||||
editForm = {
|
editForm = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -139,7 +127,7 @@
|
|||||||
bounty: 0,
|
bounty: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
origin: '',
|
origin: '',
|
||||||
affiliations: '',
|
affiliation: '',
|
||||||
epithets: '',
|
epithets: '',
|
||||||
pictureUrl: '',
|
pictureUrl: '',
|
||||||
url: '',
|
url: '',
|
||||||
@@ -179,6 +167,7 @@
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error deleting character:', error);
|
||||||
saveMessage = {
|
saveMessage = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: 'Error deleting character'
|
text: 'Error deleting character'
|
||||||
@@ -221,7 +210,7 @@
|
|||||||
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
<option value="all">All Statuses</option>
|
<option value="all">All Statuses</option>
|
||||||
{#each data.availableStatuses as status}
|
{#each data.availableStatuses as status (status)}
|
||||||
<option value={status}>{status}</option>
|
<option value={status}>{status}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -230,7 +219,7 @@
|
|||||||
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
<option value="all">All Genders</option>
|
<option value="all">All Genders</option>
|
||||||
{#each data.availableGenders as gender}
|
{#each data.availableGenders as gender (gender)}
|
||||||
<option value={gender}>{gender}</option>
|
<option value={gender}>{gender}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -239,8 +228,8 @@
|
|||||||
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
<option value="all">All Arcs</option>
|
<option value="all">All Arcs</option>
|
||||||
{#each data.arcs as arc}
|
{#each data.arcs as arc (arc.id)}
|
||||||
<option value={String(arc.id)}>{arc.name}</option>
|
<option value={arc.id}>{arc.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
@@ -284,119 +273,115 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each filteredCharacters as char}
|
{#each filteredCharacters as char (char.id)}
|
||||||
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
||||||
<!-- Character -->
|
<!-- Character -->
|
||||||
<td class="px-4 py-4 text-sm text-white w-64 max-w-64 {isFieldOverridden(char, 'name') || isFieldOverridden(char, 'pictureUrl') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm text-white w-64 max-w-64">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
{#if getFandomUrl(char.displayValues.url)}
|
{#if char.url}
|
||||||
<a
|
<a
|
||||||
href={getFandomUrl(char.displayValues.url)}
|
href={"https://onepiece.fandom.com/wiki/" + char.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="flex-shrink-0 transition-opacity hover:opacity-80"
|
class="shrink-0 transition-opacity hover:opacity-80"
|
||||||
>
|
>
|
||||||
{#if char.displayValues.pictureUrl}
|
{#if char.pictureUrl}
|
||||||
<img
|
<img
|
||||||
src={char.displayValues.pictureUrl}
|
src={char.pictureUrl}
|
||||||
alt={char.displayValues.name}
|
alt={char.name}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="h-10 w-10 rounded-full object-cover"
|
class="h-10 w-10 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-slate-700 text-gray-400">
|
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-slate-700 text-gray-400">
|
||||||
{char.displayValues.name?.charAt(0).toUpperCase() || '?'}
|
{char.name?.charAt(0).toUpperCase() || '?'}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
{#if char.displayValues.pictureUrl}
|
{#if char.pictureUrl}
|
||||||
<img
|
<img
|
||||||
src={char.displayValues.pictureUrl}
|
src={char.pictureUrl}
|
||||||
alt={char.displayValues.name}
|
alt={char.name}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="h-10 w-10 flex-shrink-0 rounded-full object-cover"
|
class="h-10 w-10 shrink-0 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-slate-700 text-gray-400">
|
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-700 text-gray-400">
|
||||||
{char.displayValues.name?.charAt(0).toUpperCase() || '?'}
|
{char.name?.charAt(0).toUpperCase() || '?'}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col min-w-0">
|
<div class="flex flex-col min-w-0">
|
||||||
{#if getFandomUrl(char.displayValues.url)}
|
{#if char.url}
|
||||||
<a
|
<a
|
||||||
href={getFandomUrl(char.displayValues.url)}
|
href="https://onepiece.fandom.com/wiki/{char.url}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="font-medium truncate text-white hover:text-amber-200 hover:underline"
|
class="font-medium truncate text-white hover:text-amber-200 hover:underline"
|
||||||
>
|
>
|
||||||
{char.displayValues.name}
|
{char.name}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="font-medium truncate">{char.displayValues.name}</span>
|
<span class="font-medium truncate">{char.name}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if char.displayValues.epithets}
|
{#if char.epithets}
|
||||||
<span class="text-xs text-gray-500 truncate">
|
<span class="text-xs text-gray-500 truncate">
|
||||||
{Array.isArray(char.displayValues.epithets)
|
{Array.isArray(char.epithets)
|
||||||
? char.displayValues.epithets.join(', ')
|
? char.epithets.join(', ')
|
||||||
: char.displayValues.epithets}
|
: char.epithets}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'status') ? 'bg-amber-500/10' : ''}">{char.displayValues.status || '-'}</td>
|
<td class="px-4 py-4 text-sm text-gray-400">{char.status || '-'}</td>
|
||||||
<!-- Gender -->
|
<!-- Gender -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'gender') ? 'bg-amber-500/10' : ''}">{char.displayValues.gender || '-'}</td>
|
<td class="px-4 py-4 text-sm text-gray-400">{char.gender || '-'}</td>
|
||||||
<!-- Affiliations -->
|
<!-- Affiliations -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'affiliations') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm text-gray-400">
|
||||||
{#if char.displayValues.affiliations}
|
{#if char.affiliation}
|
||||||
{#if Array.isArray(char.displayValues.affiliations) && char.displayValues.affiliations.length > 0}
|
{char.affiliation}
|
||||||
<span class="inline-block" title={char.displayValues.affiliations.join(', ')}>{char.displayValues.affiliations[0]}</span>
|
|
||||||
{:else}
|
|
||||||
{char.displayValues.affiliations}
|
|
||||||
{/if}
|
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<!-- Fruit -->
|
<!-- Fruit -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'devilFruitId') ? 'bg-amber-500/10' : ''}">{char.displayValues.devilFruitName || '-'}</td>
|
<td class="px-4 py-4 text-sm text-gray-400">{char.devilFruitName || '-'}</td>
|
||||||
<!-- Haki -->
|
<!-- Haki -->
|
||||||
<td class="px-4 py-4 text-sm {isFieldOverridden(char, 'hakiObservation') || isFieldOverridden(char, 'hakiArmament') || isFieldOverridden(char, 'hakiConqueror') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
{#if char.displayValues.hakiObservation}<span title="Haki de l'Observation">👁️</span>{/if}
|
{#if char.hakiObservation}<span title="Haki de l'Observation">👁️</span>{/if}
|
||||||
{#if char.displayValues.hakiArmament}<span title="Haki de l'Armement">🦾</span>{/if}
|
{#if char.hakiArmament}<span title="Haki de l'Armement">🦾</span>{/if}
|
||||||
{#if char.displayValues.hakiConqueror}<span title="Haki des Rois">👑</span>{/if}
|
{#if char.hakiConqueror}<span title="Haki des Rois">👑</span>{/if}
|
||||||
{#if !char.displayValues.hakiObservation && !char.displayValues.hakiArmament && !char.displayValues.hakiConqueror}
|
{#if !char.hakiObservation && !char.hakiArmament && !char.hakiConqueror}
|
||||||
<span class="text-gray-400">-</span>
|
<span class="text-gray-400">-</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- Bounty -->
|
<!-- Bounty -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'bounty') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm text-gray-400">
|
||||||
{#if char.displayValues.bounty != null}
|
{#if char.bounty != null}
|
||||||
{formatBounty(char.displayValues.bounty)} ฿
|
{formatBounty(char.bounty)} ฿
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<!-- Height -->
|
<!-- Height -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'height') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm text-gray-400">
|
||||||
{#if char.displayValues.height}
|
{#if char.height}
|
||||||
{char.displayValues.height} m
|
{char.height} m
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<!-- Origin -->
|
<!-- Origin -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'origin') ? 'bg-amber-500/10' : ''}">{char.displayValues.origin || '-'}</td>
|
<td class="px-4 py-4 text-sm text-gray-400">{char.origin || '-'}</td>
|
||||||
<!-- Arc -->
|
<!-- Arc -->
|
||||||
<td class="px-4 py-4 text-sm text-gray-400 {isFieldOverridden(char, 'arcId') || isFieldOverridden(char, 'arcName') ? 'bg-amber-500/10' : ''}">{char.displayValues.arcName || '-'}</td>
|
<td class="px-4 py-4 text-sm text-gray-400">{char.arcName || '-'}</td>
|
||||||
<!-- Daily Mode -->
|
<!-- Daily Mode -->
|
||||||
<td class="px-4 py-4 text-sm {isFieldOverridden(char, 'isInDailyMode') ? 'bg-amber-500/10' : ''}">
|
<td class="px-4 py-4 text-sm">
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/toggleDailyMode"
|
action="?/toggleDailyMode"
|
||||||
@@ -414,11 +399,11 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input type="hidden" name="id" value={char.id} />
|
<input type="hidden" name="id" value={char.id} />
|
||||||
<input type="hidden" name="isInDailyMode" value={(!char.displayValues.isInDailyMode).toString()} />
|
<input type="hidden" name="isInDailyMode" value={(!char.isInDailyMode).toString()} />
|
||||||
<label class="flex items-center justify-center cursor-pointer">
|
<label class="flex items-center justify-center cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={char.displayValues.isInDailyMode}
|
checked={char.isInDailyMode}
|
||||||
onchange={(e) => {
|
onchange={(e) => {
|
||||||
const form = e.currentTarget.closest('form');
|
const form = e.currentTarget.closest('form');
|
||||||
if (form) form.requestSubmit();
|
if (form) form.requestSubmit();
|
||||||
@@ -461,7 +446,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if dailyModeToast}
|
{#if dailyModeToast}
|
||||||
<div class="fixed right-6 top-6 z-[60]">
|
<div class="fixed right-6 top-6 z-60">
|
||||||
<div
|
<div
|
||||||
class={`rounded-lg border px-4 py-3 text-sm font-medium shadow-lg backdrop-blur ${
|
class={`rounded-lg border px-4 py-3 text-sm font-medium shadow-lg backdrop-blur ${
|
||||||
dailyModeToast.type === 'success'
|
dailyModeToast.type === 'success'
|
||||||
@@ -628,7 +613,7 @@
|
|||||||
class="w-full rounded-lg border border-gray-500 bg-slate-800 px-3 py-2 text-white"
|
class="w-full rounded-lg border border-gray-500 bg-slate-800 px-3 py-2 text-white"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
{#each data.arcs as arc}
|
{#each data.arcs as arc (arc.id)}
|
||||||
<option value={arc.id}>{arc.name}</option>
|
<option value={arc.id}>{arc.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -651,7 +636,7 @@
|
|||||||
class="w-full rounded-lg border border-gray-500 bg-slate-800 px-3 py-2 text-white"
|
class="w-full rounded-lg border border-gray-500 bg-slate-800 px-3 py-2 text-white"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
{#each data.devilFruits as fruit}
|
{#each data.devilFruits as fruit (fruit.id)}
|
||||||
<option value={fruit.id}>{fruit.name}</option>
|
<option value={fruit.id}>{fruit.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
let { data }: Props = $props();
|
let { data }: Props = $props();
|
||||||
|
|
||||||
let configItems = $state<ConfigItem[]>([]);
|
let configItems = $derived(data.config.map((item) => ({
|
||||||
|
key: item.key,
|
||||||
|
value: item.value ?? ''
|
||||||
|
})));
|
||||||
let newKey = $state('');
|
let newKey = $state('');
|
||||||
let newValue = $state('');
|
let newValue = $state('');
|
||||||
let editingKey = $state<string | null>(null);
|
let editingKey = $state<string | null>(null);
|
||||||
@@ -21,12 +24,7 @@
|
|||||||
let isSaving = $state(false);
|
let isSaving = $state(false);
|
||||||
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
$effect(() => {
|
;
|
||||||
configItems = data.config.map((item) => ({
|
|
||||||
key: item.key,
|
|
||||||
value: item.value ?? ''
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const startEdit = (item: ConfigItem) => {
|
const startEdit = (item: ConfigItem) => {
|
||||||
editingKey = item.key;
|
editingKey = item.key;
|
||||||
@@ -70,6 +68,7 @@
|
|||||||
saveMessage = { type: 'error', text: 'Failed to add config' };
|
saveMessage = { type: 'error', text: 'Failed to add config' };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error adding config:', error);
|
||||||
saveMessage = { type: 'error', text: 'Error adding config' };
|
saveMessage = { type: 'error', text: 'Error adding config' };
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
@@ -99,6 +98,7 @@
|
|||||||
saveMessage = { type: 'error', text: 'Failed to delete config' };
|
saveMessage = { type: 'error', text: 'Failed to delete config' };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error deleting config:', error);
|
||||||
saveMessage = { type: 'error', text: 'Error deleting config' };
|
saveMessage = { type: 'error', text: 'Error deleting config' };
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each configItems as item}
|
{#each configItems as item (item.key)}
|
||||||
{#if editingKey === item.key}
|
{#if editingKey === item.key}
|
||||||
<tr class="border-b border-white/5 bg-slate-800/50">
|
<tr class="border-b border-white/5 bg-slate-800/50">
|
||||||
<td class="px-6 py-4 text-sm text-white">{item.key}</td>
|
<td class="px-6 py-4 text-sm text-white">{item.key}</td>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
let filterType = $state<'all' | 'Paramecia' | 'Zoan' | 'Logia' | 'Unknown'>('all');
|
let filterType = $state<'all' | 'Paramecia' | 'Zoan' | 'Logia' | 'Unknown'>('all');
|
||||||
let isEditModalOpen = $state(false);
|
let isEditModalOpen = $state(false);
|
||||||
let selectedFruitId = $state<string | null>(null);
|
|
||||||
let isSaving = $state(false);
|
let isSaving = $state(false);
|
||||||
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
let saveMessage = $state<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
@@ -33,14 +32,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openEditModal = (fruit: any) => {
|
const openEditModal = (fruit: any) => {
|
||||||
selectedFruitId = fruit.id;
|
|
||||||
editForm = { ...fruit };
|
editForm = { ...fruit };
|
||||||
isEditModalOpen = true;
|
isEditModalOpen = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
isEditModalOpen = false;
|
isEditModalOpen = false;
|
||||||
selectedFruitId = null;
|
|
||||||
editForm = {
|
editForm = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -88,6 +85,7 @@
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error deleting devil fruit:', error);
|
||||||
saveMessage = {
|
saveMessage = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: 'Error deleting devil fruit'
|
text: 'Error deleting devil fruit'
|
||||||
@@ -150,7 +148,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each filteredFruits as fruit}
|
{#each filteredFruits as fruit (fruit.id)}
|
||||||
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
||||||
<td class="px-6 py-4 text-sm text-white">{fruit.name}</td>
|
<td class="px-6 py-4 text-sm text-white">{fruit.name}</td>
|
||||||
<td class="px-6 py-4 text-sm">
|
<td class="px-6 py-4 text-sm">
|
||||||
@@ -233,7 +231,7 @@
|
|||||||
bind:value={editForm.type}
|
bind:value={editForm.type}
|
||||||
class="mt-1 w-full rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
class="mt-1 w-full rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600"
|
||||||
>
|
>
|
||||||
{#each fruitTypes as type}
|
{#each fruitTypes as type (type)}
|
||||||
<option value={type}>{type}</option>
|
<option value={type}>{type}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
let isEditModalOpen = $state(false);
|
let isEditModalOpen = $state(false);
|
||||||
let isSaving = $state(false);
|
let isSaving = $state(false);
|
||||||
let saveMessage = $state<{ type: 'success' | 'error'; message: string } | null>(null);
|
let saveMessage = $state<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||||
let selectedUserId = $state<string | null>(null);
|
|
||||||
|
|
||||||
let editForm = $state<any>({
|
let editForm = $state<any>({
|
||||||
id: '',
|
id: '',
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openEditModal = (usr: any) => {
|
const openEditModal = (usr: any) => {
|
||||||
selectedUserId = usr.id;
|
|
||||||
editForm = { ...usr };
|
editForm = { ...usr };
|
||||||
isEditModalOpen = true;
|
isEditModalOpen = true;
|
||||||
saveMessage = null;
|
saveMessage = null;
|
||||||
@@ -43,7 +41,6 @@
|
|||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
isEditModalOpen = false;
|
isEditModalOpen = false;
|
||||||
selectedUserId = null;
|
|
||||||
editForm = {
|
editForm = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -120,7 +117,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each filteredUsers as usr}
|
{#each filteredUsers as usr (usr.id)}
|
||||||
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
<tr class="border-b border-white/5 hover:bg-slate-800/50">
|
||||||
<td class="px-6 py-4 text-sm text-white">{usr.name}</td>
|
<td class="px-6 py-4 text-sm text-white">{usr.name}</td>
|
||||||
<td class="px-6 py-4 text-sm text-gray-400">{usr.email}</td>
|
<td class="px-6 py-4 text-sm text-gray-400">{usr.email}</td>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ProfileButton from '$lib/components/ProfileButton.svelte';
|
import ProfileButton from '$lib/components/ProfileButton.svelte';
|
||||||
|
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -7,11 +9,14 @@
|
|||||||
<div class="min-h-screen bg-slate-950">
|
<div class="min-h-screen bg-slate-950">
|
||||||
<header class="fixed top-0 right-0 left-0 z-50 border-b border-white/5 bg-slate-950/95 backdrop-blur">
|
<header class="fixed top-0 right-0 left-0 z-50 border-b border-white/5 bg-slate-950/95 backdrop-blur">
|
||||||
<div class="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
|
<div class="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
|
||||||
<a href="/" class="text-lg font-black uppercase tracking-[0.15em] text-amber-50 transition hover:text-amber-100">
|
<a href={resolve("/")} class="text-lg font-black uppercase tracking-[0.15em] text-amber-50 transition hover:text-amber-100">
|
||||||
OnePieceDle
|
OnePieceDle
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<LanguageSwitcher />
|
||||||
<ProfileButton user={data.user} />
|
<ProfileButton user={data.user} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="pt-20">
|
<main class="pt-20">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -1,7 +1,59 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { language, t } from '$lib/i18n';
|
||||||
|
import type { CharacterWithRelations } from '$lib/server/daily-character';
|
||||||
|
|
||||||
$: yesterdayCharacter = data.yesterdayCharacter;
|
$: yesterdayCharacter = data.yesterdayCharacter;
|
||||||
|
$: isFrench = $language === 'fr';
|
||||||
|
|
||||||
|
function parseEpithets(value: unknown): string[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (value.length > 0) {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayName(character: CharacterWithRelations | null): string {
|
||||||
|
if (isFrench && typeof character?.frName === 'string' && character.frName.length > 0) {
|
||||||
|
return character.frName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayEpithets(character: CharacterWithRelations | null): string[] {
|
||||||
|
const frenchEpithets = parseEpithets(character?.frEpithets);
|
||||||
|
if (isFrench && frenchEpithets.length > 0) {
|
||||||
|
return frenchEpithets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseEpithets(character?.epithets);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWikiUrl(character: CharacterWithRelations | null): string {
|
||||||
|
if (isFrench && typeof character?.frUrl === 'string' && character.frUrl.length > 0) {
|
||||||
|
return character.frUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character?.url || '';
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -11,7 +63,7 @@
|
|||||||
<main
|
<main
|
||||||
class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100"
|
class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
<div class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||||
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
||||||
|
|
||||||
<div class="relative mx-auto flex w-full max-w-6xl flex-col items-center justify-center px-6 py-10">
|
<div class="relative mx-auto flex w-full max-w-6xl flex-col items-center justify-center px-6 py-10">
|
||||||
@@ -21,30 +73,30 @@
|
|||||||
OnePieceDle
|
OnePieceDle
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-4 max-w-2xl text-base text-slate-200 sm:text-lg">
|
<p class="mt-4 max-w-2xl text-base text-slate-200 sm:text-lg">
|
||||||
Devine le personnage de l'equipage, des marines ou du vaste monde. Chaque indice te rapproche du tresor.
|
{$t.game.home.heroDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid w-full gap-4 sm:grid-cols-2">
|
<div class="grid w-full gap-4 sm:grid-cols-2">
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Personnage du jour</h2>
|
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.home.dailyTitle}</h2>
|
||||||
<p class="mt-3 text-lg font-semibold text-white">Nouveau mystere toutes les 24 heures</p>
|
<p class="mt-3 text-lg font-semibold text-white">{$t.game.home.dailySubtitle}</p>
|
||||||
<p class="mt-2 text-sm text-slate-200">Compare tes essais, debloque des indices et garde ta serie.</p>
|
<p class="mt-2 text-sm text-slate-200">{$t.game.home.dailyDescription}</p>
|
||||||
<a
|
<a
|
||||||
href="/daily"
|
href={resolve("/daily")}
|
||||||
class="mt-5 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
|
class="mt-5 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-slate-900 transition hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
Commencer
|
{$t.game.home.dailyCta}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">Mode Infini</h2>
|
<h2 class="text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">{$t.game.home.infiniteTitle}</h2>
|
||||||
<p class="mt-3 text-lg font-semibold text-white">Des defis sans fin</p>
|
<p class="mt-3 text-lg font-semibold text-white">{$t.game.home.infiniteSubtitle}</p>
|
||||||
<p class="mt-2 text-sm text-slate-200">Enchaine les personnages et croise ton score. Pas de limite, que du plaisir.</p>
|
<p class="mt-2 text-sm text-slate-200">{$t.game.home.infiniteDescription}</p>
|
||||||
<a
|
<a
|
||||||
href="/infinite"
|
href={resolve("/infinite")}
|
||||||
class="mt-5 inline-flex w-full items-center justify-center rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
class="mt-5 inline-flex w-full items-center justify-center rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
||||||
>
|
>
|
||||||
Jouer
|
{$t.game.home.infiniteCta}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,43 +106,52 @@
|
|||||||
{#if yesterdayCharacter.pictureUrl}
|
{#if yesterdayCharacter.pictureUrl}
|
||||||
<img
|
<img
|
||||||
src={yesterdayCharacter.pictureUrl}
|
src={yesterdayCharacter.pictureUrl}
|
||||||
alt={yesterdayCharacter.name}
|
alt={getDisplayName(yesterdayCharacter)}
|
||||||
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
|
class="h-20 w-20 rounded-full border border-amber-200/40 object-cover"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||||
Photo
|
{$t.game.home.photoFallback}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.home.yesterdayCharacter}</p>
|
||||||
<p class="mt-2 text-lg font-semibold text-white">{yesterdayCharacter.name}</p>
|
<p class="mt-2 text-lg font-semibold text-white">{getDisplayName(yesterdayCharacter)}</p>
|
||||||
{#if yesterdayCharacter.epithets}
|
{#if getDisplayEpithets(yesterdayCharacter).length > 0}
|
||||||
<p class="mt-1 text-sm text-slate-400">
|
<p class="mt-1 text-sm text-slate-400">
|
||||||
{typeof yesterdayCharacter.epithets === 'string'
|
{getDisplayEpithets(yesterdayCharacter).join(', ')}
|
||||||
? JSON.parse(yesterdayCharacter.epithets).join(', ')
|
|
||||||
: (yesterdayCharacter.epithets as string[]).join(', ')}
|
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if isFrench}
|
||||||
<a
|
<a
|
||||||
href={"https://onepiece.fandom.com/fr/wiki/" + yesterdayCharacter.url}
|
href="https://onepiece.fandom.com/fr/wiki/{getWikiUrl(yesterdayCharacter)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
||||||
>
|
>
|
||||||
Voir la page
|
{$t.game.home.openPage}
|
||||||
</a>
|
</a>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href="https://onepiece.fandom.com/wiki/{getWikiUrl(yesterdayCharacter)}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="w-full rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50 sm:w-auto"
|
||||||
|
>
|
||||||
|
{$t.game.home.openPage}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
<div class="flex flex-col items-center gap-5 text-center sm:flex-row sm:text-left">
|
||||||
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
<div class="flex h-20 w-20 items-center justify-center rounded-full border border-amber-200/40 bg-slate-900/70 text-xs uppercase tracking-[0.25em] text-amber-100">
|
||||||
Photo
|
{$t.game.home.photoFallback}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">Personnage d'hier</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100">{$t.game.home.yesterdayCharacter}</p>
|
||||||
<p class="mt-2 text-lg font-semibold text-white">Aucun personnage</p>
|
<p class="mt-2 text-lg font-semibold text-white">{$t.game.home.noCharacter}</p>
|
||||||
<p class="mt-1 text-sm text-slate-200">Aucun personnage d'hier disponible</p>
|
<p class="mt-1 text-sm text-slate-200">{$t.game.home.noYesterdayCharacter}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { characterHistory, config, friendship, user, userCharacterHistory } from '$lib/server/db/schema';
|
import { character, characterHistory, config, friendship, user, userCharacterHistory } from '$lib/server/db/schema';
|
||||||
import { getDailyModeCharacters, getOrCreateTodayCharacter, getYesterdayCharacter, getTodayCharacterWinsCount, getDateKey } from '$lib/server/daily-character';
|
import { getDailyModeCharacters, getOrCreateTodayCharacter, getYesterdayCharacter, getTodayCharacterWinsCount, getDateKey } from '$lib/server/daily-character';
|
||||||
import { and, eq, inArray, like, or } from 'drizzle-orm';
|
import { and, eq, inArray, like, or } from 'drizzle-orm';
|
||||||
|
|
||||||
@@ -17,7 +17,13 @@ export async function load(event) {
|
|||||||
// Load the win count for today
|
// Load the win count for today
|
||||||
const winCount = await getTodayCharacterWinsCount(dailyCharacter.id);
|
const winCount = await getTodayCharacterWinsCount(dailyCharacter.id);
|
||||||
|
|
||||||
let friendsTodayResults: Array<{ userId: string; name: string; image: string | null; tryCount: number }> = [];
|
let friendsTodayResults: Array<{
|
||||||
|
userId: string;
|
||||||
|
name: string;
|
||||||
|
image: string | null;
|
||||||
|
tryCount: number;
|
||||||
|
triedCharacters: Array<{ id: string; name: string; pictureUrl: string | null }>;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
if (event.locals.user) {
|
if (event.locals.user) {
|
||||||
const currentUserId = event.locals.user.id;
|
const currentUserId = event.locals.user.id;
|
||||||
@@ -51,12 +57,13 @@ export async function load(event) {
|
|||||||
const todayCharacterHistoryId = todayHistoryEntry?.id;
|
const todayCharacterHistoryId = todayHistoryEntry?.id;
|
||||||
|
|
||||||
if (todayCharacterHistoryId) {
|
if (todayCharacterHistoryId) {
|
||||||
friendsTodayResults = await db
|
const friendResultsRaw = await db
|
||||||
.select({
|
.select({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
image: user.image,
|
image: user.image,
|
||||||
tryCount: userCharacterHistory.tryCount
|
tryCount: userCharacterHistory.tryCount,
|
||||||
|
triedCharacterIds: userCharacterHistory.triedCharacterIds
|
||||||
})
|
})
|
||||||
.from(userCharacterHistory)
|
.from(userCharacterHistory)
|
||||||
.innerJoin(user, eq(userCharacterHistory.userId, user.id))
|
.innerJoin(user, eq(userCharacterHistory.userId, user.id))
|
||||||
@@ -67,6 +74,33 @@ export async function load(event) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.orderBy(userCharacterHistory.tryCount);
|
.orderBy(userCharacterHistory.tryCount);
|
||||||
|
|
||||||
|
const uniqueTriedCharacterIds = Array.from(new Set(
|
||||||
|
friendResultsRaw.flatMap((entry) => entry.triedCharacterIds ?? [])
|
||||||
|
));
|
||||||
|
|
||||||
|
const triedCharacters = uniqueTriedCharacterIds.length > 0
|
||||||
|
? await db
|
||||||
|
.select({
|
||||||
|
id: character.id,
|
||||||
|
name: character.name,
|
||||||
|
pictureUrl: character.pictureUrl
|
||||||
|
})
|
||||||
|
.from(character)
|
||||||
|
.where(inArray(character.id, uniqueTriedCharacterIds))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const triedCharactersById = new Map(triedCharacters.map((entry) => [entry.id, entry]));
|
||||||
|
|
||||||
|
friendsTodayResults = friendResultsRaw.map((entry) => ({
|
||||||
|
userId: entry.userId,
|
||||||
|
name: entry.name,
|
||||||
|
image: entry.image,
|
||||||
|
tryCount: entry.tryCount,
|
||||||
|
triedCharacters: (entry.triedCharacterIds ?? [])
|
||||||
|
.map((characterId) => triedCharactersById.get(characterId))
|
||||||
|
.filter((triedEntry): triedEntry is (typeof triedCharacters)[number] => !!triedEntry)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,98 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import YesterdayCharacter from '$lib/components/YesterdayCharacter.svelte';
|
import YesterdayCharacter from '$lib/components/YesterdayCharacter.svelte';
|
||||||
import HintsPanel from '$lib/components/HintsPanel.svelte';
|
import HintsPanel from '$lib/components/HintsPanel.svelte';
|
||||||
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
|
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
|
||||||
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
|
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
|
||||||
import WinPanel from '$lib/components/WinPanel.svelte';
|
import WinPanel from '$lib/components/WinPanel.svelte';
|
||||||
|
import FriendsTodaySection from '$lib/components/FriendsTodaySection.svelte';
|
||||||
|
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
|
||||||
|
import { t } from '$lib/i18n';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
let selectedCharacters: any[] = [];
|
let selectedCharacters: CharacterWithRelations[] = [];
|
||||||
let isLoaded = false;
|
let isLoaded = false;
|
||||||
let isGeckoMoriaWin = false;
|
let isGeckoMoriaWin = false;
|
||||||
|
|
||||||
let wasOriginAvailable = false;
|
|
||||||
let wasFruitAvailable = false;
|
|
||||||
let wasAffiliationAvailable = false;
|
|
||||||
let showOriginUnlock = false;
|
let showOriginUnlock = false;
|
||||||
let showFruitUnlock = false;
|
let showFruitUnlock = false;
|
||||||
let showAffiliationUnlock = false;
|
let showAffiliationUnlock = false;
|
||||||
|
let originUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let fruitUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let affiliationUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function clearUnlockTimeout(timeout: ReturnType<typeof setTimeout> | null) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pulseUnlock(type: 'origin' | 'fruit' | 'affiliation') {
|
||||||
|
if (type === 'origin') {
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
showOriginUnlock = true;
|
||||||
|
originUnlockTimeout = setTimeout(() => {
|
||||||
|
showOriginUnlock = false;
|
||||||
|
originUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'fruit') {
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
showFruitUnlock = true;
|
||||||
|
fruitUnlockTimeout = setTimeout(() => {
|
||||||
|
showFruitUnlock = false;
|
||||||
|
fruitUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
showAffiliationUnlock = true;
|
||||||
|
affiliationUnlockTimeout = setTimeout(() => {
|
||||||
|
showAffiliationUnlock = false;
|
||||||
|
affiliationUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncHintAvailability(previousGuessCount: number, nextGuessCount: number, animateUnlocks = false) {
|
||||||
|
const nextOriginAvailable = nextGuessCount >= 5;
|
||||||
|
const nextFruitAvailable = nextGuessCount >= 10;
|
||||||
|
const nextAffiliationAvailable = nextGuessCount >= 15;
|
||||||
|
|
||||||
|
if (animateUnlocks && nextOriginAvailable && previousGuessCount < 5) {
|
||||||
|
pulseUnlock('origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animateUnlocks && nextFruitAvailable && previousGuessCount < 10) {
|
||||||
|
pulseUnlock('fruit');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animateUnlocks && nextAffiliationAvailable && previousGuessCount < 15) {
|
||||||
|
pulseUnlock('affiliation');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextOriginAvailable) {
|
||||||
|
showOriginUnlock = false;
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
originUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextFruitAvailable) {
|
||||||
|
showFruitUnlock = false;
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
fruitUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextAffiliationAvailable) {
|
||||||
|
showAffiliationUnlock = false;
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
affiliationUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Load from localStorage on mount
|
// Load from localStorage on mount
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -37,8 +112,8 @@
|
|||||||
// Reconstruct character objects from IDs
|
// Reconstruct character objects from IDs
|
||||||
if (Array.isArray(storedIds)) {
|
if (Array.isArray(storedIds)) {
|
||||||
selectedCharacters = storedIds
|
selectedCharacters = storedIds
|
||||||
.map((id: string) => data.characters.find((c: any) => c.id === id))
|
.map((id: string) => data.characters.find((c: CharacterWithRelations) => c.id === id))
|
||||||
.filter((c: any) => c !== undefined);
|
.filter((c: CharacterWithRelations | undefined): c is CharacterWithRelations => !!c);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse stored history', e);
|
console.error('Failed to parse stored history', e);
|
||||||
@@ -51,9 +126,17 @@
|
|||||||
localStorage.setItem('dailyCurrentCharacterId', dailyCurrentCharacterId);
|
localStorage.setItem('dailyCurrentCharacterId', dailyCurrentCharacterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncHintAvailability(0, selectedCharacters.length);
|
||||||
|
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
});
|
||||||
|
|
||||||
// Save to localStorage whenever selectedCharacters changes (only store IDs)
|
// Save to localStorage whenever selectedCharacters changes (only store IDs)
|
||||||
$: if (isLoaded && selectedCharacters) {
|
$: if (isLoaded && selectedCharacters) {
|
||||||
const ids = selectedCharacters.map(char => char.id);
|
const ids = selectedCharacters.map(char => char.id);
|
||||||
@@ -66,42 +149,19 @@
|
|||||||
$: columnVisibility = data.columnVisibility || {};
|
$: columnVisibility = data.columnVisibility || {};
|
||||||
$: hasWon = selectedCharacters.some(char => char.id === dailyCharacter.id);
|
$: hasWon = selectedCharacters.some(char => char.id === dailyCharacter.id);
|
||||||
|
|
||||||
// Hint availability tracking for unlock animations
|
function handleCharacterSelect(character: CharacterWithRelations) {
|
||||||
$: isOriginAvailable = selectedCharacters.length >= 5;
|
|
||||||
$: isFruitAvailable = selectedCharacters.length >= 10;
|
|
||||||
$: isAffiliationAvailable = selectedCharacters.length >= 15;
|
|
||||||
|
|
||||||
// Track hint unlocks
|
|
||||||
$: if (isLoaded) {
|
|
||||||
if (isOriginAvailable && !wasOriginAvailable) {
|
|
||||||
showOriginUnlock = true;
|
|
||||||
setTimeout(() => showOriginUnlock = false, 600);
|
|
||||||
}
|
|
||||||
wasOriginAvailable = isOriginAvailable;
|
|
||||||
|
|
||||||
if (isFruitAvailable && !wasFruitAvailable) {
|
|
||||||
showFruitUnlock = true;
|
|
||||||
setTimeout(() => showFruitUnlock = false, 600);
|
|
||||||
}
|
|
||||||
wasFruitAvailable = isFruitAvailable;
|
|
||||||
|
|
||||||
if (isAffiliationAvailable && !wasAffiliationAvailable) {
|
|
||||||
showAffiliationUnlock = true;
|
|
||||||
setTimeout(() => showAffiliationUnlock = false, 600);
|
|
||||||
}
|
|
||||||
wasAffiliationAvailable = isAffiliationAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCharacterSelect(event: CustomEvent) {
|
|
||||||
const character = event.detail;
|
|
||||||
selectCharacter(character);
|
selectCharacter(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectCharacter(character: any) {
|
function selectCharacter(character: CharacterWithRelations) {
|
||||||
|
const previousGuessCount = selectedCharacters.length;
|
||||||
selectedCharacters = [character, ...selectedCharacters];
|
selectedCharacters = [character, ...selectedCharacters];
|
||||||
|
syncHintAvailability(previousGuessCount, selectedCharacters.length, isLoaded);
|
||||||
|
|
||||||
// Check if player won
|
// Check if player won
|
||||||
if (character.id === dailyCharacter.id) {
|
if (character.id === dailyCharacter.id) {
|
||||||
|
const triedCharacterIds = selectedCharacters.map(selected => selected.id);
|
||||||
|
|
||||||
// Send request to record win in database
|
// Send request to record win in database
|
||||||
fetch('/daily', {
|
fetch('/daily', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -110,7 +170,8 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
characterId: dailyCharacter.id,
|
characterId: dailyCharacter.id,
|
||||||
tryCount: selectedCharacters.length
|
tryCount: selectedCharacters.length,
|
||||||
|
triedCharacterIds
|
||||||
})
|
})
|
||||||
}).catch(err => console.error('Failed to record win:', err));
|
}).catch(err => console.error('Failed to record win:', err));
|
||||||
|
|
||||||
@@ -122,13 +183,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetHistory() {
|
function resetHistory() {
|
||||||
|
const previousGuessCount = selectedCharacters.length;
|
||||||
selectedCharacters = [];
|
selectedCharacters = [];
|
||||||
|
syncHintAvailability(previousGuessCount, 0);
|
||||||
localStorage.removeItem('dailyCharacterHistory');
|
localStorage.removeItem('dailyCharacterHistory');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>OnePieceDle - Mode du jour</title>
|
<title>{$t.game.daily.metaTitle}</title>
|
||||||
<style>
|
<style>
|
||||||
@keyframes shadow-pulse {
|
@keyframes shadow-pulse {
|
||||||
0% {
|
0% {
|
||||||
@@ -198,7 +261,7 @@
|
|||||||
<main
|
<main
|
||||||
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin ? 'moria-screen-chaos' : ''}"
|
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin ? 'moria-screen-chaos' : ''}"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
<div class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||||
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
||||||
|
|
||||||
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
|
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
|
||||||
@@ -206,10 +269,10 @@
|
|||||||
<div class="flex w-full items-center justify-between gap-4">
|
<div class="flex w-full items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
|
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
|
||||||
Personnage du jour
|
{$t.game.daily.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-amber-300">
|
<p class="mt-2 text-sm text-amber-300">
|
||||||
{data.winCount} {data.winCount > 1 ? 'personnes' : 'personne'} {data.winCount > 1 ? 'ont' : 'a'} trouvé aujourd'hui 🎉
|
{data.winCount} {data.winCount > 1 ? $t.game.daily.winsPeoplePlural : $t.game.daily.winsPeopleSingular} {data.winCount > 1 ? $t.game.daily.winsVerbPlural : $t.game.daily.winsVerbSingular} {$t.game.daily.winsSuffix}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if hasWon}
|
{#if hasWon}
|
||||||
@@ -217,12 +280,12 @@
|
|||||||
class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
||||||
onclick={resetHistory}
|
onclick={resetHistory}
|
||||||
>
|
>
|
||||||
Recommencer
|
{$t.game.daily.reset}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
|
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
|
||||||
Devine le personnage. Chaque indice se débloque après un certain nombre de tentatives. Bonne chance !
|
{$t.game.daily.description}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -247,39 +310,14 @@
|
|||||||
<CharacterSearchInput
|
<CharacterSearchInput
|
||||||
{characters}
|
{characters}
|
||||||
{selectedCharacters}
|
{selectedCharacters}
|
||||||
on:select={handleCharacterSelect}
|
onSelect={handleCharacterSelect}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
{#if hasWon && data.friendsTodayResults && data.friendsTodayResults.length > 0}
|
{#if hasWon && data.friendsTodayResults && data.friendsTodayResults.length > 0}
|
||||||
<section class="mt-6 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<FriendsTodaySection friendsTodayResults={data.friendsTodayResults} />
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-amber-100 text-center">Tes amis aujourd'hui</p>
|
|
||||||
<div class="mt-4 space-y-2">
|
|
||||||
{#each data.friendsTodayResults as friendResult}
|
|
||||||
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-slate-950/50 px-4 py-2">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
{#if friendResult.image}
|
|
||||||
<img
|
|
||||||
src={friendResult.image}
|
|
||||||
alt={friendResult.name}
|
|
||||||
class="h-8 w-8 rounded-full border border-white/20 object-cover"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-amber-300/20 text-xs font-semibold text-amber-100">
|
|
||||||
{friendResult.name?.charAt(0).toUpperCase() || 'U'}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<p class="text-sm font-semibold text-slate-100">{friendResult.name}</p>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-amber-300">
|
|
||||||
{friendResult.tryCount} {friendResult.tryCount > 1 ? 'coups' : 'coup'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<GuessHistoryTable
|
<GuessHistoryTable
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { getDateKey } from '$lib/server/daily-character';
|
|||||||
|
|
||||||
export async function POST({ request, locals }) {
|
export async function POST({ request, locals }) {
|
||||||
try {
|
try {
|
||||||
const { characterId, tryCount } = await request.json();
|
const { characterId, tryCount, triedCharacterIds } = await request.json();
|
||||||
|
const normalizedTriedCharacterIds = Array.isArray(triedCharacterIds)
|
||||||
|
? triedCharacterIds.filter((id): id is string => typeof id === 'string')
|
||||||
|
: [];
|
||||||
|
|
||||||
if (!characterId) {
|
if (!characterId) {
|
||||||
return json({ error: 'Missing characterId' }, { status: 400 });
|
return json({ error: 'Missing characterId' }, { status: 400 });
|
||||||
@@ -51,7 +54,8 @@ export async function POST({ request, locals }) {
|
|||||||
await db.insert(userCharacterHistory).values({
|
await db.insert(userCharacterHistory).values({
|
||||||
userId: locals.user.id,
|
userId: locals.user.id,
|
||||||
characterHistoryId: todayHistoryEntry.id,
|
characterHistoryId: todayHistoryEntry.id,
|
||||||
tryCount: tryCount
|
tryCount: tryCount,
|
||||||
|
triedCharacterIds: normalizedTriedCharacterIds
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getAllCharacters } from '$lib/server/daily-character';
|
|||||||
import { like } from 'drizzle-orm';
|
import { like } from 'drizzle-orm';
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
let characters = await getAllCharacters();
|
const characters = await getAllCharacters();
|
||||||
|
|
||||||
// Load column visibility config
|
// Load column visibility config
|
||||||
const columnConfig = await db
|
const columnConfig = await db
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
|
import CharacterSearchInput from '$lib/components/CharacterSearchInput.svelte';
|
||||||
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
|
import GuessHistoryTable from '$lib/components/GuessHistoryTable.svelte';
|
||||||
import WinPanel from '$lib/components/WinPanel.svelte';
|
import WinPanel from '$lib/components/WinPanel.svelte';
|
||||||
import HintsPanel from '$lib/components/HintsPanel.svelte';
|
import HintsPanel from '$lib/components/HintsPanel.svelte';
|
||||||
|
import type { CharacterWithRelations } from '$lib/server/daily-character.js';
|
||||||
|
import { language, t } from '$lib/i18n';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
let selectedCharacters: any[] = [];
|
let selectedCharacters: CharacterWithRelations[] = [];
|
||||||
let currentCharacter: any = null;
|
let currentCharacter: CharacterWithRelations | null = null;
|
||||||
let isLoaded = false;
|
let isLoaded = false;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
type ArcFilterOption = { id: string; name: string };
|
||||||
|
let allCharacters: CharacterWithRelations[] = [];
|
||||||
|
let characters: CharacterWithRelations[] = [];
|
||||||
|
let availableArcs: ArcFilterOption[] = [];
|
||||||
|
let hasWon = false;
|
||||||
let columnVisibility: Record<string, boolean> = {};
|
let columnVisibility: Record<string, boolean> = {};
|
||||||
const columnDisplayNames: Record<string, string> = {
|
let columnDisplayNames: Record<string, string> = {};
|
||||||
status: 'Statut',
|
|
||||||
gender: 'Genre',
|
|
||||||
affiliations: 'Affiliations',
|
|
||||||
devilFruitType: 'Fruit',
|
|
||||||
haki: 'Haki',
|
|
||||||
bounty: 'Prime',
|
|
||||||
height: 'Taille',
|
|
||||||
origin: 'Origine',
|
|
||||||
arc: 'Arc'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Character filters
|
// Character filters
|
||||||
let characterFilters = {
|
let characterFilters = {
|
||||||
@@ -31,17 +28,90 @@
|
|||||||
hasDevilFruit: null as boolean | null, // null = all, true = with fruit, false = without fruit
|
hasDevilFruit: null as boolean | null, // null = all, true = with fruit, false = without fruit
|
||||||
status: [] as string[],
|
status: [] as string[],
|
||||||
hasHeight: false,
|
hasHeight: false,
|
||||||
|
hasAge: false,
|
||||||
hasOrigin: false,
|
hasOrigin: false,
|
||||||
arcs: [] as string[]
|
arcs: [] as string[]
|
||||||
};
|
};
|
||||||
|
|
||||||
let wasOriginAvailable = false;
|
|
||||||
let wasFruitAvailable = false;
|
|
||||||
let wasAffiliationAvailable = false;
|
|
||||||
let showOriginUnlock = false;
|
let showOriginUnlock = false;
|
||||||
let showFruitUnlock = false;
|
let showFruitUnlock = false;
|
||||||
let showAffiliationUnlock = false;
|
let showAffiliationUnlock = false;
|
||||||
let isGeckoMoriaWin = false;
|
let isGeckoMoriaWin = false;
|
||||||
|
let originUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let fruitUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let affiliationUnlockTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function clearUnlockTimeout(timeout: ReturnType<typeof setTimeout> | null) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pulseUnlock(type: 'origin' | 'fruit' | 'affiliation') {
|
||||||
|
if (type === 'origin') {
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
showOriginUnlock = true;
|
||||||
|
originUnlockTimeout = setTimeout(() => {
|
||||||
|
showOriginUnlock = false;
|
||||||
|
originUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'fruit') {
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
showFruitUnlock = true;
|
||||||
|
fruitUnlockTimeout = setTimeout(() => {
|
||||||
|
showFruitUnlock = false;
|
||||||
|
fruitUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
showAffiliationUnlock = true;
|
||||||
|
affiliationUnlockTimeout = setTimeout(() => {
|
||||||
|
showAffiliationUnlock = false;
|
||||||
|
affiliationUnlockTimeout = null;
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncHintAvailability(previousGuessCount: number, nextGuessCount: number, animateUnlocks = false) {
|
||||||
|
const nextOriginAvailable = nextGuessCount >= 5;
|
||||||
|
const nextFruitAvailable = nextGuessCount >= 10;
|
||||||
|
const nextAffiliationAvailable = nextGuessCount >= 15;
|
||||||
|
|
||||||
|
if (animateUnlocks && nextOriginAvailable && previousGuessCount < 5) {
|
||||||
|
pulseUnlock('origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animateUnlocks && nextFruitAvailable && previousGuessCount < 10) {
|
||||||
|
pulseUnlock('fruit');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animateUnlocks && nextAffiliationAvailable && previousGuessCount < 15) {
|
||||||
|
pulseUnlock('affiliation');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextOriginAvailable) {
|
||||||
|
showOriginUnlock = false;
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
originUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextFruitAvailable) {
|
||||||
|
showFruitUnlock = false;
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
fruitUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextAffiliationAvailable) {
|
||||||
|
showAffiliationUnlock = false;
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
affiliationUnlockTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Load from localStorage on mount
|
// Load from localStorage on mount
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -56,6 +126,7 @@
|
|||||||
try {
|
try {
|
||||||
columnVisibility = JSON.parse(storedColumnVisibility);
|
columnVisibility = JSON.parse(storedColumnVisibility);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('Failed to parse column visibility', e);
|
||||||
columnVisibility = data.columnVisibility || {};
|
columnVisibility = data.columnVisibility || {};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -71,6 +142,9 @@
|
|||||||
if (!characterFilters.arcs) {
|
if (!characterFilters.arcs) {
|
||||||
characterFilters.arcs = [];
|
characterFilters.arcs = [];
|
||||||
}
|
}
|
||||||
|
if (typeof characterFilters.hasAge !== 'boolean') {
|
||||||
|
characterFilters.hasAge = false;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse filters', e);
|
console.error('Failed to parse filters', e);
|
||||||
}
|
}
|
||||||
@@ -86,18 +160,19 @@
|
|||||||
const historyIds = JSON.parse(storedHistoryIds);
|
const historyIds = JSON.parse(storedHistoryIds);
|
||||||
|
|
||||||
// Find the character object by ID
|
// Find the character object by ID
|
||||||
currentCharacter = characters.find((c: any) => c.id === charId);
|
currentCharacter = characters.find((c: CharacterWithRelations) => c.id === charId) || null;
|
||||||
|
|
||||||
// Find all character objects by their IDs
|
// Find all character objects by their IDs
|
||||||
selectedCharacters = historyIds
|
selectedCharacters = historyIds
|
||||||
.map((id: string) => characters.find((c: any) => c.id === id))
|
.map((id: string) => characters.find((c: CharacterWithRelations) => c.id === id))
|
||||||
.filter((c: any) => c !== undefined);
|
.filter((c: CharacterWithRelations | undefined) => !!c) as CharacterWithRelations[];
|
||||||
|
|
||||||
// If character not found, generate a new one
|
// If character not found, generate a new one
|
||||||
if (!currentCharacter) {
|
if (!currentCharacter) {
|
||||||
generateNewCharacter();
|
generateNewCharacter();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('Failed to parse character data', e);
|
||||||
// If parsing fails, generate a new character
|
// If parsing fails, generate a new character
|
||||||
generateNewCharacter();
|
generateNewCharacter();
|
||||||
}
|
}
|
||||||
@@ -105,9 +180,16 @@
|
|||||||
generateNewCharacter();
|
generateNewCharacter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncHintAvailability(0, selectedCharacters.length);
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearUnlockTimeout(originUnlockTimeout);
|
||||||
|
clearUnlockTimeout(fruitUnlockTimeout);
|
||||||
|
clearUnlockTimeout(affiliationUnlockTimeout);
|
||||||
|
});
|
||||||
|
|
||||||
// Save score to localStorage whenever it changes
|
// Save score to localStorage whenever it changes
|
||||||
$: if (isLoaded) {
|
$: if (isLoaded) {
|
||||||
localStorage.setItem('infiniteScore', score.toString());
|
localStorage.setItem('infiniteScore', score.toString());
|
||||||
@@ -130,26 +212,46 @@
|
|||||||
|
|
||||||
// Save selected character IDs to localStorage whenever it changes
|
// Save selected character IDs to localStorage whenever it changes
|
||||||
$: if (isLoaded) {
|
$: if (isLoaded) {
|
||||||
const selectedIds = selectedCharacters.map((c: any) => c.id);
|
const selectedIds = selectedCharacters.map((c: CharacterWithRelations) => c.id);
|
||||||
localStorage.setItem('infiniteSelectedCharacterIds', JSON.stringify(selectedIds));
|
localStorage.setItem('infiniteSelectedCharacterIds', JSON.stringify(selectedIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
$: allCharacters = data.characters || [];
|
$: allCharacters = data.characters || [];
|
||||||
|
$: isFrench = $language === 'fr';
|
||||||
|
|
||||||
|
function getDisplayArcName(character: CharacterWithRelations, useFrench: boolean): string | null {
|
||||||
|
if (useFrench && typeof character.frArcName === 'string' && character.frArcName.length > 0) {
|
||||||
|
return character.frArcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.arcName;
|
||||||
|
}
|
||||||
|
|
||||||
// Extract unique arcs from all characters
|
// Extract unique arcs from all characters
|
||||||
$: availableArcs = [
|
$: {
|
||||||
...new Map(
|
const useFrench = isFrench;
|
||||||
|
const arcMap = new Map<string, ArcFilterOption>(
|
||||||
allCharacters
|
allCharacters
|
||||||
.filter((char: any) => char.arcId && char.arcName)
|
.filter(
|
||||||
.map((char: any) => [char.arcId, { id: char.arcId, name: char.arcName }])
|
(char: CharacterWithRelations): char is CharacterWithRelations & { arcId: string } =>
|
||||||
).values()
|
typeof char.arcId === 'string' &&
|
||||||
]
|
char.arcId.length > 0 &&
|
||||||
.sort((a: any, b: any) => (a.name || '').localeCompare(b.name || ''));
|
typeof getDisplayArcName(char, useFrench) === 'string' &&
|
||||||
|
getDisplayArcName(char, useFrench)!.length > 0
|
||||||
|
)
|
||||||
|
.map((char: CharacterWithRelations & { arcId: string }) => [
|
||||||
|
char.arcId,
|
||||||
|
{ id: char.arcId, name: getDisplayArcName(char, useFrench) as string }
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
availableArcs = [...arcMap.values()].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
// Filter characters based on selected filters
|
// Filter characters based on selected filters
|
||||||
$: characters = allCharacters.filter((char: any) => {
|
$: characters = allCharacters.filter((char: CharacterWithRelations) => {
|
||||||
// Gender filter
|
// Gender filter
|
||||||
if (characterFilters.gender.length > 0 && !characterFilters.gender.includes(char.gender)) {
|
if (characterFilters.gender.length > 0 && (char.gender == null || !characterFilters.gender.includes(char.gender))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,67 +281,68 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Age filter
|
||||||
|
if (characterFilters.hasAge && (char.age === null || char.age === undefined)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Origin filter
|
// Origin filter
|
||||||
if (characterFilters.hasOrigin && (char.origin === null || char.origin === undefined || char.origin === '')) {
|
if (characterFilters.hasOrigin && (char.origin === null || char.origin === undefined || char.origin === '')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arc filter
|
// Arc filter
|
||||||
if (characterFilters.arcs.length > 0 && !characterFilters.arcs.includes(char.arcId)) {
|
if (characterFilters.arcs.length > 0 && (char.arcId == null || !characterFilters.arcs.includes(char.arcId))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
$: hasWon = currentCharacter && selectedCharacters.some(char => char.id === currentCharacter.id);
|
$: {
|
||||||
|
const currentCharacterId = currentCharacter?.id;
|
||||||
|
hasWon = currentCharacterId != null && selectedCharacters.some(char => char.id === currentCharacterId);
|
||||||
|
}
|
||||||
$: if (hasWon && currentCharacter?.id === 'gecko_moria_gecko_moria') {
|
$: if (hasWon && currentCharacter?.id === 'gecko_moria_gecko_moria') {
|
||||||
isGeckoMoriaWin = true;
|
isGeckoMoriaWin = true;
|
||||||
} else if (!hasWon) {
|
} else if (!hasWon) {
|
||||||
isGeckoMoriaWin = false;
|
isGeckoMoriaWin = false;
|
||||||
}
|
}
|
||||||
|
$: columnDisplayNames = {
|
||||||
// Hint availability tracking for unlock animations
|
status: $t.game.components.guessHistory.status,
|
||||||
$: isOriginAvailable = selectedCharacters.length >= 5;
|
gender: $t.game.components.guessHistory.gender,
|
||||||
$: isFruitAvailable = selectedCharacters.length >= 10;
|
affiliations: $t.game.components.guessHistory.affiliations,
|
||||||
$: isAffiliationAvailable = selectedCharacters.length >= 15;
|
devilFruitType: $t.game.components.guessHistory.fruit,
|
||||||
|
haki: $t.game.components.guessHistory.haki,
|
||||||
// Track hint unlocks
|
bounty: $t.game.components.guessHistory.bounty,
|
||||||
$: if (isLoaded) {
|
height: $t.game.components.guessHistory.height,
|
||||||
if (isOriginAvailable && !wasOriginAvailable) {
|
age: $t.game.components.guessHistory.age,
|
||||||
showOriginUnlock = true;
|
origin: $t.game.components.guessHistory.origin,
|
||||||
setTimeout(() => (showOriginUnlock = false), 600);
|
arc: $t.game.components.guessHistory.arc
|
||||||
}
|
};
|
||||||
wasOriginAvailable = isOriginAvailable;
|
|
||||||
|
|
||||||
if (isFruitAvailable && !wasFruitAvailable) {
|
|
||||||
showFruitUnlock = true;
|
|
||||||
setTimeout(() => (showFruitUnlock = false), 600);
|
|
||||||
}
|
|
||||||
wasFruitAvailable = isFruitAvailable;
|
|
||||||
|
|
||||||
if (isAffiliationAvailable && !wasAffiliationAvailable) {
|
|
||||||
showAffiliationUnlock = true;
|
|
||||||
setTimeout(() => (showAffiliationUnlock = false), 600);
|
|
||||||
}
|
|
||||||
wasAffiliationAvailable = isAffiliationAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNewCharacter() {
|
function generateNewCharacter() {
|
||||||
if (characters.length === 0) return;
|
if (characters.length === 0) return;
|
||||||
currentCharacter = characters[Math.floor(Math.random() * characters.length)];
|
currentCharacter = characters[Math.floor(Math.random() * characters.length)];
|
||||||
|
syncHintAvailability(selectedCharacters.length, 0);
|
||||||
selectedCharacters = [];
|
selectedCharacters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCharacterSelect(event: CustomEvent) {
|
function handleCharacterSelect(character: CharacterWithRelations) {
|
||||||
const character = event.detail;
|
|
||||||
selectCharacter(character);
|
selectCharacter(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectCharacter(character: any) {
|
function selectCharacter(character: CharacterWithRelations) {
|
||||||
|
const current = currentCharacter;
|
||||||
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousGuessCount = selectedCharacters.length;
|
||||||
selectedCharacters = [character, ...selectedCharacters];
|
selectedCharacters = [character, ...selectedCharacters];
|
||||||
|
syncHintAvailability(previousGuessCount, selectedCharacters.length, isLoaded);
|
||||||
|
|
||||||
// Check if player won
|
// Check if player won
|
||||||
if (character.id === currentCharacter.id) {
|
if (character.id === current.id) {
|
||||||
// Increment score (saved to localStorage via reactive statement)
|
// Increment score (saved to localStorage via reactive statement)
|
||||||
score++;
|
score++;
|
||||||
// Don't auto-generate next character - wait for user to click "Recommencer"
|
// Don't auto-generate next character - wait for user to click "Recommencer"
|
||||||
@@ -265,10 +368,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function revealAnswer() {
|
function revealAnswer() {
|
||||||
|
if (!currentCharacter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset score (strike)
|
// Reset score (strike)
|
||||||
score = 0;
|
score = 0;
|
||||||
// Add the current character as the correct answer
|
// Add the current character as the correct answer
|
||||||
|
const previousGuessCount = selectedCharacters.length;
|
||||||
selectedCharacters = [currentCharacter, ...selectedCharacters];
|
selectedCharacters = [currentCharacter, ...selectedCharacters];
|
||||||
|
syncHintAvailability(previousGuessCount, selectedCharacters.length, isLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleGenderFilter(gender: string) {
|
function toggleGenderFilter(gender: string) {
|
||||||
@@ -325,6 +434,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleAgeFilter() {
|
||||||
|
characterFilters.hasAge = !characterFilters.hasAge;
|
||||||
|
if (!hasWon) {
|
||||||
|
generateNewCharacter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleOriginFilter() {
|
function toggleOriginFilter() {
|
||||||
characterFilters.hasOrigin = !characterFilters.hasOrigin;
|
characterFilters.hasOrigin = !characterFilters.hasOrigin;
|
||||||
// Regenerate character with new filters
|
// Regenerate character with new filters
|
||||||
@@ -352,6 +468,7 @@
|
|||||||
hasDevilFruit: null,
|
hasDevilFruit: null,
|
||||||
status: [],
|
status: [],
|
||||||
hasHeight: false,
|
hasHeight: false,
|
||||||
|
hasAge: false,
|
||||||
hasOrigin: false,
|
hasOrigin: false,
|
||||||
arcs: []
|
arcs: []
|
||||||
};
|
};
|
||||||
@@ -392,20 +509,26 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>OnePieceDle - Mode Infini</title>
|
<title>{$t.game.infinite.metaTitle}</title>
|
||||||
<style>
|
<style>
|
||||||
@keyframes shadow-pulse {
|
@keyframes shadow-pulse {
|
||||||
0% {
|
0% {
|
||||||
text-shadow: 0 0 20px rgba(0, 0, 0, 0.5), 0 0 40px rgba(50, 50, 50, 0.8);
|
text-shadow:
|
||||||
|
0 0 20px rgba(0, 0, 0, 0.5),
|
||||||
|
0 0 40px rgba(50, 50, 50, 0.8);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
text-shadow: 0 0 60px rgba(0, 0, 0, 0.9), 0 0 100px rgba(30, 30, 30, 1),
|
text-shadow:
|
||||||
|
0 0 60px rgba(0, 0, 0, 0.9),
|
||||||
|
0 0 100px rgba(30, 30, 30, 1),
|
||||||
inset 0 0 50px rgba(0, 0, 0, 0.7);
|
inset 0 0 50px rgba(0, 0, 0, 0.7);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
text-shadow: 0 0 20px rgba(0, 0, 0, 0.5), 0 0 40px rgba(50, 50, 50, 0.8);
|
text-shadow:
|
||||||
|
0 0 20px rgba(0, 0, 0, 0.5),
|
||||||
|
0 0 40px rgba(50, 50, 50, 0.8);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,34 +584,37 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin ? 'moria-screen-chaos' : ''}"
|
class="relative min-h-screen overflow-hidden bg-slate-950 text-slate-100 {isGeckoMoriaWin
|
||||||
|
? 'moria-screen-chaos'
|
||||||
|
: ''}"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"
|
class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)] opacity-20 mix-blend-screen"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
|
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8 sm:py-10">
|
||||||
<header class="flex flex-col items-start gap-6 w-full">
|
<header class="flex w-full flex-col items-start gap-6">
|
||||||
<div class="flex w-full items-center justify-between gap-4">
|
<div class="flex w-full items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 sm:text-5xl">
|
<h1 class="text-3xl font-black tracking-[0.25em] text-amber-50 uppercase sm:text-5xl">
|
||||||
Mode Infini
|
{$t.game.infinite.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-2xl font-bold text-amber-300">Score: {score}</p>
|
<p class="mt-2 text-2xl font-bold text-amber-300">{$t.game.infinite.score}: {score}</p>
|
||||||
</div>
|
</div>
|
||||||
{#if score > 0}
|
{#if score > 0}
|
||||||
<button
|
<button
|
||||||
class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
class="rounded-full border border-amber-200/40 bg-transparent px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-200 hover:text-amber-50"
|
||||||
onclick={resetScore}
|
onclick={resetScore}
|
||||||
>
|
>
|
||||||
Réinitialiser
|
{$t.game.infinite.resetScore}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
|
<p class="max-w-2xl text-base text-slate-200 sm:text-lg">
|
||||||
Devine des personnages à l'infini ! Chaque indice se débloque après un certain nombre de
|
{$t.game.infinite.description}
|
||||||
tentatives. Bonne chance !
|
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -496,17 +622,13 @@
|
|||||||
{#if currentCharacter}
|
{#if currentCharacter}
|
||||||
{#if hasWon}
|
{#if hasWon}
|
||||||
<div>
|
<div>
|
||||||
<WinPanel
|
<WinPanel selectedCharacter={currentCharacter} {selectedCharacters} {isGeckoMoriaWin} />
|
||||||
selectedCharacter={currentCharacter}
|
|
||||||
{selectedCharacters}
|
|
||||||
{isGeckoMoriaWin}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={nextCharacter}
|
onclick={nextCharacter}
|
||||||
class="mt-4 w-full rounded-full bg-emerald-500 px-6 py-2 text-sm font-semibold text-white transition hover:bg-emerald-600"
|
class="mt-4 w-full rounded-full bg-emerald-500 px-6 py-2 text-sm font-semibold text-white transition hover:bg-emerald-600"
|
||||||
>
|
>
|
||||||
Recommencer
|
{$t.game.infinite.nextCharacter}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -518,25 +640,27 @@
|
|||||||
{showFruitUnlock}
|
{showFruitUnlock}
|
||||||
{showAffiliationUnlock}
|
{showAffiliationUnlock}
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-center mt-2">
|
<div class="mt-2 flex justify-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={revealAnswer}
|
onclick={revealAnswer}
|
||||||
class="rounded-lg border border-red-600/40 bg-red-900/20 px-4 py-2 text-sm text-red-300 transition hover:border-red-500 hover:bg-red-900/40 hover:text-red-200"
|
class="rounded-lg border border-red-600/40 bg-red-900/20 px-4 py-2 text-sm text-red-300 transition hover:border-red-500 hover:bg-red-900/40 hover:text-red-200"
|
||||||
>
|
>
|
||||||
Révéler la réponse
|
{$t.game.infinite.revealAnswer}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<CharacterSearchInput
|
<CharacterSearchInput
|
||||||
{characters}
|
{characters}
|
||||||
{selectedCharacters}
|
{selectedCharacters}
|
||||||
on:select={handleCharacterSelect}
|
onSelect={handleCharacterSelect}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur">
|
<div
|
||||||
<p class="text-center text-slate-300">Chargement du personnage...</p>
|
class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
||||||
|
>
|
||||||
|
<p class="text-center text-slate-300">{$t.game.infinite.loadingCharacter}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
@@ -550,16 +674,18 @@
|
|||||||
|
|
||||||
<!-- Character Filters -->
|
<!-- Character Filters -->
|
||||||
<section class="mt-6">
|
<section class="mt-6">
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 sm:p-4 backdrop-blur">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 backdrop-blur sm:p-4">
|
||||||
<div class="mb-3 flex items-center justify-between gap-3">
|
<div class="mb-3 flex items-center justify-between gap-3">
|
||||||
<h3 class="text-xs font-semibold uppercase tracking-[0.2em] text-amber-200">Filtres de personnages</h3>
|
<h3 class="text-xs font-semibold tracking-[0.2em] text-amber-200 uppercase">
|
||||||
{#if characterFilters.gender.length > 0 || characterFilters.hasHaki || characterFilters.hasDevilFruit !== null || characterFilters.status.length > 0 || characterFilters.hasHeight || characterFilters.hasOrigin || characterFilters.arcs.length > 0}
|
{$t.game.infinite.filtersTitle}
|
||||||
|
</h3>
|
||||||
|
{#if characterFilters.gender.length > 0 || characterFilters.hasHaki || characterFilters.hasDevilFruit !== null || characterFilters.status.length > 0 || characterFilters.hasHeight || characterFilters.hasAge || characterFilters.hasOrigin || characterFilters.arcs.length > 0}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={clearAllFilters}
|
onclick={clearAllFilters}
|
||||||
class="text-xs text-red-300 hover:text-red-200 transition"
|
class="text-xs text-red-300 transition hover:text-red-200"
|
||||||
>
|
>
|
||||||
Réinitialiser
|
{$t.game.infinite.clearFilters}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -567,17 +693,19 @@
|
|||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<!-- Gender Filter -->
|
<!-- Gender Filter -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-slate-400 mb-2">Genre</p>
|
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterGender}</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each ['Male', 'Female'] as gender}
|
{#each ['Male', 'Female'] as gender (gender)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => toggleGenderFilter(gender)}
|
onclick={() => toggleGenderFilter(gender)}
|
||||||
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.gender.includes(gender)
|
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.gender.includes(
|
||||||
|
gender
|
||||||
|
)
|
||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
{gender === 'Male' ? 'Homme' : 'Femme'}
|
{gender === 'Male' ? $t.game.infinite.male : $t.game.infinite.female}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -585,17 +713,19 @@
|
|||||||
|
|
||||||
<!-- Status Filter -->
|
<!-- Status Filter -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-slate-400 mb-2">Statut</p>
|
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterStatus}</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each ['Alive', 'Dead', 'Unknown'] as status}
|
{#each ['Alive', 'Dead', 'Unknown'] as status (status)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => toggleStatusFilter(status)}
|
onclick={() => toggleStatusFilter(status)}
|
||||||
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.status.includes(status)
|
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.status.includes(
|
||||||
|
status
|
||||||
|
)
|
||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
{status === 'Alive' ? 'Vivant' : status === 'Dead' ? 'Mort' : 'Inconnu'}
|
{status === 'Alive' ? $t.game.infinite.alive : status === 'Dead' ? $t.game.infinite.dead : $t.game.infinite.unknown}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -603,7 +733,7 @@
|
|||||||
|
|
||||||
<!-- Haki Filter -->
|
<!-- Haki Filter -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-slate-400 mb-2">Capacités</p>
|
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterAbilities}</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -612,25 +742,30 @@
|
|||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
A du Haki
|
{$t.game.infinite.hasHaki}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={toggleDevilFruitFilter}
|
onclick={toggleDevilFruitFilter}
|
||||||
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.hasDevilFruit === true
|
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.hasDevilFruit ===
|
||||||
|
true
|
||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: characterFilters.hasDevilFruit === false
|
: characterFilters.hasDevilFruit === false
|
||||||
? 'border-purple-300/50 bg-purple-300/10 text-purple-100 hover:bg-purple-300/20'
|
? 'border-purple-300/50 bg-purple-300/10 text-purple-100 hover:bg-purple-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
{characterFilters.hasDevilFruit === null ? 'Fruit (Tous)' : characterFilters.hasDevilFruit ? 'Avec Fruit' : 'Sans Fruit'}
|
{characterFilters.hasDevilFruit === null
|
||||||
|
? $t.game.infinite.fruitAll
|
||||||
|
: characterFilters.hasDevilFruit
|
||||||
|
? $t.game.infinite.withFruit
|
||||||
|
: $t.game.infinite.withoutFruit}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Informations Filter -->
|
<!-- Informations Filter -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-slate-400 mb-2">Informations</p>
|
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterInformation}</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -639,7 +774,16 @@
|
|||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
Taille définie
|
{$t.game.infinite.heightDefined}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={toggleAgeFilter}
|
||||||
|
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.hasAge
|
||||||
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
|
>
|
||||||
|
{$t.game.infinite.ageDefined}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -648,20 +792,22 @@
|
|||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
Origine définie
|
{$t.game.infinite.originDefined}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Arc Filter -->
|
<!-- Arc Filter -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-slate-400 mb-2">Arcs</p>
|
<p class="mb-2 text-xs text-slate-400">{$t.game.infinite.filterArcs}</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each availableArcs as arc (arc.id)}
|
{#each availableArcs as arc (arc.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => toggleArcFilter(arc.id)}
|
onclick={() => toggleArcFilter(arc.id)}
|
||||||
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.arcs.includes(arc.id)
|
class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors {characterFilters.arcs.includes(
|
||||||
|
arc.id
|
||||||
|
)
|
||||||
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
? 'border-amber-300/50 bg-amber-300/10 text-amber-100 hover:bg-amber-300/20'
|
||||||
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
: 'border-white/20 bg-slate-900/40 text-slate-400 hover:bg-slate-900/60'}"
|
||||||
>
|
>
|
||||||
@@ -671,8 +817,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xs text-slate-500 mt-2">
|
<p class="mt-2 text-xs text-slate-500">
|
||||||
{characters.length} personnage{characters.length > 1 ? 's' : ''} disponible{characters.length > 1 ? 's' : ''}
|
{characters.length} {characters.length > 1 ? $t.game.infinite.availableCharactersPlural : $t.game.infinite.availableCharactersSingular}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -680,11 +826,15 @@
|
|||||||
|
|
||||||
<!-- Column Visibility Toggle -->
|
<!-- Column Visibility Toggle -->
|
||||||
<section class="mt-6">
|
<section class="mt-6">
|
||||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 sm:p-4 backdrop-blur">
|
<div class="rounded-2xl border border-white/10 bg-white/5 p-3 backdrop-blur sm:p-4">
|
||||||
<div class="mb-3 flex items-center justify-between gap-3">
|
<div class="mb-3 flex items-center justify-between gap-3">
|
||||||
<h3 class="text-xs font-semibold uppercase tracking-[0.2em] text-amber-200">Colonnes</h3>
|
<h3 class="text-xs font-semibold tracking-[0.2em] text-amber-200 uppercase">
|
||||||
|
{$t.game.infinite.columnsTitle}
|
||||||
|
</h3>
|
||||||
<p class="text-xs text-slate-400">
|
<p class="text-xs text-slate-400">
|
||||||
{Object.values(columnVisibility).filter(Boolean).length}/{Object.keys(columnVisibility).length}
|
{Object.values(columnVisibility).filter(Boolean).length}/{Object.keys(
|
||||||
|
columnVisibility
|
||||||
|
).length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { t } from '$lib/i18n';
|
||||||
import type { ActionData } from './$types';
|
import type { ActionData } from './$types';
|
||||||
|
|
||||||
export let form: ActionData;
|
export let form: ActionData;
|
||||||
@@ -24,11 +26,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>OnePieceDle - {isSignUp ? 'Inscription' : 'Connexion'}</title>
|
<title>OnePieceDle - {isSignUp ? $t.game.login.titleSignUp : $t.game.login.titleSignIn}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
|
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
<div class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"
|
class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"
|
||||||
></div>
|
></div>
|
||||||
@@ -41,7 +43,7 @@
|
|||||||
OnePieceDle
|
OnePieceDle
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-4 text-slate-300">
|
<p class="mt-4 text-slate-300">
|
||||||
{isSignUp ? 'Créer votre compte' : 'Bienvenue, pirate'}
|
{isSignUp ? $t.game.login.headerSignUp : $t.game.login.headerSignIn}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@
|
|||||||
{#if isSignUp}
|
{#if isSignUp}
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Nom
|
{$t.game.login.nameLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="name"
|
id="name"
|
||||||
@@ -71,7 +73,7 @@
|
|||||||
name="name"
|
name="name"
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
required
|
required
|
||||||
placeholder="Votre nom"
|
placeholder={$t.game.login.namePlaceholder}
|
||||||
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +83,7 @@
|
|||||||
{#if isSignUp}
|
{#if isSignUp}
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="username" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Nom d'utilisateur
|
{$t.game.login.usernameLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="username"
|
id="username"
|
||||||
@@ -89,7 +91,7 @@
|
|||||||
name="username"
|
name="username"
|
||||||
bind:value={username}
|
bind:value={username}
|
||||||
required
|
required
|
||||||
placeholder="ex: luffy_gear5"
|
placeholder={$t.game.login.usernamePlaceholder}
|
||||||
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,7 +100,7 @@
|
|||||||
<!-- Email / Username Field -->
|
<!-- Email / Username Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="identifier" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="identifier" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
{isSignUp ? 'E-mail' : 'E-mail ou nom d\'utilisateur'}
|
{isSignUp ? $t.game.login.identifierLabelSignUp : $t.game.login.identifierLabelSignIn}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="identifier"
|
id="identifier"
|
||||||
@@ -106,7 +108,7 @@
|
|||||||
name={isSignUp ? 'email' : 'identifier'}
|
name={isSignUp ? 'email' : 'identifier'}
|
||||||
bind:value={email}
|
bind:value={email}
|
||||||
required
|
required
|
||||||
placeholder={isSignUp ? 'votremail@email.com' : 'votremail@email.com ou luffy_gear5'}
|
placeholder={isSignUp ? $t.game.login.identifierPlaceholderSignUp : $t.game.login.identifierPlaceholderSignIn}
|
||||||
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +116,7 @@
|
|||||||
<!-- Password Field -->
|
<!-- Password Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="password" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Mot de passe
|
{$t.game.login.passwordLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
@@ -134,7 +136,7 @@
|
|||||||
for="confirmPassword"
|
for="confirmPassword"
|
||||||
class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100"
|
class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100"
|
||||||
>
|
>
|
||||||
Confirmer le mot de passe
|
{$t.game.login.confirmPasswordLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="confirmPassword"
|
id="confirmPassword"
|
||||||
@@ -161,20 +163,20 @@
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Chargement...' : isSignUp ? 'Créer un compte' : 'Se connecter'}
|
{isLoading ? $t.game.login.loading : isSignUp ? $t.game.login.submitSignUp : $t.game.login.submitSignIn}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Toggle Sign Up / Login -->
|
<!-- Toggle Sign Up / Login -->
|
||||||
<div class="mt-6 border-t border-white/10 pt-6">
|
<div class="mt-6 border-t border-white/10 pt-6">
|
||||||
<p class="text-center text-sm text-slate-400">
|
<p class="text-center text-sm text-slate-400">
|
||||||
{isSignUp ? 'Vous avez déjà un compte ?' : "Vous n'avez pas de compte ?"}
|
{isSignUp ? $t.game.login.togglePromptSignUp : $t.game.login.togglePromptSignIn}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={handleToggle}
|
on:click={handleToggle}
|
||||||
class="text-amber-300 transition hover:text-amber-200"
|
class="text-amber-300 transition hover:text-amber-200"
|
||||||
>
|
>
|
||||||
{isSignUp ? 'Se connecter' : "S'inscrire"}
|
{isSignUp ? $t.game.login.toggleActionSignUp : $t.game.login.toggleActionSignIn}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,8 +184,8 @@
|
|||||||
|
|
||||||
<!-- Back to Home -->
|
<!-- Back to Home -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="/" class="text-sm text-slate-400 transition hover:text-slate-300">
|
<a href={resolve("/")} class="text-sm text-slate-400 transition hover:text-slate-300">
|
||||||
← Retour à l'accueil
|
← {$t.game.login.backHome}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Actions, PageServerLoad } from './$types';
|
|||||||
import { auth } from '$lib/server/auth';
|
import { auth } from '$lib/server/auth';
|
||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { session, userCharacterHistory, characterHistory, character, friendship, user } from '$lib/server/db/schema';
|
import { session, userCharacterHistory, characterHistory, character, friendship, user } from '$lib/server/db/schema';
|
||||||
import { and, desc, eq, or, sql } from 'drizzle-orm';
|
import { and, desc, eq, inArray, or, sql } from 'drizzle-orm';
|
||||||
import { APIError } from 'better-auth/api';
|
import { APIError } from 'better-auth/api';
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
@@ -20,12 +20,13 @@ export const load: PageServerLoad = async (event) => {
|
|||||||
.where(eq(session.userId, event.locals.user.id));
|
.where(eq(session.userId, event.locals.user.id));
|
||||||
|
|
||||||
// Fetch daily history for this user
|
// Fetch daily history for this user
|
||||||
const dailyHistory = await db
|
const dailyHistoryRaw = await db
|
||||||
.select({
|
.select({
|
||||||
id: userCharacterHistory.id,
|
id: userCharacterHistory.id,
|
||||||
characterId: characterHistory.characterId,
|
characterId: characterHistory.characterId,
|
||||||
date: characterHistory.date,
|
date: characterHistory.date,
|
||||||
tryCount: userCharacterHistory.tryCount,
|
tryCount: userCharacterHistory.tryCount,
|
||||||
|
triedCharacterIds: userCharacterHistory.triedCharacterIds,
|
||||||
won: characterHistory.won,
|
won: characterHistory.won,
|
||||||
characterName: character.name,
|
characterName: character.name,
|
||||||
characterImage: character.pictureUrl
|
characterImage: character.pictureUrl
|
||||||
@@ -36,6 +37,30 @@ export const load: PageServerLoad = async (event) => {
|
|||||||
.where(eq(userCharacterHistory.userId, event.locals.user.id))
|
.where(eq(userCharacterHistory.userId, event.locals.user.id))
|
||||||
.orderBy(desc(characterHistory.date));
|
.orderBy(desc(characterHistory.date));
|
||||||
|
|
||||||
|
const uniqueTriedCharacterIds = Array.from(new Set(
|
||||||
|
dailyHistoryRaw.flatMap((entry) => entry.triedCharacterIds ?? [])
|
||||||
|
));
|
||||||
|
|
||||||
|
const triedCharacters = uniqueTriedCharacterIds.length > 0
|
||||||
|
? await db
|
||||||
|
.select({
|
||||||
|
id: character.id,
|
||||||
|
name: character.name,
|
||||||
|
pictureUrl: character.pictureUrl
|
||||||
|
})
|
||||||
|
.from(character)
|
||||||
|
.where(inArray(character.id, uniqueTriedCharacterIds))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const triedCharactersById = new Map(triedCharacters.map((entry) => [entry.id, entry]));
|
||||||
|
|
||||||
|
const dailyHistory = dailyHistoryRaw.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
triedCharacters: (entry.triedCharacterIds ?? [])
|
||||||
|
.map((characterId) => triedCharactersById.get(characterId))
|
||||||
|
.filter((triedEntry): triedEntry is (typeof triedCharacters)[number] => !!triedEntry)
|
||||||
|
}));
|
||||||
|
|
||||||
const incomingRequests = await db
|
const incomingRequests = await db
|
||||||
.select({
|
.select({
|
||||||
id: friendship.id,
|
id: friendship.id,
|
||||||
@@ -190,6 +215,7 @@ export const actions: Actions = {
|
|||||||
// Delete the session from database
|
// Delete the session from database
|
||||||
await db.delete(session).where(eq(session.id, sessionId));
|
await db.delete(session).where(eq(session.id, sessionId));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error revoking session:', error);
|
||||||
return fail(500, { message: 'Erreur lors de la révocation de la session' });
|
return fail(500, { message: 'Erreur lors de la révocation de la session' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,56 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { t, language } from '$lib/i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
form?: { success?: boolean; message?: string } | null;
|
form?: { success?: boolean; message?: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DailyHistoryEntry {
|
||||||
|
id: string;
|
||||||
|
characterId: string | null;
|
||||||
|
date: number;
|
||||||
|
tryCount: number;
|
||||||
|
won: number;
|
||||||
|
characterName: string;
|
||||||
|
characterImage: string | null;
|
||||||
|
triedCharacters?: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
pictureUrl: string | null;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let activeTab = $state<'profile' | 'password' | 'sessions' | 'daily' | 'friends'>('profile');
|
let activeTab = $state<'profile' | 'password' | 'sessions' | 'daily' | 'friends'>('profile');
|
||||||
let name = $state('');
|
let name = $derived(data.user?.name || '');
|
||||||
let friendUsername = $state('');
|
let friendUsername = $state('');
|
||||||
let showSuccess = $state(false);
|
let showSuccess = $state(false);
|
||||||
let oldPassword = $state('');
|
let oldPassword = $state('');
|
||||||
let newPassword = $state('');
|
let newPassword = $state('');
|
||||||
let confirmPassword = $state('');
|
let confirmPassword = $state('');
|
||||||
let sessions = $state<any[]>([]);
|
let sessions = $derived(data.sessions || []);
|
||||||
let dailyHistory = $state<any[]>([]);
|
let dailyHistory = $derived((data.dailyHistory || []) as DailyHistoryEntry[]);
|
||||||
let friends = $state<any[]>([]);
|
let friends = $derived(data.friends || []);
|
||||||
let incomingRequests = $state<any[]>([]);
|
let incomingRequests = $derived(data.incomingRequests || []);
|
||||||
let outgoingRequests = $state<any[]>([]);
|
let outgoingRequests = $derived(data.outgoingRequests || []);
|
||||||
let tabsElement: HTMLDivElement | undefined;
|
let tabsElement: HTMLDivElement | undefined;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
name = data.user?.name || '';
|
friends = data.friends || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
sessions = (data as any).sessions || [];
|
incomingRequests = data.incomingRequests || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
dailyHistory = (data as any).dailyHistory || [];
|
outgoingRequests = data.outgoingRequests || [];
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
friends = (data as any).friends || [];
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
incomingRequests = (data as any).incomingRequests || [];
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
outgoingRequests = (data as any).outgoingRequests || [];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -67,11 +72,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Mon Profil - OnePieceDle</title>
|
<title>{$t.game.profile.pageTitle} - OnePieceDle</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
|
<main class="relative min-h-[calc(100vh-5rem)] bg-slate-950 text-slate-100">
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
<div class="absolute inset-0 bg-linear-to-br from-slate-950/85 via-slate-900/60 to-slate-950/80"></div>
|
||||||
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
<div class="absolute inset-0 mix-blend-screen opacity-20 bg-[radial-gradient(circle_at_top,rgba(255,215,84,0.35),transparent_55%)]"></div>
|
||||||
|
|
||||||
<div class="relative mx-auto flex w-full max-w-2xl flex-col items-center px-6 py-4">
|
<div class="relative mx-auto flex w-full max-w-2xl flex-col items-center px-6 py-4">
|
||||||
@@ -79,10 +84,10 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="text-3xl font-black uppercase tracking-[0.3em] text-amber-50 sm:text-4xl">
|
<h1 class="text-3xl font-black uppercase tracking-[0.3em] text-amber-50 sm:text-4xl">
|
||||||
Mon Profil
|
{$t.game.profile.headerTitle}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-slate-300">
|
<p class="mt-2 text-sm text-slate-300">
|
||||||
Modifie les informations de ton profil
|
{$t.game.profile.headerSubtitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,43 +95,43 @@
|
|||||||
<div bind:this={tabsElement} class="sticky top-20 z-10 flex gap-2 border-b border-white/10 bg-slate-950/80 backdrop-blur">
|
<div bind:this={tabsElement} class="sticky top-20 z-10 flex gap-2 border-b border-white/10 bg-slate-950/80 backdrop-blur">
|
||||||
<button
|
<button
|
||||||
onclick={() => handleTabChange('profile')}
|
onclick={() => handleTabChange('profile')}
|
||||||
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'profile'
|
class="px-4 py-3 font-semibold uppercase tracking-widest transition {activeTab === 'profile'
|
||||||
? 'border-b-2 border-amber-300 text-amber-100'
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
: 'text-slate-400 hover:text-slate-100'}"
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
>
|
>
|
||||||
Profil
|
{$t.game.profile.tabProfile}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleTabChange('password')}
|
onclick={() => handleTabChange('password')}
|
||||||
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'password'
|
class="px-4 py-3 font-semibold uppercase tracking-widest transition {activeTab === 'password'
|
||||||
? 'border-b-2 border-amber-300 text-amber-100'
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
: 'text-slate-400 hover:text-slate-100'}"
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
>
|
>
|
||||||
Mot de passe
|
{$t.game.profile.tabPassword}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleTabChange('daily')}
|
onclick={() => handleTabChange('daily')}
|
||||||
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'daily'
|
class="px-4 py-3 font-semibold uppercase tracking-widest transition {activeTab === 'daily'
|
||||||
? 'border-b-2 border-amber-300 text-amber-100'
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
: 'text-slate-400 hover:text-slate-100'}"
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
>
|
>
|
||||||
Historique Daily
|
{$t.game.profile.tabDaily}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleTabChange('sessions')}
|
onclick={() => handleTabChange('sessions')}
|
||||||
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'sessions'
|
class="px-4 py-3 font-semibold uppercase tracking-widest transition {activeTab === 'sessions'
|
||||||
? 'border-b-2 border-amber-300 text-amber-100'
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
: 'text-slate-400 hover:text-slate-100'}"
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
>
|
>
|
||||||
Sessions
|
{$t.game.profile.tabSessions}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleTabChange('friends')}
|
onclick={() => handleTabChange('friends')}
|
||||||
class="px-4 py-3 font-semibold uppercase tracking-[0.1em] transition {activeTab === 'friends'
|
class="px-4 py-3 font-semibold uppercase tracking-widest transition {activeTab === 'friends'
|
||||||
? 'border-b-2 border-amber-300 text-amber-100'
|
? 'border-b-2 border-amber-300 text-amber-100'
|
||||||
: 'text-slate-400 hover:text-slate-100'}"
|
: 'text-slate-400 hover:text-slate-100'}"
|
||||||
>
|
>
|
||||||
Amis
|
{$t.game.profile.tabFriends}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -138,7 +143,7 @@
|
|||||||
{#if data.user.image}
|
{#if data.user.image}
|
||||||
<img
|
<img
|
||||||
src={data.user.image}
|
src={data.user.image}
|
||||||
alt={data.user.name || 'Profil'}
|
alt={data.user.name || $t.game.profile.avatarFallbackAlt}
|
||||||
class="h-24 w-24 rounded-full border-2 border-amber-300 object-cover"
|
class="h-24 w-24 rounded-full border-2 border-amber-300 object-cover"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -147,7 +152,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-sm text-slate-400">Email</p>
|
<p class="text-sm text-slate-400">{$t.game.profile.email}</p>
|
||||||
<p class="font-semibold text-white">{data.user.email}</p>
|
<p class="font-semibold text-white">{data.user.email}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +174,7 @@
|
|||||||
<!-- Name Field -->
|
<!-- Name Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="name" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Nom d'affichage
|
{$t.game.profile.displayName}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="name"
|
id="name"
|
||||||
@@ -177,7 +182,7 @@
|
|||||||
name="name"
|
name="name"
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
required
|
required
|
||||||
placeholder="Ton nom"
|
placeholder={$t.game.profile.displayNamePlaceholder}
|
||||||
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
class="mt-3 w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,7 +197,7 @@
|
|||||||
<!-- Success Message -->
|
<!-- Success Message -->
|
||||||
{#if showSuccess}
|
{#if showSuccess}
|
||||||
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
|
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
|
||||||
Profil mis à jour avec succès !
|
{$t.game.profile.profileUpdateSuccess}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -202,7 +207,7 @@
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Mise à jour...' : 'Enregistrer les modifications'}
|
{isLoading ? $t.game.profile.updating : $t.game.profile.saveChanges}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,7 +217,7 @@
|
|||||||
{#if activeTab === 'friends'}
|
{#if activeTab === 'friends'}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
||||||
Système d'amis
|
{$t.game.profile.friendsTitle}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
@@ -229,7 +234,7 @@
|
|||||||
class="mb-8 space-y-3"
|
class="mb-8 space-y-3"
|
||||||
>
|
>
|
||||||
<label for="friendUsername" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="friendUsername" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Ajouter un ami par nom d'utilisateur
|
{$t.game.profile.addFriendByUsername}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input
|
<input
|
||||||
@@ -238,7 +243,7 @@
|
|||||||
name="friendUsername"
|
name="friendUsername"
|
||||||
required
|
required
|
||||||
bind:value={friendUsername}
|
bind:value={friendUsername}
|
||||||
placeholder="ex: luffy_gear5"
|
placeholder={$t.game.profile.friendUsernamePlaceholder}
|
||||||
class="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
class="w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-slate-500 transition focus:border-amber-300 focus:outline-none focus:ring-2 focus:ring-amber-300/30"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -246,7 +251,7 @@
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
class="rounded-full bg-amber-300 px-4 py-2 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
class="rounded-full bg-amber-300 px-4 py-2 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Envoi...' : 'Envoyer'}
|
{isLoading ? $t.game.profile.sending : $t.game.profile.send}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if form?.message}
|
{#if form?.message}
|
||||||
@@ -256,12 +261,12 @@
|
|||||||
|
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes reçues</h3>
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.incomingRequests}</h3>
|
||||||
{#if incomingRequests.length === 0}
|
{#if incomingRequests.length === 0}
|
||||||
<p class="text-sm text-slate-400">Aucune demande reçue.</p>
|
<p class="text-sm text-slate-400">{$t.game.profile.noIncomingRequests}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#each incomingRequests as req}
|
{#each incomingRequests as req (req.id)}
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-white">{req.requesterName}</p>
|
<p class="font-semibold text-white">{req.requesterName}</p>
|
||||||
@@ -270,11 +275,11 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<form method="POST" action="?/acceptFriendRequest" use:enhance>
|
<form method="POST" action="?/acceptFriendRequest" use:enhance>
|
||||||
<input type="hidden" name="friendshipId" value={req.id} />
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
<button type="submit" class="rounded-lg border border-emerald-400/50 bg-emerald-900/20 px-3 py-1.5 text-xs font-semibold text-emerald-300 transition hover:bg-emerald-900/40">Accepter</button>
|
<button type="submit" class="rounded-lg border border-emerald-400/50 bg-emerald-900/20 px-3 py-1.5 text-xs font-semibold text-emerald-300 transition hover:bg-emerald-900/40">{$t.game.profile.accept}</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="?/declineFriendRequest" use:enhance>
|
<form method="POST" action="?/declineFriendRequest" use:enhance>
|
||||||
<input type="hidden" name="friendshipId" value={req.id} />
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Refuser</button>
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.decline}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -284,12 +289,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Demandes envoyées</h3>
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.outgoingRequests}</h3>
|
||||||
{#if outgoingRequests.length === 0}
|
{#if outgoingRequests.length === 0}
|
||||||
<p class="text-sm text-slate-400">Aucune demande envoyée.</p>
|
<p class="text-sm text-slate-400">{$t.game.profile.noOutgoingRequests}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#each outgoingRequests as req}
|
{#each outgoingRequests as req (req.id)}
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-white">{req.addresseeName}</p>
|
<p class="font-semibold text-white">{req.addresseeName}</p>
|
||||||
@@ -297,7 +302,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<form method="POST" action="?/cancelFriendRequest" use:enhance>
|
<form method="POST" action="?/cancelFriendRequest" use:enhance>
|
||||||
<input type="hidden" name="friendshipId" value={req.id} />
|
<input type="hidden" name="friendshipId" value={req.id} />
|
||||||
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Annuler</button>
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.cancel}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -306,12 +311,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">Mes amis</h3>
|
<h3 class="mb-3 text-sm font-semibold uppercase tracking-[0.15em] text-amber-100">{$t.game.profile.myFriends}</h3>
|
||||||
{#if friends.length === 0}
|
{#if friends.length === 0}
|
||||||
<p class="text-sm text-slate-400">Tu n'as pas encore d'amis.</p>
|
<p class="text-sm text-slate-400">{$t.game.profile.noFriends}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#each friends as friend}
|
{#each friends as friend (friend.id)}
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-white">{friend.friendName}</p>
|
<p class="font-semibold text-white">{friend.friendName}</p>
|
||||||
@@ -319,7 +324,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<form method="POST" action="?/removeFriend" use:enhance>
|
<form method="POST" action="?/removeFriend" use:enhance>
|
||||||
<input type="hidden" name="friendshipId" value={friend.id} />
|
<input type="hidden" name="friendshipId" value={friend.id} />
|
||||||
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">Supprimer</button>
|
<button type="submit" class="rounded-lg border border-red-500/50 bg-red-900/20 px-3 py-1.5 text-xs font-semibold text-red-300 transition hover:bg-red-900/40">{$t.game.profile.remove}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -334,7 +339,7 @@
|
|||||||
{#if activeTab === 'password'}
|
{#if activeTab === 'password'}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
||||||
Changer le mot de passe
|
{$t.game.profile.changePasswordTitle}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
@@ -356,7 +361,7 @@
|
|||||||
<!-- Old Password Field -->
|
<!-- Old Password Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="oldPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="oldPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Mot de passe actuel
|
{$t.game.profile.currentPassword}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="oldPassword"
|
id="oldPassword"
|
||||||
@@ -372,7 +377,7 @@
|
|||||||
<!-- New Password Field -->
|
<!-- New Password Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="newPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="newPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Nouveau mot de passe
|
{$t.game.profile.newPassword}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="newPassword"
|
id="newPassword"
|
||||||
@@ -388,7 +393,7 @@
|
|||||||
<!-- Confirm Password Field -->
|
<!-- Confirm Password Field -->
|
||||||
<div>
|
<div>
|
||||||
<label for="confirmPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
<label for="confirmPassword" class="block text-sm font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
Confirmer le mot de passe
|
{$t.game.profile.confirmPassword}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="confirmPassword"
|
id="confirmPassword"
|
||||||
@@ -411,7 +416,7 @@
|
|||||||
<!-- Success Message -->
|
<!-- Success Message -->
|
||||||
{#if showSuccess}
|
{#if showSuccess}
|
||||||
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
|
<div class="rounded-lg border border-green-500/30 bg-green-900/20 px-4 py-3 text-sm text-green-200">
|
||||||
Mot de passe changé avec succès !
|
{$t.game.profile.passwordChangeSuccess}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -421,7 +426,7 @@
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
class="w-full rounded-full bg-amber-300 px-6 py-3 text-sm font-semibold text-slate-900 transition disabled:opacity-50 hover:bg-amber-200"
|
||||||
>
|
>
|
||||||
{isLoading ? 'Changement en cours...' : 'Changer le mot de passe'}
|
{isLoading ? $t.game.profile.changing : $t.game.profile.changePassword}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -431,17 +436,17 @@
|
|||||||
{#if activeTab === 'daily'}
|
{#if activeTab === 'daily'}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
||||||
Historique des Daily
|
{$t.game.profile.dailyHistoryTitle}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{#if dailyHistory.length === 0}
|
{#if dailyHistory.length === 0}
|
||||||
<p class="text-center text-slate-400">Aucun historique disponible</p>
|
<p class="text-center text-slate-400">{$t.game.profile.noDailyHistory}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{#each dailyHistory as day}
|
{#each dailyHistory as day (day.id)}
|
||||||
<div class="flex items-center gap-4 rounded-lg border border-white/10 bg-white/5 p-4">
|
<div class="flex items-center gap-4 rounded-lg border border-white/10 bg-white/5 p-4">
|
||||||
<!-- Character Image -->
|
<!-- Character Image -->
|
||||||
<div class="flex-shrink-0">
|
<div class="shrink-0">
|
||||||
{#if day.characterImage}
|
{#if day.characterImage}
|
||||||
<img
|
<img
|
||||||
src={day.characterImage}
|
src={day.characterImage}
|
||||||
@@ -450,7 +455,7 @@
|
|||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-16 w-16 items-center justify-center rounded-lg border border-white/20 bg-slate-700">
|
<div class="flex h-16 w-16 items-center justify-center rounded-lg border border-white/20 bg-slate-700">
|
||||||
<span class="text-xs text-slate-400">N/A</span>
|
<span class="text-xs text-slate-400">{$t.game.profile.noImage}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -459,18 +464,41 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-semibold text-white">{day.characterName}</p>
|
<p class="font-semibold text-white">{day.characterName}</p>
|
||||||
<p class="text-xs text-slate-400">
|
<p class="text-xs text-slate-400">
|
||||||
{new Date(day.date).toLocaleDateString('fr-FR', {
|
{new Date(day.date).toLocaleDateString($language === 'fr' ? 'fr-FR' : 'en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="mt-3">
|
||||||
|
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-amber-100">
|
||||||
|
{$t.game.profile.triedCharactersTitle}
|
||||||
|
</p>
|
||||||
|
{#if day.triedCharacters && day.triedCharacters.length > 0}
|
||||||
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
|
{#each day.triedCharacters as triedCharacter (triedCharacter.id)}
|
||||||
|
<span class="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-2.5 py-1 text-xs text-slate-200">
|
||||||
|
{#if triedCharacter.pictureUrl}
|
||||||
|
<img
|
||||||
|
src={triedCharacter.pictureUrl}
|
||||||
|
alt={triedCharacter.name}
|
||||||
|
class="h-4 w-4 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{triedCharacter.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="mt-1 text-xs text-slate-500">{$t.game.profile.noTriedCharacters}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tries -->
|
<!-- Tries -->
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
<p class="text-xs text-slate-400">
|
<p class="text-xs text-slate-400">
|
||||||
{day.tryCount} {day.tryCount === 1 ? 'tentative' : 'tentatives'}
|
{day.tryCount} {day.tryCount === 1 ? $t.game.profile.trySingular : $t.game.profile.tryPlural}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -484,24 +512,24 @@
|
|||||||
{#if activeTab === 'sessions'}
|
{#if activeTab === 'sessions'}
|
||||||
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-[0_24px_60px_rgba(0,0,0,0.45)] backdrop-blur sm:p-8">
|
||||||
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
<h2 class="mb-6 text-2xl font-bold uppercase tracking-[0.2em] text-amber-50">
|
||||||
Sessions actives
|
{$t.game.profile.activeSessionsTitle}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{#if sessions.length === 0}
|
{#if sessions.length === 0}
|
||||||
<p class="text-center text-slate-400">Aucune session active</p>
|
<p class="text-center text-slate-400">{$t.game.profile.noActiveSessions}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{#each sessions as sess}
|
{#each sessions as sess (sess.id)}
|
||||||
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 px-4 py-4">
|
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 px-4 py-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-semibold text-white">
|
<p class="font-semibold text-white">
|
||||||
{sess.userAgent || 'Appareil inconnu'}
|
{sess.userAgent || $t.game.profile.unknownDevice}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-slate-400">
|
<p class="text-xs text-slate-400">
|
||||||
IP: {sess.ipAddress || 'Inconnue'}
|
{$t.game.profile.ip}: {sess.ipAddress || $t.game.profile.unknown}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-slate-500">
|
<p class="mt-1 text-xs text-slate-500">
|
||||||
Créée: {new Date(sess.createdAt).toLocaleDateString('fr-FR', {
|
{$t.game.profile.created}: {new Date(sess.createdAt).toLocaleDateString($language === 'fr' ? 'fr-FR' : 'en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -525,7 +553,7 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-lg border border-red-500/50 bg-red-900/20 px-4 py-2 text-xs font-semibold text-red-300 transition hover:border-red-500 hover:bg-red-900/40"
|
class="rounded-lg border border-red-500/50 bg-red-900/20 px-4 py-2 text-xs font-semibold text-red-300 transition hover:border-red-500 hover:bg-red-900/40"
|
||||||
>
|
>
|
||||||
Terminer
|
{$t.game.profile.terminate}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -537,8 +565,8 @@
|
|||||||
|
|
||||||
<!-- Back to Home -->
|
<!-- Back to Home -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="/" class="text-sm text-slate-400 transition hover:text-slate-300">
|
<a href={resolve("/")} class="text-sm text-slate-400 transition hover:text-slate-300">
|
||||||
← Retour à l'accueil
|
← {$t.game.profile.backHome}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user