diff --git a/src/lib/api/adapters/entity.adapter.ts b/src/lib/api/adapters/entity.adapter.ts index a6c1d4d6..2a1ebd93 100644 --- a/src/lib/api/adapters/entity.adapter.ts +++ b/src/lib/api/adapters/entity.adapter.ts @@ -11,6 +11,12 @@ import { BaseAdapter } from './base.adapter' import type { AdapterOptions } from './types' import { DEFAULT_ADAPTER_CONFIG } from './config' +import type { + WeaponSeriesRef, + WeaponSeries, + CreateWeaponSeriesPayload, + UpdateWeaponSeriesPayload +} from '$lib/types/api/weaponSeries' /** * Canonical weapon data from the game @@ -25,7 +31,8 @@ export interface Weapon { rarity: number element: number proficiency: number - series?: number + /** Weapon series - object with slug/name/flags */ + series?: WeaponSeriesRef | null weaponType?: number /** Gacha promotions (1=Premium, 2=Classic, 3=ClassicII, 4=Flash, 5=Legend, etc.) */ promotions?: number[] @@ -182,7 +189,8 @@ export interface WeaponKey { * Query parameters for fetching weapon keys */ export interface WeaponKeyQueryParams { - series?: number + /** Filter by weapon series slug (e.g., 'dark-opus', 'ultima') */ + seriesSlug?: string slot?: number group?: number } @@ -421,8 +429,8 @@ export interface CreateWeaponPayload { rarity?: number element?: number proficiency?: number - series?: number - new_series?: number + /** Weapon series ID (UUID) */ + weapon_series_id?: string min_hp?: number max_hp?: number max_hp_flb?: number @@ -654,7 +662,9 @@ export class EntityAdapter extends BaseAdapter { */ async getWeaponKeys(params?: WeaponKeyQueryParams): Promise { const searchParams = new URLSearchParams() - if (params?.series !== undefined) searchParams.set('series', String(params.series)) + if (params?.seriesSlug) { + searchParams.set('series_slug', params.seriesSlug) + } if (params?.slot !== undefined) searchParams.set('slot', String(params.slot)) if (params?.group !== undefined) searchParams.set('group', String(params.group)) @@ -670,7 +680,7 @@ export class EntityAdapter extends BaseAdapter { /** * Clears entity cache */ - clearEntityCache(type?: 'weapons' | 'characters' | 'summons' | 'weapon_keys') { + clearEntityCache(type?: 'weapons' | 'characters' | 'summons' | 'weapon_keys' | 'weapon_series') { if (type) { this.clearCache(`/${type}`) } else { @@ -679,6 +689,7 @@ export class EntityAdapter extends BaseAdapter { this.clearCache('/characters') this.clearCache('/summons') this.clearCache('/weapon_keys') + this.clearCache('/weapon_series') } } @@ -1176,6 +1187,87 @@ export class EntityAdapter extends BaseAdapter { body: { wiki_pages: wikiPages } }) } + + // ============================================ + // Weapon Series Methods + // ============================================ + + /** + * Gets all weapon series ordered by display order + * Returns minimal view (id, name, slug, order) + */ + async getWeaponSeriesList(): Promise { + return this.request('/weapon_series', { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour - rarely changes + }) + } + + /** + * Gets a single weapon series by ID or slug + * Returns full view with boolean flags and weapon count + * + * @param idOrSlug - UUID or slug (e.g., 'dark-opus') + */ + async getWeaponSeries(idOrSlug: string): Promise { + return this.request(`/weapon_series/${idOrSlug}`, { + method: 'GET', + cacheTTL: 3600000 // Cache for 1 hour + }) + } + + /** + * Creates a new weapon series + * Requires editor role (>= 7) + */ + async createWeaponSeries(payload: CreateWeaponSeriesPayload): Promise { + const result = await this.request('/weapon_series', { + method: 'POST', + body: { weapon_series: payload } + }) + // Clear weapon series cache + this.clearCache('/weapon_series') + return result + } + + /** + * Updates an existing weapon series + * Requires editor role (>= 7) + * + * @param id - Weapon series UUID + * @param payload - Fields to update + */ + async updateWeaponSeries(id: string, payload: UpdateWeaponSeriesPayload): Promise { + const result = await this.request(`/weapon_series/${id}`, { + method: 'PATCH', + body: { weapon_series: payload } + }) + // Clear weapon series caches + this.clearCache('/weapon_series') + return result + } + + /** + * Deletes a weapon series + * Requires editor role (>= 7) + * Will fail if series has associated weapons + * + * @param id - Weapon series UUID + */ + async deleteWeaponSeries(id: string): Promise { + await this.request(`/weapon_series/${id}`, { + method: 'DELETE' + }) + // Clear weapon series cache + this.clearCache('/weapon_series') + } + + /** + * Clears weapon series cache + */ + clearWeaponSeriesCache() { + this.clearCache('/weapon_series') + } } /** diff --git a/src/lib/api/adapters/types.ts b/src/lib/api/adapters/types.ts index 9651634d..2dc76909 100644 --- a/src/lib/api/adapters/types.ts +++ b/src/lib/api/adapters/types.ts @@ -167,8 +167,8 @@ export interface SearchFilters { /** Filter by secondary proficiency (characters only) */ proficiency2?: number[] | undefined - /** Filter by weapon series */ - series?: number[] | undefined + /** Filter by weapon series (accepts series IDs or slugs) */ + series?: string[] | undefined /** Filter by character season (1=Standard, 2=Valentine, etc.) */ season?: number[] | undefined diff --git a/src/lib/api/queries/entity.queries.ts b/src/lib/api/queries/entity.queries.ts index 9cf85bee..08c2029c 100644 --- a/src/lib/api/queries/entity.queries.ts +++ b/src/lib/api/queries/entity.queries.ts @@ -82,10 +82,39 @@ export const entityQueries = { */ weaponKeys: (params?: WeaponKeyQueryParams) => queryOptions({ - queryKey: ['weaponKeys', params?.series, params?.slot, params?.group] as const, + queryKey: ['weaponKeys', params?.seriesSlug, params?.slot, params?.group] as const, queryFn: () => entityAdapter.getWeaponKeys(params), staleTime: 1000 * 60 * 60, // 1 hour - weapon keys rarely change gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * All weapon series query options + * Returns list ordered by display order + * + * @returns Query options for fetching all weapon series + */ + weaponSeriesList: () => + queryOptions({ + queryKey: ['weaponSeries', 'list'] as const, + queryFn: () => entityAdapter.getWeaponSeriesList(), + staleTime: 1000 * 60 * 60, // 1 hour - rarely changes + gcTime: 1000 * 60 * 60 * 24 // 24 hours + }), + + /** + * Single weapon series query options + * + * @param idOrSlug - Weapon series UUID or slug (e.g., 'dark-opus') + * @returns Query options for fetching a single weapon series with full details + */ + weaponSeries: (idOrSlug: string) => + queryOptions({ + queryKey: ['weaponSeries', idOrSlug] as const, + queryFn: () => entityAdapter.getWeaponSeries(idOrSlug), + enabled: !!idOrSlug, + staleTime: 1000 * 60 * 60, // 1 hour + gcTime: 1000 * 60 * 60 * 24 // 24 hours }) } @@ -114,5 +143,8 @@ export const entityKeys = { summons: () => ['summon'] as const, summon: (id: string) => [...entityKeys.summons(), id] as const, weaponKeys: (params?: WeaponKeyQueryParams) => - ['weaponKeys', params?.series, params?.slot, params?.group] as const + ['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 }