From c80a25bd242921f6c8101a767ef8ff802cf7d364 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Tue, 21 Jul 2026 16:11:31 -0400 Subject: [PATCH] add club delete (club images not quite working) --- apps/clubs/src/clubs.app.ts | 15 ++++++++--- apps/clubs/src/test/integration/api.test.ts | 28 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/apps/clubs/src/clubs.app.ts b/apps/clubs/src/clubs.app.ts index ac0a802..180afa4 100644 --- a/apps/clubs/src/clubs.app.ts +++ b/apps/clubs/src/clubs.app.ts @@ -579,7 +579,11 @@ const app = new Hono() // `imageName` the `storage` worker handed back; an empty one clears that slot. // Co-owner or above, like the main image. The slots are positional, so clearing // one doesn't shift the others; they come back on `value.AdditionalImages`. - .put('/club/:clubId{[0-9]+}/additionalimage/:index{[0-9]+}', async (c) => { + // + // DELETE removes that slot's image — same thing as PUTting an empty name, with the + // intent spelled out. It ignores any body, so it can't accidentally set one, and + // deleting an already-empty slot is a no-op rather than an error. + .on(['PUT', 'DELETE'], '/club/:clubId{[0-9]+}/additionalimage/:index{[0-9]+}', async (c) => { const id = await authedId(c) if (id === null) return c.body(null, 401) @@ -597,9 +601,12 @@ const app = new Hono() return clubError(c, `A club has ${MAX_ADDITIONAL_IMAGES} additional image slots (0-based).`) } - const body = (await c.req.parseBody().catch(() => ({}))) as Record - const key = Object.keys(body).find((k) => k.toLowerCase() === 'imagename') - const imageName = typeof body[key ?? ''] === 'string' ? (body[key ?? ''] as string).trim() : '' + let imageName = '' + if (c.req.method !== 'DELETE') { + const body = (await c.req.parseBody().catch(() => ({}))) as Record + const key = Object.keys(body).find((k) => k.toLowerCase() === 'imagename') + imageName = typeof body[key ?? ''] === 'string' ? (body[key ?? ''] as string).trim() : '' + } const updated = await setClubAdditionalImage(c.env.DB, clubId, index, imageName) if (updated === null) return c.notFound() diff --git a/apps/clubs/src/test/integration/api.test.ts b/apps/clubs/src/test/integration/api.test.ts index 87e39a7..190adb0 100644 --- a/apps/clubs/src/test/integration/api.test.ts +++ b/apps/clubs/src/test/integration/api.test.ts @@ -655,6 +655,34 @@ describe('clubs endpoints', () => { ).json()) as { AdditionalImages: Image[] } expect(names(details.AdditionalImages)).toEqual(['a2.jpg', 'c.jpg']) + // DELETE removes a slot's image, ignoring any body, and repeats are no-ops. The + // other slots keep their positions, so slot 2 is still slot 2. + const deleteImage = async (index: number, sub = '7100', body?: string) => + exports.default.fetch(`${ORIGIN}/club/${clubId}/additionalimage/${index}`, { + method: 'DELETE', + headers: { ...(await bearer(sub)), 'Content-Type': 'application/x-www-form-urlencoded' }, + body, + }) + const dropped = (await (await deleteImage(0, '7100', 'imageName=sneaky.jpg')).json()) as Details + expect(dropped).toMatchObject({ error: '', success: true }) + expect(names(dropped.value.AdditionalImages)).toEqual(['c.jpg']) + expect( + names(((await (await deleteImage(0)).json()) as Details).value.AdditionalImages) + ).toEqual(['c.jpg']) + const refilled = (await (await setImage(0, 'a3.jpg')).json()) as Details + expect(names(refilled.value.AdditionalImages)).toEqual(['a3.jpg', 'c.jpg']) + + // Same gate as the PUT. + expect((await deleteImage(0, '7101')).status).toBe(403) + expect( + ( + await exports.default.fetch(`${ORIGIN}/club/${clubId}/additionalimage/0`, { + method: 'DELETE', + }) + ).status + ).toBe(401) + expect((await deleteImage(3)).status).toBe(400) + // There are only three slots, and only co-owners may set them. expect((await setImage(3, 'd.jpg')).status).toBe(400) expect((await setImage(0, 'hijack.jpg', '7101')).status).toBe(403)