@tscircuit/fake-snippets 0.0.36 → 0.0.38
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.
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +575 -0
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +233 -0
- package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +2 -2
- package/dist/bundle.js +499 -249
- package/dist/index.d.ts +23 -4
- package/dist/index.js +30 -2
- package/fake-snippets-api/lib/db/db-client.ts +29 -1
- package/fake-snippets-api/lib/db/schema.ts +3 -0
- package/fake-snippets-api/routes/api/package_files/create_or_update.ts +179 -0
- package/fake-snippets-api/routes/api/package_files/delete.ts +106 -0
- package/fake-snippets-api/routes/api/snippets/{list_newest.ts → list_latest.ts} +2 -2
- package/package.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +2 -2
- package/src/components/EditorNav.tsx +4 -4
- package/src/components/Footer.tsx +9 -3
- package/src/components/HiddenFilesDropdown.tsx +44 -0
- package/src/components/LatestSnippets.tsx +1 -1
- package/src/components/ViewPackagePage/components/package-header.tsx +0 -4
- package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +1 -1
- package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +1 -1
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +23 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +1 -0
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +48 -0
- package/src/hooks/use-delete-package.ts +40 -0
- package/src/hooks/use-fork-package-mutation.ts +14 -61
- package/src/pages/dashboard.tsx +8 -8
- package/src/pages/latest.tsx +212 -0
- package/src/pages/user-profile.tsx +15 -14
- package/src/components/dialogs/confirm-delete-snippet-dialog.tsx +0 -80
- package/src/pages/newest.tsx +0 -16
package/dist/index.d.ts
CHANGED
|
@@ -316,18 +316,27 @@ declare const packageFileSchema: z.ZodObject<{
|
|
|
316
316
|
file_path: z.ZodString;
|
|
317
317
|
content_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
318
318
|
created_at: z.ZodString;
|
|
319
|
+
content_mimetype: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
320
|
+
is_release_tarball: z.ZodOptional<z.ZodBoolean>;
|
|
321
|
+
npm_pack_output: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
319
322
|
}, "strip", z.ZodTypeAny, {
|
|
320
323
|
package_release_id: string;
|
|
321
324
|
created_at: string;
|
|
322
325
|
package_file_id: string;
|
|
323
326
|
file_path: string;
|
|
324
327
|
content_text?: string | null | undefined;
|
|
328
|
+
content_mimetype?: string | null | undefined;
|
|
329
|
+
is_release_tarball?: boolean | undefined;
|
|
330
|
+
npm_pack_output?: any;
|
|
325
331
|
}, {
|
|
326
332
|
package_release_id: string;
|
|
327
333
|
created_at: string;
|
|
328
334
|
package_file_id: string;
|
|
329
335
|
file_path: string;
|
|
330
336
|
content_text?: string | null | undefined;
|
|
337
|
+
content_mimetype?: string | null | undefined;
|
|
338
|
+
is_release_tarball?: boolean | undefined;
|
|
339
|
+
npm_pack_output?: any;
|
|
331
340
|
}>;
|
|
332
341
|
type PackageFile = z.infer<typeof packageFileSchema>;
|
|
333
342
|
declare const packageSchema: z.ZodObject<{
|
|
@@ -556,6 +565,9 @@ declare const createDatabase: ({ seed }?: {
|
|
|
556
565
|
package_file_id: string;
|
|
557
566
|
file_path: string;
|
|
558
567
|
content_text?: string | null | undefined;
|
|
568
|
+
content_mimetype?: string | null | undefined;
|
|
569
|
+
is_release_tarball?: boolean | undefined;
|
|
570
|
+
npm_pack_output?: any;
|
|
559
571
|
}[];
|
|
560
572
|
sessions: {
|
|
561
573
|
session_id: string;
|
|
@@ -690,7 +702,7 @@ declare const createDatabase: ({ seed }?: {
|
|
|
690
702
|
step_function_name: string | null;
|
|
691
703
|
error_message: string | null;
|
|
692
704
|
}[];
|
|
693
|
-
}, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "
|
|
705
|
+
}, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
|
|
694
706
|
addOrder: (order: Omit<Order, "order_id">) => Order;
|
|
695
707
|
getOrderById: (orderId: string) => Order | undefined;
|
|
696
708
|
getOrderFilesByOrderId: (orderId: string) => OrderFile[];
|
|
@@ -722,7 +734,7 @@ declare const createDatabase: ({ seed }?: {
|
|
|
722
734
|
updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
|
|
723
735
|
deleteAccountPackage: (accountPackageId: string) => boolean;
|
|
724
736
|
addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
|
|
725
|
-
|
|
737
|
+
getLatestSnippets: (limit: number) => Snippet[];
|
|
726
738
|
getTrendingSnippets: (limit: number, since: string) => Snippet[];
|
|
727
739
|
getPackagesByAuthor: (authorName?: string) => Package[];
|
|
728
740
|
getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
|
|
@@ -752,7 +764,9 @@ declare const createDatabase: ({ seed }?: {
|
|
|
752
764
|
getPackageReleaseById: (packageReleaseId: string) => PackageRelease | undefined;
|
|
753
765
|
addPackageRelease: (packageRelease: Omit<PackageRelease, "package_release_id">) => PackageRelease;
|
|
754
766
|
updatePackageRelease: (packageRelease: PackageRelease) => void;
|
|
767
|
+
deletePackageFile: (packageFileId: string) => boolean;
|
|
755
768
|
addPackageFile: (packageFile: Omit<PackageFile, "package_file_id">) => PackageFile;
|
|
769
|
+
updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
|
|
756
770
|
getStarCount: (packageId: string) => number;
|
|
757
771
|
getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
|
|
758
772
|
}> & Omit<{
|
|
@@ -795,6 +809,9 @@ declare const createDatabase: ({ seed }?: {
|
|
|
795
809
|
package_file_id: string;
|
|
796
810
|
file_path: string;
|
|
797
811
|
content_text?: string | null | undefined;
|
|
812
|
+
content_mimetype?: string | null | undefined;
|
|
813
|
+
is_release_tarball?: boolean | undefined;
|
|
814
|
+
npm_pack_output?: any;
|
|
798
815
|
}[];
|
|
799
816
|
sessions: {
|
|
800
817
|
session_id: string;
|
|
@@ -929,7 +946,7 @@ declare const createDatabase: ({ seed }?: {
|
|
|
929
946
|
step_function_name: string | null;
|
|
930
947
|
error_message: string | null;
|
|
931
948
|
}[];
|
|
932
|
-
}, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "
|
|
949
|
+
}, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
|
|
933
950
|
addOrder: (order: Omit<Order, "order_id">) => Order;
|
|
934
951
|
getOrderById: (orderId: string) => Order | undefined;
|
|
935
952
|
getOrderFilesByOrderId: (orderId: string) => OrderFile[];
|
|
@@ -961,7 +978,7 @@ declare const createDatabase: ({ seed }?: {
|
|
|
961
978
|
updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
|
|
962
979
|
deleteAccountPackage: (accountPackageId: string) => boolean;
|
|
963
980
|
addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
|
|
964
|
-
|
|
981
|
+
getLatestSnippets: (limit: number) => Snippet[];
|
|
965
982
|
getTrendingSnippets: (limit: number, since: string) => Snippet[];
|
|
966
983
|
getPackagesByAuthor: (authorName?: string) => Package[];
|
|
967
984
|
getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
|
|
@@ -991,7 +1008,9 @@ declare const createDatabase: ({ seed }?: {
|
|
|
991
1008
|
getPackageReleaseById: (packageReleaseId: string) => PackageRelease | undefined;
|
|
992
1009
|
addPackageRelease: (packageRelease: Omit<PackageRelease, "package_release_id">) => PackageRelease;
|
|
993
1010
|
updatePackageRelease: (packageRelease: PackageRelease) => void;
|
|
1011
|
+
deletePackageFile: (packageFileId: string) => boolean;
|
|
994
1012
|
addPackageFile: (packageFile: Omit<PackageFile, "package_file_id">) => PackageFile;
|
|
1013
|
+
updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
|
|
995
1014
|
getStarCount: (packageId: string) => number;
|
|
996
1015
|
getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
|
|
997
1016
|
};
|
package/dist/index.js
CHANGED
|
@@ -118,7 +118,10 @@ var packageFileSchema = z.object({
|
|
|
118
118
|
package_release_id: z.string(),
|
|
119
119
|
file_path: z.string(),
|
|
120
120
|
content_text: z.string().nullable().optional(),
|
|
121
|
-
created_at: z.string().datetime()
|
|
121
|
+
created_at: z.string().datetime(),
|
|
122
|
+
content_mimetype: z.string().nullable().optional(),
|
|
123
|
+
is_release_tarball: z.boolean().optional(),
|
|
124
|
+
npm_pack_output: z.any().nullable().optional()
|
|
122
125
|
});
|
|
123
126
|
var packageSchema = z.object({
|
|
124
127
|
package_id: z.string(),
|
|
@@ -2206,7 +2209,7 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
2206
2209
|
is_unlisted: false
|
|
2207
2210
|
};
|
|
2208
2211
|
},
|
|
2209
|
-
|
|
2212
|
+
getLatestSnippets: (limit) => {
|
|
2210
2213
|
const state = get();
|
|
2211
2214
|
const snippetPackages = state.packages.filter((pkg) => pkg.is_snippet === true).map((pkg) => {
|
|
2212
2215
|
const packageRelease = state.packageReleases.find(
|
|
@@ -2825,6 +2828,20 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
2825
2828
|
)
|
|
2826
2829
|
}));
|
|
2827
2830
|
},
|
|
2831
|
+
deletePackageFile: (packageFileId) => {
|
|
2832
|
+
let deleted = false;
|
|
2833
|
+
set((state) => {
|
|
2834
|
+
const index = state.packageFiles.findIndex(
|
|
2835
|
+
(file) => file.package_file_id === packageFileId
|
|
2836
|
+
);
|
|
2837
|
+
if (index !== -1) {
|
|
2838
|
+
state.packageFiles.splice(index, 1);
|
|
2839
|
+
deleted = true;
|
|
2840
|
+
}
|
|
2841
|
+
return state;
|
|
2842
|
+
});
|
|
2843
|
+
return deleted;
|
|
2844
|
+
},
|
|
2828
2845
|
addPackageFile: (packageFile) => {
|
|
2829
2846
|
const newPackageFile = {
|
|
2830
2847
|
package_file_id: `package_file_${Date.now()}`,
|
|
@@ -2835,6 +2852,17 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
2835
2852
|
}));
|
|
2836
2853
|
return newPackageFile;
|
|
2837
2854
|
},
|
|
2855
|
+
updatePackageFile: (packageFileId, updates) => {
|
|
2856
|
+
set((state2) => ({
|
|
2857
|
+
packageFiles: state2.packageFiles.map(
|
|
2858
|
+
(file) => file.package_file_id === packageFileId ? { ...file, ...updates } : file
|
|
2859
|
+
)
|
|
2860
|
+
}));
|
|
2861
|
+
const state = get();
|
|
2862
|
+
return state.packageFiles.find(
|
|
2863
|
+
(file) => file.package_file_id === packageFileId
|
|
2864
|
+
);
|
|
2865
|
+
},
|
|
2838
2866
|
getStarCount: (packageId) => {
|
|
2839
2867
|
const state = get();
|
|
2840
2868
|
return state.accountPackages.filter(
|
|
@@ -320,7 +320,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
320
320
|
is_unlisted: false,
|
|
321
321
|
}
|
|
322
322
|
},
|
|
323
|
-
|
|
323
|
+
getLatestSnippets: (limit: number): Snippet[] => {
|
|
324
324
|
const state = get()
|
|
325
325
|
|
|
326
326
|
// Get all packages that are snippets
|
|
@@ -1147,6 +1147,20 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
1147
1147
|
),
|
|
1148
1148
|
}))
|
|
1149
1149
|
},
|
|
1150
|
+
deletePackageFile: (packageFileId: string): boolean => {
|
|
1151
|
+
let deleted = false
|
|
1152
|
+
set((state) => {
|
|
1153
|
+
const index = state.packageFiles.findIndex(
|
|
1154
|
+
(file) => file.package_file_id === packageFileId,
|
|
1155
|
+
)
|
|
1156
|
+
if (index !== -1) {
|
|
1157
|
+
state.packageFiles.splice(index, 1)
|
|
1158
|
+
deleted = true
|
|
1159
|
+
}
|
|
1160
|
+
return state
|
|
1161
|
+
})
|
|
1162
|
+
return deleted
|
|
1163
|
+
},
|
|
1150
1164
|
addPackageFile: (
|
|
1151
1165
|
packageFile: Omit<PackageFile, "package_file_id">,
|
|
1152
1166
|
): PackageFile => {
|
|
@@ -1159,6 +1173,20 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
|
|
|
1159
1173
|
}))
|
|
1160
1174
|
return newPackageFile
|
|
1161
1175
|
},
|
|
1176
|
+
updatePackageFile: (
|
|
1177
|
+
packageFileId: string,
|
|
1178
|
+
updates: Partial<Omit<PackageFile, "package_file_id">>,
|
|
1179
|
+
): PackageFile => {
|
|
1180
|
+
set((state) => ({
|
|
1181
|
+
packageFiles: state.packageFiles.map((file) =>
|
|
1182
|
+
file.package_file_id === packageFileId ? { ...file, ...updates } : file,
|
|
1183
|
+
),
|
|
1184
|
+
}))
|
|
1185
|
+
const state = get()
|
|
1186
|
+
return state.packageFiles.find(
|
|
1187
|
+
(file) => file.package_file_id === packageFileId,
|
|
1188
|
+
)!
|
|
1189
|
+
},
|
|
1162
1190
|
getStarCount: (packageId: string): number => {
|
|
1163
1191
|
const state = get()
|
|
1164
1192
|
return state.accountPackages.filter(
|
|
@@ -138,6 +138,9 @@ export const packageFileSchema = z.object({
|
|
|
138
138
|
file_path: z.string(),
|
|
139
139
|
content_text: z.string().nullable().optional(),
|
|
140
140
|
created_at: z.string().datetime(),
|
|
141
|
+
content_mimetype: z.string().nullable().optional(),
|
|
142
|
+
is_release_tarball: z.boolean().optional(),
|
|
143
|
+
npm_pack_output: z.any().nullable().optional(),
|
|
141
144
|
})
|
|
142
145
|
export type PackageFile = z.infer<typeof packageFileSchema>
|
|
143
146
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as zt from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
|
|
3
|
+
import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
const routeSpec = {
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "session",
|
|
9
|
+
jsonBody: z
|
|
10
|
+
.object({
|
|
11
|
+
file_path: z.string(),
|
|
12
|
+
is_release_tarball: z.boolean().optional().default(false),
|
|
13
|
+
content_mimetype: z.string().optional(),
|
|
14
|
+
content_text: z.string().optional(),
|
|
15
|
+
content_base64: z.string().optional(),
|
|
16
|
+
package_release_id: z.string().optional(),
|
|
17
|
+
package_name_with_version: z.string().optional(),
|
|
18
|
+
npm_pack_output: z.any().optional(),
|
|
19
|
+
})
|
|
20
|
+
.refine((v) => {
|
|
21
|
+
if (v.package_release_id) return true
|
|
22
|
+
if (v.package_name_with_version) return true
|
|
23
|
+
return false
|
|
24
|
+
}, "Must specify either package_release_id or package_name_with_version")
|
|
25
|
+
.refine((v) => {
|
|
26
|
+
if (v.package_release_id && v.package_name_with_version) return false
|
|
27
|
+
return true
|
|
28
|
+
}, "Cannot specify both package_release_id and package_name_with_version")
|
|
29
|
+
.refine((v) => {
|
|
30
|
+
if (v.content_base64 && v.content_text) return false
|
|
31
|
+
if (!v.content_base64 && !v.content_text) return false
|
|
32
|
+
return true
|
|
33
|
+
}, "Either content_base64 or content_text is required"),
|
|
34
|
+
jsonResponse: z.object({
|
|
35
|
+
ok: z.boolean(),
|
|
36
|
+
package_file: zt.packageFileSchema,
|
|
37
|
+
}),
|
|
38
|
+
} as const
|
|
39
|
+
|
|
40
|
+
export default withRouteSpec(routeSpec)(async (req, ctx) => {
|
|
41
|
+
const {
|
|
42
|
+
file_path,
|
|
43
|
+
content_mimetype: providedContentMimetype,
|
|
44
|
+
content_base64,
|
|
45
|
+
content_text,
|
|
46
|
+
is_release_tarball,
|
|
47
|
+
npm_pack_output,
|
|
48
|
+
} = req.jsonBody
|
|
49
|
+
|
|
50
|
+
if (is_release_tarball && !npm_pack_output) {
|
|
51
|
+
return ctx.error(400, {
|
|
52
|
+
error_code: "missing_options",
|
|
53
|
+
message: "npm_pack_output is required for release tarballs",
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!is_release_tarball && npm_pack_output) {
|
|
58
|
+
return ctx.error(404, {
|
|
59
|
+
error_code: "invalid_options",
|
|
60
|
+
message: "npm_pack_output is only valid for release tarballs",
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let packageReleaseId = req.jsonBody.package_release_id
|
|
65
|
+
|
|
66
|
+
if (!packageReleaseId && req.jsonBody.package_name_with_version) {
|
|
67
|
+
const foundPackageReleaseId = await findPackageReleaseId(
|
|
68
|
+
{ package_name_with_version: req.jsonBody.package_name_with_version },
|
|
69
|
+
ctx,
|
|
70
|
+
)
|
|
71
|
+
if (foundPackageReleaseId) {
|
|
72
|
+
packageReleaseId = foundPackageReleaseId
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!packageReleaseId) {
|
|
77
|
+
return ctx.error(404, {
|
|
78
|
+
error_code: "package_release_not_found",
|
|
79
|
+
message: "Package release not found",
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Verify the package release exists
|
|
84
|
+
const packageRelease = ctx.db.packageReleases.find(
|
|
85
|
+
(pr) => pr.package_release_id === packageReleaseId,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if (!packageRelease) {
|
|
89
|
+
return ctx.error(404, {
|
|
90
|
+
error_code: "package_release_not_found",
|
|
91
|
+
message: "Package release not found",
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get the package to check permissions
|
|
96
|
+
const existingPackage = ctx.db.packages.find(
|
|
97
|
+
(p) => p.package_id === packageRelease.package_id,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if (!existingPackage) {
|
|
101
|
+
return ctx.error(404, {
|
|
102
|
+
error_code: "package_not_found",
|
|
103
|
+
message: "Package not found",
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if user has permission to create/update the file
|
|
108
|
+
if (existingPackage.creator_account_id !== ctx.auth.account_id) {
|
|
109
|
+
return ctx.error(403, {
|
|
110
|
+
error_code: "forbidden",
|
|
111
|
+
message: "You don't have permission to modify files in this package",
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if file exists
|
|
116
|
+
const exisitingFile = ctx.db.packageFiles.find(
|
|
117
|
+
(pf) =>
|
|
118
|
+
pf.package_release_id === packageReleaseId && pf.file_path === file_path,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Determine content mimetype
|
|
122
|
+
const contentMimetype =
|
|
123
|
+
providedContentMimetype ||
|
|
124
|
+
(file_path.endsWith(".ts") || file_path.endsWith(".tsx")
|
|
125
|
+
? "text/typescript"
|
|
126
|
+
: null) ||
|
|
127
|
+
(file_path.endsWith(".js") ? "application/javascript" : null) ||
|
|
128
|
+
(file_path.endsWith(".json") ? "application/json" : null) ||
|
|
129
|
+
(file_path.endsWith(".md") ? "text/markdown" : null) ||
|
|
130
|
+
(file_path.endsWith(".html") ? "text/html" : null) ||
|
|
131
|
+
(file_path.endsWith(".css") ? "text/css" : null) ||
|
|
132
|
+
"application/octet-stream"
|
|
133
|
+
|
|
134
|
+
if (exisitingFile) {
|
|
135
|
+
const package_file = ctx.db.updatePackageFile(
|
|
136
|
+
exisitingFile.package_file_id,
|
|
137
|
+
{
|
|
138
|
+
content_text:
|
|
139
|
+
content_text ||
|
|
140
|
+
(content_base64
|
|
141
|
+
? Buffer.from(content_base64, "base64").toString()
|
|
142
|
+
: null),
|
|
143
|
+
content_mimetype: contentMimetype,
|
|
144
|
+
is_release_tarball: is_release_tarball || false,
|
|
145
|
+
npm_pack_output: npm_pack_output || null,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return ctx.json({
|
|
150
|
+
ok: true,
|
|
151
|
+
package_file,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create the package file
|
|
156
|
+
const newPackageFile = {
|
|
157
|
+
package_file_id: crypto.randomUUID(),
|
|
158
|
+
package_release_id: packageReleaseId,
|
|
159
|
+
file_path,
|
|
160
|
+
content_text:
|
|
161
|
+
content_text ||
|
|
162
|
+
(content_base64
|
|
163
|
+
? Buffer.from(content_base64, "base64").toString()
|
|
164
|
+
: null),
|
|
165
|
+
content_mimetype: contentMimetype,
|
|
166
|
+
is_directory: false,
|
|
167
|
+
is_release_tarball: is_release_tarball || false,
|
|
168
|
+
npm_pack_output: npm_pack_output || null,
|
|
169
|
+
created_at: new Date().toISOString(),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add to the test database
|
|
173
|
+
ctx.db.addPackageFile(newPackageFile)
|
|
174
|
+
|
|
175
|
+
return ctx.json({
|
|
176
|
+
ok: true,
|
|
177
|
+
package_file: newPackageFile,
|
|
178
|
+
})
|
|
179
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["POST"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
jsonBody: z
|
|
9
|
+
.object({
|
|
10
|
+
package_release_id: z.string().optional(),
|
|
11
|
+
package_name_with_version: z.string().optional(),
|
|
12
|
+
file_path: z.string(),
|
|
13
|
+
})
|
|
14
|
+
.refine((v) => {
|
|
15
|
+
if (v.package_release_id) return true
|
|
16
|
+
if (v.package_name_with_version) return true
|
|
17
|
+
return false
|
|
18
|
+
}, "Must specify either package_release_id or package_name_with_version")
|
|
19
|
+
.refine((v) => {
|
|
20
|
+
if (v.package_release_id && v.package_name_with_version) return false
|
|
21
|
+
return true
|
|
22
|
+
}, "Cannot specify both package_release_id and package_name_with_version"),
|
|
23
|
+
jsonResponse: z.object({
|
|
24
|
+
ok: z.boolean(),
|
|
25
|
+
}),
|
|
26
|
+
})(async (req, ctx) => {
|
|
27
|
+
const { file_path } = req.jsonBody
|
|
28
|
+
|
|
29
|
+
let packageReleaseId = req.jsonBody.package_release_id
|
|
30
|
+
|
|
31
|
+
if (!packageReleaseId && req.jsonBody.package_name_with_version) {
|
|
32
|
+
const foundPackageReleaseId = await findPackageReleaseId(
|
|
33
|
+
req.jsonBody.package_name_with_version,
|
|
34
|
+
ctx,
|
|
35
|
+
)
|
|
36
|
+
if (foundPackageReleaseId) {
|
|
37
|
+
packageReleaseId = foundPackageReleaseId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!packageReleaseId) {
|
|
42
|
+
return ctx.error(404, {
|
|
43
|
+
error_code: "package_release_not_found",
|
|
44
|
+
message: "Package release not found",
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Get the package release to check permissions
|
|
49
|
+
const packageRelease = ctx.db.packageReleases.find(
|
|
50
|
+
(pr) => pr.package_release_id === packageReleaseId,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if (!packageRelease) {
|
|
54
|
+
return ctx.error(404, {
|
|
55
|
+
error_code: "package_release_not_found",
|
|
56
|
+
message: "Package release not found",
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get the package to check permissions
|
|
61
|
+
const existingpackage = ctx.db.packages.find(
|
|
62
|
+
(p) => p.package_id === packageRelease.package_id,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (!existingpackage) {
|
|
66
|
+
return ctx.error(404, {
|
|
67
|
+
error_code: "package_not_found",
|
|
68
|
+
message: "Package not found",
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if user has permission to delete the file
|
|
73
|
+
if (existingpackage.creator_account_id !== ctx.auth.account_id) {
|
|
74
|
+
return ctx.error(403, {
|
|
75
|
+
error_code: "forbidden",
|
|
76
|
+
message: "You don't have permission to delete files in this package",
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Find the file
|
|
81
|
+
const packageFile = ctx.db.packageFiles.find(
|
|
82
|
+
(f) =>
|
|
83
|
+
f.file_path === file_path && f.package_release_id === packageReleaseId,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (!packageFile) {
|
|
87
|
+
return ctx.error(404, {
|
|
88
|
+
error_code: "file_not_found",
|
|
89
|
+
message: "Package file not found",
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Delete the file using deletePackageFile method
|
|
94
|
+
const deleted = ctx.db.deletePackageFile(packageFile.package_file_id)
|
|
95
|
+
|
|
96
|
+
if (!deleted) {
|
|
97
|
+
return ctx.error(500, {
|
|
98
|
+
error_code: "file_deletion_failed",
|
|
99
|
+
message: "Failed to delete package file",
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return ctx.json({
|
|
104
|
+
ok: true,
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -8,6 +8,6 @@ export default withRouteSpec({
|
|
|
8
8
|
snippets: z.array(snippetSchema),
|
|
9
9
|
}),
|
|
10
10
|
})(async (req, ctx) => {
|
|
11
|
-
const
|
|
12
|
-
return ctx.json({ snippets:
|
|
11
|
+
const latestSnippets = ctx.db.getLatestSnippets(20)
|
|
12
|
+
return ctx.json({ snippets: latestSnippets })
|
|
13
13
|
})
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ const staticRoutes = [
|
|
|
9
9
|
{ url: "/playground", changefreq: "weekly", priority: 0.9 },
|
|
10
10
|
{ url: "/quickstart", changefreq: "monthly", priority: 0.8 },
|
|
11
11
|
{ url: "/dashboard", changefreq: "weekly", priority: 0.7 },
|
|
12
|
-
{ url: "/
|
|
12
|
+
{ url: "/latest", changefreq: "daily", priority: 0.8 },
|
|
13
13
|
{ url: "/search", changefreq: "weekly", priority: 0.7 },
|
|
14
14
|
{ url: "/settings", changefreq: "monthly", priority: 0.6 },
|
|
15
15
|
{ url: "/community/join-redirect", changefreq: "monthly", priority: 0.6 },
|
package/src/App.tsx
CHANGED
|
@@ -61,7 +61,7 @@ const EditorPage = lazyImport(async () => {
|
|
|
61
61
|
})
|
|
62
62
|
const LandingPage = lazyImport(() => import("@/pages/landing"))
|
|
63
63
|
const MyOrdersPage = lazyImport(() => import("@/pages/my-orders"))
|
|
64
|
-
const
|
|
64
|
+
const LatestPage = lazyImport(() => import("@/pages/latest"))
|
|
65
65
|
const PreviewPage = lazyImport(() => import("@/pages/preview"))
|
|
66
66
|
const QuickstartPage = lazyImport(() => import("@/pages/quickstart"))
|
|
67
67
|
const SearchPage = lazyImport(() => import("@/pages/search"))
|
|
@@ -112,7 +112,7 @@ function App() {
|
|
|
112
112
|
<Route path="/quickstart" component={QuickstartPage} />
|
|
113
113
|
<Route path="/dashboard" component={DashboardPage} />
|
|
114
114
|
<Route path="/ai" component={AiPage} />
|
|
115
|
-
<Route path="/
|
|
115
|
+
<Route path="/latest" component={LatestPage} />
|
|
116
116
|
<Route path="/settings" component={SettingsPage} />
|
|
117
117
|
<Route path="/search" component={SearchPage} />
|
|
118
118
|
<Route path="/trending" component={TrendingPage} />
|
|
@@ -38,7 +38,7 @@ import { useMutation, useQueryClient } from "react-query"
|
|
|
38
38
|
import { Link, useLocation } from "wouter"
|
|
39
39
|
import { useAxios } from "../hooks/use-axios"
|
|
40
40
|
import { useToast } from "../hooks/use-toast"
|
|
41
|
-
import {
|
|
41
|
+
import { useConfirmDeletePackageDialog } from "./dialogs/confirm-delete-package-dialog"
|
|
42
42
|
import { useCreateOrderDialog } from "./dialogs/create-order-dialog"
|
|
43
43
|
import { useFilesDialog } from "./dialogs/files-dialog"
|
|
44
44
|
import { useViewTsFilesDialog } from "./dialogs/view-ts-files-dialog"
|
|
@@ -83,7 +83,7 @@ export default function EditorNav({
|
|
|
83
83
|
openDialog: openupdateDescriptionDialog,
|
|
84
84
|
} = useUpdateDescriptionDialog()
|
|
85
85
|
const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
|
|
86
|
-
|
|
86
|
+
useConfirmDeletePackageDialog()
|
|
87
87
|
const { Dialog: CreateOrderDialog, openDialog: openCreateOrderDialog } =
|
|
88
88
|
useCreateOrderDialog()
|
|
89
89
|
const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
|
|
@@ -479,8 +479,8 @@ export default function EditorNav({
|
|
|
479
479
|
currentName={snippet?.unscoped_name ?? ""}
|
|
480
480
|
/>
|
|
481
481
|
<DeleteDialog
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
packageId={snippet?.snippet_id ?? ""}
|
|
483
|
+
packageName={snippet?.unscoped_name ?? ""}
|
|
484
484
|
/>
|
|
485
485
|
<CreateOrderDialog />
|
|
486
486
|
<FilesDialog snippetId={snippet?.snippet_id ?? ""} />
|
|
@@ -26,7 +26,6 @@ export default function Footer() {
|
|
|
26
26
|
{ name: "Home", href: "/" },
|
|
27
27
|
{ name: "Dashboard", href: "/dashboard" },
|
|
28
28
|
{ name: "Editor", href: "/editor" },
|
|
29
|
-
{ name: "Create with AI", href: "https://chat.tscircuit.com" },
|
|
30
29
|
{
|
|
31
30
|
name: "My Profile",
|
|
32
31
|
href: `/${session?.github_username}`,
|
|
@@ -44,14 +43,21 @@ export default function Footer() {
|
|
|
44
43
|
{item.name}
|
|
45
44
|
</PrefetchPageLink>
|
|
46
45
|
))}
|
|
46
|
+
<a
|
|
47
|
+
href="https://chat.tscircuit.com"
|
|
48
|
+
className="hover:underline"
|
|
49
|
+
target="_blank"
|
|
50
|
+
>
|
|
51
|
+
Create with AI
|
|
52
|
+
</a>
|
|
47
53
|
</footer>
|
|
48
54
|
</div>
|
|
49
55
|
|
|
50
56
|
<div className="space-y-4">
|
|
51
57
|
<h3 className="font-semibold uppercase">Explore</h3>
|
|
52
58
|
<footer className="flex flex-col space-y-2">
|
|
53
|
-
<PrefetchPageLink href="/
|
|
54
|
-
|
|
59
|
+
<PrefetchPageLink href="/latest" className="hover:underline">
|
|
60
|
+
Latest Snippets
|
|
55
61
|
</PrefetchPageLink>
|
|
56
62
|
<PrefetchPageLink href="/trending" className="hover:underline">
|
|
57
63
|
Trending Snippets
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from "@/components/ui/dropdown-menu"
|
|
9
|
+
import { Settings, Check } from "lucide-react"
|
|
10
|
+
|
|
11
|
+
interface HiddenFilesDropdownProps {
|
|
12
|
+
showHiddenFiles: boolean
|
|
13
|
+
onToggleHiddenFiles: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function HiddenFilesDropdown({
|
|
17
|
+
showHiddenFiles,
|
|
18
|
+
onToggleHiddenFiles,
|
|
19
|
+
}: HiddenFilesDropdownProps) {
|
|
20
|
+
return (
|
|
21
|
+
<DropdownMenu>
|
|
22
|
+
<DropdownMenuTrigger asChild>
|
|
23
|
+
<button className="ml-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 focus:outline-none">
|
|
24
|
+
<Settings className="h-4 w-4" />
|
|
25
|
+
</button>
|
|
26
|
+
</DropdownMenuTrigger>
|
|
27
|
+
<DropdownMenuContent
|
|
28
|
+
align="end"
|
|
29
|
+
className="bg-white dark:bg-[#161b22] border border-gray-200 dark:border-[#30363d] rounded-md shadow-lg"
|
|
30
|
+
>
|
|
31
|
+
<DropdownMenuItem
|
|
32
|
+
onSelect={(e) => e.preventDefault()}
|
|
33
|
+
onClick={onToggleHiddenFiles}
|
|
34
|
+
className="text-xs text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-[#21262d] flex items-center gap-2"
|
|
35
|
+
>
|
|
36
|
+
<Check
|
|
37
|
+
className={`h-4 w-4 transition-opacity ${showHiddenFiles ? "opacity-100" : "opacity-0"}`}
|
|
38
|
+
/>
|
|
39
|
+
Show Hidden Files
|
|
40
|
+
</DropdownMenuItem>
|
|
41
|
+
</DropdownMenuContent>
|
|
42
|
+
</DropdownMenu>
|
|
43
|
+
)
|
|
44
|
+
}
|