Skip to content

Commit

Permalink
feat(api): Add feature to fork projects
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed May 24, 2024
1 parent 2efae39 commit 015b3fb
Show file tree
Hide file tree
Showing 12 changed files with 11,140 additions and 7,880 deletions.
10 changes: 5 additions & 5 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"db:reset": "pnpx dotenv-cli -e ../../.env -- pnpm dlx prisma migrate reset --force --schema=src/prisma/schema.prisma",
"sourcemaps": "sentry-cli sourcemaps inject ./dist && sentry-cli sourcemaps upload ./dist || echo 'Failed to upload source maps to Sentry'",
"e2e:prepare": "cd ../../ && docker compose down && docker compose -f docker-compose-test.yml up -d && cd apps/api && pnpm db:generate-types && cross-env NODE_ENV='e2e' DATABASE_URL='postgresql://prisma:prisma@localhost:5432/tests' pnpm run db:deploy-migrations",
"e2e": "pnpm run e2e:prepare && cross-env NODE_ENV='e2e' DATABASE_URL='postgresql://prisma:prisma@localhost:5432/tests' turbo run test --no-cache --filter=api -- --runInBand --config=jest.e2e-config.ts --coverage --coverageDirectory=../../coverage-e2e/api --coverageReporters=json && pnpm run e2e:teardown",
"e2e": "pnpm run e2e:prepare && cross-env NODE_ENV='e2e' DATABASE_URL='postgresql://prisma:prisma@localhost:5432/tests' jest --runInBand --config=jest.e2e-config.ts --coverage --coverageDirectory=../../coverage-e2e/api --coverageReporters=json && pnpm run e2e:teardown",

Check failure

Code scanning / SonarCloud

PostgreSQL database passwords should not be disclosed High

Make sure this PostgreSQL database password gets changed and removed from the code. See more on SonarCloud
"e2e:teardown": "cd ../../ && docker compose -f docker-compose-test.yml down",
"unit": "pnpm db:generate-types && turbo run test -- --config=jest.config.ts"
"unit": "pnpm db:generate-types && jest --config=jest.config.ts"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
Expand Down Expand Up @@ -56,16 +56,17 @@
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@prisma/client": "^5.13.0",
"@types/uuid": "^9.0.8",
"@types/cookie-parser": "^1.4.7",
"@types/eccrypto": "^1.1.6",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"dotenv-cli": "^7.4.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"ajv": "^7",
"dotenv-cli": "^7.4.2",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-node": "^11.1.0",
Expand All @@ -78,7 +79,6 @@
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export class CreateEnvironment {

@IsString()
@IsOptional()
description: string
description?: string

@IsBoolean()
@IsOptional()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "forkedFromId" TEXT,
ADD COLUMN "isForked" BOOLEAN NOT NULL DEFAULT false;

-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_forkedFromId_fkey" FOREIGN KEY ("forkedFromId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
5 changes: 5 additions & 0 deletions apps/api/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ model Project {
isDisabled Boolean @default(false) // This is set to true when the user stops his subscription and still has premium features in use
accessLevel ProjectAccessLevel @default(PRIVATE)
pendingCreation Boolean @default(false)
isForked Boolean @default(false)
lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull)
lastUpdatedById String?
Expand All @@ -311,6 +312,10 @@ model Project {
environments Environment[]
workspaceRoles ProjectWorkspaceRoleAssociation[]
integrations Integration[]
forks Project[] @relation("Fork")
forkedFromId String?
forkedFrom Project? @relation("Fork", fields: [forkedFromId], references: [id], onDelete: SetNull, onUpdate: Cascade)
}

model ProjectWorkspaceRoleAssociation {
Expand Down
43 changes: 42 additions & 1 deletion apps/api/src/project/controller/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CreateProject } from '../dto/create.project/create.project'
import { UpdateProject } from '../dto/update.project/update.project'
import { RequiredApiKeyAuthorities } from '../../decorators/required-api-key-authorities.decorator'
import { AlphanumericReasonValidationPipe } from '../../common/alphanumeric-reason-pipe'
import { ForkProject } from '../dto/fork.project/fork.project'

@Controller('project')
export class ProjectController {
Expand Down Expand Up @@ -61,12 +62,52 @@ export class ProjectController {
return await this.service.getProjectById(user, projectId)
}

@Post(':projectId/fork')
@RequiredApiKeyAuthorities(Authority.READ_PROJECT, Authority.CREATE_PROJECT)
async forkProject(
@CurrentUser() user: User,
@Param('projectId') projectId: Project['id'],
@Body() forkMetadata: ForkProject
) {
return await this.service.forkProject(user, projectId, forkMetadata)
}

@Put(':projectId/sync-fork')
@RequiredApiKeyAuthorities(Authority.READ_PROJECT, Authority.UPDATE_PROJECT)
async syncFork(
@CurrentUser() user: User,
@Param('projectId') projectId: Project['id'],
@Param('hardSync') hardSync: boolean = false
) {
return await this.service.syncFork(user, projectId, hardSync)
}

@Put(':projectId/unlink-fork')
@RequiredApiKeyAuthorities(Authority.UPDATE_PROJECT)
async unlinkFork(
@CurrentUser() user: User,
@Param('projectId') projectId: Project['id']
) {
return await this.service.unlinkParentOfFork(user, projectId)
}

@Get(':projectId/forks')
@RequiredApiKeyAuthorities(Authority.READ_PROJECT)
async getForks(
@CurrentUser() user: User,
@Param('projectId') projectId: Project['id'],
@Query('page') page: number = 0,
@Query('limit') limit: number = 10
) {
return await this.service.getAllProjectForks(user, projectId, page, limit)
}

@Get('/all/:workspaceId')
@RequiredApiKeyAuthorities(Authority.READ_PROJECT)
async getAllProjects(
@CurrentUser() user: User,
@Param('workspaceId') workspaceId: Workspace['id'],
@Query('page') page: number = 1,
@Query('page') page: number = 0,
@Query('limit') limit: number = 10,
@Query('sort') sort: string = 'name',
@Query('order') order: string = 'asc',
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/project/dto/fork.project/fork.project.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ForkProject } from './fork.project'

describe('ForkProject', () => {
it('should be defined', () => {
expect(new ForkProject()).toBeDefined()
})
})
16 changes: 16 additions & 0 deletions apps/api/src/project/dto/fork.project/fork.project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Workspace } from '@prisma/client'
import { IsOptional, IsString } from 'class-validator'

export class ForkProject {
@IsString()
@IsOptional()
workspaceId?: Workspace['id']

@IsString()
@IsOptional()
name?: string

@IsString()
@IsOptional()
storePrivateKey?: boolean
}
Loading

0 comments on commit 015b3fb

Please sign in to comment.