feat: implement character overrides and update daily character retrieval logic

This commit is contained in:
2026-03-01 17:43:33 +01:00
parent 00bd718699
commit 031db29d62
6 changed files with 1236 additions and 9 deletions

View File

@@ -0,0 +1,30 @@
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;

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@
"when": 1772325597983,
"tag": "0000_graceful_master_mold",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1772383366179,
"tag": "0001_nostalgic_hercules",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
import { db } from '$lib/server/db';
import { arc, character, characterHistory, devilFruit } from '$lib/server/db/schema';
import { desc, eq } from 'drizzle-orm';
import { arc, character, characterHistory, characterOverride, devilFruit } from '$lib/server/db/schema';
import { desc, eq, inArray } from 'drizzle-orm';
const characterWithRelationsSelect = {
id: character.id,
@@ -32,6 +32,113 @@ export type CharacterWithRelations = typeof character.$inferSelect & {
arcName: string | null;
};
type CharacterOverrideRow = typeof characterOverride.$inferSelect;
type RelationMaps = {
arcNameById: Map<string, string | null>;
devilFruitById: Map<string, { name: string | null; type: string | null }>;
};
function isNotNullish<T>(value: T | null | undefined): value is T {
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
)
);
}
function getDateKey(date: Date): string {
return date.toISOString().split('T')[0];
}
@@ -50,13 +157,15 @@ function pickDailyCharacter(characters: CharacterWithRelations[], date: Date): C
}
export async function getDailyModeCharacters(): Promise<CharacterWithRelations[]> {
return db
const characters = (await db
.select(characterWithRelationsSelect)
.from(character)
.leftJoin(arc, eq(character.arcId, arc.id))
.leftJoin(devilFruit, eq(character.devilFruitId, devilFruit.id))
.where(eq(character.isInDailyMode, true))
.all() as Promise<CharacterWithRelations[]>;
.all()) as CharacterWithRelations[];
return applyCharacterOverrides(characters);
}
export async function getCharacterById(characterId: string): Promise<CharacterWithRelations | null> {
@@ -68,7 +177,12 @@ export async function getCharacterById(characterId: string): Promise<CharacterWi
.where(eq(character.id, characterId))
.limit(1);
return (found ?? null) as CharacterWithRelations | null;
if (!found) {
return null;
}
const [overriddenCharacter] = await applyCharacterOverrides([found as CharacterWithRelations]);
return overriddenCharacter ?? null;
}
export async function getOrCreateTodayCharacter(

View File

@@ -63,7 +63,7 @@ export const characterOverride = sqliteTable('characterOverride', {
bounty: integer('bounty'),
height: real('height'),
origin: text('origin'),
firstAppearance: integer('firstAppearance').notNull(),
firstAppearance: integer('firstAppearance'),
pictureUrl: text('pictureUrl'),
epithets: text('epithets', { mode: 'json' }).$type<string[]>(),
status: text('status'),

View File

@@ -14,9 +14,9 @@
<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 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 items-center justify-between px-6 py-10">
<div class="flex w-full flex-1 flex-col items-center justify-between gap-12">
<div class="text-center">
<div class="relative mx-auto flex min-h-screen w-full max-w-6xl flex-col items-center justify-center px-6 py-10">
<div class="flex w-full flex-col items-center gap-8">
<div class="text-center mb-12">
<h1 class="text-4xl font-black uppercase tracking-[0.3em] text-amber-50 sm:text-6xl">
OnePieceDle
</h1>