update services and uncap utilities

This commit is contained in:
Justin Edmund 2025-09-23 22:10:22 -07:00
parent 53df1db2bc
commit 0324adb0a4
3 changed files with 346 additions and 71 deletions

View file

@ -1,5 +1,6 @@
import type { Party, GridWeapon, GridSummon, GridCharacter } from '$lib/types/api/party' import type { Party, GridWeapon, GridSummon, GridCharacter } from '$lib/types/api/party'
import { gridAdapter, partyAdapter } from '$lib/api/adapters' import { gridAdapter, partyAdapter } from '$lib/api/adapters'
import { getDefaultMaxUncapLevel } from '$lib/utils/uncap'
export interface GridOperation { export interface GridOperation {
type: 'add' | 'replace' | 'remove' | 'move' | 'swap' type: 'add' | 'replace' | 'remove' | 'move' | 'swap'
@ -32,21 +33,31 @@ export class GridService {
partyId: string, partyId: string,
weaponId: string, weaponId: string,
position: number, position: number,
editKey?: string editKey?: string,
options?: { mainhand?: boolean; shortcode?: string }
): Promise<GridUpdateResult> { ): Promise<GridUpdateResult> {
try { try {
const gridWeapon = await gridAdapter.createWeapon({ const gridWeapon = await gridAdapter.createWeapon({
partyId, partyId,
weaponId, weaponId,
position, position,
uncapLevel: 0, mainhand: options?.mainhand,
uncapLevel: getDefaultMaxUncapLevel('weapon'),
transcendenceStage: 0 transcendenceStage: 0
}) }, this.buildHeaders(editKey))
// Fetch updated party to return console.log('[GridService] Created grid weapon:', gridWeapon)
const party = await partyAdapter.getByShortcode(partyId)
return { party } // 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) { } catch (error: any) {
console.error('[GridService] Error creating weapon:', error)
if (error.type === 'conflict') { if (error.type === 'conflict') {
return { return {
party: null as any, // Will be handled by conflict resolution party: null as any, // Will be handled by conflict resolution
@ -61,14 +72,15 @@ export class GridService {
partyId: string, partyId: string,
gridWeaponId: string, gridWeaponId: string,
newWeaponId: string, newWeaponId: string,
editKey?: string editKey?: string,
options?: { shortcode?: string }
): Promise<GridUpdateResult> { ): Promise<GridUpdateResult> {
try { try {
// First remove the old weapon // 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 // Then add the new one (pass shortcode along)
const result = await this.addWeapon(partyId, newWeaponId, 0, editKey) const result = await this.addWeapon(partyId, newWeaponId, 0, editKey, options)
return result return result
} catch (error: any) { } catch (error: any) {
if (error.type === 'conflict') { if (error.type === 'conflict') {
@ -84,12 +96,18 @@ export class GridService {
async removeWeapon( async removeWeapon(
partyId: string, partyId: string,
gridWeaponId: string, gridWeaponId: string,
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }) ): Promise<Party | null> {
await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId }, this.buildHeaders(editKey))
// Return updated party // Clear party cache if shortcode provided
return partyAdapter.getByShortcode(partyId) if (options?.shortcode) {
partyAdapter.clearPartyCache(options.shortcode)
}
// Don't fetch - let caller handle refresh
return null
} }
async updateWeapon( async updateWeapon(
@ -101,50 +119,71 @@ export class GridService {
transcendenceStep?: number transcendenceStep?: number
element?: number element?: number
}, },
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
): Promise<Party | null> {
await gridAdapter.updateWeapon(gridWeaponId, { await gridAdapter.updateWeapon(gridWeaponId, {
position: updates.position, position: updates.position,
uncapLevel: updates.uncapLevel, uncapLevel: updates.uncapLevel,
transcendenceStage: updates.transcendenceStep, transcendenceStage: updates.transcendenceStep,
element: updates.element element: updates.element
}) }, this.buildHeaders(editKey))
// Return updated party // Clear party cache if shortcode provided
return partyAdapter.getByShortcode(partyId) if (options?.shortcode) {
partyAdapter.clearPartyCache(options.shortcode)
}
// Don't fetch - let caller handle refresh
return null
} }
async moveWeapon( async moveWeapon(
partyId: string, partyId: string,
gridWeaponId: string, gridWeaponId: string,
newPosition: number, newPosition: number,
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
): Promise<Party | null> {
await gridAdapter.updateWeaponPosition({ await gridAdapter.updateWeaponPosition({
partyId, partyId,
id: gridWeaponId, id: gridWeaponId,
position: newPosition 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( async swapWeapons(
partyId: string, partyId: string,
gridWeaponId1: string, gridWeaponId1: string,
gridWeaponId2: string, gridWeaponId2: string,
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
): Promise<Party | null> {
await gridAdapter.swapWeapons({ await gridAdapter.swapWeapons({
partyId, partyId,
sourceId: gridWeaponId1, sourceId: gridWeaponId1,
targetId: gridWeaponId2 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( async updateWeaponUncap(
partyId: string,
gridWeaponId: string, gridWeaponId: string,
uncapLevel?: number, uncapLevel?: number,
transcendenceStep?: number, transcendenceStep?: number,
@ -152,10 +191,10 @@ export class GridService {
): Promise<any> { ): Promise<any> {
return gridAdapter.updateWeaponUncap({ return gridAdapter.updateWeaponUncap({
id: gridWeaponId, id: gridWeaponId,
partyId: 'unknown', // This is a design issue - needs partyId partyId,
uncapLevel: uncapLevel ?? 3, uncapLevel: uncapLevel ?? 3,
transcendenceStep transcendenceStep
}) }, this.buildHeaders(editKey))
} }
// Summon Grid Operations // Summon Grid Operations
@ -164,40 +203,59 @@ export class GridService {
partyId: string, partyId: string,
summonId: string, summonId: string,
position: number, position: number,
editKey?: string editKey?: string,
options?: { main?: boolean; friend?: boolean; shortcode?: string }
): Promise<Party> { ): Promise<Party> {
await gridAdapter.createSummon({ const gridSummon = await gridAdapter.createSummon({
partyId, partyId,
summonId, summonId,
position, position,
uncapLevel: 0, main: options?.main,
friend: options?.friend,
uncapLevel: getDefaultMaxUncapLevel('summon'),
transcendenceStage: 0 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( async replaceSummon(
partyId: string, partyId: string,
gridSummonId: string, gridSummonId: string,
newSummonId: string, newSummonId: string,
editKey?: string editKey?: string,
options?: { shortcode?: string }
): Promise<Party> { ): Promise<Party> {
// First remove the old summon // 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 // Then add the new one (pass shortcode along)
return this.addSummon(partyId, newSummonId, 0, editKey) return this.addSummon(partyId, newSummonId, 0, editKey, { ...options })
} }
async removeSummon( async removeSummon(
partyId: string, partyId: string,
gridSummonId: string, gridSummonId: string,
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
await gridAdapter.deleteSummon({ id: gridSummonId, partyId }) ): Promise<Party | null> {
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( async updateSummon(
@ -209,19 +267,71 @@ export class GridService {
uncapLevel?: number uncapLevel?: number
transcendenceStep?: number transcendenceStep?: number
}, },
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
): Promise<Party | null> {
await gridAdapter.updateSummon(gridSummonId, { await gridAdapter.updateSummon(gridSummonId, {
position: updates.position, position: updates.position,
quickSummon: updates.quickSummon, quickSummon: updates.quickSummon,
uncapLevel: updates.uncapLevel, uncapLevel: updates.uncapLevel,
transcendenceStage: updates.transcendenceStep 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<Party | null> {
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<Party | null> {
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( async updateSummonUncap(
partyId: string,
gridSummonId: string, gridSummonId: string,
uncapLevel?: number, uncapLevel?: number,
transcendenceStep?: number, transcendenceStep?: number,
@ -229,10 +339,10 @@ export class GridService {
): Promise<any> { ): Promise<any> {
return gridAdapter.updateSummonUncap({ return gridAdapter.updateSummonUncap({
id: gridSummonId, id: gridSummonId,
partyId: 'unknown', // This is a design issue - needs partyId partyId,
uncapLevel: uncapLevel ?? 3, uncapLevel: uncapLevel ?? 3,
transcendenceStep transcendenceStep
}) }, this.buildHeaders(editKey))
} }
// Character Grid Operations // Character Grid Operations
@ -241,19 +351,27 @@ export class GridService {
partyId: string, partyId: string,
characterId: string, characterId: string,
position: number, position: number,
editKey?: string editKey?: string,
options?: { shortcode?: string }
): Promise<GridUpdateResult> { ): Promise<GridUpdateResult> {
try { try {
await gridAdapter.createCharacter({ const gridCharacter = await gridAdapter.createCharacter({
partyId, partyId,
characterId, characterId,
position, position,
uncapLevel: 0, uncapLevel: getDefaultMaxUncapLevel('character'),
transcendenceStage: 0 transcendenceStage: 0
}) }, this.buildHeaders(editKey))
const party = await partyAdapter.getByShortcode(partyId) console.log('[GridService] Created grid character:', gridCharacter)
return { party }
// 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) { } catch (error: any) {
if (error.type === 'conflict') { if (error.type === 'conflict') {
return { return {
@ -269,14 +387,15 @@ export class GridService {
partyId: string, partyId: string,
gridCharacterId: string, gridCharacterId: string,
newCharacterId: string, newCharacterId: string,
editKey?: string editKey?: string,
options?: { shortcode?: string }
): Promise<GridUpdateResult> { ): Promise<GridUpdateResult> {
try { try {
// First remove the old character // 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 // Then add the new one (pass shortcode along)
return this.addCharacter(partyId, newCharacterId, 0, editKey) return this.addCharacter(partyId, newCharacterId, 0, editKey, options)
} catch (error: any) { } catch (error: any) {
if (error.type === 'conflict') { if (error.type === 'conflict') {
return { return {
@ -291,11 +410,18 @@ export class GridService {
async removeCharacter( async removeCharacter(
partyId: string, partyId: string,
gridCharacterId: string, gridCharacterId: string,
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId }) ): Promise<Party | null> {
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( async updateCharacter(
@ -307,19 +433,71 @@ export class GridService {
transcendenceStep?: number transcendenceStep?: number
perpetuity?: boolean perpetuity?: boolean
}, },
editKey?: string editKey?: string,
): Promise<Party> { options?: { shortcode?: string }
): Promise<Party | null> {
await gridAdapter.updateCharacter(gridCharacterId, { await gridAdapter.updateCharacter(gridCharacterId, {
position: updates.position, position: updates.position,
uncapLevel: updates.uncapLevel, uncapLevel: updates.uncapLevel,
transcendenceStage: updates.transcendenceStep, transcendenceStage: updates.transcendenceStep,
perpetualModifiers: updates.perpetuity ? {} : undefined 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<Party | null> {
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<Party | null> {
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( async updateCharacterUncap(
partyId: string,
gridCharacterId: string, gridCharacterId: string,
uncapLevel?: number, uncapLevel?: number,
transcendenceStep?: number, transcendenceStep?: number,
@ -327,10 +505,10 @@ export class GridService {
): Promise<any> { ): Promise<any> {
return gridAdapter.updateCharacterUncap({ return gridAdapter.updateCharacterUncap({
id: gridCharacterId, id: gridCharacterId,
partyId: 'unknown', // This is a design issue - needs partyId partyId,
uncapLevel: uncapLevel ?? 3, uncapLevel: uncapLevel ?? 3,
transcendenceStep transcendenceStep
}) }, this.buildHeaders(editKey))
} }
// Drag and Drop Helpers // Drag and Drop Helpers
@ -417,4 +595,4 @@ export class GridService {
} }
return headers return headers
} }
} }

View file

@ -1,5 +1,7 @@
import type { Party } from '$lib/types/api/party' import type { Party } from '$lib/types/api/party'
import { partyAdapter } from '$lib/api/adapters' import { partyAdapter } from '$lib/api/adapters'
import { authStore } from '$lib/stores/auth.store'
import { browser } from '$app/environment'
export interface EditabilityResult { export interface EditabilityResult {
canEdit: boolean canEdit: boolean
@ -37,6 +39,13 @@ export class PartyService {
async getByShortcode(shortcode: string): Promise<Party> { async getByShortcode(shortcode: string): Promise<Party> {
return partyAdapter.getByShortcode(shortcode) return partyAdapter.getByShortcode(shortcode)
} }
/**
* Clear party cache for a specific shortcode
*/
clearPartyCache(shortcode: string): void {
partyAdapter.clearPartyCache(shortcode)
}
/** /**
* Create a new party * Create a new party
@ -114,8 +123,77 @@ export class PartyService {
* Delete a party * Delete a party
*/ */
async delete(id: string, editKey?: string): Promise<void> { async delete(id: string, editKey?: string): Promise<void> {
// 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) const headers = this.buildHeaders(editKey)
return partyAdapter.delete(id, headers)
// Get auth token from authStore
const authHeaders: Record<string, string> = {}
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)
} }
/** /**

View file

@ -39,3 +39,22 @@ export function getCharacterMaxUncapLevel(character: CharacterUncapData): number
const { special, uncap } = character const { special, uncap } = character
return getMaxUncapLevel(special, uncap.flb, uncap.ulb) 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
}
}