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 { 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<GridUpdateResult> {
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<GridUpdateResult> {
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<Party> {
await gridAdapter.deleteWeapon({ id: gridWeaponId, partyId })
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<Party> {
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<Party> {
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<Party> {
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<any> {
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<Party> {
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<Party> {
// 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<Party> {
await gridAdapter.deleteSummon({ id: gridSummonId, partyId })
editKey?: string,
options?: { shortcode?: string }
): 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(
@ -209,19 +267,71 @@ export class GridService {
uncapLevel?: number
transcendenceStep?: number
},
editKey?: string
): Promise<Party> {
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<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(
partyId: string,
gridSummonId: string,
uncapLevel?: number,
transcendenceStep?: number,
@ -229,10 +339,10 @@ export class GridService {
): Promise<any> {
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<GridUpdateResult> {
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<GridUpdateResult> {
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<Party> {
await gridAdapter.deleteCharacter({ id: gridCharacterId, partyId })
editKey?: string,
options?: { shortcode?: string }
): 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(
@ -307,19 +433,71 @@ export class GridService {
transcendenceStep?: number
perpetuity?: boolean
},
editKey?: string
): Promise<Party> {
editKey?: string,
options?: { shortcode?: string }
): Promise<Party | null> {
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<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(
partyId: string,
gridCharacterId: string,
uncapLevel?: number,
transcendenceStep?: number,
@ -327,10 +505,10 @@ export class GridService {
): Promise<any> {
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
}
}
}

View file

@ -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<Party> {
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<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)
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
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
}
}