@tscircuit/fake-snippets 0.0.108 → 0.0.110

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 (203) 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 +31 -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 +99 -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 +389 -450
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1255 -625
  36. package/dist/index.d.ts +296 -4
  37. package/dist/index.js +325 -24
  38. package/dist/schema.d.ts +282 -1
  39. package/dist/schema.js +54 -2
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +214 -3
  42. package/fake-snippets-api/lib/db/schema.ts +62 -0
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
  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 +32 -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 +46 -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 +67 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +93 -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 +54 -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 +27 -24
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -10
  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 +133 -36
  71. package/src/components/FileSidebar.tsx +41 -50
  72. package/src/components/Footer.tsx +8 -10
  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 +44 -16
  77. package/src/components/HiddenFilesDropdown.tsx +0 -2
  78. package/src/components/NotFound.tsx +5 -5
  79. package/src/components/PackageBreadcrumb.tsx +6 -12
  80. package/src/components/PackageCard.tsx +0 -1
  81. package/src/components/PackageSearchResults.tsx +1 -1
  82. package/src/components/PrefetchPageLink.tsx +7 -1
  83. package/src/components/ProfileRouter.tsx +32 -0
  84. package/src/components/SearchComponent.tsx +12 -8
  85. package/src/components/UserCard.tsx +80 -0
  86. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  87. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
  89. package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
  90. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
  91. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
  92. package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
  93. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
  94. package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
  95. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  96. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  97. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  98. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  99. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  100. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  101. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  102. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  103. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  104. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  105. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  106. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  107. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  108. package/src/components/organization/OrganizationCard.tsx +204 -0
  109. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  110. package/src/components/organization/OrganizationHeader.tsx +154 -0
  111. package/src/components/organization/OrganizationMembers.tsx +146 -0
  112. package/src/components/package-port/CodeAndPreview.tsx +32 -46
  113. package/src/components/package-port/CodeEditor.tsx +28 -31
  114. package/src/components/package-port/CodeEditorHeader.tsx +128 -63
  115. package/src/components/package-port/EditorNav.tsx +32 -49
  116. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  117. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  118. package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
  119. package/src/components/ui/tree-view.tsx +6 -3
  120. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  121. package/src/hooks/use-create-org-mutation.ts +38 -0
  122. package/src/hooks/use-create-package-mutation.ts +3 -0
  123. package/src/hooks/use-current-package-id.ts +5 -30
  124. package/src/hooks/use-current-package-info.ts +29 -5
  125. package/src/hooks/use-current-package-release.ts +4 -3
  126. package/src/hooks/use-download-zip.ts +2 -2
  127. package/src/hooks/use-global-store.ts +6 -4
  128. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  129. package/src/hooks/use-list-org-members.ts +27 -0
  130. package/src/hooks/use-list-user-orgs.ts +25 -0
  131. package/src/hooks/use-org-by-github-handle.ts +26 -0
  132. package/src/hooks/use-org.ts +24 -0
  133. package/src/hooks/use-organization.ts +42 -0
  134. package/src/hooks/use-package-as-snippet.ts +4 -2
  135. package/src/hooks/use-package-builds.ts +6 -2
  136. package/src/hooks/use-package-files.ts +5 -3
  137. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  138. package/src/hooks/use-package-release-images.ts +105 -0
  139. package/src/hooks/use-package-release.ts +2 -2
  140. package/src/hooks/use-package-stars.ts +80 -4
  141. package/src/hooks/use-preview-images.ts +6 -3
  142. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  143. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  144. package/src/hooks/use-update-org-mutation.ts +41 -0
  145. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  146. package/src/hooks/useFileManagement.ts +183 -35
  147. package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
  148. package/src/hooks/usePackageFilesLoader.ts +2 -2
  149. package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
  150. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  151. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  152. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  153. package/src/lib/normalize-svg-for-tile.ts +50 -0
  154. package/src/lib/posthog.ts +11 -9
  155. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  156. package/src/lib/sentry.ts +14 -0
  157. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  158. package/src/lib/ts-lib-cache.ts +122 -7
  159. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  160. package/src/lib/utils/findTargetFile.ts +45 -10
  161. package/src/lib/utils/isComponentExported.ts +10 -0
  162. package/src/main.tsx +2 -1
  163. package/src/pages/authorize.tsx +0 -2
  164. package/src/pages/create-organization.tsx +168 -0
  165. package/src/pages/dashboard.tsx +38 -6
  166. package/src/pages/datasheet.tsx +1 -1
  167. package/src/pages/datasheets.tsx +3 -3
  168. package/src/pages/editor.tsx +4 -6
  169. package/src/pages/landing.tsx +6 -7
  170. package/src/pages/latest.tsx +3 -0
  171. package/src/pages/organization-profile.tsx +199 -0
  172. package/src/pages/organization-settings.tsx +566 -0
  173. package/src/pages/package-editor.tsx +21 -21
  174. package/src/pages/preview-release.tsx +76 -136
  175. package/src/pages/quickstart.tsx +159 -123
  176. package/src/pages/release-detail.tsx +119 -31
  177. package/src/pages/search.tsx +192 -57
  178. package/src/pages/settings-redirect.tsx +44 -0
  179. package/src/pages/trending.tsx +29 -20
  180. package/src/pages/user-profile.tsx +58 -7
  181. package/src/pages/view-package.tsx +21 -26
  182. package/vite.config.ts +9 -0
  183. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  184. package/src/components/Footer2.tsx +0 -100
  185. package/src/components/JLCPCBImportDialog.tsx +0 -280
  186. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  187. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
  188. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
  189. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  190. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  191. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  192. package/src/components/PageSearchComponent.tsx +0 -148
  193. package/src/components/ShippingInformationForm.tsx +0 -423
  194. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  195. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  196. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  197. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  198. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  199. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  200. package/src/components/ViewSnippetHeader.tsx +0 -181
  201. package/src/components/ui/input-otp.tsx +0 -69
  202. package/src/pages/package-builds.tsx +0 -33
  203. package/src/pages/settings.tsx +0 -25
@@ -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,46 @@
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
+ }),
11
+ auth: "session",
12
+ jsonResponse: z.object({
13
+ org: publicOrgSchema,
14
+ }),
15
+ })(async (req, ctx) => {
16
+ const { name } = req.commonParams
17
+
18
+ const existing = ctx.db.getOrg({ github_handle: name })
19
+
20
+ if (existing) {
21
+ return ctx.error(400, {
22
+ error_code: "org_already_exists",
23
+ message: "An organization with this name already exists",
24
+ })
25
+ }
26
+ const newOrg = {
27
+ owner_account_id: ctx.auth.account_id,
28
+ name: name,
29
+ created_at: new Date(),
30
+ can_manage_org: true,
31
+ }
32
+
33
+ const org = ctx.db.addOrganization(newOrg)
34
+
35
+ // Add the creator as a member of the organization
36
+ ctx.db.addOrganizationAccount({
37
+ org_id: org.org_id,
38
+ account_id: ctx.auth.account_id,
39
+ is_owner: true,
40
+ })
41
+
42
+ const fullOrg = ctx.db.getOrg({ org_id: org.org_id }, ctx.auth)
43
+ return ctx.json({
44
+ org: publicMapOrg(fullOrg!),
45
+ })
46
+ })
@@ -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: "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,67 @@
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: "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
+ if (!org.can_manage_org) {
39
+ return ctx.error(403, {
40
+ error_code: "not_authorized",
41
+ message: "You do not have permission to manage this organization",
42
+ })
43
+ }
44
+
45
+ const members = ctx.db.orgAccounts
46
+ .map((m) => {
47
+ if (m.org_id == org.org_id) return ctx.db.getAccount(m.account_id)
48
+ return undefined
49
+ })
50
+ .filter(
51
+ (member): member is NonNullable<typeof member> => member !== undefined,
52
+ )
53
+
54
+ const hasOwner = members.some((m) => m?.account_id === org.owner_account_id)
55
+ let fullMembers = members
56
+
57
+ if (!hasOwner) {
58
+ const owner = ctx.db.accounts.find(
59
+ (acc) => acc.account_id === org.owner_account_id,
60
+ )
61
+ if (owner) {
62
+ fullMembers = [...members, owner]
63
+ }
64
+ }
65
+
66
+ return ctx.json({ members: fullMembers })
67
+ })
@@ -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,93 @@
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
+ }),
17
+ ),
18
+ auth: "session",
19
+ jsonResponse: z.object({
20
+ org: publicOrgSchema,
21
+ }),
22
+ })(async (req, ctx) => {
23
+ const { org_id, name, display_name } = req.commonParams as {
24
+ org_id: string
25
+ name?: string
26
+ display_name?: string
27
+ }
28
+
29
+ const org = ctx.db.getOrg({ org_id }, ctx.auth)
30
+
31
+ if (!org) {
32
+ return ctx.error(404, {
33
+ error_code: "org_not_found",
34
+ message: "Organization not found",
35
+ })
36
+ }
37
+
38
+ if (!org.can_manage_org) {
39
+ return ctx.error(403, {
40
+ error_code: "not_authorized",
41
+ message: "You do not have permission to manage this organization",
42
+ })
43
+ }
44
+
45
+ // No changes provided
46
+ if (!name && display_name === undefined) {
47
+ return ctx.json({ org: publicMapOrg(org) })
48
+ }
49
+
50
+ if (name && name !== org.github_handle) {
51
+ // Validate duplicate name
52
+ const duplicate = ctx.db.getOrg({ github_handle: name })
53
+
54
+ if (duplicate && duplicate.org_id !== org_id) {
55
+ return ctx.error(400, {
56
+ error_code: "org_already_exists",
57
+ message: "An organization with this name already exists",
58
+ })
59
+ }
60
+ }
61
+
62
+ const updates: {
63
+ github_handle?: string
64
+ org_display_name?: string
65
+ } = {}
66
+
67
+ if (name) {
68
+ updates.github_handle = name
69
+ }
70
+
71
+ if (display_name !== undefined) {
72
+ const trimmedDisplayName = display_name.trim()
73
+ const fallbackDisplayName =
74
+ name ?? org.org_display_name ?? org.github_handle ?? ""
75
+ updates.org_display_name =
76
+ trimmedDisplayName.length > 0 ? trimmedDisplayName : fallbackDisplayName
77
+ }
78
+
79
+ const updated = ctx.db.updateOrganization(org_id, updates)
80
+
81
+ if (!updated) {
82
+ return ctx.error(500, {
83
+ error_code: "update_failed",
84
+ message: "Failed to update organization",
85
+ })
86
+ }
87
+
88
+ const updatedOrgWithPermissions = ctx.db.getOrg({ org_id }, ctx.auth)
89
+
90
+ return ctx.json({
91
+ org: publicMapOrg(updatedOrgWithPermissions!),
92
+ })
93
+ })
@@ -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,57 @@ 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.github_handle?.toLowerCase() === requested_owner_lower,
70
+ )
71
+
72
+ if (!memberOrg) {
73
+ throw ctx.error(403, {
74
+ error_code: "forbidden",
75
+ message:
76
+ "You must be a member of the organization to create a package under it",
77
+ })
78
+ }
79
+
80
+ owner_org_id = memberOrg.org_id
81
+ owner_github_username = memberOrg.github_handle
82
+ }
83
+
84
+ const existingPackage = ctx.db.packages.find((pkg) => pkg.name === final_name)
85
+
86
+ if (existingPackage) {
87
+ throw ctx.error(400, {
88
+ error_code: "package_already_exists",
89
+ message: "A package with this name already exists",
90
+ })
91
+ }
92
+
47
93
  const newPackage = ctx.db.addPackage({
48
- name: name?.includes("/")
49
- ? name
50
- : `${ctx.auth.github_username}/${String(unscoped_name)}`,
94
+ name: final_name,
51
95
  description: description ?? null,
52
96
  creator_account_id: ctx.auth.account_id,
53
- owner_org_id: ctx.auth.personal_org_id,
54
- owner_github_username: ctx.auth.github_username,
97
+ owner_org_id,
98
+ owner_github_username,
55
99
  latest_package_release_id: null,
56
100
  latest_package_release_fs_sha: null,
57
101
  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
  })
@@ -4,8 +4,10 @@ import {
4
4
  convertCircuitJsonToPcbSvg,
5
5
  convertCircuitJsonToSchematicSvg,
6
6
  } from "circuit-to-svg"
7
+ import { convertCircuitJsonToSimple3dSvg } from "circuit-json-to-simple-3d"
7
8
  import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
8
9
  import { z } from "zod"
10
+ import { renderAsync } from "@resvg/resvg-js"
9
11
 
10
12
  // Define the view types and extensions
11
13
  const VIEW_TYPES = ["schematic", "pcb", "assembly", "3d"] as const
@@ -68,7 +70,8 @@ export default withRouteSpec({
68
70
  const circuit_json_file = ctx.db.packageFiles.find(
69
71
  (pf) =>
70
72
  pf.package_release_id === pkg_release.package_release_id &&
71
- pf.file_path === "circuit.json",
73
+ (pf.file_path === "circuit.json" ||
74
+ pf.file_path === "/dist/circuit.json"),
72
75
  )
73
76
 
74
77
  if (!circuit_json_file?.content_text) {
@@ -82,7 +85,6 @@ export default withRouteSpec({
82
85
 
83
86
  // Convert circuit json to svg
84
87
  let svg = ""
85
-
86
88
  if (outputType === "schematic") {
87
89
  svg = convertCircuitJsonToSchematicSvg(circuit_json as AnyCircuitElement[])
88
90
  } else if (outputType === "pcb") {
@@ -90,13 +92,14 @@ export default withRouteSpec({
90
92
  } else if (outputType === "assembly") {
91
93
  svg = convertCircuitJsonToAssemblySvg(circuit_json as AnyCircuitElement[])
92
94
  } else if (outputType === "3d") {
93
- // For 3D view, we'll return a simple SVG placeholder
94
- svg = `<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
95
- <rect width="100%" height="100%" fill="#f0f0f0"/>
96
- <text x="50%" y="50%" text-anchor="middle" fill="#666">3D Preview</text>
97
- </svg>`
95
+ svg = await convertCircuitJsonToSimple3dSvg(circuit_json, {
96
+ background: {
97
+ color: "#fff",
98
+ opacity: 0.0,
99
+ },
100
+ defaultZoomMultiplier: 1.1,
101
+ })
98
102
  }
99
-
100
103
  if (format === "svg") {
101
104
  return new Response(svg, {
102
105
  headers: {
@@ -106,9 +109,8 @@ export default withRouteSpec({
106
109
  })
107
110
  }
108
111
 
109
- // For PNG format, we'll return the SVG as PNG
110
- // In a real implementation, this would use @resvg/resvg-js
111
- return new Response(svg, {
112
+ const pngBuffer = await renderAsync(svg)
113
+ return new Response(pngBuffer.asPng().buffer as ArrayBuffer, {
112
114
  headers: {
113
115
  "Content-Type": "image/png",
114
116
  "Cache-Control": "public, max-age=86400, s-maxage=86400",
@@ -13,7 +13,16 @@ export default withRouteSpec({
13
13
  }),
14
14
  jsonResponse: z.object({
15
15
  ok: z.boolean(),
16
- packages: z.array(packageSchema),
16
+ packages: z.array(
17
+ packageSchema.extend({
18
+ starred_at: z.string().nullable(),
19
+ user_permissions: z
20
+ .object({
21
+ can_manage_packages: z.boolean(),
22
+ })
23
+ .optional(),
24
+ }),
25
+ ),
17
26
  }),
18
27
  })(async (req, ctx) => {
19
28
  const { creator_account_id, owner_github_username, name, is_writable } =
@@ -28,6 +37,17 @@ export default withRouteSpec({
28
37
  })
29
38
  }
30
39
 
40
+ // Helper to check if user can manage a package
41
+ const canManagePackage = (pkg: any) => {
42
+ if (!auth) return false
43
+ // Check if user is a member of the package's owner org
44
+ const state = ctx.db.getState()
45
+ return state.orgAccounts.some(
46
+ (oa) =>
47
+ oa.account_id === auth.account_id && oa.org_id === pkg.owner_org_id,
48
+ )
49
+ }
50
+
31
51
  let packages = ctx.db.packages
32
52
 
33
53
  // Apply filters
@@ -46,7 +66,7 @@ export default withRouteSpec({
46
66
  }
47
67
 
48
68
  if (is_writable && auth) {
49
- packages = packages.filter((p) => p.owner_org_id === auth.personal_org_id)
69
+ packages = packages.filter(canManagePackage)
50
70
  }
51
71
 
52
72
  // Get star timestamps for authenticated user
@@ -65,6 +85,13 @@ export default withRouteSpec({
65
85
  ...p,
66
86
  latest_package_release_id: p.latest_package_release_id || null,
67
87
  starred_at: starTimestamps.get(p.package_id) || null,
88
+ ...(auth
89
+ ? {
90
+ user_permissions: {
91
+ can_manage_packages: canManagePackage(p),
92
+ },
93
+ }
94
+ : {}),
68
95
  })),
69
96
  })
70
97
  })