From bb1b206aea1c3f7cbb5c7b44b3d1dd71b6fa9639 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 2 Apr 2026 21:59:07 +0200 Subject: [PATCH] feat: extend PATCH /organization to accept logo upload and removal Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/api/src/routers/user.ts | 61 +++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/apps/api/src/routers/user.ts b/apps/api/src/routers/user.ts index b4ce427..be238d5 100644 --- a/apps/api/src/routers/user.ts +++ b/apps/api/src/routers/user.ts @@ -4,6 +4,7 @@ import { Hono } from "hono"; import { createFactory } from "hono/factory"; import { getOrganizationBillingState } from "../helpers/billing.js"; import { createInvitedUser, getOrganizationPlan, MAX_TABLO_LIMIT } from "../helpers/helpers.js"; +import { uploadOrgIcons, deleteOrgIcons } from "../helpers/orgIcons.js"; import type { AuthEnv } from "../types/app.types.js"; const factory = createFactory(); @@ -439,13 +440,8 @@ const getOrganization = factory.createHandlers(async (c) => { const updateOrganization = factory.createHandlers(async (c) => { const user = c.get("user"); const supabase = c.get("supabase"); + const s3Client = c.get("s3_client"); const body = await c.req.json(); - const rawName = typeof body?.name === "string" ? body.name : ""; - const name = rawName.trim(); - - if (name.length < 2 || name.length > 100) { - return c.json({ error: "Organization name must be between 2 and 100 characters" }, 400); - } const { data: profile, error: profileError } = await supabase .from("profiles") @@ -461,13 +457,54 @@ const updateOrganization = factory.createHandlers(async (c) => { return c.json({ error: "Temporary users cannot update organization settings" }, 403); } - const { error: updateError } = await supabase - .from("organizations") - .update({ name }) - .eq("id", profile.organization_id); + const organizationId = profile.organization_id; + const updateData: Record = {}; - if (updateError) { - return c.json({ error: updateError.message }, 500); + // Handle name update + if (body?.name !== undefined) { + const rawName = typeof body.name === "string" ? body.name : ""; + const name = rawName.trim(); + if (name.length < 2 || name.length > 100) { + return c.json({ error: "Organization name must be between 2 and 100 characters" }, 400); + } + updateData.name = name; + } + + // Handle logo upload + if (body?.logo !== undefined) { + if (body.logo === null) { + // Remove logo + await deleteOrgIcons(s3Client, organizationId); + updateData.logo_url = null; + } else if (body.logo?.content && body.logo?.contentType) { + const { content, contentType } = body.logo; + + // Validate content type + const allowedTypes = ["image/png", "image/jpeg", "image/webp"]; + if (!allowedTypes.includes(contentType)) { + return c.json({ error: "Logo must be PNG, JPEG, or WebP" }, 400); + } + + const imageBuffer = Buffer.from(content, "base64"); + try { + const basePath = await uploadOrgIcons(s3Client, organizationId, imageBuffer); + updateData.logo_url = basePath; + } catch (err: unknown) { + const message = err instanceof Error ? err.message : "Failed to process logo"; + return c.json({ error: message }, 400); + } + } + } + + if (Object.keys(updateData).length > 0) { + const { error: updateError } = await supabase + .from("organizations") + .update(updateData) + .eq("id", organizationId); + + if (updateError) { + return c.json({ error: updateError.message }, 500); + } } return c.json({ message: "Organization updated successfully" });