From 02463db741e57e6300d2ddc8399212e9cca1fade Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Sat, 25 Sep 2021 12:54:04 +1000 Subject: [PATCH] Start project fork feature Updated schema, project service and UI Still some polish to go. Co-authored-by: Frank Noirot --- .../migration.sql | 5 +++ app/api/db/schema.prisma | 7 ++- app/api/src/graphql/projects.sdl.ts | 10 ++++- app/api/src/services/projects/projects.ts | 23 +++++++--- .../src/components/IdeHeader/IdeHeader.tsx | 43 ++++++++++++++++++- 5 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 app/api/db/migrations/20210925001652_add_project_forking/migration.sql diff --git a/app/api/db/migrations/20210925001652_add_project_forking/migration.sql b/app/api/db/migrations/20210925001652_add_project_forking/migration.sql new file mode 100644 index 0000000..a5c9a3e --- /dev/null +++ b/app/api/db/migrations/20210925001652_add_project_forking/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "forkedFromId" TEXT; + +-- AddForeignKey +ALTER TABLE "Project" ADD FOREIGN KEY ("forkedFromId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/app/api/db/schema.prisma b/app/api/db/schema.prisma index 98cdfd0..d91dda8 100644 --- a/app/api/db/schema.prisma +++ b/app/api/db/schema.prisma @@ -53,9 +53,12 @@ model Project { deleted Boolean @default(false) cadPackage CadPackage @default(openscad) socialCard SocialCard? + forkedFromId String? + forkedFrom Project? @relation("Fork", fields: [forkedFromId], references: [id]) - Comment Comment[] - Reaction ProjectReaction[] + childForks Project[] @relation("Fork") + Comment Comment[] + Reaction ProjectReaction[] @@unique([title, userId]) } diff --git a/app/api/src/graphql/projects.sdl.ts b/app/api/src/graphql/projects.sdl.ts index 7faa41c..507d1db 100644 --- a/app/api/src/graphql/projects.sdl.ts +++ b/app/api/src/graphql/projects.sdl.ts @@ -14,6 +14,9 @@ export const schema = gql` socialCard: SocialCard Comment: [Comment]! Reaction(userId: String): [ProjectReaction]! + forkedFromId: String + forkedFrom: Project + childForks: [Project]! } enum CadPackage { @@ -37,6 +40,11 @@ export const schema = gql` cadPackage: CadPackage! } + input ForkProjectInput { + userId: String! + forkedFromId: String + } + input UpdateProjectInput { title: String description: String @@ -47,7 +55,7 @@ export const schema = gql` type Mutation { createProject(input: CreateProjectInput!): Project! - forkProject(input: CreateProjectInput!): Project! + forkProject(input: ForkProjectInput!): Project! updateProject(id: String!, input: UpdateProjectInput!): Project! updateProjectImages( id: String! diff --git a/app/api/src/services/projects/projects.ts b/app/api/src/services/projects/projects.ts index 2f0c806..94b3121 100644 --- a/app/api/src/services/projects/projects.ts +++ b/app/api/src/services/projects/projects.ts @@ -78,13 +78,26 @@ export const createProject = async ({ input }: CreateProjectArgs) => { } export const forkProject = async ({ input }) => { - // Only difference between create and fork project is that fork project will generate a unique title - // (for the user) if there is a conflict + requireAuth() + const projectData = await db.project.findUnique({ + where: { + id: input.forkedFromId, + }, + }) const isUniqueCallback = isUniqueProjectTitle(input.userId) - const title = await generateUniqueString(input.title, isUniqueCallback) - // TODO change the description to `forked from userName/projectName ${rest of description}` + let title = projectData.title + + title = await generateUniqueString(title, isUniqueCallback) + + const { code, description, cadPackage } = projectData return db.project.create({ - data: foreignKeyReplacement({ ...input, title }), + data: foreignKeyReplacement({ + ...input, + title, + code, + description, + cadPackage, + }), }) } diff --git a/app/web/src/components/IdeHeader/IdeHeader.tsx b/app/web/src/components/IdeHeader/IdeHeader.tsx index 7ca76a6..02e3a91 100644 --- a/app/web/src/components/IdeHeader/IdeHeader.tsx +++ b/app/web/src/components/IdeHeader/IdeHeader.tsx @@ -8,11 +8,24 @@ import ExternalScript from 'src/components/EncodedUrl/ExternalScript' import Svg from 'src/components/Svg/Svg' import NavPlusButton from 'src/components/NavPlusButton' import ProfileSlashLogin from 'src/components/ProfileSlashLogin' +import { useMutation } from '@redwoodjs/web' import Gravatar from 'src/components/Gravatar/Gravatar' import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle' import CaptureButton from 'src/components/CaptureButton/CaptureButton' + import { ReactNode } from 'react' +const FORK_PROJECT_MUTATION = gql` + mutation ForkProjectMutation($input: ForkProjectInput!) { + forkProject(input: $input) { + id + title + description + code + } + } +` + const TopButton = ({ onClick, children, @@ -111,7 +124,6 @@ const IdeHeader = ({ ) : ( children )} - {/* Fork */}
@@ -130,6 +142,26 @@ function DefaultTopButtons({ handleRender, canEdit, }) { + const { currentUser } = useAuth() + const [createFork] = useMutation(FORK_PROJECT_MUTATION, { + onCompleted: () => {}, + }) + const handleFork = () => { + const prom = createFork({ + variables: { + input: { + userId: currentUser.sub, + forkedFromId: project.id, + }, + }, + }) + // toast.promise(prom, { + // loading: 'Saving Image/s', + // success: Image/s saved!, + // error: Problem saving., + // }) + } + return ( <> {canEdit && !projectTitle && ( @@ -212,6 +244,15 @@ function DefaultTopButtons({ ) }} + {currentUser?.id && ( + + + + )} ) }