/** * Collection Adapter * * Handles all collection-related API operations including CRUD for * characters, weapons, summons, and job accessories in a user's collection. * * @module adapters/collection */ import { BaseAdapter } from './base.adapter' import type { AdapterOptions, PaginatedResponse } from './types' import { DEFAULT_ADAPTER_CONFIG } from './config' import type { CollectionCharacter, CollectionWeapon, CollectionSummon, CollectionJobAccessory, CollectionCharacterInput, CollectionWeaponInput, CollectionSummonInput, CollectionJobAccessoryInput, CollectionFilters } from '$lib/types/api/collection' /** * Parameters for listing collection items with pagination */ export interface CollectionListParams extends CollectionFilters { page?: number limit?: number } /** * Response structure for paginated collection list */ export interface CollectionCharacterListResponse { characters: CollectionCharacter[] meta: { count: number totalPages: number perPage: number currentPage: number } } /** * Collection adapter for managing user collections */ export class CollectionAdapter extends BaseAdapter { constructor(options?: AdapterOptions) { super(options) } // ============================================ // Collection Characters // ============================================ /** * Lists a user's collection characters with optional filters * Works for any user - privacy is enforced server-side */ async listCharacters( userId: string, params: CollectionListParams = {} ): Promise> { const response = await this.request( `/users/${userId}/collection/characters`, { method: 'GET', query: params } ) return { results: response.characters, page: response.meta.currentPage, total: response.meta.count, totalPages: response.meta.totalPages, perPage: response.meta.perPage } } /** * Gets a single collection character by ID */ async getCharacter(id: string): Promise { return this.request(`/collection/characters/${id}`, { method: 'GET' }) } /** * Adds a character to the collection */ async addCharacter(input: CollectionCharacterInput): Promise { return this.request('/collection/characters', { method: 'POST', body: { collectionCharacter: input } }) } /** * Adds multiple characters to the collection in a single batch request */ async addCharacters(inputs: CollectionCharacterInput[]): Promise { if (inputs.length === 0) return [] const response = await this.request<{ characters: CollectionCharacter[] meta: { created: number; skipped: number; errors: any[] } }>('/collection/characters/batch', { method: 'POST', body: { collectionCharacters: inputs } }) return response.characters } /** * Updates a collection character */ async updateCharacter( id: string, input: Partial ): Promise { return this.request(`/collection/characters/${id}`, { method: 'PATCH', body: { collectionCharacter: input } }) } /** * Removes a character from the collection */ async removeCharacter(id: string): Promise { return this.request(`/collection/characters/${id}`, { method: 'DELETE' }) } /** * Gets the IDs of all characters in a user's collection * Useful for filtering out already-owned characters in the add modal */ async getCollectedCharacterIds(userId: string): Promise { // Fetch all pages to get complete list const allIds: string[] = [] let page = 1 let hasMore = true while (hasMore) { const response = await this.listCharacters(userId, { page, limit: 100 }) allIds.push(...response.results.map((c) => c.character.id)) hasMore = page < response.totalPages page++ } return allIds } // ============================================ // Collection Weapons // ============================================ /** * Lists a user's collection weapons with optional filters * Works for any user - privacy is enforced server-side */ async listWeapons( userId: string, params: CollectionListParams = {} ): Promise> { const response = await this.request<{ weapons?: CollectionWeapon[] collectionWeapons?: CollectionWeapon[] meta: { count: number; totalPages: number; perPage: number; currentPage: number } }>(`/users/${userId}/collection/weapons`, { method: 'GET', query: params }) // Handle both 'weapons' and 'collectionWeapons' response keys // (backend currently returns 'collectionWeapons', should return 'weapons') const weapons = response.weapons ?? response.collectionWeapons ?? [] return { results: weapons, page: response.meta.currentPage, total: response.meta.count, totalPages: response.meta.totalPages, perPage: response.meta.perPage } } /** * Adds a weapon to the collection */ async addWeapon(input: CollectionWeaponInput): Promise { return this.request('/collection/weapons', { method: 'POST', body: { collectionWeapon: input } }) } /** * Adds multiple weapons to the collection in a single batch request * Handles quantity expansion - each quantity > 1 creates multiple entries */ async addWeapons( inputs: Array ): Promise { // Expand inputs based on quantity const expanded = inputs.flatMap((input) => { const count = input.quantity ?? 1 // eslint-disable-next-line @typescript-eslint/no-unused-vars const { quantity, ...rest } = input return Array.from({ length: count }, () => ({ ...rest })) as CollectionWeaponInput[] }) if (expanded.length === 0) return [] const response = await this.request<{ weapons: CollectionWeapon[] meta: { created: number; errors: any[] } }>('/collection/weapons/batch', { method: 'POST', body: { collectionWeapons: expanded } }) return response.weapons } /** * Updates a collection weapon */ async updateWeapon(id: string, input: Partial): Promise { return this.request(`/collection/weapons/${id}`, { method: 'PATCH', body: { collectionWeapon: input } }) } /** * Removes a weapon from the collection */ async removeWeapon(id: string): Promise { return this.request(`/collection/weapons/${id}`, { method: 'DELETE' }) } // ============================================ // Collection Summons // ============================================ /** * Lists a user's collection summons with optional filters * Works for any user - privacy is enforced server-side */ async listSummons( userId: string, params: CollectionListParams = {} ): Promise> { const response = await this.request<{ summons?: CollectionSummon[] collectionSummons?: CollectionSummon[] meta: { count: number; totalPages: number; perPage: number; currentPage: number } }>(`/users/${userId}/collection/summons`, { method: 'GET', query: params }) // Handle both 'summons' and 'collectionSummons' response keys // (backend currently returns 'collectionSummons', should return 'summons') const summons = response.summons ?? response.collectionSummons ?? [] return { results: summons, page: response.meta.currentPage, total: response.meta.count, totalPages: response.meta.totalPages, perPage: response.meta.perPage } } /** * Adds a summon to the collection */ async addSummon(input: CollectionSummonInput): Promise { return this.request('/collection/summons', { method: 'POST', body: { collectionSummon: input } }) } /** * Adds multiple summons to the collection in a single batch request * Handles quantity expansion - each quantity > 1 creates multiple entries */ async addSummons( inputs: Array ): Promise { // Expand inputs based on quantity const expanded = inputs.flatMap((input) => { const count = input.quantity ?? 1 // eslint-disable-next-line @typescript-eslint/no-unused-vars const { quantity, ...rest } = input return Array.from({ length: count }, () => ({ ...rest })) as CollectionSummonInput[] }) if (expanded.length === 0) return [] const response = await this.request<{ summons: CollectionSummon[] meta: { created: number; errors: any[] } }>('/collection/summons/batch', { method: 'POST', body: { collectionSummons: expanded } }) return response.summons } /** * Updates a collection summon */ async updateSummon(id: string, input: Partial): Promise { return this.request(`/collection/summons/${id}`, { method: 'PATCH', body: { collectionSummon: input } }) } /** * Removes a summon from the collection */ async removeSummon(id: string): Promise { return this.request(`/collection/summons/${id}`, { method: 'DELETE' }) } // ============================================ // Collection Job Accessories // ============================================ /** * Lists the current user's collection job accessories */ async listJobAccessories(): Promise { const response = await this.request<{ jobAccessories: CollectionJobAccessory[] }>( '/collection/job_accessories', { method: 'GET' } ) return response.jobAccessories } /** * Adds a job accessory to the collection */ async addJobAccessory(input: CollectionJobAccessoryInput): Promise { return this.request('/collection/job_accessories', { method: 'POST', body: { collectionJobAccessory: input } }) } /** * Removes a job accessory from the collection */ async removeJobAccessory(id: string): Promise { return this.request(`/collection/job_accessories/${id}`, { method: 'DELETE' }) } // ============================================ // Cache Management // ============================================ /** * Clears collection-related cache */ clearCollectionCache() { this.clearCache('/collection') this.clearCache('/users') } } /** * Default collection adapter instance */ export const collectionAdapter = new CollectionAdapter(DEFAULT_ADAPTER_CONFIG)