diff --git a/src/lib/services/grid.service.ts b/src/lib/services/grid.service.ts index c8e9141e..59813ead 100644 --- a/src/lib/services/grid.service.ts +++ b/src/lib/services/grid.service.ts @@ -1,5 +1,6 @@ import type { Party, GridWeapon, GridSummon, GridCharacter } from '$lib/types/api/party' import { gridAdapter, partyAdapter } from '$lib/api/adapters' +import { getDefaultMaxUncapLevel } from '$lib/utils/uncap' export interface GridOperation { type: 'add' | 'replace' | 'remove' | 'move' | 'swap' @@ -32,21 +33,31 @@ export class GridService { partyId: string, weaponId: string, position: number, - editKey?: string + editKey?: string, + options?: { mainhand?: boolean; shortcode?: string } ): Promise { try { const gridWeapon = await gridAdapter.createWeapon({ partyId, weaponId, position, - uncapLevel: 0, + mainhand: options?.mainhand, + uncapLevel: getDefaultMaxUncapLevel('weapon'), transcendenceStage: 0 - }) + }, this.buildHeaders(editKey)) - // Fetch updated party to return - const party = await partyAdapter.getByShortcode(partyId) - return { party } + console.log('[GridService] Created grid weapon:', gridWeapon) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Return success without fetching party - the caller should refresh if needed + // partyId is a UUID, not a shortcode, so we can't fetch here + return { party: null as any } } catch (error: any) { + console.error('[GridService] Error creating weapon:', error) if (error.type === 'conflict') { return { party: null as any, // Will be handled by conflict resolution @@ -61,14 +72,15 @@ export class GridService { partyId: string, gridWeaponId: string, newWeaponId: string, - editKey?: string + editKey?: string, + options?: { shortcode?: string } ): Promise { try { // First remove the old weapon - await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }) + await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }, this.buildHeaders(editKey)) - // Then add the new one - const result = await this.addWeapon(partyId, newWeaponId, 0, editKey) + // Then add the new one (pass shortcode along) + const result = await this.addWeapon(partyId, newWeaponId, 0, editKey, options) return result } catch (error: any) { if (error.type === 'conflict') { @@ -84,12 +96,18 @@ export class GridService { async removeWeapon( partyId: string, gridWeaponId: string, - editKey?: string - ): Promise { - await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }) + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }, this.buildHeaders(editKey)) - // Return updated party - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateWeapon( @@ -101,50 +119,71 @@ export class GridService { transcendenceStep?: number element?: number }, - editKey?: string - ): Promise { + editKey?: string, + options?: { shortcode?: string } + ): Promise { await gridAdapter.updateWeapon(gridWeaponId, { position: updates.position, uncapLevel: updates.uncapLevel, transcendenceStage: updates.transcendenceStep, element: updates.element - }) + }, this.buildHeaders(editKey)) - // Return updated party - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async moveWeapon( partyId: string, gridWeaponId: string, newPosition: number, - editKey?: string - ): Promise { + editKey?: string, + options?: { shortcode?: string } + ): Promise { await gridAdapter.updateWeaponPosition({ partyId, id: gridWeaponId, position: newPosition - }) + }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async swapWeapons( partyId: string, gridWeaponId1: string, gridWeaponId2: string, - editKey?: string - ): Promise { + editKey?: string, + options?: { shortcode?: string } + ): Promise { await gridAdapter.swapWeapons({ partyId, sourceId: gridWeaponId1, targetId: gridWeaponId2 - }) + }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateWeaponUncap( + partyId: string, gridWeaponId: string, uncapLevel?: number, transcendenceStep?: number, @@ -152,10 +191,10 @@ export class GridService { ): Promise { return gridAdapter.updateWeaponUncap({ id: gridWeaponId, - partyId: 'unknown', // This is a design issue - needs partyId + partyId, uncapLevel: uncapLevel ?? 3, transcendenceStep - }) + }, this.buildHeaders(editKey)) } // Summon Grid Operations @@ -164,40 +203,59 @@ export class GridService { partyId: string, summonId: string, position: number, - editKey?: string + editKey?: string, + options?: { main?: boolean; friend?: boolean; shortcode?: string } ): Promise { - await gridAdapter.createSummon({ + const gridSummon = await gridAdapter.createSummon({ partyId, summonId, position, - uncapLevel: 0, + main: options?.main, + friend: options?.friend, + uncapLevel: getDefaultMaxUncapLevel('summon'), transcendenceStage: 0 - }) + }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + console.log('[GridService] Created grid summon:', gridSummon) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - partyId is UUID not shortcode + return null as any } async replaceSummon( partyId: string, gridSummonId: string, newSummonId: string, - editKey?: string + editKey?: string, + options?: { shortcode?: string } ): Promise { // First remove the old summon - await gridAdapter.deleteSummon({ id: gridSummonId, partyId }) + await gridAdapter.deleteSummon({ id: gridSummonId, partyId }, this.buildHeaders(editKey)) - // Then add the new one - return this.addSummon(partyId, newSummonId, 0, editKey) + // Then add the new one (pass shortcode along) + return this.addSummon(partyId, newSummonId, 0, editKey, { ...options }) } async removeSummon( partyId: string, gridSummonId: string, - editKey?: string - ): Promise { - await gridAdapter.deleteSummon({ id: gridSummonId, partyId }) + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.deleteSummon({ id: gridSummonId, partyId }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateSummon( @@ -209,19 +267,71 @@ export class GridService { uncapLevel?: number transcendenceStep?: number }, - editKey?: string - ): Promise { + editKey?: string, + options?: { shortcode?: string } + ): Promise { await gridAdapter.updateSummon(gridSummonId, { position: updates.position, quickSummon: updates.quickSummon, uncapLevel: updates.uncapLevel, transcendenceStage: updates.transcendenceStep - }) + }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null + } + + async moveSummon( + partyId: string, + gridSummonId: string, + newPosition: number, + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.updateSummonPosition({ + partyId, + id: gridSummonId, + position: newPosition + }, this.buildHeaders(editKey)) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null + } + + async swapSummons( + partyId: string, + gridSummonId1: string, + gridSummonId2: string, + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.swapSummons({ + partyId, + sourceId: gridSummonId1, + targetId: gridSummonId2 + }, this.buildHeaders(editKey)) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateSummonUncap( + partyId: string, gridSummonId: string, uncapLevel?: number, transcendenceStep?: number, @@ -229,10 +339,10 @@ export class GridService { ): Promise { return gridAdapter.updateSummonUncap({ id: gridSummonId, - partyId: 'unknown', // This is a design issue - needs partyId + partyId, uncapLevel: uncapLevel ?? 3, transcendenceStep - }) + }, this.buildHeaders(editKey)) } // Character Grid Operations @@ -241,19 +351,27 @@ export class GridService { partyId: string, characterId: string, position: number, - editKey?: string + editKey?: string, + options?: { shortcode?: string } ): Promise { try { - await gridAdapter.createCharacter({ + const gridCharacter = await gridAdapter.createCharacter({ partyId, characterId, position, - uncapLevel: 0, + uncapLevel: getDefaultMaxUncapLevel('character'), transcendenceStage: 0 - }) + }, this.buildHeaders(editKey)) - const party = await partyAdapter.getByShortcode(partyId) - return { party } + console.log('[GridService] Created grid character:', gridCharacter) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - partyId is UUID not shortcode + return { party: null as any } } catch (error: any) { if (error.type === 'conflict') { return { @@ -269,14 +387,15 @@ export class GridService { partyId: string, gridCharacterId: string, newCharacterId: string, - editKey?: string + editKey?: string, + options?: { shortcode?: string } ): Promise { try { // First remove the old character - await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId }) + await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId }, this.buildHeaders(editKey)) - // Then add the new one - return this.addCharacter(partyId, newCharacterId, 0, editKey) + // Then add the new one (pass shortcode along) + return this.addCharacter(partyId, newCharacterId, 0, editKey, options) } catch (error: any) { if (error.type === 'conflict') { return { @@ -291,11 +410,18 @@ export class GridService { async removeCharacter( partyId: string, gridCharacterId: string, - editKey?: string - ): Promise { - await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId }) + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateCharacter( @@ -307,19 +433,71 @@ export class GridService { transcendenceStep?: number perpetuity?: boolean }, - editKey?: string - ): Promise { + editKey?: string, + options?: { shortcode?: string } + ): Promise { await gridAdapter.updateCharacter(gridCharacterId, { position: updates.position, uncapLevel: updates.uncapLevel, transcendenceStage: updates.transcendenceStep, perpetualModifiers: updates.perpetuity ? {} : undefined - }) + }, this.buildHeaders(editKey)) - return partyAdapter.getByShortcode(partyId) + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null + } + + async moveCharacter( + partyId: string, + gridCharacterId: string, + newPosition: number, + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.updateCharacterPosition({ + partyId, + id: gridCharacterId, + position: newPosition + }, this.buildHeaders(editKey)) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null + } + + async swapCharacters( + partyId: string, + gridCharacterId1: string, + gridCharacterId2: string, + editKey?: string, + options?: { shortcode?: string } + ): Promise { + await gridAdapter.swapCharacters({ + partyId, + sourceId: gridCharacterId1, + targetId: gridCharacterId2 + }, this.buildHeaders(editKey)) + + // Clear party cache if shortcode provided + if (options?.shortcode) { + partyAdapter.clearPartyCache(options.shortcode) + } + + // Don't fetch - let caller handle refresh + return null } async updateCharacterUncap( + partyId: string, gridCharacterId: string, uncapLevel?: number, transcendenceStep?: number, @@ -327,10 +505,10 @@ export class GridService { ): Promise { return gridAdapter.updateCharacterUncap({ id: gridCharacterId, - partyId: 'unknown', // This is a design issue - needs partyId + partyId, uncapLevel: uncapLevel ?? 3, transcendenceStep - }) + }, this.buildHeaders(editKey)) } // Drag and Drop Helpers @@ -417,4 +595,4 @@ export class GridService { } return headers } -} \ No newline at end of file +} diff --git a/src/lib/services/party.service.ts b/src/lib/services/party.service.ts index 72489716..9f437a96 100644 --- a/src/lib/services/party.service.ts +++ b/src/lib/services/party.service.ts @@ -1,5 +1,7 @@ import type { Party } from '$lib/types/api/party' import { partyAdapter } from '$lib/api/adapters' +import { authStore } from '$lib/stores/auth.store' +import { browser } from '$app/environment' export interface EditabilityResult { canEdit: boolean @@ -37,6 +39,13 @@ export class PartyService { async getByShortcode(shortcode: string): Promise { return partyAdapter.getByShortcode(shortcode) } + + /** + * Clear party cache for a specific shortcode + */ + clearPartyCache(shortcode: string): void { + partyAdapter.clearPartyCache(shortcode) + } /** * Create a new party @@ -114,8 +123,77 @@ export class PartyService { * Delete a party */ async delete(id: string, editKey?: string): Promise { + // The API expects the party ID, not shortcode, for delete + // We need to make a direct request with the ID const headers = this.buildHeaders(editKey) - return partyAdapter.delete(id, headers) + + // Get auth token from authStore + const authHeaders: Record = {} + if (browser) { + const token = await authStore.checkAndRefresh() + if (token) { + authHeaders['Authorization'] = `Bearer ${token}` + } + } + + const finalHeaders = { + 'Content-Type': 'application/json', + ...authHeaders, + ...headers + } + + const url = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1'}/parties/${id}` + + console.log('[PartyService] DELETE Request Details:', { + url, + method: 'DELETE', + headers: finalHeaders, + credentials: 'include', + partyId: id, + hasEditKey: !!editKey, + hasAuthToken: !!authHeaders['Authorization'] + }) + + // Make direct API call since adapter expects shortcode but API needs ID + const response = await fetch(url, { + method: 'DELETE', + credentials: 'include', + headers: finalHeaders + }) + + console.log('[PartyService] DELETE Response:', { + status: response.status, + statusText: response.statusText, + ok: response.ok, + headers: Object.fromEntries(response.headers.entries()) + }) + + if (!response.ok) { + // Try to parse error body for more details + let errorBody = null + try { + const contentType = response.headers.get('content-type') + if (contentType && contentType.includes('application/json')) { + errorBody = await response.json() + } else { + errorBody = await response.text() + } + } catch (e) { + console.error('[PartyService] Could not parse error response body:', e) + } + + console.error('[PartyService] DELETE Failed:', { + status: response.status, + statusText: response.statusText, + errorBody, + url, + partyId: id + }) + + throw new Error(`Failed to delete party: ${response.status} ${response.statusText}${errorBody ? ` - ${JSON.stringify(errorBody)}` : ''}`) + } + + console.log('[PartyService] DELETE Success - Party deleted:', id) } /** diff --git a/src/lib/utils/uncap.ts b/src/lib/utils/uncap.ts index 6252294a..818b21c5 100644 --- a/src/lib/utils/uncap.ts +++ b/src/lib/utils/uncap.ts @@ -39,3 +39,22 @@ export function getCharacterMaxUncapLevel(character: CharacterUncapData): number const { special, uncap } = character return getMaxUncapLevel(special, uncap.flb, uncap.ulb) } + +/** + * Get the default max uncap level for an item type (without transcendence) + * @param type - The type of item (character, weapon, or summon) + * @returns The default maximum uncap level + */ +export function getDefaultMaxUncapLevel(type: 'character' | 'weapon' | 'summon'): number { + switch (type) { + case 'character': + // Most characters can go to 5* (uncap level 5) + return 5 + case 'weapon': + case 'summon': + // Weapons and summons typically max at 3* without transcendence + return 3 + default: + return 3 + } +}