hensei-web/src/lib/api/adapters/party.adapter.ts

434 lines
No EOL
8.5 KiB
TypeScript

/**
* Party Adapter
*
* Handles all party-related API operations including CRUD, grids, and remixing.
* Provides a clean interface for party management with automatic
* request handling, caching, and error management.
*
* @module adapters/party
*/
import { BaseAdapter } from './base.adapter'
import type { RequestOptions, AdapterOptions, PaginatedResponse } from './types'
import { DEFAULT_ADAPTER_CONFIG } from './config'
/**
* Party data structure
*/
export interface Party {
id: string
shortcode: string
name?: string
description?: string
visibility: 'public' | 'private' | 'unlisted'
user: {
id: string
username: string
}
job?: {
id: string
name: Record<string, string>
skills: Array<{
id: string
name: Record<string, string>
slot: number
}>
accessory?: {
id: string
name: Record<string, string>
}
}
raid?: {
id: string
name: Record<string, string>
group?: {
id: string
name: Record<string, string>
}
}
weapons: GridWeapon[]
summons: GridSummon[]
characters: GridCharacter[]
guidebook?: {
id: string
title: string
}
extras?: Record<string, any>
createdAt: string
updatedAt: string
}
/**
* Grid weapon structure
*/
export interface GridWeapon {
id: string
position: number
mainhand: boolean
uncapLevel: number
transcendenceStage: number
weaponKeys: Array<{
id: string
slot: number
}>
weapon: {
id: string
granblueId: string
name: Record<string, string>
element: number
rarity: number
}
}
/**
* Grid summon structure
*/
export interface GridSummon {
id: string
position: number
quickSummon: boolean
transcendenceStage: number
summon: {
id: string
granblueId: string
name: Record<string, string>
element: number
rarity: number
}
}
/**
* Grid character structure
*/
export interface GridCharacter {
id: string
position: number
uncapLevel: number
transcendenceStage: number
perpetualModifiers?: Record<string, any>
awakenings?: Array<{
id: string
level: number
}>
character: {
id: string
granblueId: string
name: Record<string, string>
element: number
rarity: number
}
}
/**
* Parameters for creating a new party
*/
export interface CreatePartyParams {
name?: string
description?: string
visibility?: 'public' | 'private' | 'unlisted'
jobId?: string
raidId?: string
guidebookId?: string
extras?: Record<string, any>
}
/**
* Parameters for updating a party
*/
export interface UpdatePartyParams extends CreatePartyParams {
shortcode: string
}
/**
* Parameters for listing user parties
*/
export interface ListUserPartiesParams {
username: string
page?: number
per?: number
visibility?: 'public' | 'private' | 'unlisted' | 'all'
raidId?: string
characterId?: string
weaponId?: string
summonId?: string
}
/**
* Grid operation for batch updates
*/
export interface GridOperation {
type: 'move' | 'swap' | 'remove'
entity: 'weapon' | 'character' | 'summon'
id?: string
sourceId?: string
targetId?: string
position?: number
container?: string
}
/**
* Options for grid update operation
*/
export interface GridUpdateOptions {
maintainCharacterSequence?: boolean
validateBeforeExecute?: boolean
}
/**
* Response from grid update operation
*/
export interface GridUpdateResponse {
party: Party
operationsApplied: number
changes: Array<{
entity: string
id: string
action: string
from?: number
to?: number
with?: string
}>
}
/**
* Party adapter for managing parties and their grids
*/
export class PartyAdapter extends BaseAdapter {
constructor(options?: AdapterOptions) {
super(options)
// Temporarily disable cache until cache invalidation is fixed
this.disableCache = true
}
/**
* Creates a new party
*/
async create(params: CreatePartyParams): Promise<Party> {
const response = await this.request<{ party: Party }>('/parties', {
method: 'POST',
body: {
party: params
}
})
return response.party
}
/**
* Gets a party by shortcode
*/
async getByShortcode(shortcode: string): Promise<Party> {
const response = await this.request<{ party: Party }>(`/parties/${shortcode}`, {
cacheTTL: 60000 // Cache for 1 minute
})
return response.party
}
/**
* Updates a party
*/
async update(params: UpdatePartyParams): Promise<Party> {
const { shortcode, ...updateParams } = params
const response = await this.request<{ party: Party }>(`/parties/${shortcode}`, {
method: 'PATCH',
body: {
party: updateParams
}
})
return response.party
}
/**
* Deletes a party
*/
async delete(shortcode: string): Promise<void> {
return this.request<void>(`/parties/${shortcode}`, {
method: 'DELETE'
})
}
/**
* Creates a remix (copy) of an existing party
*/
async remix(shortcode: string): Promise<Party> {
const response = await this.request<{ party: Party }>(`/parties/${shortcode}/remix`, {
method: 'POST'
})
return response.party
}
/**
* Lists all public parties (explore page)
*/
async list(params: { page?: number; per?: number } = {}): Promise<PaginatedResponse<Party>> {
const response = await this.request<{
results: Party[]
meta?: {
count?: number
totalPages?: number
perPage?: number
}
}>('/parties', {
method: 'GET',
query: params,
cacheTTL: 30000 // Cache for 30 seconds
})
return {
results: response.results,
page: params.page || 1,
total: response.meta?.count || 0,
totalPages: response.meta?.totalPages || 1,
perPage: response.meta?.perPage || 20
}
}
/**
* Lists parties for a specific user
*/
async listUserParties(params: ListUserPartiesParams): Promise<PaginatedResponse<Party>> {
const { username, ...queryParams } = params
return this.request<PaginatedResponse<Party>>(`/users/${username}/parties`, {
method: 'GET',
query: queryParams,
cacheTTL: 30000 // Cache for 30 seconds
})
}
/**
* Performs atomic batch grid updates
* Supports move, swap, and remove operations on grid items
*/
async gridUpdate(
shortcode: string,
operations: GridOperation[],
options?: GridUpdateOptions
): Promise<GridUpdateResponse> {
return this.request(`/parties/${shortcode}/grid_update`, {
method: 'POST',
body: {
operations,
options
}
})
}
/**
* Updates the job for a party
*/
async updateJob(
shortcode: string,
jobId: string
): Promise<Party> {
return this.request<Party>(`/parties/${shortcode}/jobs`, {
method: 'PUT',
body: {
job_id: jobId
}
})
}
/**
* Updates job skills for a party
*/
async updateJobSkills(
shortcode: string,
skills: Array<{ id: string; slot: number }>
): Promise<Party> {
return this.request<Party>(`/parties/${shortcode}/job_skills`, {
method: 'PUT',
body: {
skills
}
})
}
/**
* Removes a job skill from a party
*/
async removeJobSkill(
shortcode: string,
skillSlot: number
): Promise<Party> {
return this.request<Party>(`/parties/${shortcode}/job_skills`, {
method: 'DELETE',
body: {
slot: skillSlot
}
})
}
/**
* Gets party preview image
*/
async getPreview(shortcode: string): Promise<Blob> {
return this.request<Blob>(`/parties/${shortcode}/preview`, {
method: 'GET',
headers: {
'Accept': 'image/png'
}
})
}
/**
* Gets party preview status
*/
async getPreviewStatus(shortcode: string): Promise<{
state: string
generatedAt?: string
readyForPreview: boolean
}> {
return this.request(`/parties/${shortcode}/preview_status`, {
method: 'GET'
})
}
/**
* Regenerates party preview
*/
async regeneratePreview(shortcode: string): Promise<{ status: string }> {
return this.request(`/parties/${shortcode}/regenerate_preview`, {
method: 'POST'
})
}
/**
* Favorite a party
*/
async favorite(shortcode: string): Promise<void> {
await this.request(`/parties/${shortcode}/favorite`, {
method: 'POST'
})
// Clear cache for the party to reflect updated state
this.clearCache(`/parties/${shortcode}`)
}
/**
* Unfavorite a party
*/
async unfavorite(shortcode: string): Promise<void> {
await this.request(`/parties/${shortcode}/unfavorite`, {
method: 'DELETE'
})
// Clear cache for the party to reflect updated state
this.clearCache(`/parties/${shortcode}`)
}
/**
* Clears the cache for party-related data
*/
clearPartyCache(shortcode?: string) {
if (shortcode) {
// Clear specific party cache
this.clearCache(`/parties/${shortcode}`)
} else {
// Clear all party and user caches
this.clearCache('/parties')
this.clearCache('/users')
}
}
}
/**
* Default party adapter instance
*/
export const partyAdapter = new PartyAdapter(DEFAULT_ADAPTER_CONFIG)