From 11650a64dc44d98897e28a71737e6be1efedac7c Mon Sep 17 00:00:00 2001 From: Rishabh Gupta <38923768+imrishabh18@users.noreply.github.com> Date: Mon, 3 Feb 2025 03:04:19 +0530 Subject: [PATCH] feat: add the package_release fake for registry (#633) * feat: add the package_release fake for registry * format fix --- .../routes/package_releases/create.test.ts | 131 ++++++++++++ .../routes/package_releases/get.test.ts | 89 +++++++++ .../routes/package_releases/list.test.ts | 157 +++++++++++++++ .../routes/package_releases/update.test.ts | 189 ++++++++++++++++++ fake-snippets-api/lib/db/db-client.ts | 30 +++ fake-snippets-api/lib/db/schema.ts | 2 + .../public-map-package-release.ts | 10 + .../routes/api/package_releases/create.ts | 83 ++++++++ .../routes/api/package_releases/get.ts | 34 ++++ .../routes/api/package_releases/list.ts | 77 +++++++ .../routes/api/package_releases/update.ts | 96 +++++++++ 11 files changed, 898 insertions(+) create mode 100644 bun-tests/fake-snippets-api/routes/package_releases/create.test.ts create mode 100644 bun-tests/fake-snippets-api/routes/package_releases/get.test.ts create mode 100644 bun-tests/fake-snippets-api/routes/package_releases/list.test.ts create mode 100644 bun-tests/fake-snippets-api/routes/package_releases/update.test.ts create mode 100644 fake-snippets-api/lib/public-mapping/public-map-package-release.ts create mode 100644 fake-snippets-api/routes/api/package_releases/create.ts create mode 100644 fake-snippets-api/routes/api/package_releases/get.ts create mode 100644 fake-snippets-api/routes/api/package_releases/list.ts create mode 100644 fake-snippets-api/routes/api/package_releases/update.ts diff --git a/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts b/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts new file mode 100644 index 00000000..43b4d722 --- /dev/null +++ b/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts @@ -0,0 +1,131 @@ +import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server" +import { expect, test } from "bun:test" + +test("create package release", async () => { + const { axios } = await getTestServer() + + // First create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + expect(packageResponse.status).toBe(200) + const createdPackage = packageResponse.data.package + + // Create a package release + const releaseResponse = await axios.post("/api/package_releases/create", { + package_id: createdPackage.package_id, + version: "1.0.0", + is_latest: true, + }) + + expect(releaseResponse.status).toBe(200) + expect(releaseResponse.data.ok).toBe(true) + expect(releaseResponse.data.package_release).toBeDefined() + expect(releaseResponse.data.package_release.package_id).toBe( + createdPackage.package_id, + ) + expect(releaseResponse.data.package_release.version).toBe("1.0.0") + expect(releaseResponse.data.package_release.is_latest).toBe(true) + expect(releaseResponse.data.package_release.is_locked).toBe(false) +}) + +test("create package release using package_name_with_version", async () => { + const { axios } = await getTestServer() + + // First create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package-2", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + expect(packageResponse.status).toBe(200) + const createdPackage = packageResponse.data.package + + // Create a package release using package_name_with_version + const releaseResponse = await axios.post("/api/package_releases/create", { + package_name_with_version: `${createdPackage.name}@2.0.0`, + }) + + expect(releaseResponse.status).toBe(200) + expect(releaseResponse.data.ok).toBe(true) + expect(releaseResponse.data.package_release).toBeDefined() + expect(releaseResponse.data.package_release.package_id).toBe( + createdPackage.package_id, + ) + expect(releaseResponse.data.package_release.version).toBe("2.0.0") + expect(releaseResponse.data.package_release.is_latest).toBe(true) +}) + +test("create package release - version already exists", async () => { + const { axios } = await getTestServer() + + // First create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package-3", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + expect(packageResponse.status).toBe(200) + const createdPackage = packageResponse.data.package + + // Create first release + await axios.post("/api/package_releases/create", { + package_id: createdPackage.package_id, + version: "1.0.0", + }) + + // Try to create release with same version + try { + await axios.post("/api/package_releases/create", { + package_id: createdPackage.package_id, + version: "1.0.0", + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(400) + expect(error.data.error.error_code).toBe("version_already_exists") + expect(error.data.error.message).toBe( + "Version 1.0.0 already exists for this package", + ) + } +}) + +test("create package release - package not found", async () => { + const { axios } = await getTestServer() + + try { + await axios.post("/api/package_releases/create", { + package_name_with_version: "non-existent-package@1.0.0", + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(404) + expect(error.data.error.error_code).toBe("package_not_found") + expect(error.data.error.message).toBe( + "Package not found: non-existent-package", + ) + } +}) diff --git a/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts b/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts new file mode 100644 index 00000000..391bd3bb --- /dev/null +++ b/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts @@ -0,0 +1,89 @@ +import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server" +import { expect, test } from "bun:test" +import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema" + +test("POST /api/package_releases/get - should return package release by package_release_id", async () => { + const { axios } = await getTestServer() + + // First create a package with valid name format + const packageResponse = await axios.post("/api/packages/create", { + name: "@test/package-1", + description: "A test package", + }) + expect(packageResponse.status).toBe(200) + const createdPackage = packageResponse.data.package + + // Create a package release + const releaseResponse = await axios.post("/api/package_releases/create", { + package_id: createdPackage.package_id, + version: "1.0.0", + is_latest: true, + }) + expect(releaseResponse.status).toBe(200) + const createdRelease = releaseResponse.data.package_release + + // Get the created package release + const getResponse = await axios.post("/api/package_releases/get", { + package_release_id: createdRelease.package_release_id, + }) + + expect(getResponse.status).toBe(200) + const responseBody = getResponse.data + expect(responseBody.ok).toBe(true) + expect(responseBody.package_release).toEqual( + packageReleaseSchema.parse(createdRelease), + ) +}) + +test("POST /api/package_releases/get - should return 404 if package release not found", async () => { + const { axios } = await getTestServer() + + try { + await axios.post("/api/package_releases/get", { + package_release_id: "123e4567-e89b-12d3-a456-426614174000", // valid UUID format + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(404) + expect(error.data.error.error_code).toBe("package_release_not_found") + expect(error.data.error.message).toBe("Package release not found") + } +}) + +test("POST /api/package_releases/get - should find release by package_name_with_version", async () => { + const { axios } = await getTestServer() + + // First create a package with valid name format + const packageResponse = await axios.post("/api/packages/create", { + name: "@test/package-2", + description: "Another test package", + }) + expect(packageResponse.status).toBe(200) + const createdPackage = packageResponse.data.package + + // Create a package release + const version = "2.0.0" + const releaseResponse = await axios.post("/api/package_releases/create", { + package_name_with_version: `${createdPackage.name}@${version}`, + is_latest: true, + }) + expect(releaseResponse.status).toBe(200) + const createdRelease = releaseResponse.data.package_release + + const listResponse = await axios.post("/api/package_releases/list", { + package_name: createdPackage.name, + }) + console.log(listResponse.data) + + // Get the release using package_name_with_version + const getResponse = await axios.post("/api/package_releases/get", { + package_release_id: createdRelease.package_release_id, + }) + + expect(getResponse.status).toBe(200) + const responseBody = getResponse.data + expect(responseBody.ok).toBe(true) + expect(responseBody.package_release).toEqual( + packageReleaseSchema.parse(createdRelease), + ) +}) diff --git a/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts b/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts new file mode 100644 index 00000000..82a12239 --- /dev/null +++ b/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts @@ -0,0 +1,157 @@ +import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server" +import { expect, test } from "bun:test" + +test("list package releases", async () => { + const { axios, db } = await getTestServer() + + // First create a test package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + const packageId = packageResponse.data.package.package_id + + // Add some test package releases + const releases = [ + { + package_id: packageId, + version: "1.0.0", + is_latest: false, + commit_sha: "abc123", + is_locked: false, + created_at: "2023-01-01T00:00:00Z", + }, + { + package_id: packageId, + version: "1.1.0", + is_latest: false, + commit_sha: "def456", + is_locked: false, + created_at: "2023-01-02T00:00:00Z", + }, + { + package_id: packageId, + version: "2.0.0", + is_latest: true, + commit_sha: "ghi789", + is_locked: false, + created_at: "2023-01-03T00:00:00Z", + }, + ] + + for (const release of releases) { + db.addPackageRelease(release as any) + } + + // Test listing by package_id + const { data: packageIdData } = await axios.post( + "/api/package_releases/list", + { + package_id: packageId, + }, + ) + expect(packageIdData.ok).toBe(true) + expect(packageIdData.package_releases).toHaveLength(3) + expect(packageIdData.package_releases[0].package_id).toBe(packageId) + + // Test listing by package_name + const { data: packageNameData } = await axios.post( + "/api/package_releases/list", + { + package_name: "test-package", + }, + ) + expect(packageNameData.ok).toBe(true) + expect(packageNameData.package_releases).toHaveLength(3) + + // Test listing latest releases only + const { data: latestData } = await axios.post("/api/package_releases/list", { + package_id: packageId, + is_latest: true, + }) + expect(latestData.ok).toBe(true) + expect(latestData.package_releases).toHaveLength(1) + expect(latestData.package_releases[0].version).toBe("2.0.0") + expect(latestData.package_releases[0].is_latest).toBe(true) + + // Test listing by specific version + const { data: versionData } = await axios.post("/api/package_releases/list", { + package_id: packageId, + version: "1.0.0", + }) + expect(versionData.ok).toBe(true) + expect(versionData.package_releases).toHaveLength(1) + expect(versionData.package_releases[0].version).toBe("1.0.0") + + // Test listing by commit_sha + const { data: commitData } = await axios.post("/api/package_releases/list", { + package_id: packageId, + commit_sha: "abc123", + }) + expect(commitData.ok).toBe(true) + expect(commitData.package_releases).toHaveLength(1) + expect(commitData.package_releases[0].commit_sha).toBe("abc123") +}) + +test("list package releases - validation errors", async () => { + const { axios } = await getTestServer() + + // Test error when both package_id and package_name provided + try { + await axios.post("/api/package_releases/list", { + package_id: "some-id", + package_name: "some-name", + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(400) + expect(error.data.message).toContain( + "package_id and package_name are mutually exclusive", + ) + } + + // Test error when neither package_id nor package_name provided + try { + await axios.post("/api/package_releases/list", { + is_latest: true, + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(400) + expect(error.data.message).toContain( + "package_id or package_name is required", + ) + } +}) + +test("list package releases - non-existent package", async () => { + const { axios } = await getTestServer() + + // Test with non-existent package_name + const { data: nonExistentData } = await axios.post( + "/api/package_releases/list", + { + package_name: "non-existent-package", + }, + ) + expect(nonExistentData.ok).toBe(true) + expect(nonExistentData.package_releases).toHaveLength(0) + + // Test with non-existent package_id + const { data: nonExistentIdData } = await axios.post( + "/api/package_releases/list", + { + package_id: "123e4567-e89b-12d3-a456-426614174000", + }, + ) + expect(nonExistentIdData.ok).toBe(true) + expect(nonExistentIdData.package_releases).toHaveLength(0) +}) diff --git a/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts b/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts new file mode 100644 index 00000000..1605c602 --- /dev/null +++ b/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts @@ -0,0 +1,189 @@ +import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server" +import { expect, test } from "bun:test" + +test("update package release", async () => { + const { axios, db } = await getTestServer() + + // First create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + const packageId = packageResponse.data.package.package_id + + // Create a package release + const releaseResponse = await axios.post("/api/package_releases/create", { + package_id: packageId, + version: "1.0.0", + is_latest: true, + }) + const release = releaseResponse.data.package_release + + // Update the package release + const response = await axios.post("/api/package_releases/update", { + package_release_id: release.package_release_id, + is_locked: true, + license: "MIT", + }) + + expect(response.status).toBe(200) + expect(response.data.ok).toBe(true) + + // Verify the release was updated + const updatedRelease = db.packageReleases.find( + (pr) => pr.package_release_id === release.package_release_id, + ) + expect(updatedRelease?.is_locked).toBe(true) + expect(updatedRelease?.license).toBe("MIT") +}) + +test("update package release using package_name_with_version", async () => { + const { axios, db } = await getTestServer() + + // First create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package-2", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + const packageName = packageResponse.data.package.name + const version = "2.0.0" + + // Create a package release + await axios.post("/api/package_releases/create", { + package_id: packageResponse.data.package.package_id, + version, + is_latest: true, + }) + + // Update using package_name_with_version + const response = await axios.post("/api/package_releases/update", { + package_name_with_version: `${packageName}@${version}`, + is_locked: true, + }) + + expect(response.status).toBe(200) + expect(response.data.ok).toBe(true) + + // Verify the release was updated + const updatedRelease = db.packageReleases.find((pr) => pr.version === version) + expect(updatedRelease?.is_locked).toBe(true) +}) + +test("update package release - handle is_latest flag", async () => { + const { axios, db } = await getTestServer() + + // Create a package + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package-3", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + const packageId = packageResponse.data.package.package_id + + // Create two releases + const release1 = await axios.post("/api/package_releases/create", { + package_id: packageId, + version: "1.0.0", + is_latest: true, + }) + const release2 = await axios.post("/api/package_releases/create", { + package_id: packageId, + version: "2.0.0", + is_latest: false, + }) + + // Update second release to be latest + await axios.post("/api/package_releases/update", { + package_release_id: release2.data.package_release.package_release_id, + is_latest: true, + }) + + // Verify first release is no longer latest + const firstRelease = db.packageReleases.find( + (pr) => + pr.package_release_id === + release1.data.package_release.package_release_id, + ) + expect(firstRelease?.is_latest).toBe(false) + + // Verify second release is now latest + const secondRelease = db.packageReleases.find( + (pr) => + pr.package_release_id === + release2.data.package_release.package_release_id, + ) + expect(secondRelease?.is_latest).toBe(true) +}) + +test("update non-existent package release", async () => { + const { axios } = await getTestServer() + + try { + await axios.post("/api/package_releases/update", { + package_release_id: "123e4567-e89b-12d3-a456-426614174000", + is_locked: true, + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(404) + expect(error.data.error.error_code).toBe("package_release_not_found") + expect(error.data.error.message).toBe("Package release not found") + } +}) + +test("update package release - no fields provided", async () => { + const { axios } = await getTestServer() + + // Create a package and release first + const packageResponse = await axios.post( + "/api/packages/create", + { + name: "test-package-4", + description: "Test Description", + }, + { + headers: { + Authorization: "Bearer 1234", + }, + }, + ) + const releaseResponse = await axios.post("/api/package_releases/create", { + package_id: packageResponse.data.package.package_id, + version: "1.0.0", + }) + + try { + await axios.post("/api/package_releases/update", { + package_release_id: + releaseResponse.data.package_release.package_release_id, + }) + throw new Error("Expected request to fail") + } catch (error: any) { + expect(error.status).toBe(400) + expect(error.data.error.error_code).toBe("no_fields_provided") + expect(error.data.error.message).toBe("No fields provided to update") + } +}) diff --git a/fake-snippets-api/lib/db/db-client.ts b/fake-snippets-api/lib/db/db-client.ts index 51653669..c2e676e2 100644 --- a/fake-snippets-api/lib/db/db-client.ts +++ b/fake-snippets-api/lib/db/db-client.ts @@ -17,6 +17,7 @@ import { packageReleaseSchema, packageSchema, Package, + PackageRelease, } from "./schema.ts" import { combine } from "zustand/middleware" import { seed as seedFn } from "./seed" @@ -387,4 +388,33 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({ ...pkg, } }, + getPackageReleaseById: ( + package_release_id: string, + ): PackageRelease | undefined => { + const state = get() + return state.packageReleases.find( + (pr) => pr.package_release_id === package_release_id, + ) + }, + addPackageRelease: ( + packageRelease: Omit, + ): PackageRelease => { + const newPackageRelease = { + package_release_id: `package_release_${Date.now()}`, + ...packageRelease, + } + set((state) => ({ + packageReleases: [...state.packageReleases, newPackageRelease], + })) + return newPackageRelease + }, + updatePackageRelease: (packageRelease: PackageRelease): void => { + set((state) => ({ + packageReleases: state.packageReleases.map((pr) => + pr.package_release_id === packageRelease.package_release_id + ? packageRelease + : pr, + ), + })) + }, })) diff --git a/fake-snippets-api/lib/db/schema.ts b/fake-snippets-api/lib/db/schema.ts index 335be063..79ea29ca 100644 --- a/fake-snippets-api/lib/db/schema.ts +++ b/fake-snippets-api/lib/db/schema.ts @@ -107,6 +107,8 @@ export const packageReleaseSchema = z.object({ is_locked: z.boolean(), is_latest: z.boolean(), created_at: z.string().datetime(), + commit_sha: z.string().nullable().optional(), + license: z.string().nullable().optional(), }) export type PackageRelease = z.infer diff --git a/fake-snippets-api/lib/public-mapping/public-map-package-release.ts b/fake-snippets-api/lib/public-mapping/public-map-package-release.ts new file mode 100644 index 00000000..d7be5ccf --- /dev/null +++ b/fake-snippets-api/lib/public-mapping/public-map-package-release.ts @@ -0,0 +1,10 @@ +import * as ZT from "fake-snippets-api/lib/db/schema" + +export const publicMapPackageRelease = ( + internal_package_release: ZT.PackageRelease, +): ZT.PackageRelease => { + return { + ...internal_package_release, + created_at: internal_package_release.created_at, + } +} diff --git a/fake-snippets-api/routes/api/package_releases/create.ts b/fake-snippets-api/routes/api/package_releases/create.ts new file mode 100644 index 00000000..41fcbb26 --- /dev/null +++ b/fake-snippets-api/routes/api/package_releases/create.ts @@ -0,0 +1,83 @@ +import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema" +import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec" +import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release" +import { z } from "zod" + +export default withRouteSpec({ + methods: ["POST"], + auth: "none", + jsonBody: z.object({ + package_id: z.string().optional(), + version: z.string().optional(), + is_latest: z.boolean().optional(), + commit_sha: z.string().optional(), + package_name_with_version: z.string().optional(), + }), + jsonResponse: z.object({ + ok: z.boolean(), + package_release: packageReleaseSchema, + }), +})(async (req, ctx) => { + let { + package_id, + is_latest = true, + version, + commit_sha, + package_name_with_version, + } = req.jsonBody + + if (package_name_with_version && !version && !package_id) { + const [packageName, parsedVersion] = package_name_with_version.split("@") + const pkg = ctx.db.packages.find((p) => p.name === packageName) + + if (!pkg) { + return ctx.error(404, { + error_code: "package_not_found", + message: `Package not found: ${packageName}`, + }) + } + + package_id = pkg.package_id + version = parsedVersion + } + + if (!package_id || !version) { + return ctx.error(400, { + error_code: "missing_options", + message: "package_id and version are required", + }) + } + + // Check if version already exists + const existingRelease = ctx.db.packageReleases.find( + (pr) => pr.package_id === package_id && pr.version === version, + ) + + if (existingRelease) { + return ctx.error(400, { + error_code: "version_already_exists", + message: `Version ${version} already exists for this package`, + }) + } + + // Update previous latest if needed + if (is_latest) { + ctx.db.packageReleases + .filter((pr) => pr.package_id === package_id && pr.is_latest) + .forEach((pr) => (pr.is_latest = false)) + } + + const newPackageRelease = ctx.db.addPackageRelease({ + package_id, + is_latest, + version, + is_locked: false, + created_at: new Date().toISOString(), + commit_sha: commit_sha ?? null, + }) + + return ctx.json({ + ok: true, + package_release: publicMapPackageRelease(newPackageRelease), + }) +}) diff --git a/fake-snippets-api/routes/api/package_releases/get.ts b/fake-snippets-api/routes/api/package_releases/get.ts new file mode 100644 index 00000000..d9ecc955 --- /dev/null +++ b/fake-snippets-api/routes/api/package_releases/get.ts @@ -0,0 +1,34 @@ +import * as zt from "fake-snippets-api/lib/db/schema" +import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release" +import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec" +import { z } from "zod" + +export default withRouteSpec({ + methods: ["POST"], + auth: "none", + jsonBody: z.object({ + package_release_id: z.string().optional(), + package_name_with_version: z.string().optional(), + }), + jsonResponse: z.object({ + ok: z.boolean(), + package_release: zt.packageReleaseSchema, + }), +})(async (req, ctx) => { + const { package_release_id } = req.jsonBody + + const foundRelease = + package_release_id && ctx.db.getPackageReleaseById(package_release_id) + + if (!foundRelease) { + return ctx.error(404, { + error_code: "package_release_not_found", + message: "Package release not found", + }) + } + + return ctx.json({ + ok: true, + package_release: publicMapPackageRelease(foundRelease), + }) +}) diff --git a/fake-snippets-api/routes/api/package_releases/list.ts b/fake-snippets-api/routes/api/package_releases/list.ts new file mode 100644 index 00000000..d582011c --- /dev/null +++ b/fake-snippets-api/routes/api/package_releases/list.ts @@ -0,0 +1,77 @@ +import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema" +import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec" +import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release" +import { z } from "zod" + +export default withRouteSpec({ + methods: ["POST"], + auth: "none", + jsonBody: z + .object({ + package_id: z.string().optional(), + package_name: z.string().optional(), + is_latest: z.boolean().optional(), + version: z.string().optional(), + commit_sha: z.string().optional(), + }) + .refine(({ package_id, package_name }) => { + if (package_id && package_name) { + return false + } + return true + }, "package_id and package_name are mutually exclusive") + .refine(({ package_id, package_name }) => { + if (!package_id && !package_name) { + return false + } + return true + }, "package_id or package_name is required"), + jsonResponse: z.object({ + ok: z.boolean(), + package_releases: z.array(packageReleaseSchema), + }), +})(async (req, ctx) => { + const { package_id, package_name, is_latest, version, commit_sha } = + req.jsonBody + + if (!package_id && !package_name && !is_latest && !version && !commit_sha) { + return ctx.error(400, { + error_code: "invalid_query", + message: + "At least one of package_id, package_name, is_latest, version or commit_sha is required", + }) + } + + let releases = ctx.db.packageReleases + + // Apply filters + if (package_id) { + releases = releases.filter((pr) => pr.package_id === package_id) + } + + if (package_name) { + const pkg = ctx.db.packages.find((p) => p.name === package_name) + if (pkg) { + releases = releases.filter((pr) => pr.package_id === pkg.package_id) + } else { + releases = [] + } + } + + if (is_latest !== undefined) { + releases = releases.filter((pr) => pr.is_latest === is_latest) + } + + if (version) { + releases = releases.filter((pr) => pr.version === version) + } + + if (commit_sha) { + releases = releases.filter((pr) => pr.commit_sha === commit_sha) + } + + return ctx.json({ + ok: true, + package_releases: releases.map((pr) => publicMapPackageRelease(pr)), + }) +}) diff --git a/fake-snippets-api/routes/api/package_releases/update.ts b/fake-snippets-api/routes/api/package_releases/update.ts new file mode 100644 index 00000000..123950cc --- /dev/null +++ b/fake-snippets-api/routes/api/package_releases/update.ts @@ -0,0 +1,96 @@ +import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec" +import { z } from "zod" + +export default withRouteSpec({ + methods: ["POST"], + auth: "none", + jsonBody: z.object({ + package_release_id: z.string().optional(), + package_name_with_version: z.string().optional(), + is_locked: z.boolean().optional(), + is_latest: z.boolean().optional(), + license: z.string().optional(), + }), + jsonResponse: z.object({ + ok: z.boolean(), + }), +})(async (req, ctx) => { + const { + package_release_id, + package_name_with_version, + is_locked, + is_latest, + license, + } = req.jsonBody + let releaseId = package_release_id + + // Handle package_name_with_version lookup + if (!releaseId && package_name_with_version) { + const [packageName, version] = package_name_with_version.split("@") + const pkg = ctx.db.packages.find((p) => p.name === packageName) + if (pkg) { + const release = ctx.db.packageReleases.find( + (pr) => pr.package_id === pkg.package_id && pr.version === version, + ) + if (release) { + releaseId = release.package_release_id + } + } + } + + if (!releaseId) { + return ctx.error(404, { + error_code: "package_release_not_found", + message: "Package release not found", + }) + } + + const delta = { is_locked, is_latest, license } + if ( + Object.keys(delta).filter( + (k) => delta[k as keyof typeof delta] !== undefined, + ).length === 0 + ) { + return ctx.error(400, { + error_code: "no_fields_provided", + message: "No fields provided to update", + }) + } + + const release = ctx.db.packageReleases.find( + (pr) => pr.package_release_id === releaseId, + ) + if (!release) { + return ctx.error(404, { + error_code: "package_release_not_found", + message: "Package release not found", + }) + } + + // Handle is_latest updates + if (is_latest !== undefined && is_latest) { + ctx.db.packageReleases + .filter( + (pr) => + pr.package_id === release.package_id && + pr.package_release_id !== releaseId && + pr.is_latest, + ) + .forEach((pr) => { + pr.is_latest = false + }) + } + + // Update the release + Object.assign(release, { + ...(is_locked !== undefined && { is_locked }), + ...(is_latest !== undefined && { is_latest }), + ...(license !== undefined && { license }), + }) + + ctx.db.updatePackageRelease(release) + + return ctx.json({ + ok: true, + }) +})