Add new services for business logic
This commit is contained in:
parent
cf351ef1fc
commit
32c1c9016e
3 changed files with 856 additions and 0 deletions
192
src/lib/services/conflict.service.ts
Normal file
192
src/lib/services/conflict.service.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import type { Party, GridWeapon, GridCharacter } from '$lib/api/schemas/party'
|
||||
import type { FetchLike } from '$lib/api/core'
|
||||
import * as partiesApi from '$lib/api/resources/parties'
|
||||
|
||||
export interface ConflictData {
|
||||
conflicts: string[]
|
||||
incoming: string
|
||||
position: number
|
||||
}
|
||||
|
||||
export interface ConflictResolution {
|
||||
action: 'replace' | 'cancel'
|
||||
removeIds: string[]
|
||||
addId: string
|
||||
position: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Conflict service - handles conflict resolution for weapons and characters
|
||||
*/
|
||||
export class ConflictService {
|
||||
constructor(private fetch: FetchLike) {}
|
||||
|
||||
/**
|
||||
* Resolve a conflict by choosing which items to keep
|
||||
*/
|
||||
async resolveConflict(
|
||||
partyId: string,
|
||||
conflictType: 'weapon' | 'character',
|
||||
resolution: ConflictResolution,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const headers = this.buildHeaders(editKey)
|
||||
|
||||
if (conflictType === 'weapon') {
|
||||
return this.resolveWeaponConflict(partyId, resolution, headers)
|
||||
} else {
|
||||
return this.resolveCharacterConflict(partyId, resolution, headers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if adding an item would cause conflicts
|
||||
*/
|
||||
checkConflicts(
|
||||
party: Party,
|
||||
itemType: 'weapon' | 'character',
|
||||
itemId: string
|
||||
): ConflictData | null {
|
||||
if (itemType === 'weapon') {
|
||||
return this.checkWeaponConflicts(party, itemId)
|
||||
} else {
|
||||
return this.checkCharacterConflicts(party, itemId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format conflict message for display
|
||||
*/
|
||||
formatConflictMessage(
|
||||
conflictType: 'weapon' | 'character',
|
||||
conflictingItems: Array<{ name: string; position: number }>,
|
||||
incomingItem: { name: string }
|
||||
): string {
|
||||
const itemTypeLabel = conflictType === 'weapon' ? 'weapon' : 'character'
|
||||
const conflictNames = conflictingItems.map(i => i.name).join(', ')
|
||||
|
||||
if (conflictingItems.length === 1) {
|
||||
return `Adding ${incomingItem.name} would conflict with ${conflictNames}. Which ${itemTypeLabel} would you like to keep?`
|
||||
}
|
||||
|
||||
return `Adding ${incomingItem.name} would conflict with: ${conflictNames}. Which ${itemTypeLabel}s would you like to keep?`
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private async resolveWeaponConflict(
|
||||
partyId: string,
|
||||
resolution: ConflictResolution,
|
||||
headers: Record<string, string>
|
||||
): Promise<Party> {
|
||||
// Build payload to remove conflicting weapons and add the new one
|
||||
const payload = {
|
||||
weapons: [
|
||||
// Remove conflicting weapons
|
||||
...resolution.removeIds.map(id => ({
|
||||
id,
|
||||
_destroy: true
|
||||
})),
|
||||
// Add the new weapon
|
||||
{
|
||||
weaponId: resolution.addId,
|
||||
position: resolution.position,
|
||||
uncapLevel: 0,
|
||||
transcendenceLevel: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return partiesApi.updateWeaponGrid(this.fetch, partyId, payload, headers)
|
||||
}
|
||||
|
||||
private async resolveCharacterConflict(
|
||||
partyId: string,
|
||||
resolution: ConflictResolution,
|
||||
headers: Record<string, string>
|
||||
): Promise<Party> {
|
||||
// Build payload to remove conflicting characters and add the new one
|
||||
const payload = {
|
||||
characters: [
|
||||
// Remove conflicting characters
|
||||
...resolution.removeIds.map(id => ({
|
||||
id,
|
||||
_destroy: true
|
||||
})),
|
||||
// Add the new character
|
||||
{
|
||||
characterId: resolution.addId,
|
||||
position: resolution.position,
|
||||
uncapLevel: 0,
|
||||
transcendenceLevel: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return partiesApi.updateCharacterGrid(this.fetch, partyId, payload, headers)
|
||||
}
|
||||
|
||||
private checkWeaponConflicts(party: Party, weaponId: string): ConflictData | null {
|
||||
// Check for duplicate weapons (simplified - actual logic would be more complex)
|
||||
const existingWeapon = party.weapons.find(w => w.weapon.id === weaponId)
|
||||
|
||||
if (existingWeapon) {
|
||||
return {
|
||||
conflicts: [existingWeapon.id],
|
||||
incoming: weaponId,
|
||||
position: existingWeapon.position
|
||||
}
|
||||
}
|
||||
|
||||
// Could check for other conflict types here (e.g., same series weapons)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private checkCharacterConflicts(party: Party, characterId: string): ConflictData | null {
|
||||
// Check for duplicate characters
|
||||
const existingCharacter = party.characters.find(c => c.character.id === characterId)
|
||||
|
||||
if (existingCharacter) {
|
||||
return {
|
||||
conflicts: [existingCharacter.id],
|
||||
incoming: characterId,
|
||||
position: existingCharacter.position
|
||||
}
|
||||
}
|
||||
|
||||
// Check for conflicts with other versions of the same character
|
||||
// This would need character metadata to determine conflicts
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private buildHeaders(editKey?: string): Record<string, string> {
|
||||
const headers: Record<string, string> = {}
|
||||
if (editKey) {
|
||||
headers['X-Edit-Key'] = editKey
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conflict constraints for a specific type
|
||||
*/
|
||||
getConflictConstraints(itemType: 'weapon' | 'character'): {
|
||||
allowDuplicates: boolean
|
||||
maxPerType?: number
|
||||
checkVariants: boolean
|
||||
} {
|
||||
if (itemType === 'weapon') {
|
||||
return {
|
||||
allowDuplicates: false,
|
||||
checkVariants: true // Check for same series weapons
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
allowDuplicates: false,
|
||||
checkVariants: true // Check for different versions of same character
|
||||
}
|
||||
}
|
||||
}
|
||||
450
src/lib/services/grid.service.ts
Normal file
450
src/lib/services/grid.service.ts
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
import type { Party, GridWeapon, GridSummon, GridCharacter } from '$lib/api/schemas/party'
|
||||
import * as partiesApi from '$lib/api/resources/parties'
|
||||
import type { FetchLike } from '$lib/api/core'
|
||||
|
||||
export interface GridOperation {
|
||||
type: 'add' | 'replace' | 'remove' | 'move' | 'swap'
|
||||
itemId?: string
|
||||
position?: number
|
||||
targetPosition?: number
|
||||
uncapLevel?: number
|
||||
transcendenceLevel?: number
|
||||
data?: any
|
||||
}
|
||||
|
||||
export interface GridUpdateResult {
|
||||
party: Party
|
||||
conflicts?: {
|
||||
conflicts: string[]
|
||||
incoming: string
|
||||
position: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid service - handles grid operations for weapons, summons, and characters
|
||||
*/
|
||||
export class GridService {
|
||||
constructor(private fetch: FetchLike) {}
|
||||
|
||||
// Weapon Grid Operations
|
||||
|
||||
async addWeapon(
|
||||
partyId: string,
|
||||
weaponId: string,
|
||||
position: number,
|
||||
editKey?: string
|
||||
): Promise<GridUpdateResult> {
|
||||
const payload = {
|
||||
weaponId,
|
||||
position,
|
||||
uncapLevel: 0,
|
||||
transcendenceLevel: 0
|
||||
}
|
||||
|
||||
try {
|
||||
const party = await partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
return { party }
|
||||
} catch (error: any) {
|
||||
if (error.type === 'conflict') {
|
||||
return {
|
||||
party: null as any, // Will be handled by conflict resolution
|
||||
conflicts: error
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async replaceWeapon(
|
||||
partyId: string,
|
||||
gridWeaponId: string,
|
||||
newWeaponId: string,
|
||||
editKey?: string
|
||||
): Promise<GridUpdateResult> {
|
||||
const payload = {
|
||||
id: gridWeaponId,
|
||||
weaponId: newWeaponId
|
||||
}
|
||||
|
||||
try {
|
||||
const party = await partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
return { party }
|
||||
} catch (error: any) {
|
||||
if (error.type === 'conflict') {
|
||||
return {
|
||||
party: null as any,
|
||||
conflicts: error
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async removeWeapon(
|
||||
partyId: string,
|
||||
gridWeaponId: string,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridWeaponId,
|
||||
_destroy: true
|
||||
}
|
||||
|
||||
return partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async moveWeapon(
|
||||
partyId: string,
|
||||
gridWeaponId: string,
|
||||
newPosition: number,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridWeaponId,
|
||||
position: newPosition
|
||||
}
|
||||
|
||||
return partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async swapWeapons(
|
||||
partyId: string,
|
||||
gridWeaponId1: string,
|
||||
gridWeaponId2: string,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
swap: [gridWeaponId1, gridWeaponId2]
|
||||
}
|
||||
|
||||
return partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async updateWeaponUncap(
|
||||
partyId: string,
|
||||
gridWeaponId: string,
|
||||
uncapLevel: number,
|
||||
transcendenceLevel: number,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridWeaponId,
|
||||
uncapLevel,
|
||||
transcendenceLevel
|
||||
}
|
||||
|
||||
// Set uncap to 6 when transcending
|
||||
if (transcendenceLevel > 0 && uncapLevel < 6) {
|
||||
payload.uncapLevel = 6
|
||||
}
|
||||
|
||||
return partiesApi.updateWeaponGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
// Summon Grid Operations
|
||||
|
||||
async addSummon(
|
||||
partyId: string,
|
||||
summonId: string,
|
||||
position: number,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
summonId,
|
||||
position,
|
||||
uncapLevel: 0,
|
||||
transcendenceLevel: 0
|
||||
}
|
||||
|
||||
return partiesApi.updateSummonGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async replaceSummon(
|
||||
partyId: string,
|
||||
gridSummonId: string,
|
||||
newSummonId: string,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridSummonId,
|
||||
summonId: newSummonId
|
||||
}
|
||||
|
||||
return partiesApi.updateSummonGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async removeSummon(
|
||||
partyId: string,
|
||||
gridSummonId: string,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridSummonId,
|
||||
_destroy: true
|
||||
}
|
||||
|
||||
return partiesApi.updateSummonGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async updateSummonUncap(
|
||||
partyId: string,
|
||||
gridSummonId: string,
|
||||
uncapLevel: number,
|
||||
transcendenceLevel: number,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridSummonId,
|
||||
uncapLevel,
|
||||
transcendenceLevel
|
||||
}
|
||||
|
||||
// Set uncap to 6 when transcending
|
||||
if (transcendenceLevel > 0 && uncapLevel < 6) {
|
||||
payload.uncapLevel = 6
|
||||
}
|
||||
|
||||
return partiesApi.updateSummonGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
// Character Grid Operations
|
||||
|
||||
async addCharacter(
|
||||
partyId: string,
|
||||
characterId: string,
|
||||
position: number,
|
||||
editKey?: string
|
||||
): Promise<GridUpdateResult> {
|
||||
const payload = {
|
||||
characterId,
|
||||
position,
|
||||
uncapLevel: 0,
|
||||
transcendenceLevel: 0
|
||||
}
|
||||
|
||||
try {
|
||||
const party = await partiesApi.updateCharacterGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
return { party }
|
||||
} catch (error: any) {
|
||||
if (error.type === 'conflict') {
|
||||
return {
|
||||
party: null as any,
|
||||
conflicts: error
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async replaceCharacter(
|
||||
partyId: string,
|
||||
gridCharacterId: string,
|
||||
newCharacterId: string,
|
||||
editKey?: string
|
||||
): Promise<GridUpdateResult> {
|
||||
const payload = {
|
||||
id: gridCharacterId,
|
||||
characterId: newCharacterId
|
||||
}
|
||||
|
||||
try {
|
||||
const party = await partiesApi.updateCharacterGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
return { party }
|
||||
} catch (error: any) {
|
||||
if (error.type === 'conflict') {
|
||||
return {
|
||||
party: null as any,
|
||||
conflicts: error
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async removeCharacter(
|
||||
partyId: string,
|
||||
gridCharacterId: string,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridCharacterId,
|
||||
_destroy: true
|
||||
}
|
||||
|
||||
return partiesApi.updateCharacterGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
async updateCharacterUncap(
|
||||
partyId: string,
|
||||
gridCharacterId: string,
|
||||
uncapLevel: number,
|
||||
transcendenceLevel: number,
|
||||
perpetuity: boolean,
|
||||
editKey?: string
|
||||
): Promise<Party> {
|
||||
const payload = {
|
||||
id: gridCharacterId,
|
||||
uncapLevel,
|
||||
transcendenceLevel,
|
||||
perpetuity
|
||||
}
|
||||
|
||||
return partiesApi.updateCharacterGrid(
|
||||
this.fetch,
|
||||
partyId,
|
||||
payload,
|
||||
this.buildHeaders(editKey)
|
||||
)
|
||||
}
|
||||
|
||||
// Drag and Drop Helpers
|
||||
|
||||
/**
|
||||
* Normalize drag and drop intent to a grid operation
|
||||
*/
|
||||
normalizeDragIntent(
|
||||
dragType: 'weapon' | 'summon' | 'character',
|
||||
draggedItem: any,
|
||||
targetPosition: number,
|
||||
targetItem?: any
|
||||
): GridOperation {
|
||||
// If dropping on an empty slot
|
||||
if (!targetItem) {
|
||||
return {
|
||||
type: 'add',
|
||||
itemId: draggedItem.id,
|
||||
position: targetPosition
|
||||
}
|
||||
}
|
||||
|
||||
// If dragging from grid to grid
|
||||
if (draggedItem.gridId && targetItem.gridId) {
|
||||
return {
|
||||
type: 'swap',
|
||||
itemId: draggedItem.gridId,
|
||||
targetPosition: targetItem.gridId
|
||||
}
|
||||
}
|
||||
|
||||
// If dragging from outside to occupied slot
|
||||
return {
|
||||
type: 'replace',
|
||||
itemId: targetItem.gridId,
|
||||
targetPosition: draggedItem.id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply optimistic update to local state
|
||||
*/
|
||||
applyOptimisticUpdate<T extends GridWeapon | GridSummon | GridCharacter>(
|
||||
items: T[],
|
||||
operation: GridOperation
|
||||
): T[] {
|
||||
const updated = [...items]
|
||||
|
||||
switch (operation.type) {
|
||||
case 'add':
|
||||
// Add new item at position
|
||||
break
|
||||
|
||||
case 'remove':
|
||||
return updated.filter(item => item.id !== operation.itemId)
|
||||
|
||||
case 'move':
|
||||
const item = updated.find(i => i.id === operation.itemId)
|
||||
if (item && operation.targetPosition !== undefined) {
|
||||
item.position = operation.targetPosition
|
||||
}
|
||||
break
|
||||
|
||||
case 'swap':
|
||||
const item1 = updated.find(i => i.id === operation.itemId)
|
||||
const item2 = updated.find(i => i.id === operation.targetPosition)
|
||||
if (item1 && item2) {
|
||||
const tempPos = item1.position
|
||||
item1.position = item2.position
|
||||
item2.position = tempPos
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
// Private helpers
|
||||
|
||||
private buildHeaders(editKey?: string): Record<string, string> {
|
||||
const headers: Record<string, string> = {}
|
||||
if (editKey) {
|
||||
headers['X-Edit-Key'] = editKey
|
||||
}
|
||||
return headers
|
||||
}
|
||||
}
|
||||
214
src/lib/services/party.service.ts
Normal file
214
src/lib/services/party.service.ts
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import type { Party } from '$lib/api/schemas/party'
|
||||
import * as partiesApi from '$lib/api/resources/parties'
|
||||
import type { FetchLike } from '$lib/api/core'
|
||||
|
||||
export interface EditabilityResult {
|
||||
canEdit: boolean
|
||||
headers?: Record<string, string>
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface PartyUpdatePayload {
|
||||
name?: string | null
|
||||
description?: string | null
|
||||
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(private fetch: FetchLike) {}
|
||||
|
||||
/**
|
||||
* Get party by shortcode
|
||||
*/
|
||||
async getByShortcode(shortcode: string): Promise<Party> {
|
||||
return partiesApi.getByShortcode(this.fetch, shortcode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new party
|
||||
*/
|
||||
async create(payload: PartyUpdatePayload, editKey?: string): Promise<Party> {
|
||||
const headers = this.buildHeaders(editKey)
|
||||
const apiPayload = this.mapToApiPayload(payload)
|
||||
return partiesApi.create(this.fetch, apiPayload, headers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update party details
|
||||
*/
|
||||
async update(id: string, payload: PartyUpdatePayload, editKey?: string): Promise<Party> {
|
||||
const headers = this.buildHeaders(editKey)
|
||||
const apiPayload = this.mapToApiPayload(payload)
|
||||
return partiesApi.update(this.fetch, 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 partiesApi.update(this.fetch, 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 result = await partiesApi.remix(this.fetch, shortcode, localId, headers)
|
||||
|
||||
// Store edit key if returned
|
||||
if (result.editKey && typeof window !== 'undefined') {
|
||||
localStorage.setItem(`edit_key_${result.party.shortcode}`, result.editKey)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Favorite a party
|
||||
*/
|
||||
async favorite(id: string): Promise<void> {
|
||||
return partiesApi.favorite(this.fetch, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfavorite a party
|
||||
*/
|
||||
async unfavorite(id: string): Promise<void> {
|
||||
return partiesApi.unfavorite(this.fetch, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a party
|
||||
*/
|
||||
async delete(id: string, editKey?: string): Promise<void> {
|
||||
const headers = this.buildHeaders(editKey)
|
||||
return partiesApi.deleteParty(this.fetch, id, headers)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue