Skip to content

Commit 0960393

Browse files
authored
add the hook for fork mutation of packages (#782)
* add the hook for the fork mutation of package * format * remove the hardcoded value to test it on prod
1 parent e5a7988 commit 0960393

3 files changed

Lines changed: 177 additions & 13 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ export default withRouteSpec({
7676
commit_sha: commit_sha ?? null,
7777
})
7878

79+
// Update the package's latest_package_release_id if this is the latest release
80+
if (is_latest) {
81+
ctx.db.updatePackage(package_id, {
82+
latest_package_release_id: newPackageRelease.package_release_id,
83+
latest_version: version,
84+
})
85+
}
86+
7987
return ctx.json({
8088
ok: true,
8189
package_release: publicMapPackageRelease(newPackageRelease),
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
Package,
3+
PackageFile,
4+
PackageRelease,
5+
} from "fake-snippets-api/lib/db/schema"
6+
import { useMutation } from "react-query"
7+
import { useAxios } from "./use-axios"
8+
import { useGlobalStore } from "./use-global-store"
9+
import { useToast } from "./use-toast"
10+
import { useCreatePackageMutation } from "./use-create-package-mutation"
11+
import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
12+
import { useCreatePackageFilesMutation } from "./use-create-package-files-mutation"
13+
14+
export const useForkPackageMutation = ({
15+
onSuccess,
16+
}: {
17+
onSuccess?: (forkedPackage: Package) => void
18+
} = {}) => {
19+
const axios = useAxios()
20+
const session = useGlobalStore((s) => s.session)
21+
const { toast } = useToast()
22+
23+
const { mutateAsync: createPackage } = useCreatePackageMutation()
24+
const { mutateAsync: createRelease } = useCreatePackageReleaseMutation()
25+
const { mutateAsync: createFile } = useCreatePackageFilesMutation()
26+
27+
return useMutation(
28+
["forkPackage"],
29+
async (packageId: string) => {
30+
if (!session) throw new Error("No session")
31+
32+
// Step 1: Fetch source package data
33+
const { data: packageData } = await axios.get("/packages/get", {
34+
params: { package_id: packageId },
35+
})
36+
const sourcePackage: Package = packageData.package
37+
if (!sourcePackage) throw new Error("Source package not found")
38+
39+
console.log("sourcePackage", sourcePackage)
40+
// Step 2: Fetch latest release
41+
const { data: releaseData } = await axios.post("/package_releases/get", {
42+
package_release_id: sourcePackage.latest_package_release_id,
43+
})
44+
const sourceRelease: PackageRelease = releaseData.package_release
45+
if (!sourceRelease) throw new Error("Source release not found")
46+
47+
// Step 3: Fetch all files for the release
48+
const { data: filesData } = await axios.post("/package_files/list", {
49+
package_release_id: sourceRelease.package_release_id,
50+
})
51+
const sourceFiles: PackageFile[] = filesData.package_files
52+
if (!sourceFiles?.length) throw new Error("No source files found")
53+
54+
// Step 4: Create new package
55+
const newPackage = await createPackage({
56+
name: `@${session.github_username}/${sourcePackage.unscoped_name}`,
57+
description: `Fork of ${sourcePackage.name}`,
58+
is_private: false,
59+
is_unlisted: false,
60+
})
61+
62+
// Step 5: Create new release
63+
const newRelease = await createRelease({
64+
package_id: newPackage.package_id,
65+
version: sourceRelease.version ?? undefined,
66+
is_latest: true,
67+
})
68+
69+
// Step 6: Create all files
70+
const newFiles = await Promise.all(
71+
sourceFiles.map((file: PackageFile) =>
72+
createFile({
73+
package_release_id: newRelease.package_release_id,
74+
file_path: file.file_path,
75+
content_text: file.content_text ?? undefined,
76+
}),
77+
),
78+
)
79+
80+
return {
81+
package: newPackage,
82+
release: newRelease,
83+
files: newFiles,
84+
}
85+
},
86+
{
87+
onSuccess: (result) => {
88+
toast({
89+
title: "Package Forked",
90+
description: `Successfully forked package to @${session?.github_username}/${result.package.unscoped_name}`,
91+
})
92+
onSuccess?.(result.package)
93+
},
94+
onError: (error: any) => {
95+
toast({
96+
title: "Error",
97+
description: "Failed to fork package. Please try again.",
98+
variant: "destructive",
99+
})
100+
},
101+
},
102+
)
103+
}

src/pages/beta.tsx

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
22
import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
33
import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
4+
import { useForkPackageMutation } from "@/hooks/use-fork-package-mutation"
45
import { usePackageById } from "@/hooks/use-package-by-package-id"
56
import { usePackageFile } from "@/hooks/use-package-files"
67
import { usePackageRelease } from "@/hooks/use-package-release"
@@ -10,23 +11,34 @@ export const BetaPage = () => {
1011
const [createdPackageId, setCreatedPackageId] = useState<string | null>(null)
1112
const [createdReleaseId, setCreatedReleaseId] = useState<string | null>(null)
1213
const [createdFileId, setCreatedFileId] = useState<string | null>(null)
14+
const [forkedPackageId, setForkedPackageId] = useState<string | null>(null)
15+
const [packageName, setPackageName] = useState("")
1316

1417
// Demo data
1518
const demoData = {
1619
package: {
17-
name: "@testuser/test-package",
20+
name: packageName,
1821
description: "Test package",
1922
is_private: false,
2023
},
2124
release: {
2225
version: "1.0.0",
2326
is_latest: true,
2427
},
25-
file: {
26-
file_path: "src/index.ts",
27-
content_text: 'export const hello = () => console.log("Hello!");',
28-
content_mimetype: "text/typescript",
29-
},
28+
files: [
29+
{
30+
file_path: "src/index.ts",
31+
content_text: 'export const hello = () => console.log("Hello!");',
32+
},
33+
{
34+
file_path: "src/utils.ts",
35+
content_text: "export const add = (a: number, b: number) => a + b;",
36+
},
37+
{
38+
file_path: "README.md",
39+
content_text: "# Test Package\n\nA test package with multiple files.",
40+
},
41+
],
3042
}
3143

3244
// Mutations and queries
@@ -42,6 +54,9 @@ export const BetaPage = () => {
4254
useCreatePackageFilesMutation({
4355
onSuccess: (file) => setCreatedFileId(file.package_file_id),
4456
})
57+
const { mutate: forkPackage, isLoading: isForking } = useForkPackageMutation({
58+
onSuccess: (forkedPackage) => setForkedPackageId(forkedPackage.package_id),
59+
})
4560

4661
const { data: packageData } = usePackageById(createdPackageId)
4762
const { data: releaseData } = usePackageRelease(
@@ -50,18 +65,33 @@ export const BetaPage = () => {
5065
const { data: fileData } = usePackageFile(
5166
createdFileId ? { package_file_id: createdFileId } : null,
5267
)
68+
const { data: forkedPackageData } = usePackageById(forkedPackageId)
5369

5470
return (
5571
<div className="p-4">
5672
<h1 className="text-xl font-bold mb-4">Package Management Demo</h1>
57-
<div className="grid grid-cols-3 gap-4">
73+
<div className="mb-4">
74+
<div className="flex items-center gap-2">
75+
<input
76+
type="text"
77+
value={packageName}
78+
onChange={(e) => setPackageName(e.target.value)}
79+
placeholder="test-package"
80+
className="flex-1 px-3 py-2 border rounded text-sm"
81+
disabled={Boolean(createdPackageId)}
82+
/>
83+
</div>
84+
</div>
85+
<div className="grid grid-cols-4 gap-4">
5886
{/* Package Creation */}
5987
<div className="bg-white p-4 rounded-lg shadow">
6088
<div className="flex items-center justify-between mb-2">
6189
<h2 className="font-semibold">1. Create Package</h2>
6290
<button
6391
onClick={() => createPackage(demoData.package)}
64-
disabled={isCreatingPackage || Boolean(createdPackageId)}
92+
disabled={
93+
isCreatingPackage || Boolean(createdPackageId) || !packageName
94+
}
6595
className="bg-blue-500 text-white px-3 py-1 text-sm rounded hover:bg-blue-600 disabled:bg-blue-300"
6696
>
6797
{createdPackageId ? "✓" : "Create"}
@@ -102,14 +132,18 @@ export const BetaPage = () => {
102132
{/* File Creation */}
103133
<div className="bg-white p-4 rounded-lg shadow">
104134
<div className="flex items-center justify-between mb-2">
105-
<h2 className="font-semibold">3. Create File</h2>
135+
<h2 className="font-semibold">3. Create Files</h2>
106136
<button
107137
onClick={() =>
108138
createdReleaseId &&
109-
createFile({
110-
package_release_id: createdReleaseId,
111-
...demoData.file,
112-
})
139+
Promise.all(
140+
demoData.files.map((file) =>
141+
createFile({
142+
package_release_id: createdReleaseId,
143+
...file,
144+
}),
145+
),
146+
)
113147
}
114148
disabled={!createdReleaseId || Boolean(createdFileId)}
115149
className="bg-blue-500 text-white px-3 py-1 text-sm rounded hover:bg-blue-600 disabled:bg-blue-300"
@@ -123,6 +157,25 @@ export const BetaPage = () => {
123157
</pre>
124158
)}
125159
</div>
160+
161+
{/* Fork Package */}
162+
<div className="bg-white p-4 rounded-lg shadow">
163+
<div className="flex items-center justify-between mb-2">
164+
<h2 className="font-semibold">4. Fork Package</h2>
165+
<button
166+
onClick={() => createdPackageId && forkPackage(createdPackageId)}
167+
disabled={!createdPackageId || isForking}
168+
className="bg-green-500 text-white px-3 py-1 text-sm rounded hover:bg-green-600 disabled:bg-green-300"
169+
>
170+
{forkedPackageId ? "✓" : "Fork"}
171+
</button>
172+
</div>
173+
{forkedPackageData && (
174+
<pre className="text-xs bg-gray-50 p-2 rounded overflow-auto max-h-40">
175+
{JSON.stringify(forkedPackageData, null, 2)}
176+
</pre>
177+
)}
178+
</div>
126179
</div>
127180
</div>
128181
)

0 commit comments

Comments
 (0)