Skip to content

Commit

Permalink
refactor: move project cloning into sql function
Browse files Browse the repository at this point in the history
Our project cloning does a lot of roundtrips to database
and got fragmented across 3 packages. The logic is too complicated
to represent with postgrest.

Here written single sql function which is invoked safely
with postgrest rpc.
  • Loading branch information
TrySound committed Jul 25, 2024
1 parent fa99a90 commit 8fef789
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 133 deletions.
74 changes: 0 additions & 74 deletions packages/asset-uploader/src/db/clone.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/asset-uploader/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./load";
export * from "./clone";
20 changes: 15 additions & 5 deletions packages/postgrest/src/__generated__/db-types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions packages/prisma-client/migrations-cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ export const createSchema = async ({ name }: { name: string }) => {

const sqlScript = await prismaMigrations.cliDiff();

if (isNoopSql(sqlScript ?? "")) {
logger.info("No changes to apply");
process.exit(0);
}

const migrationName = prismaMigrations.generateMigrationName(name);

const filePath = prismaMigrations.getMigrationFilePath(migrationName, "sql");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
DROP FUNCTION IF EXISTS clone_project;
CREATE FUNCTION clone_project(
project_id text,
user_id text,
title text,
domain text
) RETURNS "Project" AS $$
DECLARE
old_project "Project";
new_project "Project";
BEGIN
SELECT * FROM "Project" WHERE id=project_id INTO old_project;

INSERT INTO "Project" (
id,
"userId",
title,
domain,
"previewImageAssetId"
)
VALUES (
extensions.uuid_generate_v4(),
user_id,
title,
domain,
old_project."previewImageAssetId"
)
RETURNING * INTO new_project;

INSERT INTO "Asset" (id, name, "projectId")
SELECT asset.id, asset.name, new_project.id AS "projectId"
FROM "Asset" AS asset, "File" AS file
WHERE
asset.name = file.name AND
file.status = 'UPLOADED' AND
asset."projectId" = old_project.id;

INSERT INTO "Build" (
id,
"projectId",
pages,
"styleSources",
"styleSourceSelections",
styles,
breakpoints,
props,
instances,
"dataSources",
resources
)
SELECT
extensions.uuid_generate_v4() AS id,
new_project.id AS "projectId",
pages,
"styleSources",
"styleSourceSelections",
styles,
breakpoints,
props,
instances,
"dataSources",
resources
FROM "Build"
WHERE "projectId" = old_project.id AND deployment IS NULL;

RETURN new_project;
END;
$$ LANGUAGE plpgsql;
2 changes: 1 addition & 1 deletion packages/project-build/src/db/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ const zBuildCloneResult = z
.length(1)
.transform((result) => result[0]);

export const cloneBuild = async (
const cloneBuild = async (
props: {
fromProjectId: Build["projectId"];
toProjectId: Build["projectId"];
Expand Down
58 changes: 11 additions & 47 deletions packages/project/src/db/project.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { prisma, Prisma } from "@webstudio-is/prisma-client";
import { cloneAssets } from "@webstudio-is/asset-uploader/index.server";
import {
authorizeProject,
type AppContext,
AuthorizationError,
} from "@webstudio-is/trpc-interface/index.server";
import {
createBuild,
cloneBuild,
} from "@webstudio-is/project-build/index.server";
import { createBuild } from "@webstudio-is/project-build/index.server";
import { MarketplaceApprovalStatus, Project, Title } from "../shared/schema";
import { generateDomain, validateProjectDomain } from "./project-domain";

Expand Down Expand Up @@ -158,54 +154,22 @@ export const clone = async (
}

const { userId } = context.authorization;

if (userId === undefined) {
throw new Error("The user must be authenticated to clone the project");
}

const newProjectId = crypto.randomUUID();

const clonedProject = await prisma.$transaction(async (client) => {
await cloneAssets(
{
fromProjectId: project.id,
toProjectId: newProjectId,

// Permission check on newProjectId will fail until this transaction is committed.
// We have to skip it, but it's ok because registerProjectOwner is right above
checkPermissions: false,
},
context,
client
);

const clonedProject = await client.project.create({
data: {
id: newProjectId,
userId: userId,
title: title ?? `${project.title} (copy)`,
domain: generateDomain(project.title),
previewImageAssetId: project.previewImageAsset?.id,
},
include: {
previewImageAsset: true,
},
});

await cloneBuild(
{
fromProjectId: project.id,
toProjectId: newProjectId,
deployment: undefined,
},
context,
client
);

return clonedProject;
const clonedProject = await context.postgrest.client.rpc("clone_project", {
project_id: projectId,
user_id: userId,
title: title ?? `${project.title} (copy)`,
domain: generateDomain(project.title),
});
if (clonedProject.error) {
console.log(clonedProject.error);

Check failure on line 168 in packages/project/src/db/project.ts

View workflow job for this annotation

GitHub Actions / checks

Unexpected console statement
throw clonedProject.error;
}

return Project.parse(clonedProject);
return { id: clonedProject.data.id };
};

export const updateDomain = async (
Expand Down

0 comments on commit 8fef789

Please sign in to comment.