295 lines
8.5 KiB
TypeScript
295 lines
8.5 KiB
TypeScript
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
|
|
headers?: Record<string, string>
|
|
reason?: string
|
|
}
|
|
|
|
export interface PartyUpdatePayload {
|
|
name?: string | null
|
|
description?: string | null
|
|
element?: number
|
|
raidId?: string
|
|
chargeAttack?: boolean
|
|
fullAuto?: boolean
|
|
autoGuard?: boolean
|
|
autoSummon?: boolean
|
|
clearTime?: number | null
|
|
buttonCount?: number | null
|
|
chainCount?: number | null
|
|
turnCount?: number | null
|
|
jobId?: string
|
|
visibility?: number
|
|
localId?: string
|
|
}
|
|
|
|
/**
|
|
* Party service - handles business logic for party operations
|
|
*/
|
|
export class PartyService {
|
|
constructor() {}
|
|
|
|
/**
|
|
* Get party by shortcode
|
|
*/
|
|
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
|
|
*/
|
|
async create(payload: PartyUpdatePayload, editKey?: string): Promise<{
|
|
party: Party
|
|
editKey?: string
|
|
}> {
|
|
const headers = this.buildHeaders(editKey)
|
|
const apiPayload = this.mapToApiPayload(payload)
|
|
const party = await partyAdapter.create(apiPayload, headers)
|
|
|
|
// Note: Edit key handling may need to be adjusted based on how the API returns it
|
|
return { party, editKey: undefined }
|
|
}
|
|
|
|
/**
|
|
* Update party details
|
|
*/
|
|
async update(id: string, payload: PartyUpdatePayload, editKey?: string): Promise<Party> {
|
|
const headers = this.buildHeaders(editKey)
|
|
const apiPayload = this.mapToApiPayload(payload)
|
|
return partyAdapter.update({ shortcode: id, ...apiPayload }, headers)
|
|
}
|
|
|
|
/**
|
|
* Update party guidebooks
|
|
*/
|
|
async updateGuidebooks(
|
|
id: string,
|
|
position: number,
|
|
guidebookId: string | null,
|
|
editKey?: string
|
|
): Promise<Party> {
|
|
const headers = this.buildHeaders(editKey)
|
|
const payload: any = {}
|
|
|
|
// Map position to guidebook1_id, guidebook2_id, guidebook3_id
|
|
if (position >= 0 && position <= 2) {
|
|
payload[`guidebook${position + 1}Id`] = guidebookId
|
|
}
|
|
|
|
return partyAdapter.update({ shortcode: id, ...payload }, headers)
|
|
}
|
|
|
|
/**
|
|
* Remix a party (create a copy)
|
|
*/
|
|
async remix(shortcode: string, localId?: string, editKey?: string): Promise<{
|
|
party: Party
|
|
editKey?: string
|
|
}> {
|
|
const headers = this.buildHeaders(editKey)
|
|
const party = await partyAdapter.remix(shortcode, headers)
|
|
|
|
// Note: Edit key handling may need to be adjusted
|
|
return { party, editKey: undefined }
|
|
}
|
|
|
|
/**
|
|
* Favorite a party
|
|
*/
|
|
async favorite(id: string): Promise<void> {
|
|
return partyAdapter.favorite(id)
|
|
}
|
|
|
|
/**
|
|
* Unfavorite a party
|
|
*/
|
|
async unfavorite(id: string): Promise<void> {
|
|
return partyAdapter.unfavorite(id)
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
|
|
// 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)
|
|
}
|
|
|
|
/**
|
|
* Compute editability for a party
|
|
*/
|
|
computeEditability(
|
|
party: Party,
|
|
authUserId?: string,
|
|
localId?: string,
|
|
editKey?: string
|
|
): EditabilityResult {
|
|
// Owner can always edit
|
|
if (authUserId && party.user?.id === authUserId) {
|
|
return { canEdit: true, reason: 'owner' }
|
|
}
|
|
|
|
// Local owner can edit if no server user
|
|
const isLocalOwner = localId && party.localId === localId
|
|
const hasNoServerUser = !party.user?.id
|
|
|
|
if (isLocalOwner && hasNoServerUser) {
|
|
const base = { canEdit: true, reason: 'local_owner' as const }
|
|
return editKey ? { ...base, headers: { 'X-Edit-Key': editKey } } : base
|
|
}
|
|
|
|
// Check for edit key permission
|
|
if (editKey && typeof window !== 'undefined') {
|
|
const storedKey = localStorage.getItem(`edit_key_${party.shortcode}`)
|
|
if (storedKey === editKey) {
|
|
return { canEdit: true, headers: { 'X-Edit-Key': editKey }, reason: 'edit_key' }
|
|
}
|
|
}
|
|
|
|
return { canEdit: false, reason: 'no_permission' }
|
|
}
|
|
|
|
/**
|
|
* Get or create local ID for anonymous users
|
|
*/
|
|
getLocalId(): string {
|
|
if (typeof window === 'undefined') return ''
|
|
|
|
let localId = localStorage.getItem('local_id')
|
|
if (!localId) {
|
|
localId = crypto.randomUUID()
|
|
localStorage.setItem('local_id', localId)
|
|
}
|
|
return localId
|
|
}
|
|
|
|
/**
|
|
* Get edit key for a party
|
|
*/
|
|
getEditKey(shortcode: string): string | null {
|
|
if (typeof window === 'undefined') return null
|
|
return localStorage.getItem(`edit_key_${shortcode}`)
|
|
}
|
|
|
|
/**
|
|
* Store edit key for a party
|
|
*/
|
|
storeEditKey(shortcode: string, editKey: string): void {
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem(`edit_key_${shortcode}`, editKey)
|
|
}
|
|
}
|
|
|
|
// Private helpers
|
|
|
|
private buildHeaders(editKey?: string): Record<string, string> {
|
|
const headers: Record<string, string> = {}
|
|
if (editKey) {
|
|
headers['X-Edit-Key'] = editKey
|
|
}
|
|
return headers
|
|
}
|
|
|
|
private mapToApiPayload(payload: PartyUpdatePayload): Partial<Party> {
|
|
const mapped: any = {}
|
|
|
|
if (payload.name !== undefined) mapped.name = payload.name
|
|
if (payload.description !== undefined) mapped.description = payload.description
|
|
if (payload.element !== undefined) mapped.element = payload.element
|
|
if (payload.raidId !== undefined) mapped.raid = { id: payload.raidId }
|
|
if (payload.chargeAttack !== undefined) mapped.chargeAttack = payload.chargeAttack
|
|
if (payload.fullAuto !== undefined) mapped.fullAuto = payload.fullAuto
|
|
if (payload.autoGuard !== undefined) mapped.autoGuard = payload.autoGuard
|
|
if (payload.autoSummon !== undefined) mapped.autoSummon = payload.autoSummon
|
|
if (payload.clearTime !== undefined) mapped.clearTime = payload.clearTime
|
|
if (payload.buttonCount !== undefined) mapped.buttonCount = payload.buttonCount
|
|
if (payload.chainCount !== undefined) mapped.chainCount = payload.chainCount
|
|
if (payload.turnCount !== undefined) mapped.turnCount = payload.turnCount
|
|
if (payload.jobId !== undefined) mapped.job = { id: payload.jobId }
|
|
if (payload.visibility !== undefined) mapped.visibility = payload.visibility
|
|
if (payload.localId !== undefined) mapped.localId = payload.localId
|
|
|
|
return mapped
|
|
}
|
|
}
|