Skip to content

Commit b024e55

Browse files
authored
feat: sync api to prod, org.org_name (#1769)
* feat: sync api to prod, org.org_name * f * Update create.test.ts * Enhance organization handling in API and tests - Added `github_handle` to the organization seeding in `get-test-server.ts`. - Updated test description for clarity in `update.test.ts`. - Improved organization name handling in package creation logic in `create.ts` by allowing `org_name` as a fallback for `owner_github_username`.
1 parent 06d062e commit b024e55

11 files changed

Lines changed: 174 additions & 28 deletions

File tree

bun-tests/fake-snippets-api/fixtures/get-test-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const seedDatabase = (db: DbClient) => {
126126
// Seed a organization for account2
127127
const organization = db.addOrganization({
128128
name: "jane",
129+
github_handle: "jane",
129130
owner_account_id: account2.account_id,
130131
})
131132

bun-tests/fake-snippets-api/routes/orgs/create.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ test("POST /api/orgs/create - should create a new org for the user", async () =>
2323
test("POST /api/orgs/create - should reject duplicate org names", async () => {
2424
const { axios, seed } = await getTestServer()
2525
try {
26-
const orgResponse = await axios.post("/api/orgs/create", {
27-
name: seed.organization.github_handle,
26+
await axios.post("/api/orgs/create", {
27+
name: seed.organization.org_name,
2828
})
2929
throw new Error("Expected request to fail")
3030
} catch (error: any) {

bun-tests/fake-snippets-api/routes/orgs/update.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,55 @@ test("POST /api/orgs/update - should reject duplicate name", async () => {
9797
)
9898
}
9999
})
100+
101+
test("POST /api/orgs/update - should update github_handle when owner", async () => {
102+
const { axios, db } = await getTestServer()
103+
104+
const createResponse = await axios.post("/api/orgs/create", {
105+
name: "handle-owner",
106+
})
107+
const org = createResponse.data.org
108+
109+
const updateResponse = await axios.post("/api/orgs/update", {
110+
org_id: org.org_id,
111+
github_handle: "handle-owner",
112+
})
113+
114+
expect(updateResponse.status).toBe(200)
115+
116+
const state = db.getState()
117+
const updatedOrg = state.organizations.find(
118+
(o: any) => o.org_id === org.org_id,
119+
)
120+
expect(updatedOrg?.github_handle).toBe("handle-owner")
121+
})
122+
123+
test("POST /api/orgs/update - should reject duplicate github_handle", async () => {
124+
const { axios } = await getTestServer()
125+
126+
const orgAResponse = await axios.post("/api/orgs/create", {
127+
name: "dup-handle-a",
128+
})
129+
const orgA = orgAResponse.data.org
130+
131+
await axios.post("/api/orgs/update", {
132+
org_id: orgA.org_id,
133+
github_handle: "duplicate-handle",
134+
})
135+
136+
const orgBResponse = await axios.post("/api/orgs/create", {
137+
name: "dup-handle-b",
138+
})
139+
const orgB = orgBResponse.data.org
140+
141+
try {
142+
await axios.post("/api/orgs/update", {
143+
org_id: orgB.org_id,
144+
github_handle: "duplicate-handle",
145+
})
146+
throw new Error("Expected request to fail")
147+
} catch (error: any) {
148+
expect(error.status).toBe(400)
149+
expect(error.data.error.error_code).toBe("org_github_handle_already_exists")
150+
}
151+
})

fake-snippets-api/lib/db/db-client.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,13 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
178178
return state.orderFiles.find((file) => file.order_file_id === orderFileId)
179179
},
180180
addAccount: (
181-
account: Omit<Account, "account_id"> & Partial<Pick<Account, "account_id">>,
181+
account: Omit<Account, "account_id" | "is_tscircuit_staff"> &
182+
Partial<Pick<Account, "account_id" | "is_tscircuit_staff">>,
182183
) => {
183184
const newAccount = {
184185
account_id: account.account_id || `account_${get().idCounter + 1}`,
185186
...account,
187+
is_tscircuit_staff: Boolean(account.is_tscircuit_staff),
186188
}
187189

188190
set((state) => {
@@ -1549,10 +1551,12 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
15491551
org_id?: string
15501552
is_personal_org?: boolean
15511553
owner_account_id: string
1554+
github_handle?: string
15521555
}) => {
15531556
const newOrganization: Organization = {
1557+
org_name: organization.name,
15541558
org_id: organization.org_id || `org_${get().idCounter + 1}`,
1555-
github_handle: organization.name,
1559+
github_handle: organization.github_handle,
15561560
is_personal_org: organization.is_personal_org || false,
15571561
created_at: new Date().toISOString(),
15581562
...organization,
@@ -1630,7 +1634,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
16301634
orgs = orgs.filter((org) => org.org_id === filters.org_id)
16311635
}
16321636
if (filters?.org_name) {
1633-
orgs = orgs.filter((org) => org.github_handle === filters.org_name)
1637+
orgs = orgs.filter((org) => org.org_name === filters.org_name)
16341638
}
16351639
// if (filters?.org_name && auth?.account_id) {
16361640
// const account = get().accounts.find(x => x.account_id == auth?.account_id)

fake-snippets-api/lib/db/schema.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ export const shippingInfoSchema = z.object({
6767
country: z.string(),
6868
phone: z.string(),
6969
})
70-
7170
export const accountSchema = z.object({
7271
account_id: z.string(),
7372
github_username: z.string(),
7473
shippingInfo: shippingInfoSchema.optional(),
7574
personal_org_id: z.string().optional(),
75+
is_tscircuit_staff: z.boolean().default(false),
7676
})
7777
export type Account = z.infer<typeof accountSchema>
7878

@@ -401,11 +401,12 @@ export type PackageBuild = z.infer<typeof packageBuildSchema>
401401

402402
export const orgSchema = z.object({
403403
org_id: z.string(),
404-
github_handle: z.string(),
404+
github_handle: z.string().optional(),
405405
owner_account_id: z.string(),
406406
is_personal_org: z.boolean().default(false),
407407
created_at: z.string().datetime(),
408408
org_display_name: z.string().optional(),
409+
org_name: z.string(),
409410
})
410411
export type Organization = z.infer<typeof orgSchema>
411412

fake-snippets-api/lib/middleware/with-session-auth.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Middleware } from "winterspec/middleware"
22
import type { CtxErrorFn } from "./with-ctx-error"
3+
import type { DbClient } from "../db/db-client"
34

45
export const withSessionAuth: Middleware<
56
{
67
error: CtxErrorFn
7-
db: any
8+
db: DbClient
89
},
910
{
1011
auth: {
@@ -13,6 +14,13 @@ export const withSessionAuth: Middleware<
1314
personal_org_id: string
1415
github_username: string
1516
session_id: string
17+
orgs: Array<{
18+
org_id: string
19+
name: string
20+
user_permissions: {
21+
can_manage_packages: boolean
22+
}
23+
}>
1624
}
1725
},
1826
{}
@@ -22,28 +30,72 @@ export const withSessionAuth: Middleware<
2230
const token = req.headers.get("authorization")?.split("Bearer ")?.[1]
2331

2432
// Only check database accounts when we're in a Bun test environment
25-
if (process.env.BUN_TEST === "true" && ctx.db?.accounts) {
26-
const account = ctx.db.accounts.find((acc: any) => acc.account_id === token)
27-
33+
if (process.env.BUN_TEST === "true" && ctx.db?.getState) {
34+
const state = ctx.db.getState()
35+
const account = state.accounts.find((acc: any) => acc.account_id === token)
2836
if (account) {
37+
// Fetch orgs for this account
38+
const orgAccounts = state.orgAccounts.filter(
39+
(oa: any) => oa.account_id === account.account_id,
40+
)
41+
42+
const orgs = orgAccounts.map((oa: any) => {
43+
const org = state.organizations.find((o: any) => o.org_id === oa.org_id)
44+
return {
45+
org_id: oa.org_id,
46+
name:
47+
org?.org_display_name ||
48+
org?.org_name ||
49+
org?.github_handle ||
50+
oa.org_id,
51+
user_permissions: { can_manage_packages: true },
52+
}
53+
})
54+
2955
ctx.auth = {
3056
type: "session",
3157
account_id: account.account_id,
3258
personal_org_id: account.personal_org_id || `org-${account.account_id}`,
3359
github_username: account.github_username,
3460
session_id: `session-${account.account_id}`,
61+
orgs:
62+
orgs.length > 0
63+
? orgs
64+
: [
65+
{
66+
org_id:
67+
account.personal_org_id || `org-${account.account_id}`,
68+
name:
69+
account.github_username ||
70+
account.personal_org_id ||
71+
`org-${account.account_id}`,
72+
user_permissions: { can_manage_packages: true },
73+
},
74+
],
3575
}
3676
return next(req, ctx)
3777
}
3878
}
3979

40-
// For all other environments or if account not found in test, use hardcoded auth
80+
// Fallback auth for non-test environments or when no token is found
81+
const fallbackAccountId = "account-1234"
82+
const fallbackOrgId = "org-1234"
83+
84+
const fallbackOrgs = [
85+
{
86+
org_id: fallbackOrgId,
87+
name: "org-1234",
88+
user_permissions: { can_manage_packages: true },
89+
},
90+
]
91+
4192
ctx.auth = {
4293
type: "session",
43-
account_id: "account-1234",
44-
personal_org_id: "org-1234",
94+
account_id: fallbackAccountId,
95+
personal_org_id: fallbackOrgId,
4596
github_username: "testuser",
4697
session_id: "session-1234",
98+
orgs: fallbackOrgs,
4799
}
48100

49101
return next(req, ctx)

fake-snippets-api/lib/public-mapping/public-map-org.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ export const publicMapOrg = (
1616
created_at,
1717
is_personal_org,
1818
org_display_name,
19+
org_name,
1920
...org
2021
} = internal_org
2122
return {
2223
org_id: org.org_id,
23-
display_name: org_display_name ?? github_handle ?? "",
24+
display_name: org_display_name ?? org_name,
2425
owner_account_id: org.owner_account_id,
25-
name: github_handle,
26+
name: org_name,
2627
member_count: Number(member_count) || 0,
2728
package_count: Number(package_count) || 0,
2829
is_personal_org: Boolean(is_personal_org),

fake-snippets-api/routes/api/orgs/create.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ export default withRouteSpec({
77
methods: ["GET", "POST"],
88
commonParams: z.object({
99
name: z.string(),
10+
github_handle: z.string().optional(),
1011
}),
1112
auth: "session",
1213
jsonResponse: z.object({
1314
org: publicOrgSchema,
1415
}),
1516
})(async (req, ctx) => {
16-
const { name } = req.commonParams
17+
const { name, github_handle } = req.commonParams
1718

18-
const existing = ctx.db.getOrg({ github_handle: name })
19+
const existing = ctx.db.getOrg({ org_name: name })
1920

2021
if (existing) {
2122
return ctx.error(400, {
@@ -28,6 +29,7 @@ export default withRouteSpec({
2829
name: name,
2930
created_at: new Date(),
3031
can_manage_org: true,
32+
...(github_handle ? { github_handle } : {}),
3133
}
3234

3335
const org = ctx.db.addOrganization(newOrg)

fake-snippets-api/routes/api/orgs/update.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@ export default withRouteSpec({
1313
z.object({
1414
name: z.string().optional(),
1515
display_name: z.string().optional(),
16+
github_handle: z.string().trim().min(1).nullable().optional(),
1617
}),
1718
),
1819
auth: "session",
1920
jsonResponse: z.object({
2021
org: publicOrgSchema,
2122
}),
2223
})(async (req, ctx) => {
23-
const { org_id, name, display_name } = req.commonParams as {
24+
const { org_id, name, display_name, github_handle } = req.commonParams as {
2425
org_id: string
2526
name?: string
2627
display_name?: string
28+
github_handle?: string
2729
}
2830

2931
const org = ctx.db.getOrg({ org_id }, ctx.auth)
@@ -43,13 +45,13 @@ export default withRouteSpec({
4345
}
4446

4547
// No changes provided
46-
if (!name && display_name === undefined) {
48+
if (!name && display_name === undefined && github_handle === null) {
4749
return ctx.json({ org: publicMapOrg(org) })
4850
}
4951

50-
if (name && name !== org.github_handle) {
52+
if (name && name !== org.org_name) {
5153
// Validate duplicate name
52-
const duplicate = ctx.db.getOrg({ github_handle: name })
54+
const duplicate = ctx.db.getOrg({ org_name: name })
5355

5456
if (duplicate && duplicate.org_id !== org_id) {
5557
return ctx.error(400, {
@@ -58,20 +60,43 @@ export default withRouteSpec({
5860
})
5961
}
6062
}
63+
if (
64+
github_handle !== undefined &&
65+
github_handle !== org.github_handle &&
66+
github_handle !== null
67+
) {
68+
const duplicateHandle = ctx.db.getOrg({ github_handle })
69+
? ctx.db.getOrg({ github_handle })?.org_id != org_id
70+
: false
6171

72+
if (duplicateHandle) {
73+
return ctx.error(400, {
74+
error_code: "org_github_handle_already_exists",
75+
message: "An organization with this GitHub handle already exists",
76+
})
77+
}
78+
}
6279
const updates: {
63-
github_handle?: string
80+
org_name?: string
6481
org_display_name?: string
82+
github_handle?: string
6583
} = {}
6684

6785
if (name) {
6886
updates.github_handle = name
87+
updates.org_name = name
88+
}
89+
90+
if (github_handle !== undefined) {
91+
updates.github_handle = github_handle
6992
}
7093

7194
if (display_name !== undefined) {
7295
const trimmedDisplayName = display_name.trim()
96+
const handleForFallback =
97+
github_handle !== undefined ? github_handle : org.github_handle
7398
const fallbackDisplayName =
74-
name ?? org.org_display_name ?? org.github_handle ?? ""
99+
name ?? org.org_display_name ?? org.org_name ?? handleForFallback ?? ""
75100
updates.org_display_name =
76101
trimmedDisplayName.length > 0 ? trimmedDisplayName : fallbackDisplayName
77102
}

fake-snippets-api/routes/api/packages/create.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default withRouteSpec({
6666
.find(
6767
(o) =>
6868
o.org_display_name?.toLowerCase() === requested_owner_lower ||
69+
o.org_name?.toLowerCase() === requested_owner_lower ||
6970
o.github_handle?.toLowerCase() === requested_owner_lower,
7071
)
7172

@@ -78,10 +79,12 @@ export default withRouteSpec({
7879
}
7980

8081
owner_org_id = memberOrg.org_id
81-
owner_github_username = memberOrg.github_handle
82+
owner_github_username = memberOrg.github_handle || memberOrg.org_name
8283
}
8384

84-
const existingPackage = ctx.db.packages.find((pkg) => pkg.name === final_name)
85+
const existingPackage = ctx.db
86+
.getState()
87+
.packages.find((pkg) => pkg.name === final_name)
8588

8689
if (existingPackage) {
8790
throw ctx.error(400, {

0 commit comments

Comments
 (0)