From da4c3d09f9dd0e0aaaf610c9f19a2ca03a29d5b0 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 15 Sep 2025 21:24:37 -0700 Subject: [PATCH] add grid api endpoints and services --- src/lib/api/client.ts | 119 +++++++++++++++++- src/lib/api/resources/grid.ts | 87 +++++++++++++ src/lib/services/grid.service.ts | 78 +++++++++++- .../api/parties/[id]/characters/+server.ts | 7 +- .../[id]/characters/[characterId]/+server.ts | 38 ++++++ .../api/parties/[id]/summons/+server.ts | 7 +- .../[id]/summons/[summonId]/+server.ts | 38 ++++++ .../api/parties/[id]/weapons/+server.ts | 9 +- .../[id]/weapons/[weaponId]/+server.ts | 38 ++++++ 9 files changed, 402 insertions(+), 19 deletions(-) create mode 100644 src/routes/api/parties/[id]/characters/[characterId]/+server.ts create mode 100644 src/routes/api/parties/[id]/summons/[summonId]/+server.ts create mode 100644 src/routes/api/parties/[id]/weapons/[weaponId]/+server.ts diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index afb4cc37..5fffeb34 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -243,7 +243,11 @@ export class APIClient { throw new Error(error.error || `Failed to update party: ${response.statusText}`) } - return response.json() + const data = await response.json() + // The API returns { party: { ... } }, extract the party object + const party = data.party || data + // Transform the response to match our clean types + return transformResponse(party) } /** @@ -298,12 +302,46 @@ export class APIClient { return response.json() } + /** + * Update a weapon in a party + */ + async updateWeapon( + partyId: string, + gridWeaponId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + element?: number + } + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/weapons/${gridWeaponId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(updates) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update weapon: ${response.statusText}`) + } + + return response.json() + } + /** * Remove a weapon from a party */ async removeWeapon(partyId: string, gridWeaponId: string): Promise { const editKey = this.getEditKey(partyId) + console.log('Removing weapon:', { partyId, gridWeaponId, editKey }) + const response = await fetch(`/api/parties/${partyId}/weapons`, { method: 'DELETE', headers: { @@ -314,7 +352,16 @@ export class APIClient { }) if (!response.ok) { - const error = await response.json() + console.error('Remove weapon failed:', response.status, response.statusText) + // Try to get the response text to see what the server is returning + const text = await response.text() + console.error('Response body:', text) + let error = { error: 'Failed to remove weapon' } + try { + error = JSON.parse(text) + } catch (e) { + // Not JSON, use the text as is + } throw new Error(error.error || `Failed to remove weapon: ${response.statusText}`) } } @@ -351,6 +398,38 @@ export class APIClient { return response.json() } + /** + * Update a summon in a party + */ + async updateSummon( + partyId: string, + gridSummonId: string, + updates: { + position?: number + quickSummon?: boolean + uncapLevel?: number + transcendenceStep?: number + } + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/summons/${gridSummonId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(updates) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update summon: ${response.statusText}`) + } + + return response.json() + } + /** * Remove a summon from a party */ @@ -367,7 +446,7 @@ export class APIClient { }) if (!response.ok) { - const error = await response.json() + const error = await response.json().catch(() => ({ error: 'Failed to remove summon' })) throw new Error(error.error || `Failed to remove summon: ${response.statusText}`) } } @@ -404,6 +483,38 @@ export class APIClient { return response.json() } + /** + * Update a character in a party + */ + async updateCharacter( + partyId: string, + gridCharacterId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + perpetuity?: boolean + } + ): Promise { + const editKey = this.getEditKey(partyId) + + const response = await fetch(`/api/parties/${partyId}/characters/${gridCharacterId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(updates) + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || `Failed to update character: ${response.statusText}`) + } + + return response.json() + } + /** * Remove a character from a party */ @@ -420,7 +531,7 @@ export class APIClient { }) if (!response.ok) { - const error = await response.json() + const error = await response.json().catch(() => ({ error: 'Failed to remove character' })) throw new Error(error.error || `Failed to remove character: ${response.statusText}`) } } diff --git a/src/lib/api/resources/grid.ts b/src/lib/api/resources/grid.ts index 14954085..c9c20bef 100644 --- a/src/lib/api/resources/grid.ts +++ b/src/lib/api/resources/grid.ts @@ -47,6 +47,35 @@ export async function addWeapon( return res.json() } +export async function updateWeapon( + fetch: FetchLike, + partyId: string, + gridWeaponId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + element?: number + }, + headers?: Record +): Promise { + const res = await fetch(buildUrl(`/grid_weapons/${gridWeaponId}`), { + method: 'PUT', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + ...headers + }, + body: JSON.stringify({ weapon: updates }) + }) + + if (!res.ok) { + throw new Error(`Failed to update weapon: ${res.statusText}`) + } + + return res.json() +} + export async function removeWeapon( fetch: FetchLike, partyId: string, @@ -113,6 +142,35 @@ export async function addSummon( return res.json() } +export async function updateSummon( + fetch: FetchLike, + partyId: string, + gridSummonId: string, + updates: { + position?: number + quickSummon?: boolean + uncapLevel?: number + transcendenceStep?: number + }, + headers?: Record +): Promise { + const res = await fetch(buildUrl(`/grid_summons/${gridSummonId}`), { + method: 'PUT', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + ...headers + }, + body: JSON.stringify({ summon: updates }) + }) + + if (!res.ok) { + throw new Error(`Failed to update summon: ${res.statusText}`) + } + + return res.json() +} + export async function removeSummon( fetch: FetchLike, partyId: string, @@ -175,6 +233,35 @@ export async function addCharacter( return res.json() } +export async function updateCharacter( + fetch: FetchLike, + partyId: string, + gridCharacterId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + perpetuity?: boolean + }, + headers?: Record +): Promise { + const res = await fetch(buildUrl(`/grid_characters/${gridCharacterId}`), { + method: 'PUT', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + ...headers + }, + body: JSON.stringify({ character: updates }) + }) + + if (!res.ok) { + throw new Error(`Failed to update character: ${res.statusText}`) + } + + return res.json() +} + export async function removeCharacter( fetch: FetchLike, partyId: string, diff --git a/src/lib/services/grid.service.ts b/src/lib/services/grid.service.ts index f6eae42f..0c9b218b 100644 --- a/src/lib/services/grid.service.ts +++ b/src/lib/services/grid.service.ts @@ -109,6 +109,30 @@ export class GridService { ) } + async updateWeapon( + partyId: string, + gridWeaponId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + element?: number + }, + editKey?: string + ): Promise { + const payload = { + id: gridWeaponId, + ...updates + } + + return partiesApi.updateWeaponGrid( + this.fetch, + partyId, + payload, + this.buildHeaders(editKey) + ) + } + async moveWeapon( partyId: string, gridWeaponId: string, @@ -119,7 +143,7 @@ export class GridService { id: gridWeaponId, position: newPosition } - + return partiesApi.updateWeaponGrid( this.fetch, partyId, @@ -223,7 +247,31 @@ export class GridService { id: gridSummonId, _destroy: true } - + + return partiesApi.updateSummonGrid( + this.fetch, + partyId, + payload, + this.buildHeaders(editKey) + ) + } + + async updateSummon( + partyId: string, + gridSummonId: string, + updates: { + position?: number + quickSummon?: boolean + uncapLevel?: number + transcendenceStep?: number + }, + editKey?: string + ): Promise { + const payload = { + id: gridSummonId, + ...updates + } + return partiesApi.updateSummonGrid( this.fetch, partyId, @@ -331,7 +379,31 @@ export class GridService { id: gridCharacterId, _destroy: true } - + + return partiesApi.updateCharacterGrid( + this.fetch, + partyId, + payload, + this.buildHeaders(editKey) + ) + } + + async updateCharacter( + partyId: string, + gridCharacterId: string, + updates: { + position?: number + uncapLevel?: number + transcendenceStep?: number + perpetuity?: boolean + }, + editKey?: string + ): Promise { + const payload = { + id: gridCharacterId, + ...updates + } + return partiesApi.updateCharacterGrid( this.fetch, partyId, diff --git a/src/routes/api/parties/[id]/characters/+server.ts b/src/routes/api/parties/[id]/characters/+server.ts index 0a28b552..391d6029 100644 --- a/src/routes/api/parties/[id]/characters/+server.ts +++ b/src/routes/api/parties/[id]/characters/+server.ts @@ -50,14 +50,13 @@ export const DELETE: RequestHandler = async ({ request, params, fetch }) => { const body = await request.json() const editKey = request.headers.get('X-Edit-Key') - // Forward to Rails API - const response = await fetch(buildUrl('/characters'), { + // Forward to Rails API - use grid_characters endpoint with the ID + const response = await fetch(buildUrl(`/grid_characters/${body.gridCharacterId}`), { method: 'DELETE', headers: { 'Content-Type': 'application/json', ...(editKey ? { 'X-Edit-Key': editKey } : {}) - }, - body: JSON.stringify({ grid_character_id: body.gridCharacterId }) + } }) if (response.ok) { diff --git a/src/routes/api/parties/[id]/characters/[characterId]/+server.ts b/src/routes/api/parties/[id]/characters/[characterId]/+server.ts new file mode 100644 index 00000000..f891b945 --- /dev/null +++ b/src/routes/api/parties/[id]/characters/[characterId]/+server.ts @@ -0,0 +1,38 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { buildUrl } from '$lib/api/core' + +/** + * PUT /api/parties/[id]/characters/[characterId] - Update character in party + * Proxies to Rails API with proper authentication + */ + +export const PUT: RequestHandler = async ({ request, params, fetch }) => { + try { + const body = await request.json() + const editKey = request.headers.get('X-Edit-Key') + + // Transform to Rails API format + const railsBody = { + character: body + } + + // Forward to Rails API + const response = await fetch(buildUrl(`/grid_characters/${params.characterId}`), { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(railsBody) + }) + + const data = await response.json() + return json(data, { status: response.status }) + } catch (error) { + console.error('Error updating character:', error) + return json( + { error: 'Failed to update character' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/routes/api/parties/[id]/summons/+server.ts b/src/routes/api/parties/[id]/summons/+server.ts index 527af39a..23bffeb9 100644 --- a/src/routes/api/parties/[id]/summons/+server.ts +++ b/src/routes/api/parties/[id]/summons/+server.ts @@ -52,14 +52,13 @@ export const DELETE: RequestHandler = async ({ request, params, fetch }) => { const body = await request.json() const editKey = request.headers.get('X-Edit-Key') - // Forward to Rails API - const response = await fetch(buildUrl('/summons'), { + // Forward to Rails API - use grid_summons endpoint with the ID + const response = await fetch(buildUrl(`/grid_summons/${body.gridSummonId}`), { method: 'DELETE', headers: { 'Content-Type': 'application/json', ...(editKey ? { 'X-Edit-Key': editKey } : {}) - }, - body: JSON.stringify({ grid_summon_id: body.gridSummonId }) + } }) if (response.ok) { diff --git a/src/routes/api/parties/[id]/summons/[summonId]/+server.ts b/src/routes/api/parties/[id]/summons/[summonId]/+server.ts new file mode 100644 index 00000000..fe226882 --- /dev/null +++ b/src/routes/api/parties/[id]/summons/[summonId]/+server.ts @@ -0,0 +1,38 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { buildUrl } from '$lib/api/core' + +/** + * PUT /api/parties/[id]/summons/[summonId] - Update summon in party + * Proxies to Rails API with proper authentication + */ + +export const PUT: RequestHandler = async ({ request, params, fetch }) => { + try { + const body = await request.json() + const editKey = request.headers.get('X-Edit-Key') + + // Transform to Rails API format + const railsBody = { + summon: body + } + + // Forward to Rails API + const response = await fetch(buildUrl(`/grid_summons/${params.summonId}`), { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(railsBody) + }) + + const data = await response.json() + return json(data, { status: response.status }) + } catch (error) { + console.error('Error updating summon:', error) + return json( + { error: 'Failed to update summon' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/routes/api/parties/[id]/weapons/+server.ts b/src/routes/api/parties/[id]/weapons/+server.ts index 80386835..0d1c104a 100644 --- a/src/routes/api/parties/[id]/weapons/+server.ts +++ b/src/routes/api/parties/[id]/weapons/+server.ts @@ -51,14 +51,15 @@ export const DELETE: RequestHandler = async ({ request, params, fetch }) => { const body = await request.json() const editKey = request.headers.get('X-Edit-Key') - // Forward to Rails API - const response = await fetch(buildUrl('/weapons'), { + console.log('DELETE weapon request:', { body, params }) + + // Forward to Rails API - use grid_weapons endpoint with the ID + const response = await fetch(buildUrl(`/grid_weapons/${body.gridWeaponId}`), { method: 'DELETE', headers: { 'Content-Type': 'application/json', ...(editKey ? { 'X-Edit-Key': editKey } : {}) - }, - body: JSON.stringify({ grid_weapon_id: body.gridWeaponId }) + } }) if (response.ok) { diff --git a/src/routes/api/parties/[id]/weapons/[weaponId]/+server.ts b/src/routes/api/parties/[id]/weapons/[weaponId]/+server.ts new file mode 100644 index 00000000..c8919619 --- /dev/null +++ b/src/routes/api/parties/[id]/weapons/[weaponId]/+server.ts @@ -0,0 +1,38 @@ +import { json, type RequestHandler } from '@sveltejs/kit' +import { buildUrl } from '$lib/api/core' + +/** + * PUT /api/parties/[id]/weapons/[weaponId] - Update weapon in party + * Proxies to Rails API with proper authentication + */ + +export const PUT: RequestHandler = async ({ request, params, fetch }) => { + try { + const body = await request.json() + const editKey = request.headers.get('X-Edit-Key') + + // Transform to Rails API format + const railsBody = { + weapon: body + } + + // Forward to Rails API + const response = await fetch(buildUrl(`/grid_weapons/${params.weaponId}`), { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(editKey ? { 'X-Edit-Key': editKey } : {}) + }, + body: JSON.stringify(railsBody) + }) + + const data = await response.json() + return json(data, { status: response.status }) + } catch (error) { + console.error('Error updating weapon:', error) + return json( + { error: 'Failed to update weapon' }, + { status: 500 } + ) + } +} \ No newline at end of file