@tscircuit/fake-snippets 0.0.109 → 0.0.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +32 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +151 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +361 -453
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1313 -639
  36. package/dist/index.d.ts +313 -6
  37. package/dist/index.js +328 -24
  38. package/dist/schema.d.ts +290 -1
  39. package/dist/schema.js +54 -1
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +219 -4
  42. package/fake-snippets-api/lib/db/schema.ts +63 -1
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +33 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +48 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +60 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +118 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +57 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +25 -19
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +27 -8
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +17 -5
  71. package/src/components/FileSidebar.tsx +11 -17
  72. package/src/components/Footer.tsx +8 -9
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +43 -15
  77. package/src/components/NotFound.tsx +5 -5
  78. package/src/components/PackageBreadcrumb.tsx +6 -12
  79. package/src/components/PackageSearchResults.tsx +1 -1
  80. package/src/components/PrefetchPageLink.tsx +7 -1
  81. package/src/components/ProfileRouter.tsx +32 -0
  82. package/src/components/SearchComponent.tsx +12 -8
  83. package/src/components/SentryNotFoundReporter.tsx +44 -0
  84. package/src/components/UserCard.tsx +80 -0
  85. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  86. package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
  87. package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
  88. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
  89. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
  90. package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
  91. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
  92. package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
  93. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  94. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  95. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  96. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  97. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  98. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  99. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  100. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  101. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  102. package/src/components/organization/OrganizationCard.tsx +206 -0
  103. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  104. package/src/components/organization/OrganizationHeader.tsx +154 -0
  105. package/src/components/organization/OrganizationMembers.tsx +146 -0
  106. package/src/components/package-port/CodeAndPreview.tsx +15 -12
  107. package/src/components/package-port/CodeEditor.tsx +4 -30
  108. package/src/components/package-port/CodeEditorHeader.tsx +123 -61
  109. package/src/components/package-port/EditorNav.tsx +32 -49
  110. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  111. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  112. package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
  113. package/src/components/ui/tree-view.tsx +6 -3
  114. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  115. package/src/hooks/use-create-org-mutation.ts +38 -0
  116. package/src/hooks/use-create-package-mutation.ts +3 -0
  117. package/src/hooks/use-current-package-release.ts +4 -3
  118. package/src/hooks/use-download-zip.ts +2 -2
  119. package/src/hooks/use-global-store.ts +6 -4
  120. package/src/hooks/use-hydration.ts +30 -0
  121. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  122. package/src/hooks/use-list-org-members.ts +27 -0
  123. package/src/hooks/use-list-user-orgs.ts +25 -0
  124. package/src/hooks/use-org-by-github-handle.ts +26 -0
  125. package/src/hooks/use-org.ts +24 -0
  126. package/src/hooks/use-organization.ts +42 -0
  127. package/src/hooks/use-package-as-snippet.ts +4 -2
  128. package/src/hooks/use-package-builds.ts +6 -2
  129. package/src/hooks/use-package-files.ts +5 -3
  130. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  131. package/src/hooks/use-package-release-images.ts +105 -0
  132. package/src/hooks/use-package-release.ts +2 -2
  133. package/src/hooks/use-package-stars.ts +80 -4
  134. package/src/hooks/use-preview-images.ts +6 -3
  135. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  136. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  137. package/src/hooks/use-update-org-mutation.ts +41 -0
  138. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  139. package/src/hooks/useFileManagement.ts +51 -22
  140. package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
  141. package/src/hooks/usePackageFilesLoader.ts +2 -2
  142. package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
  143. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
  144. package/src/lib/download-fns/download-kicad-files.ts +22 -11
  145. package/src/lib/download-fns/download-step.ts +12 -0
  146. package/src/lib/normalize-svg-for-tile.ts +50 -0
  147. package/src/lib/posthog.ts +11 -9
  148. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  149. package/src/lib/sentry.ts +14 -0
  150. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  151. package/src/lib/ts-lib-cache.ts +122 -7
  152. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  153. package/src/lib/utils/findTargetFile.ts +45 -10
  154. package/src/lib/utils/isComponentExported.ts +2 -1
  155. package/src/main.tsx +2 -1
  156. package/src/pages/create-organization.tsx +169 -0
  157. package/src/pages/dashboard.tsx +38 -6
  158. package/src/pages/datasheet.tsx +1 -1
  159. package/src/pages/datasheets.tsx +3 -3
  160. package/src/pages/editor.tsx +4 -6
  161. package/src/pages/landing.tsx +6 -6
  162. package/src/pages/latest.tsx +3 -0
  163. package/src/pages/organization-profile.tsx +199 -0
  164. package/src/pages/organization-settings.tsx +569 -0
  165. package/src/pages/package-editor.tsx +21 -21
  166. package/src/pages/preview-release.tsx +75 -145
  167. package/src/pages/quickstart.tsx +159 -123
  168. package/src/pages/release-detail.tsx +119 -31
  169. package/src/pages/search.tsx +197 -57
  170. package/src/pages/settings-redirect.tsx +44 -0
  171. package/src/pages/trending.tsx +29 -20
  172. package/src/pages/user-profile.tsx +58 -7
  173. package/src/pages/user-settings.tsx +161 -0
  174. package/src/pages/view-package.tsx +30 -16
  175. package/vite.config.ts +9 -0
  176. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  177. package/src/components/JLCPCBImportDialog.tsx +0 -280
  178. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  179. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
  180. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
  181. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  182. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  183. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  184. package/src/components/PageSearchComponent.tsx +0 -148
  185. package/src/pages/package-builds.tsx +0 -33
@@ -125,8 +125,8 @@ export const getPackageFileIdFromFileDescriptor = async (
125
125
 
126
126
  if ("package_name_with_version" in descriptor) {
127
127
  const { package_name_with_version, file_path } = descriptor
128
- const packageName = package_name_with_version.split("@")[1]
129
- const version = package_name_with_version.split("@")[2]
128
+ const packageName = package_name_with_version.split("@")[0]
129
+ const version = package_name_with_version.split("@")[1]
130
130
 
131
131
  // Find the package
132
132
  const pkg = ctx.db.packages.find((p: ZT.Package) => p.name === packageName)
@@ -0,0 +1,33 @@
1
+ import { z } from "zod"
2
+ import * as ZT from "fake-snippets-api/lib/db/schema"
3
+
4
+ export const publicMapOrg = (
5
+ internal_org: ZT.Organization & {
6
+ member_count: number
7
+ package_count: number
8
+ can_manage_org: boolean
9
+ },
10
+ ): z.infer<typeof ZT.publicOrgSchema> => {
11
+ const {
12
+ can_manage_org,
13
+ github_handle,
14
+ member_count,
15
+ package_count,
16
+ created_at,
17
+ is_personal_org,
18
+ org_display_name,
19
+ org_name,
20
+ ...org
21
+ } = internal_org
22
+ return {
23
+ org_id: org.org_id,
24
+ display_name: org_display_name ?? org_name,
25
+ owner_account_id: org.owner_account_id,
26
+ name: org_name,
27
+ member_count: Number(member_count) || 0,
28
+ package_count: Number(package_count) || 0,
29
+ is_personal_org: Boolean(is_personal_org),
30
+ created_at: String(created_at),
31
+ ...(can_manage_org ? { user_permissions: { can_manage_org: true } } : {}),
32
+ }
33
+ }
@@ -25,6 +25,16 @@ export const publicMapPackageBuild = (
25
25
  ? internalPackageBuild.circuit_json_build_logs
26
26
  : [],
27
27
  circuit_json_build_error: internalPackageBuild.circuit_json_build_error,
28
+ image_generation_in_progress:
29
+ internalPackageBuild.image_generation_in_progress,
30
+ image_generation_started_at:
31
+ internalPackageBuild.image_generation_started_at,
32
+ image_generation_completed_at:
33
+ internalPackageBuild.image_generation_completed_at,
34
+ image_generation_logs: options.include_logs
35
+ ? internalPackageBuild.image_generation_logs
36
+ : [],
37
+ image_generation_error: internalPackageBuild.image_generation_error,
28
38
  build_started_at: internalPackageBuild.build_started_at,
29
39
  build_completed_at: internalPackageBuild.build_completed_at,
30
40
  build_error: internalPackageBuild.build_error,
@@ -24,6 +24,23 @@ export const publicMapPackageRelease = (
24
24
  circuit_json_build_logs: options.include_logs
25
25
  ? internal_package_release.circuit_json_build_logs
26
26
  : [],
27
+ image_generation_logs: options.include_logs
28
+ ? internal_package_release.image_generation_logs
29
+ : null,
30
+ image_generation_in_progress:
31
+ internal_package_release.image_generation_in_progress,
32
+ image_generation_started_at:
33
+ internal_package_release.image_generation_started_at ?? null,
34
+ image_generation_completed_at:
35
+ internal_package_release.image_generation_completed_at ?? null,
36
+ image_generation_is_stale:
37
+ internal_package_release.image_generation_is_stale,
38
+ image_generation_error:
39
+ internal_package_release.image_generation_error ?? null,
40
+ image_generation_error_last_updated_at:
41
+ internal_package_release.image_generation_error_last_updated_at ?? null,
42
+ image_generation_display_status:
43
+ internal_package_release.image_generation_display_status,
27
44
  is_pr_preview: Boolean(internal_package_release.is_pr_preview),
28
45
  github_pr_number: internal_package_release.github_pr_number,
29
46
  branch_name: internal_package_release.branch_name,
@@ -0,0 +1,52 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+
4
+ export default withRouteSpec({
5
+ methods: ["GET", "POST"],
6
+ commonParams: z.object({
7
+ org_id: z.string(),
8
+ account_id: z.string().optional(),
9
+ github_username: z.string().optional(),
10
+ }),
11
+ auth: "session",
12
+ jsonResponse: z.object({}),
13
+ })(async (req, ctx) => {
14
+ const { org_id, account_id, github_username } = req.commonParams
15
+
16
+ const org = ctx.db.getOrg({ org_id }, ctx.auth)
17
+
18
+ if (!org) {
19
+ return ctx.error(404, {
20
+ error_code: "org_not_found",
21
+ message: "Organization not found",
22
+ })
23
+ }
24
+
25
+ if (!org.can_manage_org) {
26
+ return ctx.error(403, {
27
+ error_code: "not_authorized",
28
+ message: "You do not have permission to manage this organization",
29
+ })
30
+ }
31
+
32
+ let account = ctx.db.accounts.find((acc) => acc.account_id === account_id)
33
+ if (!account) {
34
+ account = ctx.db.accounts.find(
35
+ (acc) => acc.github_username === github_username,
36
+ )
37
+ }
38
+
39
+ if (!account) {
40
+ return ctx.error(404, {
41
+ error_code: "account_not_found",
42
+ message: "Account not found",
43
+ })
44
+ }
45
+
46
+ ctx.db.addOrganizationAccount({
47
+ org_id,
48
+ account_id: account.account_id,
49
+ })
50
+
51
+ return ctx.json({})
52
+ })
@@ -0,0 +1,48 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicMapOrg } from "fake-snippets-api/lib/public-mapping/public-map-org"
4
+ import { publicOrgSchema } from "fake-snippets-api/lib/db/schema"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["GET", "POST"],
8
+ commonParams: z.object({
9
+ name: z.string(),
10
+ github_handle: z.string().optional(),
11
+ }),
12
+ auth: "session",
13
+ jsonResponse: z.object({
14
+ org: publicOrgSchema,
15
+ }),
16
+ })(async (req, ctx) => {
17
+ const { name, github_handle } = req.commonParams
18
+
19
+ const existing = ctx.db.getOrg({ org_name: name })
20
+
21
+ if (existing) {
22
+ return ctx.error(400, {
23
+ error_code: "org_already_exists",
24
+ message: "An organization with this name already exists",
25
+ })
26
+ }
27
+ const newOrg = {
28
+ owner_account_id: ctx.auth.account_id,
29
+ name: name,
30
+ created_at: new Date(),
31
+ can_manage_org: true,
32
+ ...(github_handle ? { github_handle } : {}),
33
+ }
34
+
35
+ const org = ctx.db.addOrganization(newOrg)
36
+
37
+ // Add the creator as a member of the organization
38
+ ctx.db.addOrganizationAccount({
39
+ org_id: org.org_id,
40
+ account_id: ctx.auth.account_id,
41
+ is_owner: true,
42
+ })
43
+
44
+ const fullOrg = ctx.db.getOrg({ org_id: org.org_id }, ctx.auth)
45
+ return ctx.json({
46
+ org: publicMapOrg(fullOrg!),
47
+ })
48
+ })
@@ -0,0 +1,39 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicMapOrg } from "fake-snippets-api/lib/public-mapping/public-map-org"
4
+ import { publicOrgSchema } from "fake-snippets-api/lib/db/schema"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["GET", "POST"],
8
+ commonParams: z
9
+ .object({ org_id: z.string() })
10
+ .or(z.object({ org_name: z.string() }))
11
+ .or(z.object({ github_handle: z.string() })),
12
+ auth: "optional_session",
13
+ jsonResponse: z.object({
14
+ org: publicOrgSchema,
15
+ }),
16
+ })(async (req, ctx) => {
17
+ const params = req.commonParams as {
18
+ org_id?: string
19
+ org_name?: string
20
+ github_handle?: string
21
+ }
22
+
23
+ const org = ctx.db.getOrg(
24
+ {
25
+ org_id: params.org_id,
26
+ org_name: params.org_name,
27
+ github_handle: params.github_handle,
28
+ },
29
+ ctx.auth,
30
+ )
31
+
32
+ if (!org) {
33
+ return ctx.error(404, {
34
+ error_code: "org_not_found",
35
+ message: "Organization not found",
36
+ })
37
+ }
38
+ return ctx.json({ org: publicMapOrg(org) })
39
+ })
@@ -0,0 +1,31 @@
1
+ import { publicOrgSchema } from "fake-snippets-api/lib/db/schema"
2
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
3
+ import { publicMapOrg } from "fake-snippets-api/lib/public-mapping/public-map-org"
4
+ import { z } from "zod"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["GET", "POST"],
8
+ auth: "optional_session",
9
+ commonParams: z.object({
10
+ github_handle: z.string().optional(),
11
+ }),
12
+ jsonResponse: z.object({
13
+ ok: z.boolean(),
14
+ orgs: z.array(publicOrgSchema),
15
+ }),
16
+ })(async (req, ctx) => {
17
+ const { github_handle } = req.commonParams
18
+ const orgs = ctx.db.getOrgs(
19
+ {
20
+ owner_account_id: ctx.auth?.account_id,
21
+ github_handle,
22
+ },
23
+ {
24
+ account_id: ctx.auth?.account_id,
25
+ },
26
+ )
27
+ return ctx.json({
28
+ ok: true,
29
+ orgs: orgs.map((org) => publicMapOrg(org)),
30
+ })
31
+ })
@@ -0,0 +1,60 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { accountSchema } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["GET", "POST"],
7
+ commonParams: z
8
+ .object({
9
+ org_id: z.string(),
10
+ })
11
+ .or(
12
+ z.object({
13
+ name: z.string(),
14
+ }),
15
+ ),
16
+ auth: "optional_session",
17
+ jsonResponse: z.object({
18
+ members: z.array(accountSchema),
19
+ }),
20
+ })(async (req, ctx) => {
21
+ const params = req.commonParams as { org_id?: string; name?: string }
22
+
23
+ const org = ctx.db.getOrg(
24
+ {
25
+ org_id: params.org_id,
26
+ org_name: params.name,
27
+ },
28
+ ctx.auth,
29
+ )
30
+
31
+ if (!org) {
32
+ return ctx.error(404, {
33
+ error_code: "org_not_found",
34
+ message: "Organization not found",
35
+ })
36
+ }
37
+
38
+ const members = ctx.db.orgAccounts
39
+ .map((m) => {
40
+ if (m.org_id == org.org_id) return ctx.db.getAccount(m.account_id)
41
+ return undefined
42
+ })
43
+ .filter(
44
+ (member): member is NonNullable<typeof member> => member !== undefined,
45
+ )
46
+
47
+ const hasOwner = members.some((m) => m?.account_id === org.owner_account_id)
48
+ let fullMembers = members
49
+
50
+ if (!hasOwner) {
51
+ const owner = ctx.db.accounts.find(
52
+ (acc) => acc.account_id === org.owner_account_id,
53
+ )
54
+ if (owner) {
55
+ fullMembers = [...members, owner]
56
+ }
57
+ }
58
+
59
+ return ctx.json({ members: fullMembers })
60
+ })
@@ -0,0 +1,46 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+
4
+ export default withRouteSpec({
5
+ methods: ["GET", "POST"],
6
+ commonParams: z.object({
7
+ org_id: z.string(),
8
+ account_id: z.string(),
9
+ }),
10
+ auth: "session",
11
+ jsonResponse: z.object({}),
12
+ })(async (req, ctx) => {
13
+ const { org_id, account_id } = req.commonParams
14
+
15
+ const org = ctx.db.getOrg({ org_id }, ctx.auth)
16
+
17
+ if (!org) {
18
+ return ctx.error(404, {
19
+ error_code: "org_not_found",
20
+ message: "Organization not found",
21
+ })
22
+ }
23
+
24
+ if (!org.can_manage_org) {
25
+ return ctx.error(403, {
26
+ error_code: "not_authorized",
27
+ message: "You do not have permission to manage this organization",
28
+ })
29
+ }
30
+
31
+ const account = ctx.db.accounts.find((acc) => acc.account_id === account_id)
32
+
33
+ if (!account) {
34
+ return ctx.error(404, {
35
+ error_code: "account_not_found",
36
+ message: "Account not found",
37
+ })
38
+ }
39
+
40
+ ctx.db.removeOrganizationAccount({
41
+ org_id,
42
+ account_id,
43
+ })
44
+
45
+ return ctx.json({})
46
+ })
@@ -0,0 +1,118 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { publicOrgSchema } from "fake-snippets-api/lib/db/schema"
4
+ import { publicMapOrg } from "fake-snippets-api/lib/public-mapping/public-map-org"
5
+
6
+ export default withRouteSpec({
7
+ methods: ["POST", "PATCH"],
8
+ commonParams: z
9
+ .object({
10
+ org_id: z.string(),
11
+ })
12
+ .and(
13
+ z.object({
14
+ name: z.string().optional(),
15
+ display_name: z.string().optional(),
16
+ github_handle: z.string().trim().min(1).nullable().optional(),
17
+ }),
18
+ ),
19
+ auth: "session",
20
+ jsonResponse: z.object({
21
+ org: publicOrgSchema,
22
+ }),
23
+ })(async (req, ctx) => {
24
+ const { org_id, name, display_name, github_handle } = req.commonParams as {
25
+ org_id: string
26
+ name?: string
27
+ display_name?: string
28
+ github_handle?: string
29
+ }
30
+
31
+ const org = ctx.db.getOrg({ org_id }, ctx.auth)
32
+
33
+ if (!org) {
34
+ return ctx.error(404, {
35
+ error_code: "org_not_found",
36
+ message: "Organization not found",
37
+ })
38
+ }
39
+
40
+ if (!org.can_manage_org) {
41
+ return ctx.error(403, {
42
+ error_code: "not_authorized",
43
+ message: "You do not have permission to manage this organization",
44
+ })
45
+ }
46
+
47
+ // No changes provided
48
+ if (!name && display_name === undefined && github_handle === null) {
49
+ return ctx.json({ org: publicMapOrg(org) })
50
+ }
51
+
52
+ if (name && name !== org.org_name) {
53
+ // Validate duplicate name
54
+ const duplicate = ctx.db.getOrg({ org_name: name })
55
+
56
+ if (duplicate && duplicate.org_id !== org_id) {
57
+ return ctx.error(400, {
58
+ error_code: "org_already_exists",
59
+ message: "An organization with this name already exists",
60
+ })
61
+ }
62
+ }
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
71
+
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
+ }
79
+ const updates: {
80
+ org_name?: string
81
+ org_display_name?: string
82
+ github_handle?: string
83
+ } = {}
84
+
85
+ if (name) {
86
+ updates.github_handle = name
87
+ updates.org_name = name
88
+ }
89
+
90
+ if (github_handle !== undefined) {
91
+ updates.github_handle = github_handle
92
+ }
93
+
94
+ if (display_name !== undefined) {
95
+ const trimmedDisplayName = display_name.trim()
96
+ const handleForFallback =
97
+ github_handle !== undefined ? github_handle : org.github_handle
98
+ const fallbackDisplayName =
99
+ name ?? org.org_display_name ?? org.org_name ?? handleForFallback ?? ""
100
+ updates.org_display_name =
101
+ trimmedDisplayName.length > 0 ? trimmedDisplayName : fallbackDisplayName
102
+ }
103
+
104
+ const updated = ctx.db.updateOrganization(org_id, updates)
105
+
106
+ if (!updated) {
107
+ return ctx.error(500, {
108
+ error_code: "update_failed",
109
+ message: "Failed to update organization",
110
+ })
111
+ }
112
+
113
+ const updatedOrgWithPermissions = ctx.db.getOrg({ org_id }, ctx.auth)
114
+
115
+ return ctx.json({
116
+ org: publicMapOrg(updatedOrgWithPermissions!),
117
+ })
118
+ })
@@ -4,9 +4,9 @@ import * as ZT from "fake-snippets-api/lib/db/schema"
4
4
  import { getPackageFileIdFromFileDescriptor } from "fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor"
5
5
 
6
6
  const routeSpec = {
7
- methods: ["POST"],
7
+ methods: ["GET"],
8
8
  auth: "none",
9
- jsonBody: z
9
+ queryParams: z
10
10
  .object({
11
11
  package_file_id: z.string(),
12
12
  })
@@ -45,10 +45,7 @@ const routeSpec = {
45
45
  } as const
46
46
 
47
47
  export default withRouteSpec(routeSpec)(async (req, ctx) => {
48
- const packageFileId = await getPackageFileIdFromFileDescriptor(
49
- req.jsonBody,
50
- ctx,
51
- )
48
+ const packageFileId = await getPackageFileIdFromFileDescriptor(req.query, ctx)
52
49
 
53
50
  const packageFile = ctx.db.packageFiles.find(
54
51
  (pf: ZT.PackageFile) => pf.package_file_id === packageFileId,
@@ -4,16 +4,19 @@ import * as ZT from "fake-snippets-api/lib/db/schema"
4
4
  import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
5
5
 
6
6
  const routeSpec = {
7
- methods: ["POST"],
7
+ methods: ["GET"],
8
8
  auth: "none",
9
- jsonBody: z
9
+ queryParams: z
10
10
  .object({
11
11
  package_release_id: z.string(),
12
12
  })
13
13
  .or(
14
14
  z.object({
15
15
  package_name: z.string(),
16
- use_latest_version: z.literal(true),
16
+ use_latest_version: z.preprocess(
17
+ (val) => (val === "true" ? true : val),
18
+ z.literal(true),
19
+ ),
17
20
  }),
18
21
  )
19
22
  .or(
@@ -28,7 +31,7 @@ const routeSpec = {
28
31
  } as const
29
32
 
30
33
  export default withRouteSpec(routeSpec)(async (req, ctx) => {
31
- const packageReleaseId = await findPackageReleaseId(req.jsonBody, ctx)
34
+ const packageReleaseId = await findPackageReleaseId(req.query, ctx)
32
35
 
33
36
  if (!packageReleaseId) {
34
37
  return ctx.error(404, {
@@ -25,16 +25,17 @@ export default withRouteSpec({
25
25
  })(async (req, ctx) => {
26
26
  const { name, description, is_private, is_unlisted } = req.jsonBody
27
27
 
28
- const existingPackage = ctx.db.packages.find((pkg) => pkg.name === name)
28
+ let owner_segment = name?.split("/")[0]
29
+ let unscoped_name = name?.split("/")[1]
29
30
 
30
- if (existingPackage) {
31
+ if (name && !unscoped_name) {
31
32
  throw ctx.error(400, {
32
- error_code: "package_already_exists",
33
- message: "A package with this name already exists",
33
+ error_code: "invalid_package_name",
34
+ message:
35
+ "Package name must include an author segment (e.g. author/package_name)",
34
36
  })
35
37
  }
36
38
 
37
- let unscoped_name = name?.includes("/") ? name?.split("/")[1] : name
38
39
  if (!unscoped_name) {
39
40
  const state = ctx.db.getState()
40
41
  const count = state.packages.filter(
@@ -44,14 +45,60 @@ export default withRouteSpec({
44
45
  unscoped_name = `untitled-package-${count}`
45
46
  }
46
47
 
48
+ if (!owner_segment) {
49
+ owner_segment = ctx.auth.github_username
50
+ }
51
+
52
+ const final_name = name ?? `${owner_segment}/${unscoped_name}`
53
+
54
+ const requested_owner_lower = owner_segment.toLowerCase()
55
+ const personal_owner_lower = ctx.auth.github_username.toLowerCase()
56
+
57
+ let owner_org_id = ctx.auth.personal_org_id
58
+ let owner_github_username = ctx.auth.github_username
59
+
60
+ if (requested_owner_lower !== personal_owner_lower) {
61
+ const state = ctx.db.getState()
62
+ const memberOrg = state.orgAccounts
63
+ .filter((oa) => oa.account_id === ctx.auth.account_id)
64
+ .map((oa) => state.organizations.find((o) => o.org_id === oa.org_id))
65
+ .filter((o): o is NonNullable<typeof o> => o !== undefined)
66
+ .find(
67
+ (o) =>
68
+ o.org_display_name?.toLowerCase() === requested_owner_lower ||
69
+ o.org_name?.toLowerCase() === requested_owner_lower ||
70
+ o.github_handle?.toLowerCase() === requested_owner_lower,
71
+ )
72
+
73
+ if (!memberOrg) {
74
+ throw ctx.error(403, {
75
+ error_code: "forbidden",
76
+ message:
77
+ "You must be a member of the organization to create a package under it",
78
+ })
79
+ }
80
+
81
+ owner_org_id = memberOrg.org_id
82
+ owner_github_username = memberOrg.github_handle || memberOrg.org_name
83
+ }
84
+
85
+ const existingPackage = ctx.db
86
+ .getState()
87
+ .packages.find((pkg) => pkg.name === final_name)
88
+
89
+ if (existingPackage) {
90
+ throw ctx.error(400, {
91
+ error_code: "package_already_exists",
92
+ message: "A package with this name already exists",
93
+ })
94
+ }
95
+
47
96
  const newPackage = ctx.db.addPackage({
48
- name: name?.includes("/")
49
- ? name
50
- : `${ctx.auth.github_username}/${String(unscoped_name)}`,
97
+ name: final_name,
51
98
  description: description ?? null,
52
99
  creator_account_id: ctx.auth.account_id,
53
- owner_org_id: ctx.auth.personal_org_id,
54
- owner_github_username: ctx.auth.github_username,
100
+ owner_org_id,
101
+ owner_github_username,
55
102
  latest_package_release_id: null,
56
103
  latest_package_release_fs_sha: null,
57
104
  latest_version: null,
@@ -16,6 +16,11 @@ export default withRouteSpec({
16
16
  package: packageSchema
17
17
  .extend({
18
18
  is_starred: z.boolean(),
19
+ user_permissions: z
20
+ .object({
21
+ can_manage_packages: z.boolean(),
22
+ })
23
+ .optional(),
19
24
  })
20
25
  .optional(),
21
26
  }),
@@ -46,6 +51,17 @@ export default withRouteSpec({
46
51
  })
47
52
  }
48
53
 
54
+ // Check if user can manage the package
55
+ const canManagePackage =
56
+ auth &&
57
+ ctx.db
58
+ .getState()
59
+ .orgAccounts.some(
60
+ (org_account) =>
61
+ org_account.account_id === auth.account_id &&
62
+ org_account.org_id === foundPackage.owner_org_id,
63
+ )
64
+
49
65
  return ctx.json({
50
66
  ok: true,
51
67
  package: {
@@ -53,6 +69,13 @@ export default withRouteSpec({
53
69
  is_starred: auth
54
70
  ? ctx.db.hasStarred(auth.account_id, foundPackage.package_id)
55
71
  : false,
72
+ ...(auth
73
+ ? {
74
+ user_permissions: {
75
+ can_manage_packages: canManagePackage ?? false,
76
+ },
77
+ }
78
+ : {}),
56
79
  },
57
80
  })
58
81
  })