From 73ab222ca3919d9b344bdfaf6a5c7d7bf2d3d859 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Mon, 17 Nov 2025 12:21:38 +0530 Subject: [PATCH 1/9] refactor: replace 'name' with 'tscircuit_handle' in organization-related API and database logic --- .../fixtures/get-test-server.ts | 2 +- .../routes/orgs/create.test.ts | 42 +++---------- .../fake-snippets-api/routes/orgs/get.test.ts | 1 - .../routes/orgs/get_member.test.ts | 2 +- .../routes/orgs/update.test.ts | 61 +++---------------- fake-snippets-api/lib/db/db-client.ts | 15 +++-- fake-snippets-api/lib/db/schema.ts | 9 +-- fake-snippets-api/lib/db/seed.ts | 2 +- .../lib/middleware/with-session-auth.ts | 12 +--- .../lib/public-mapping/public-map-org.ts | 5 +- .../routes/api/accounts/update.ts | 1 - fake-snippets-api/routes/api/orgs/create.ts | 32 +++------- fake-snippets-api/routes/api/orgs/get.ts | 3 - .../routes/api/orgs/get_member.ts | 5 +- .../routes/api/orgs/list_members.ts | 17 ++---- fake-snippets-api/routes/api/orgs/update.ts | 39 +++--------- .../routes/api/packages/create.ts | 21 ++++--- 17 files changed, 68 insertions(+), 201 deletions(-) diff --git a/bun-tests/fake-snippets-api/fixtures/get-test-server.ts b/bun-tests/fake-snippets-api/fixtures/get-test-server.ts index bfc35edc7..2b1f15f8f 100644 --- a/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +++ b/bun-tests/fake-snippets-api/fixtures/get-test-server.ts @@ -78,7 +78,7 @@ const seedDatabase = (db: DbClient) => { // Create personal organization for the main test account const personalOrg = db.addOrganization({ org_id: "personal-org-1", - name: "testuser", + tscircuit_handle: "testuser", owner_account_id: account.account_id, is_personal_org: true, }) diff --git a/bun-tests/fake-snippets-api/routes/orgs/create.test.ts b/bun-tests/fake-snippets-api/routes/orgs/create.test.ts index 576ca4e96..eb5aa31ef 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/create.test.ts @@ -5,13 +5,12 @@ test("POST /api/orgs/create - should create a new org for the user", async () => const { axios } = await getTestServer() const orgName = "acme-corp" const createResponse = await axios.post("/api/orgs/create", { - name: orgName, + tscircuit_handle: orgName, }) expect(createResponse.status).toBe(200) const responseBody = createResponse.data expect(responseBody.org).toBeDefined() - expect(responseBody.org.name).toBe(orgName) expect(responseBody.org.github_handle).toBeNull() expect(responseBody.org.tscircuit_handle).toBe(orgName) expect(responseBody.org.owner_account_id).toBe( @@ -26,7 +25,7 @@ test("POST /api/orgs/create - should reject duplicate org names", async () => { const { axios, seed } = await getTestServer() try { await axios.post("/api/orgs/create", { - name: seed.organization.tscircuit_handle, + tscircuit_handle: seed.organization.tscircuit_handle, }) throw new Error("Expected request to fail") } catch (error: any) { @@ -43,7 +42,7 @@ test("POST /api/orgs/create - should accept display_name and use it", async () = const name = "acme-corp-69" const displayName = "ACME Corporation" const createResponse = await axios.post("/api/orgs/create", { - name: name, + tscircuit_handle: name, display_name: displayName, }) expect(createResponse.status).toBe(200) @@ -55,11 +54,11 @@ test("POST /api/orgs/create - should accept display_name and use it", async () = expect(responseBody.org.display_name).toBe(displayName) }) -test("POST /api/orgs/create - should map name as display_name when not provided", async () => { +test("POST /api/orgs/create - should map tscircuit_handle as display_name when not provided", async () => { const { axios, db } = await getTestServer() const name = "acme-corp" const createResponse = await axios.post("/api/orgs/create", { - name: name, + tscircuit_handle: name, }) expect(createResponse.status).toBe(200) const responseBody = createResponse.data @@ -83,36 +82,13 @@ test("POST /api/orgs/create - should reject invalid org names", async () => { for (const name of invalidNames) { try { - await axios.post("/api/orgs/create", { name }) + const response = await axios.post("/api/orgs/create", { + tscircuit_handle: name, + }) + console.log(44, response.data) throw new Error(`Expected request to fail for name: ${name}`) } catch (error: any) { expect(error.status).toBe(400) } } }, 10000) - -test("POST /orgs/create accepts explicit tscircuit_handle", async () => { - const { jane_axios, db, seed } = await getTestServer() - - const tscHandle = "Custom_Handle-1" - const orgName = "custom-handle-org" - - const { - data: { org }, - } = await jane_axios.post("/api/orgs/create", { - name: orgName, - tscircuit_handle: tscHandle, - }) - - expect(org.name).toBe(orgName) - expect(org.tscircuit_handle).toBe(tscHandle) - expect(org.github_handle).toBeNull() - - const created = db.getOrg({ - org_id: org.org_id, - }) - - expect(created?.tscircuit_handle).toBe(tscHandle) - expect(created?.github_handle).toBeNull() - expect(created?.owner_account_id).toBe(seed.account2.account_id) -}) diff --git a/bun-tests/fake-snippets-api/routes/orgs/get.test.ts b/bun-tests/fake-snippets-api/routes/orgs/get.test.ts index 8d8fa2ddc..b7dfc8eb9 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/get.test.ts @@ -17,7 +17,6 @@ test("GET /api/orgs/get - should return org by org_id", async () => { const responseBody2 = getNotOwnerResponse.data expect(responseBody.org).toBeDefined() expect(responseBody.org.org_id).toBe(seed.organization.org_id) - expect(responseBody.org.name).toBe(seed.organization.org_name) expect(responseBody.org.github_handle).toBe(seed.organization.github_handle) expect(responseBody.org.tscircuit_handle).toBe( seed.organization.tscircuit_handle, diff --git a/bun-tests/fake-snippets-api/routes/orgs/get_member.test.ts b/bun-tests/fake-snippets-api/routes/orgs/get_member.test.ts index 6c246f8e8..66a7e6a43 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/get_member.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/get_member.test.ts @@ -37,7 +37,7 @@ test("GET /api/orgs/get_member - should return member by org_name and account_id const getResponse = await jane_axios.get("/api/orgs/get_member", { params: { - org_name: seed.organization.org_name, + org_id: seed.organization.org_id, account_id: seed.account.account_id, }, }) diff --git a/bun-tests/fake-snippets-api/routes/orgs/update.test.ts b/bun-tests/fake-snippets-api/routes/orgs/update.test.ts index b35257aae..ef2f69a9b 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/update.test.ts @@ -1,42 +1,21 @@ import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server" import { expect, test } from "bun:test" -test("POST /api/orgs/update - should update org name when owner", async () => { - const { axios } = await getTestServer() - - const createResponse = await axios.post("/api/orgs/create", { - name: "old-name", - }) - const org = createResponse.data.org - - const updateResponse = await axios.post("/api/orgs/update", { - org_id: org.org_id, - name: "new-name", - }) - - expect(updateResponse.status).toBe(200) - const responseBody = updateResponse.data - expect(responseBody.org).toBeDefined() - expect(responseBody.org.name).toBe("new-name") - expect(responseBody.org.github_handle).toBeNull() - expect(responseBody.org.user_permissions?.can_manage_org).toBe(true) -}) - test("PATCH /api/orgs/update - should update org name using PATCH method", async () => { const { axios } = await getTestServer() const createResponse = await axios.post("/api/orgs/create", { - name: "patch-test", + tscircuit_handle: "patch-test", }) const org = createResponse.data.org const updateResponse = await axios.patch("/api/orgs/update", { org_id: org.org_id, - name: "patch-updated", + display_name: "patch-updated", }) expect(updateResponse.status).toBe(200) - expect(updateResponse.data.org.name).toBe("patch-updated") + expect(updateResponse.data.org.display_name).toBe("patch-updated") expect(updateResponse.data.org.tscircuit_handle).toBe("patch-test") expect(updateResponse.data.org.github_handle).toBeNull() }) @@ -45,7 +24,6 @@ test("POST /api/orgs/update - should return current org when no changes provided const { axios } = await getTestServer() const createResponse = await axios.post("/api/orgs/create", { - name: "no-change", tscircuit_handle: "no-change-handle", }) const org = createResponse.data.org @@ -55,7 +33,7 @@ test("POST /api/orgs/update - should return current org when no changes provided }) expect(updateResponse.status).toBe(200) - expect(updateResponse.data.org.name).toBe("no-change") + expect(updateResponse.data.org.name).toBe("no-change-handle") expect(updateResponse.data.org.tscircuit_handle).toBe("no-change-handle") }) @@ -77,36 +55,11 @@ test("POST /api/orgs/update - should fail when user lacks management permissions } }) -test("POST /api/orgs/update - should reject duplicate name", async () => { - const { axios } = await getTestServer() - - await axios.post("/api/orgs/create", { - name: "dup-a", - }) - const org2Response = await axios.post("/api/orgs/create", { - name: "dup-b", - }) - - try { - const updateResponse = await axios.post("/api/orgs/update", { - org_id: org2Response.data.org.org_id, - name: "dup-a", - }) - throw new Error("Expected request to fail") - } catch (error: any) { - expect(error.status).toBe(400) - expect(error.data.error.error_code).toBe("org_already_exists") - expect(error.data.error.message).toBe( - "An organization with this name already exists", - ) - } -}) - test("POST /api/orgs/update - should update tscircuit_handle when owner", async () => { const { axios, db } = await getTestServer() const createResponse = await axios.post("/api/orgs/create", { - name: "handle-owner", + tscircuit_handle: "handle-owner", }) const org = createResponse.data.org @@ -129,7 +82,7 @@ test("POST /api/orgs/update - should reject duplicate tscircuit_handle", async ( const { axios } = await getTestServer() const orgAResponse = await axios.post("/api/orgs/create", { - name: "dup-handle-a", + tscircuit_handle: "dup-handle-a", }) const orgA = orgAResponse.data.org @@ -139,7 +92,7 @@ test("POST /api/orgs/update - should reject duplicate tscircuit_handle", async ( }) const orgBResponse = await axios.post("/api/orgs/create", { - name: "dup-handle-b", + tscircuit_handle: "dup-handle-b", }) const orgB = orgBResponse.data.org diff --git a/fake-snippets-api/lib/db/db-client.ts b/fake-snippets-api/lib/db/db-client.ts index 35f23f243..4baaa0b4e 100644 --- a/fake-snippets-api/lib/db/db-client.ts +++ b/fake-snippets-api/lib/db/db-client.ts @@ -1676,7 +1676,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ return updated }, addOrganization: (organization: { - name: string + name?: string org_id?: string is_personal_org?: boolean owner_account_id: string @@ -1685,13 +1685,16 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ tscircuit_handle?: string }) => { const newOrganization: Organization = { - org_name: organization.name, org_id: organization.org_id || `org_${get().idCounter + 1}`, github_handle: organization.github_handle ?? null, is_personal_org: organization.is_personal_org || false, created_at: new Date().toISOString(), - org_display_name: organization.org_display_name ?? organization.name, - tscircuit_handle: organization.tscircuit_handle || organization.name, + org_display_name: + organization.org_display_name ?? + organization.tscircuit_handle ?? + undefined, + tscircuit_handle: + organization.tscircuit_handle || organization.name || null, ...organization, } set((state) => ({ @@ -1778,7 +1781,6 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ getOrg: ( filters: { org_id?: string - org_name?: string github_handle?: string tscircuit_handle?: string }, @@ -1789,9 +1791,6 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ if (filters?.org_id) { orgs = orgs.filter((org) => org.org_id === filters.org_id) } - if (filters?.org_name) { - orgs = orgs.filter((org) => org.org_name === filters.org_name) - } // if (filters?.org_name && auth?.account_id) { // const account = get().accounts.find(x => x.account_id == auth?.account_id) // orgs = orgs.filter((org) => org.github_handle === account?.github_username) diff --git a/fake-snippets-api/lib/db/schema.ts b/fake-snippets-api/lib/db/schema.ts index 51b1f28ef..0f5d0573d 100644 --- a/fake-snippets-api/lib/db/schema.ts +++ b/fake-snippets-api/lib/db/schema.ts @@ -463,7 +463,6 @@ export const orgSchema = z.object({ org_display_name: z.string().optional(), github_handle: z.string().nullable(), tscircuit_handle: z.string().nullable(), - org_name: z.string(), }) export type Organization = z.infer @@ -527,9 +526,11 @@ export type DatabaseSchema = z.infer export const tscircuitHandleSchema = z .string() - .min(1) + .min(5) .max(40) .regex( - /^[0-9A-Za-z_-]+$/, - "tscircuit_handle may only contain letters, numbers, underscores, and hyphens", + /^[a-z0-9_-]+$/, + "Name must contain only lowercase letters, numbers, underscores, and hyphens", ) + .regex(/^[a-z0-9]/, "Name must start with a letter or number") + .regex(/[a-z0-9]$/, "Name must end with a letter or number") diff --git a/fake-snippets-api/lib/db/seed.ts b/fake-snippets-api/lib/db/seed.ts index 22ddbb952..c6e3679b2 100644 --- a/fake-snippets-api/lib/db/seed.ts +++ b/fake-snippets-api/lib/db/seed.ts @@ -1880,7 +1880,7 @@ export const SquareWaveModule = () => ( const testOrg = db.addOrganization({ org_display_name: "Test Organization", - name: "test-organization", + tscircuit_handle: "test-organization", github_handle: "tscircuit", owner_account_id: account_id, }) diff --git a/fake-snippets-api/lib/middleware/with-session-auth.ts b/fake-snippets-api/lib/middleware/with-session-auth.ts index 92e488b41..eb4178622 100644 --- a/fake-snippets-api/lib/middleware/with-session-auth.ts +++ b/fake-snippets-api/lib/middleware/with-session-auth.ts @@ -70,11 +70,7 @@ export const withSessionAuth: Middleware< ) return { org_id: oa.org_id, - name: - org?.org_display_name || - org?.org_name || - org?.github_handle || - oa.org_id, + name: org?.org_display_name || org?.github_handle || oa.org_id, user_permissions: { can_manage_packages: true }, } }) @@ -135,11 +131,7 @@ export const withSessionAuth: Middleware< const org = state.organizations.find((o: any) => o.org_id === oa.org_id) return { org_id: oa.org_id, - name: - org?.org_display_name || - org?.org_name || - org?.github_handle || - oa.org_id, + name: org?.org_display_name || org?.github_handle || oa.org_id, user_permissions: { can_manage_packages: true }, } }) diff --git a/fake-snippets-api/lib/public-mapping/public-map-org.ts b/fake-snippets-api/lib/public-mapping/public-map-org.ts index 53260c1f9..125035540 100644 --- a/fake-snippets-api/lib/public-mapping/public-map-org.ts +++ b/fake-snippets-api/lib/public-mapping/public-map-org.ts @@ -17,14 +17,13 @@ export const publicMapOrg = ( is_personal_org, org_display_name, tscircuit_handle, - org_name, ...org } = internal_org return { org_id: org.org_id, - display_name: org_display_name ?? org_name, + display_name: org_display_name ?? tscircuit_handle ?? undefined, owner_account_id: org.owner_account_id, - name: org_name, + name: tscircuit_handle, member_count: Number(member_count) || 0, package_count: Number(package_count) || 0, is_personal_org: Boolean(is_personal_org), diff --git a/fake-snippets-api/routes/api/accounts/update.ts b/fake-snippets-api/routes/api/accounts/update.ts index 1492e5785..a75ea2b9c 100644 --- a/fake-snippets-api/routes/api/accounts/update.ts +++ b/fake-snippets-api/routes/api/accounts/update.ts @@ -73,7 +73,6 @@ export default withRouteSpec({ .organizations.find((org) => org.org_id === account.personal_org_id) if (personalOrg && personalOrg.is_personal_org) { ctx.db.updateOrganization(account.personal_org_id, { - org_name: requestedHandle, tscircuit_handle: requestedHandle, }) } diff --git a/fake-snippets-api/routes/api/orgs/create.ts b/fake-snippets-api/routes/api/orgs/create.ts index 3a0f59203..6ee0b0352 100644 --- a/fake-snippets-api/routes/api/orgs/create.ts +++ b/fake-snippets-api/routes/api/orgs/create.ts @@ -1,40 +1,25 @@ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec" import { z } from "zod" import { publicMapOrg } from "fake-snippets-api/lib/public-mapping/public-map-org" -import { publicOrgSchema } from "fake-snippets-api/lib/db/schema" +import { + publicOrgSchema, + tscircuitHandleSchema, +} from "fake-snippets-api/lib/db/schema" export default withRouteSpec({ methods: ["GET", "POST"], commonParams: z.object({ - name: z - .string() - .min(5) - .max(40) - .regex( - /^[a-z0-9_-]+$/, - "Name must contain only lowercase letters, numbers, underscores, and hyphens", - ) - .regex(/^[a-z0-9]/, "Name must start with a letter or number") - .regex(/[a-z0-9]$/, "Name must end with a letter or number"), display_name: z.string().min(5).max(40).optional(), - tscircuit_handle: z - .string() - .min(1) - .max(40) - .regex( - /^[0-9A-Za-z_-]+$/, - "tscircuit_handle may only contain letters, numbers, underscores, and hyphens", - ) - .optional(), + tscircuit_handle: tscircuitHandleSchema, }), auth: "session", jsonResponse: z.object({ org: publicOrgSchema, }), })(async (req, ctx) => { - const { name, display_name, tscircuit_handle } = req.commonParams + const { display_name, tscircuit_handle } = req.commonParams - const existing = ctx.db.getOrg({ tscircuit_handle: tscircuit_handle ?? name }) + const existing = ctx.db.getOrg({ tscircuit_handle }) if (existing) { return ctx.error(400, { @@ -45,11 +30,10 @@ export default withRouteSpec({ const newOrg = { owner_account_id: ctx.auth.account_id, - name, org_display_name: display_name, created_at: new Date(), can_manage_org: true, - tscircuit_handle: tscircuit_handle || name, + tscircuit_handle: tscircuit_handle, } const org = ctx.db.addOrganization(newOrg) diff --git a/fake-snippets-api/routes/api/orgs/get.ts b/fake-snippets-api/routes/api/orgs/get.ts index e370d8c4f..88c48afe2 100644 --- a/fake-snippets-api/routes/api/orgs/get.ts +++ b/fake-snippets-api/routes/api/orgs/get.ts @@ -7,7 +7,6 @@ export default withRouteSpec({ methods: ["GET", "POST"], commonParams: z .object({ org_id: z.string() }) - .or(z.object({ org_name: z.string() })) .or(z.object({ github_handle: z.string() })) .or(z.object({ tscircuit_handle: z.string() })), auth: "optional_session", @@ -17,7 +16,6 @@ export default withRouteSpec({ })(async (req, ctx) => { const params = req.commonParams as { org_id?: string - org_name?: string github_handle?: string tscircuit_handle?: string } @@ -25,7 +23,6 @@ export default withRouteSpec({ const org = ctx.db.getOrg( { org_id: params.org_id, - org_name: params.org_name, github_handle: params.github_handle, tscircuit_handle: params.tscircuit_handle, }, diff --git a/fake-snippets-api/routes/api/orgs/get_member.ts b/fake-snippets-api/routes/api/orgs/get_member.ts index 55666f5cd..ceee54062 100644 --- a/fake-snippets-api/routes/api/orgs/get_member.ts +++ b/fake-snippets-api/routes/api/orgs/get_member.ts @@ -9,7 +9,6 @@ export default withRouteSpec({ methods: ["GET"], commonParams: z.object({ org_id: z.string().optional(), - org_name: z.string().optional(), account_id: z.string(), }), auth: "optional_session", @@ -19,12 +18,11 @@ export default withRouteSpec({ ), }), })(async (req, ctx) => { - const { org_id, org_name, account_id } = req.commonParams + const { org_id, account_id } = req.commonParams const org = ctx.db.getOrg( { org_id, - org_name, }, ctx.auth, ) @@ -44,7 +42,6 @@ export default withRouteSpec({ const memberOrg = ctx.db.getOrg( { org_id, - org_name, }, { account_id: account_id!, diff --git a/fake-snippets-api/routes/api/orgs/list_members.ts b/fake-snippets-api/routes/api/orgs/list_members.ts index f3aea823e..4f17777ac 100644 --- a/fake-snippets-api/routes/api/orgs/list_members.ts +++ b/fake-snippets-api/routes/api/orgs/list_members.ts @@ -7,15 +7,9 @@ import { export default withRouteSpec({ methods: ["GET", "POST"], - commonParams: z - .object({ - org_id: z.string(), - }) - .or( - z.object({ - name: z.string(), - }), - ), + commonParams: z.object({ + org_id: z.string(), + }), auth: "optional_session", jsonResponse: z.object({ org_members: z.array( @@ -26,12 +20,11 @@ export default withRouteSpec({ ), }), })(async (req, ctx) => { - const params = req.commonParams as { org_id?: string; name?: string } + const params = req.commonParams as { org_id?: string } const org = ctx.db.getOrg( { org_id: params.org_id, - org_name: params.name, }, ctx.auth, ) @@ -51,7 +44,6 @@ export default withRouteSpec({ const memberOrg = ctx.db.getOrg( { org_id: org.org_id, - org_name: org.org_name, }, { account_id: account.account_id, @@ -78,7 +70,6 @@ export default withRouteSpec({ const memberOrg = ctx.db.getOrg( { org_id: org.org_id, - org_name: org.org_name, }, { account_id: owner.account_id, diff --git a/fake-snippets-api/routes/api/orgs/update.ts b/fake-snippets-api/routes/api/orgs/update.ts index a12220cb9..629087288 100644 --- a/fake-snippets-api/routes/api/orgs/update.ts +++ b/fake-snippets-api/routes/api/orgs/update.ts @@ -14,7 +14,6 @@ export default withRouteSpec({ }) .and( z.object({ - name: z.string().min(5).max(40).optional(), display_name: z.string().max(50).optional(), tscircuit_handle: tscircuitHandleSchema.optional(), }), @@ -24,9 +23,8 @@ export default withRouteSpec({ org: publicOrgSchema, }), })(async (req, ctx) => { - const { org_id, name, display_name, tscircuit_handle } = req.commonParams as { + const { org_id, display_name, tscircuit_handle } = req.commonParams as { org_id: string - name?: string display_name?: string tscircuit_handle?: string | null } @@ -48,55 +46,32 @@ export default withRouteSpec({ } // No changes provided - if (!name && display_name === undefined && tscircuit_handle === undefined) { + if (display_name === undefined && tscircuit_handle === undefined) { return ctx.json({ org: publicMapOrg(org) }) } - if (name && name !== org.org_name) { - const duplicate = ctx.db.getOrg({ org_name: name }) + if (tscircuit_handle && tscircuit_handle !== org.tscircuit_handle) { + const duplicate = ctx.db.getOrg({ tscircuit_handle }) if (duplicate && duplicate.org_id !== org_id) { return ctx.error(400, { - error_code: "org_already_exists", - message: "An organization with this name already exists", + error_code: "org_tscircuit_handle_already_exists", + message: "An organization with this tscircuit_handle already exists", }) } } - - if ( - tscircuit_handle !== undefined && - tscircuit_handle !== org.tscircuit_handle - ) { - if (tscircuit_handle !== null) { - const duplicate = ctx.db.getOrg({ tscircuit_handle }) - - if (duplicate && duplicate.org_id !== org_id) { - return ctx.error(400, { - error_code: "org_tscircuit_handle_already_exists", - message: "An organization with this tscircuit_handle already exists", - }) - } - } - } - const updates: { - org_name?: string org_display_name?: string tscircuit_handle?: string | null } = {} - if (name) { - updates.org_name = name - } - if (tscircuit_handle !== undefined) { updates.tscircuit_handle = tscircuit_handle } if (display_name !== undefined) { const trimmedDisplayName = display_name.trim() - const fallbackDisplayName = - name ?? org.org_name ?? org.tscircuit_handle ?? undefined + const fallbackDisplayName = org.tscircuit_handle ?? undefined updates.org_display_name = trimmedDisplayName.length > 0 ? trimmedDisplayName : fallbackDisplayName } diff --git a/fake-snippets-api/routes/api/packages/create.ts b/fake-snippets-api/routes/api/packages/create.ts index a5031ffb4..c985b74af 100644 --- a/fake-snippets-api/routes/api/packages/create.ts +++ b/fake-snippets-api/routes/api/packages/create.ts @@ -55,7 +55,16 @@ export default withRouteSpec({ } if (!owner_segment) { - owner_segment = org_id ? org!.org_name : ctx.auth.github_username + owner_segment = org_id + ? (org!.tscircuit_handle ?? undefined) + : (ctx.auth.tscircuit_handle ?? undefined) + } + + if (!owner_segment) { + return ctx.error(400, { + error_code: "missing_owner_segment", + message: "Owner segment is required", + }) } const final_name = name ?? `${owner_segment}/${unscoped_name}` @@ -72,12 +81,7 @@ export default withRouteSpec({ .filter((oa) => oa.account_id === ctx.auth.account_id) .map((oa) => state.organizations.find((o) => o.org_id === oa.org_id)) .filter((o): o is NonNullable => o !== undefined) - .find( - (o) => - o.org_display_name?.toLowerCase() === requested_owner_lower || - o.org_name?.toLowerCase() === requested_owner_lower || - o.github_handle?.toLowerCase() === requested_owner_lower, - ) + .find((o) => o.tscircuit_handle?.toLowerCase() === requested_owner_lower) if (!memberOrg) { return ctx.error(403, { @@ -88,7 +92,8 @@ export default withRouteSpec({ } owner_org_id = memberOrg.org_id - owner_github_username = memberOrg.github_handle || memberOrg.org_name + owner_github_username = + memberOrg.github_handle ?? memberOrg.tscircuit_handle ?? "" } const existingPackage = ctx.db From 690e673e19b8fa7239468d8afe4bf422702a5770 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Mon, 17 Nov 2025 12:25:07 +0530 Subject: [PATCH 2/9] g --- bun-tests/fake-snippets-api/routes/orgs/delete.test.ts | 6 +++--- .../fake-snippets-api/routes/orgs/remove_member.test.ts | 4 ++-- .../routes/package_releases/create.test.ts | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bun-tests/fake-snippets-api/routes/orgs/delete.test.ts b/bun-tests/fake-snippets-api/routes/orgs/delete.test.ts index efa1cd675..11704fd7a 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/delete.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/delete.test.ts @@ -5,7 +5,7 @@ test("POST /api/orgs/delete - should delete an organization", async () => { const { jane_axios, seed } = await getTestServer() const createResponse = await jane_axios.post("/api/orgs/create", { - name: "test-org-to-delete", + tscircuit_handle: "test-org-to-delete", display_name: "Test Organization to Delete", }) const org = createResponse.data.org @@ -29,10 +29,10 @@ test("POST /api/orgs/delete - should delete an organization", async () => { }) test("POST /api/orgs/delete - should fail for non-owner (403)", async () => { - const { jane_axios, axios, seed } = await getTestServer() + const { jane_axios, axios } = await getTestServer() const createResponse = await jane_axios.post("/api/orgs/create", { - name: "test-org-protected", + tscircuit_handle: "test-org-protected", display_name: "Test Organization Protected", }) const org = createResponse.data.org diff --git a/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts b/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts index 28a208b68..4bcc5444b 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts @@ -5,7 +5,7 @@ test("POST /api/orgs/remove_member - should remove a user from an org (resets to const { jane_axios, seed } = await getTestServer() const createResponse = await jane_axios.post("/api/orgs/create", { - name: "globex", + tscircuit_handle: "globex", }) const org = createResponse.data.org @@ -27,7 +27,7 @@ test("POST /api/orgs/remove_member - should fail for non-owner (403)", async () const { jane_axios, axios, seed } = await getTestServer() const createResponse = await jane_axios.post("/api/orgs/create", { - name: "initech", + tscircuit_handle: "initech", }) const org = createResponse.data.org diff --git a/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts b/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts index 336a7d5a9..e5d9e1238 100644 --- a/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +++ b/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts @@ -137,12 +137,11 @@ test("create package release - package not found", async () => { } }) -// test test("create package release under org", async () => { const { axios } = await getTestServer() const orgResponse = await axios.post("/api/orgs/create", { - name: "testorg", + tscircuit_handle: "testorg", }) expect(orgResponse.status).toBe(200) From caedf36e4880aba8d318dadaeb2f2dbb5ce330e4 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Mon, 17 Nov 2025 12:30:50 +0530 Subject: [PATCH 3/9] Discard changes to bun-tests/fake-snippets-api/fixtures/get-test-server.ts --- bun-tests/fake-snippets-api/fixtures/get-test-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bun-tests/fake-snippets-api/fixtures/get-test-server.ts b/bun-tests/fake-snippets-api/fixtures/get-test-server.ts index 2b1f15f8f..bfc35edc7 100644 --- a/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +++ b/bun-tests/fake-snippets-api/fixtures/get-test-server.ts @@ -78,7 +78,7 @@ const seedDatabase = (db: DbClient) => { // Create personal organization for the main test account const personalOrg = db.addOrganization({ org_id: "personal-org-1", - tscircuit_handle: "testuser", + name: "testuser", owner_account_id: account.account_id, is_personal_org: true, }) From 3eaf9f454549f3c2bf30f4c1bc74377ff8304434 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Mon, 17 Nov 2025 13:25:05 +0530 Subject: [PATCH 4/9] backwards compatibility --- fake-snippets-api/routes/api/orgs/create.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fake-snippets-api/routes/api/orgs/create.ts b/fake-snippets-api/routes/api/orgs/create.ts index 6ee0b0352..c35e4efa4 100644 --- a/fake-snippets-api/routes/api/orgs/create.ts +++ b/fake-snippets-api/routes/api/orgs/create.ts @@ -8,18 +8,24 @@ import { export default withRouteSpec({ methods: ["GET", "POST"], - commonParams: z.object({ - display_name: z.string().min(5).max(40).optional(), - tscircuit_handle: tscircuitHandleSchema, - }), + commonParams: z + .object({ + display_name: z.string().min(5).max(40).optional(), + tscircuit_handle: tscircuitHandleSchema.optional(), + name: tscircuitHandleSchema.optional(), + }) + .refine((data) => data.tscircuit_handle || data.name, { + message: "Either tscircuit_handle or name is required", + }), auth: "session", jsonResponse: z.object({ org: publicOrgSchema, }), })(async (req, ctx) => { - const { display_name, tscircuit_handle } = req.commonParams + const { display_name, tscircuit_handle, name } = req.commonParams + const handle = tscircuit_handle || name - const existing = ctx.db.getOrg({ tscircuit_handle }) + const existing = ctx.db.getOrg({ tscircuit_handle: handle }) if (existing) { return ctx.error(400, { @@ -33,7 +39,7 @@ export default withRouteSpec({ org_display_name: display_name, created_at: new Date(), can_manage_org: true, - tscircuit_handle: tscircuit_handle, + tscircuit_handle: handle, } const org = ctx.db.addOrganization(newOrg) From 3926dfb03cbc18f8b62cdcfd05a0784452604986 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Mon, 17 Nov 2025 13:32:32 +0530 Subject: [PATCH 5/9] Update create.test.ts --- bun-tests/fake-snippets-api/routes/orgs/create.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/bun-tests/fake-snippets-api/routes/orgs/create.test.ts b/bun-tests/fake-snippets-api/routes/orgs/create.test.ts index eb5aa31ef..90e89ff02 100644 --- a/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +++ b/bun-tests/fake-snippets-api/routes/orgs/create.test.ts @@ -85,7 +85,6 @@ test("POST /api/orgs/create - should reject invalid org names", async () => { const response = await axios.post("/api/orgs/create", { tscircuit_handle: name, }) - console.log(44, response.data) throw new Error(`Expected request to fail for name: ${name}`) } catch (error: any) { expect(error.status).toBe(400) From 15f601e347db388cae514f6e01c0abad1f914fa4 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Tue, 18 Nov 2025 18:05:48 +0530 Subject: [PATCH 6/9] Update schema.ts --- fake-snippets-api/lib/db/schema.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fake-snippets-api/lib/db/schema.ts b/fake-snippets-api/lib/db/schema.ts index 0f5d0573d..7d3d40a93 100644 --- a/fake-snippets-api/lib/db/schema.ts +++ b/fake-snippets-api/lib/db/schema.ts @@ -529,8 +529,6 @@ export const tscircuitHandleSchema = z .min(5) .max(40) .regex( - /^[a-z0-9_-]+$/, - "Name must contain only lowercase letters, numbers, underscores, and hyphens", + /^[0-9A-Za-z_-]+$/, + "tscircuit_handle may only contain letters, numbers, underscores, and hyphens", ) - .regex(/^[a-z0-9]/, "Name must start with a letter or number") - .regex(/[a-z0-9]$/, "Name must end with a letter or number") From f0a24a8b014915d74b7ff4cc51f2d936638b7dd2 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Tue, 18 Nov 2025 18:07:35 +0530 Subject: [PATCH 7/9] f --- fake-snippets-api/lib/db/db-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fake-snippets-api/lib/db/db-client.ts b/fake-snippets-api/lib/db/db-client.ts index 87915256c..96524b8bb 100644 --- a/fake-snippets-api/lib/db/db-client.ts +++ b/fake-snippets-api/lib/db/db-client.ts @@ -1685,6 +1685,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ tscircuit_handle?: string }) => { const newOrganization: Organization = { + ...organization, org_id: organization.org_id || `org_${get().idCounter + 1}`, github_handle: organization.github_handle ?? null, is_personal_org: organization.is_personal_org || false, @@ -1695,7 +1696,6 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ undefined, tscircuit_handle: organization.tscircuit_handle || organization.name || null, - ...organization, } set((state) => ({ idCounter: state.idCounter + 1, From 283782809b65ee3dfc22fed6fdda81b5b3697d90 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Tue, 18 Nov 2025 18:13:39 +0530 Subject: [PATCH 8/9] Revert "f" This reverts commit f0a24a8b014915d74b7ff4cc51f2d936638b7dd2. --- fake-snippets-api/lib/db/db-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fake-snippets-api/lib/db/db-client.ts b/fake-snippets-api/lib/db/db-client.ts index 96524b8bb..87915256c 100644 --- a/fake-snippets-api/lib/db/db-client.ts +++ b/fake-snippets-api/lib/db/db-client.ts @@ -1685,7 +1685,6 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ tscircuit_handle?: string }) => { const newOrganization: Organization = { - ...organization, org_id: organization.org_id || `org_${get().idCounter + 1}`, github_handle: organization.github_handle ?? null, is_personal_org: organization.is_personal_org || false, @@ -1696,6 +1695,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ undefined, tscircuit_handle: organization.tscircuit_handle || organization.name || null, + ...organization, } set((state) => ({ idCounter: state.idCounter + 1, From 3d1cb18ce01fa58a1b6a1db754e5b37c2eda0873 Mon Sep 17 00:00:00 2001 From: Arnav K Date: Thu, 20 Nov 2025 18:49:15 +0530 Subject: [PATCH 9/9] f --- fake-snippets-api/lib/db/schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fake-snippets-api/lib/db/schema.ts b/fake-snippets-api/lib/db/schema.ts index 7d3d40a93..56617c9b4 100644 --- a/fake-snippets-api/lib/db/schema.ts +++ b/fake-snippets-api/lib/db/schema.ts @@ -529,6 +529,6 @@ export const tscircuitHandleSchema = z .min(5) .max(40) .regex( - /^[0-9A-Za-z_-]+$/, - "tscircuit_handle may only contain letters, numbers, underscores, and hyphens", + /^[0-9A-Za-z][0-9A-Za-z_-]*[0-9A-Za-z]$/, + "tscircuit_handle must start and end with a letter or number, and may only contain letters, numbers, underscores, and hyphens", )