diff --git a/src/lib/api/adapters/__tests__/entity.adapter.test.ts b/src/lib/api/adapters/__tests__/entity.adapter.test.ts index 18ea6bee..89b4437c 100644 --- a/src/lib/api/adapters/__tests__/entity.adapter.test.ts +++ b/src/lib/api/adapters/__tests__/entity.adapter.test.ts @@ -91,7 +91,11 @@ describe('EntityAdapter', () => { }, rarity: 5, element: 0, - series: 2, + series: { + id: 'series-1', + slug: 'optimus', + name: { en: 'Optimus Series', ja: 'オプティマスシリーズ' } + }, minHp: 200, maxHp: 1000, minAttack: 300, diff --git a/src/lib/api/adapters/entity.adapter.ts b/src/lib/api/adapters/entity.adapter.ts index 6e8a8d5e..f259ba1b 100644 --- a/src/lib/api/adapters/entity.adapter.ts +++ b/src/lib/api/adapters/entity.adapter.ts @@ -17,6 +17,18 @@ import type { CreateWeaponSeriesPayload, UpdateWeaponSeriesPayload } from '$lib/types/api/weaponSeries' +import type { + CharacterSeriesRef, + CharacterSeries, + CreateCharacterSeriesPayload, + UpdateCharacterSeriesPayload +} from '$lib/types/api/characterSeries' +import type { + SummonSeriesRef, + SummonSeries, + CreateSummonSeriesPayload, + UpdateSummonSeriesPayload +} from '$lib/types/api/summonSeries' /** * Canonical weapon data from the game @@ -132,8 +144,8 @@ export interface Character { season?: number | null /** Human-readable season name */ seasonName?: string | null - /** Series integer array (1=Standard, 2=Grand, 3=Zodiac, etc.) */ - series?: number[] + /** Series - array of objects with slug/name or legacy integers */ + series?: CharacterSeriesRef[] | number[] /** Human-readable series names */ seriesNames?: string[] /** Whether character can be pulled from gacha */ @@ -228,7 +240,8 @@ export interface Summon { } rarity: number element: number - series?: number + /** Series - object with slug/name or null */ + series?: SummonSeriesRef | null /** Gacha promotions (1=Premium, 2=Classic, 3=ClassicII, 4=Flash, 5=Legend, etc.) */ promotions?: number[] /** Human-readable promotion names */ @@ -1297,6 +1310,171 @@ export class EntityAdapter extends BaseAdapter { clearWeaponSeriesCache() { this.clearCache('/weapon_series') } + + // ============================================================ + // CHARACTER SERIES METHODS + // ============================================================ + + /** + * Gets all character series, sorted by order + * Returns list view with basic info (no character count) + */ + async getCharacterSeriesList(): Promise { + return this.request('/character_series', { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour - rarely changes + }) + } + + /** + * Gets a single character series by ID or slug + * Returns full view with character count + * + * @param idOrSlug - UUID or slug (e.g., 'grand') + */ + async getCharacterSeries(idOrSlug: string): Promise { + return this.request(`/character_series/${idOrSlug}`, { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour + }) + } + + /** + * Creates a new character series + * Requires editor role (>= 7) + */ + async createCharacterSeries(payload: CreateCharacterSeriesPayload): Promise { + const result = await this.request('/character_series', { + method: 'POST', + body: { character_series: payload } + }) + // Clear character series cache + this.clearCache('/character_series') + return result + } + + /** + * Updates an existing character series + * Requires editor role (>= 7) + * + * @param id - Character series UUID + * @param payload - Fields to update + */ + async updateCharacterSeries( + id: string, + payload: UpdateCharacterSeriesPayload + ): Promise { + const result = await this.request(`/character_series/${id}`, { + method: 'PATCH', + body: { character_series: payload } + }) + // Clear character series cache + this.clearCache('/character_series') + return result + } + + /** + * Deletes a character series + * Requires editor role (>= 7) + * Will fail if series has associated characters + * + * @param id - Character series UUID + */ + async deleteCharacterSeries(id: string): Promise { + await this.request(`/character_series/${id}`, { + method: 'DELETE' + }) + // Clear character series cache + this.clearCache('/character_series') + } + + /** + * Clears character series cache + */ + clearCharacterSeriesCache() { + this.clearCache('/character_series') + } + + // ============================================================ + // SUMMON SERIES METHODS + // ============================================================ + + /** + * Gets all summon series, sorted by order + * Returns list view with basic info (no summon count) + */ + async getSummonSeriesList(): Promise { + return this.request('/summon_series', { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour - rarely changes + }) + } + + /** + * Gets a single summon series by ID or slug + * Returns full view with summon count + * + * @param idOrSlug - UUID or slug (e.g., 'magna') + */ + async getSummonSeries(idOrSlug: string): Promise { + return this.request(`/summon_series/${idOrSlug}`, { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour + }) + } + + /** + * Creates a new summon series + * Requires editor role (>= 7) + */ + async createSummonSeries(payload: CreateSummonSeriesPayload): Promise { + const result = await this.request('/summon_series', { + method: 'POST', + body: { summon_series: payload } + }) + // Clear summon series cache + this.clearCache('/summon_series') + return result + } + + /** + * Updates an existing summon series + * Requires editor role (>= 7) + * + * @param id - Summon series UUID + * @param payload - Fields to update + */ + async updateSummonSeries(id: string, payload: UpdateSummonSeriesPayload): Promise { + const result = await this.request(`/summon_series/${id}`, { + method: 'PATCH', + body: { summon_series: payload } + }) + // Clear summon series cache + this.clearCache('/summon_series') + return result + } + + /** + * Deletes a summon series + * Requires editor role (>= 7) + * Will fail if series has associated summons + * + * @param id - Summon series UUID + */ + async deleteSummonSeries(id: string): Promise { + await this.request(`/summon_series/${id}`, { + method: 'DELETE' + }) + // Clear summon series cache + this.clearCache('/summon_series') + } + + /** + * Clears summon series cache + */ + clearSummonSeriesCache() { + this.clearCache('/summon_series') + } } /** diff --git a/src/lib/api/queries/entity.queries.ts b/src/lib/api/queries/entity.queries.ts index 08c2029c..dca8b8ce 100644 --- a/src/lib/api/queries/entity.queries.ts +++ b/src/lib/api/queries/entity.queries.ts @@ -115,6 +115,64 @@ export const entityQueries = { enabled: !!idOrSlug, staleTime: 1000 * 60 * 60, // 1 hour gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * All character series query options + * Returns list ordered by display order + * + * @returns Query options for fetching all character series + */ + characterSeriesList: () => + queryOptions({ + queryKey: ['characterSeries', 'list'] as const, + queryFn: () => entityAdapter.getCharacterSeriesList(), + staleTime: 1000 * 60 * 60, // 1 hour - rarely changes + gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * Single character series query options + * + * @param idOrSlug - Character series UUID or slug (e.g., 'grand') + * @returns Query options for fetching a single character series with full details + */ + characterSeries: (idOrSlug: string) => + queryOptions({ + queryKey: ['characterSeries', idOrSlug] as const, + queryFn: () => entityAdapter.getCharacterSeries(idOrSlug), + enabled: !!idOrSlug, + staleTime: 1000 * 60 * 60, // 1 hour + gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * All summon series query options + * Returns list ordered by display order + * + * @returns Query options for fetching all summon series + */ + summonSeriesList: () => + queryOptions({ + queryKey: ['summonSeries', 'list'] as const, + queryFn: () => entityAdapter.getSummonSeriesList(), + staleTime: 1000 * 60 * 60, // 1 hour - rarely changes + gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * Single summon series query options + * + * @param idOrSlug - Summon series UUID or slug (e.g., 'magna') + * @returns Query options for fetching a single summon series with full details + */ + summonSeries: (idOrSlug: string) => + queryOptions({ + queryKey: ['summonSeries', idOrSlug] as const, + queryFn: () => entityAdapter.getSummonSeries(idOrSlug), + enabled: !!idOrSlug, + staleTime: 1000 * 60 * 60, // 1 hour + gcTime: 1000 * 60 * 60 * 24 // 24 hours }) } @@ -146,5 +204,11 @@ export const entityKeys = { ['weaponKeys', params?.seriesSlug, params?.slot, params?.group] as const, weaponSeriesList: () => ['weaponSeries', 'list'] as const, weaponSeries: (idOrSlug: string) => ['weaponSeries', idOrSlug] as const, - allWeaponSeries: () => ['weaponSeries'] as const + allWeaponSeries: () => ['weaponSeries'] as const, + characterSeriesList: () => ['characterSeries', 'list'] as const, + characterSeries: (idOrSlug: string) => ['characterSeries', idOrSlug] as const, + allCharacterSeries: () => ['characterSeries'] as const, + summonSeriesList: () => ['summonSeries', 'list'] as const, + summonSeries: (idOrSlug: string) => ['summonSeries', idOrSlug] as const, + allSummonSeries: () => ['summonSeries'] as const }