update collection adapter/queries for unified api

- add userId param to list methods
- remove public collection methods (now unified)
- update query keys to include userId
This commit is contained in:
Justin Edmund 2025-12-02 15:31:46 -08:00
parent 35b0560749
commit b8a48771dd
2 changed files with 96 additions and 133 deletions

View file

@ -19,8 +19,7 @@ import type {
CollectionWeaponInput, CollectionWeaponInput,
CollectionSummonInput, CollectionSummonInput,
CollectionJobAccessoryInput, CollectionJobAccessoryInput,
CollectionFilters, CollectionFilters
CollectionResponse
} from '$lib/types/api/collection' } from '$lib/types/api/collection'
/** /**
@ -57,15 +56,20 @@ export class CollectionAdapter extends BaseAdapter {
// ============================================ // ============================================
/** /**
* Lists the current user's collection characters with optional filters * Lists a user's collection characters with optional filters
* Works for any user - privacy is enforced server-side
*/ */
async listCharacters( async listCharacters(
userId: string,
params: CollectionListParams = {} params: CollectionListParams = {}
): Promise<PaginatedResponse<CollectionCharacter>> { ): Promise<PaginatedResponse<CollectionCharacter>> {
const response = await this.request<CollectionCharacterListResponse>('/collection/characters', { const response = await this.request<CollectionCharacterListResponse>(
method: 'GET', `/users/${userId}/collection/characters`,
query: params {
}) method: 'GET',
query: params
}
)
return { return {
results: response.characters, results: response.characters,
@ -131,17 +135,17 @@ export class CollectionAdapter extends BaseAdapter {
} }
/** /**
* Gets the IDs of all characters in the current user's collection * Gets the IDs of all characters in a user's collection
* Useful for filtering out already-owned characters in the add modal * Useful for filtering out already-owned characters in the add modal
*/ */
async getCollectedCharacterIds(): Promise<string[]> { async getCollectedCharacterIds(userId: string): Promise<string[]> {
// Fetch all pages to get complete list // Fetch all pages to get complete list
const allIds: string[] = [] const allIds: string[] = []
let page = 1 let page = 1
let hasMore = true let hasMore = true
while (hasMore) { while (hasMore) {
const response = await this.listCharacters({ page, limit: 100 }) const response = await this.listCharacters(userId, { page, limit: 100 })
allIds.push(...response.results.map((c) => c.character.id)) allIds.push(...response.results.map((c) => c.character.id))
hasMore = page < response.totalPages hasMore = page < response.totalPages
page++ page++
@ -155,13 +159,17 @@ export class CollectionAdapter extends BaseAdapter {
// ============================================ // ============================================
/** /**
* Lists the current user's collection weapons with optional filters * Lists a user's collection weapons with optional filters
* Works for any user - privacy is enforced server-side
*/ */
async listWeapons(params: CollectionListParams = {}): Promise<PaginatedResponse<CollectionWeapon>> { async listWeapons(
userId: string,
params: CollectionListParams = {}
): Promise<PaginatedResponse<CollectionWeapon>> {
const response = await this.request<{ const response = await this.request<{
weapons: CollectionWeapon[] weapons: CollectionWeapon[]
meta: { count: number; totalPages: number; perPage: number; currentPage: number } meta: { count: number; totalPages: number; perPage: number; currentPage: number }
}>('/collection/weapons', { }>(`/users/${userId}/collection/weapons`, {
method: 'GET', method: 'GET',
query: params query: params
}) })
@ -213,13 +221,17 @@ export class CollectionAdapter extends BaseAdapter {
// ============================================ // ============================================
/** /**
* Lists the current user's collection summons with optional filters * Lists a user's collection summons with optional filters
* Works for any user - privacy is enforced server-side
*/ */
async listSummons(params: CollectionListParams = {}): Promise<PaginatedResponse<CollectionSummon>> { async listSummons(
userId: string,
params: CollectionListParams = {}
): Promise<PaginatedResponse<CollectionSummon>> {
const response = await this.request<{ const response = await this.request<{
summons: CollectionSummon[] summons: CollectionSummon[]
meta: { count: number; totalPages: number; perPage: number; currentPage: number } meta: { count: number; totalPages: number; perPage: number; currentPage: number }
}>('/collection/summons', { }>(`/users/${userId}/collection/summons`, {
method: 'GET', method: 'GET',
query: params query: params
}) })
@ -304,49 +316,6 @@ export class CollectionAdapter extends BaseAdapter {
}) })
} }
// ============================================
// Public Collection (viewing other users)
// ============================================
/**
* Gets a user's public collection (respects privacy settings)
* @param userId - The user's ID
* @param type - Optional type filter: 'characters', 'weapons', 'summons', 'job_accessories'
*/
async getPublicCollection(
userId: string,
type?: 'characters' | 'weapons' | 'summons' | 'job_accessories'
): Promise<CollectionResponse> {
return this.request<CollectionResponse>(`/users/${userId}/collection`, {
method: 'GET',
query: type ? { type } : undefined
})
}
/**
* Gets a user's public character collection
*/
async getPublicCharacters(userId: string): Promise<CollectionCharacter[]> {
const response = await this.getPublicCollection(userId, 'characters')
return response.characters || []
}
/**
* Gets a user's public weapon collection
*/
async getPublicWeapons(userId: string): Promise<CollectionWeapon[]> {
const response = await this.getPublicCollection(userId, 'weapons')
return response.weapons || []
}
/**
* Gets a user's public summon collection
*/
async getPublicSummons(userId: string): Promise<CollectionSummon[]> {
const response = await this.getPublicCollection(userId, 'summons')
return response.summons || []
}
// ============================================ // ============================================
// Cache Management // Cache Management
// ============================================ // ============================================

View file

@ -27,6 +27,14 @@ export interface CollectionPageResult<T> {
perPage: number perPage: number
} }
/**
* Initial data structure for collection infinite queries
*/
export interface CollectionInitialData<T> {
pages: CollectionPageResult<T>[]
pageParams: number[]
}
/** /**
* Collection query options factory * Collection query options factory
* *
@ -35,25 +43,33 @@ export interface CollectionPageResult<T> {
* import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query' * import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
* import { collectionQueries } from '$lib/api/queries/collection.queries' * import { collectionQueries } from '$lib/api/queries/collection.queries'
* *
* // Own collection characters * // Any user's collection characters (privacy enforced server-side)
* const characters = createInfiniteQuery(() => collectionQueries.characters()) * const characters = createInfiniteQuery(() => collectionQueries.characters(userId))
*
* // Public collection
* const publicChars = createQuery(() => collectionQueries.publicCharacters(userId))
* *
* // Collected character IDs (for filtering add modal) * // Collected character IDs (for filtering add modal)
* const ownedIds = createQuery(() => collectionQueries.collectedCharacterIds()) * const ownedIds = createQuery(() => collectionQueries.collectedCharacterIds(userId))
* ``` * ```
*/ */
export const collectionQueries = { export const collectionQueries = {
/** /**
* Current user's collection characters with infinite scroll * User's collection characters with infinite scroll
* Works for any user - privacy is enforced server-side
*
* @param userId - The user whose collection to fetch
* @param filters - Optional filters for element, rarity, etc.
* @param enabled - Whether the query is enabled (default: true)
* @param initialData - Optional initial data for SSR hydration
*/ */
characters: (filters?: CollectionFilters) => characters: (
userId: string,
filters?: CollectionFilters,
enabled: boolean = true,
initialData?: CollectionInitialData<CollectionCharacter>
) =>
infiniteQueryOptions({ infiniteQueryOptions({
queryKey: ['collection', 'characters', filters] as const, queryKey: ['collection', 'characters', userId, filters] as const,
queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionCharacter>> => { queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionCharacter>> => {
const response = await collectionAdapter.listCharacters({ const response = await collectionAdapter.listCharacters(userId, {
...filters, ...filters,
page: pageParam page: pageParam
}) })
@ -73,17 +89,20 @@ export const collectionQueries = {
return undefined return undefined
}, },
staleTime: 1000 * 60 * 2, // 2 minutes staleTime: 1000 * 60 * 2, // 2 minutes
gcTime: 1000 * 60 * 15 // 15 minutes gcTime: 1000 * 60 * 15, // 15 minutes
enabled,
initialData
}), }),
/** /**
* Current user's collection weapons with infinite scroll * User's collection weapons with infinite scroll
* Works for any user - privacy is enforced server-side
*/ */
weapons: (filters?: CollectionFilters) => weapons: (userId: string, filters?: CollectionFilters) =>
infiniteQueryOptions({ infiniteQueryOptions({
queryKey: ['collection', 'weapons', filters] as const, queryKey: ['collection', 'weapons', userId, filters] as const,
queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionWeapon>> => { queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionWeapon>> => {
const response = await collectionAdapter.listWeapons({ const response = await collectionAdapter.listWeapons(userId, {
...filters, ...filters,
page: pageParam page: pageParam
}) })
@ -107,13 +126,14 @@ export const collectionQueries = {
}), }),
/** /**
* Current user's collection summons with infinite scroll * User's collection summons with infinite scroll
* Works for any user - privacy is enforced server-side
*/ */
summons: (filters?: CollectionFilters) => summons: (userId: string, filters?: CollectionFilters) =>
infiniteQueryOptions({ infiniteQueryOptions({
queryKey: ['collection', 'summons', filters] as const, queryKey: ['collection', 'summons', userId, filters] as const,
queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionSummon>> => { queryFn: async ({ pageParam }): Promise<CollectionPageResult<CollectionSummon>> => {
const response = await collectionAdapter.listSummons({ const response = await collectionAdapter.listSummons(userId, {
...filters, ...filters,
page: pageParam page: pageParam
}) })
@ -137,13 +157,14 @@ export const collectionQueries = {
}), }),
/** /**
* Get IDs of characters already in the user's collection * Get IDs of characters already in a user's collection
* Used to filter out owned characters in the add modal * Used to filter out owned characters in the add modal
*/ */
collectedCharacterIds: () => collectedCharacterIds: (userId: string) =>
queryOptions({ queryOptions({
queryKey: ['collection', 'characters', 'ids'] as const, queryKey: ['collection', 'characters', 'ids', userId] as const,
queryFn: () => collectionAdapter.getCollectedCharacterIds(), queryFn: () => collectionAdapter.getCollectedCharacterIds(userId),
enabled: !!userId,
staleTime: 1000 * 60 * 5, // 5 minutes staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30 // 30 minutes gcTime: 1000 * 60 * 30 // 30 minutes
}), }),
@ -158,42 +179,6 @@ export const collectionQueries = {
enabled: !!id, enabled: !!id,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30 gcTime: 1000 * 60 * 30
}),
/**
* Public collection for a user (respects privacy)
*/
publicCharacters: (userId: string) =>
queryOptions({
queryKey: ['collection', 'public', userId, 'characters'] as const,
queryFn: () => collectionAdapter.getPublicCharacters(userId),
enabled: !!userId,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30
}),
/**
* Public weapon collection for a user
*/
publicWeapons: (userId: string) =>
queryOptions({
queryKey: ['collection', 'public', userId, 'weapons'] as const,
queryFn: () => collectionAdapter.getPublicWeapons(userId),
enabled: !!userId,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30
}),
/**
* Public summon collection for a user
*/
publicSummons: (userId: string) =>
queryOptions({
queryKey: ['collection', 'public', userId, 'summons'] as const,
queryFn: () => collectionAdapter.getPublicSummons(userId),
enabled: !!userId,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30
}) })
} }
@ -210,24 +195,33 @@ export const collectionQueries = {
* // Invalidate all collection data * // Invalidate all collection data
* queryClient.invalidateQueries({ queryKey: collectionKeys.all }) * queryClient.invalidateQueries({ queryKey: collectionKeys.all })
* *
* // Invalidate only characters * // Invalidate only characters for a user
* queryClient.invalidateQueries({ queryKey: collectionKeys.characters() }) * queryClient.invalidateQueries({ queryKey: collectionKeys.characters(userId) })
* ``` * ```
*/ */
export const collectionKeys = { export const collectionKeys = {
all: ['collection'] as const, all: ['collection'] as const,
characters: () => [...collectionKeys.all, 'characters'] as const, characters: (userId?: string) =>
characterList: (filters?: CollectionFilters) => userId
[...collectionKeys.characters(), filters] as const, ? ([...collectionKeys.all, 'characters', userId] as const)
: ([...collectionKeys.all, 'characters'] as const),
characterList: (userId: string, filters?: CollectionFilters) =>
[...collectionKeys.characters(userId), filters] as const,
character: (id: string) => [...collectionKeys.all, 'character', id] as const, character: (id: string) => [...collectionKeys.all, 'character', id] as const,
characterIds: () => [...collectionKeys.characters(), 'ids'] as const, characterIds: (userId?: string) =>
weapons: () => [...collectionKeys.all, 'weapons'] as const, userId
weaponList: (filters?: CollectionFilters) => [...collectionKeys.weapons(), filters] as const, ? ([...collectionKeys.all, 'characters', 'ids', userId] as const)
summons: () => [...collectionKeys.all, 'summons'] as const, : ([...collectionKeys.all, 'characters', 'ids'] as const),
summonList: (filters?: CollectionFilters) => [...collectionKeys.summons(), filters] as const, weapons: (userId?: string) =>
public: (userId: string) => [...collectionKeys.all, 'public', userId] as const, userId
publicCharacters: (userId: string) => ? ([...collectionKeys.all, 'weapons', userId] as const)
[...collectionKeys.public(userId), 'characters'] as const, : ([...collectionKeys.all, 'weapons'] as const),
publicWeapons: (userId: string) => [...collectionKeys.public(userId), 'weapons'] as const, weaponList: (userId: string, filters?: CollectionFilters) =>
publicSummons: (userId: string) => [...collectionKeys.public(userId), 'summons'] as const [...collectionKeys.weapons(userId), filters] as const,
summons: (userId?: string) =>
userId
? ([...collectionKeys.all, 'summons', userId] as const)
: ([...collectionKeys.all, 'summons'] as const),
summonList: (userId: string, filters?: CollectionFilters) =>
[...collectionKeys.summons(userId), filters] as const
} }