diff --git a/apps/api/prisma/migrations/20220818093615_public_repositories/migration.sql b/apps/api/prisma/migrations/20220818093615_public_repositories/migration.sql new file mode 100644 index 000000000..7f08c29fd --- /dev/null +++ b/apps/api/prisma/migrations/20220818093615_public_repositories/migration.sql @@ -0,0 +1,42 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_GitSource" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "forPublic" BOOLEAN NOT NULL DEFAULT false, + "type" TEXT, + "apiUrl" TEXT, + "htmlUrl" TEXT, + "customPort" INTEGER NOT NULL DEFAULT 22, + "organization" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "githubAppId" TEXT, + "gitlabAppId" TEXT, + CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource"; +DROP TABLE "GitSource"; +ALTER TABLE "new_GitSource" RENAME TO "GitSource"; +CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId"); +CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId"); +CREATE TABLE "new_ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "autodeploy" BOOLEAN NOT NULL DEFAULT true, + "isBot" BOOLEAN NOT NULL DEFAULT false, + "isPublicRepository" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt" FROM "ApplicationSettings"; +DROP TABLE "ApplicationSettings"; +ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 695478cef..6af0c4535 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -119,16 +119,17 @@ model Application { } model ApplicationSettings { - id String @id @default(cuid()) - applicationId String @unique - dualCerts Boolean @default(false) - debug Boolean @default(false) - previews Boolean @default(false) - autodeploy Boolean @default(true) - isBot Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - application Application @relation(fields: [applicationId], references: [id]) + id String @id @default(cuid()) + applicationId String @unique + dualCerts Boolean @default(false) + debug Boolean @default(false) + previews Boolean @default(false) + autodeploy Boolean @default(true) + isBot Boolean @default(false) + isPublicRepository Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + application Application @relation(fields: [applicationId], references: [id]) } model ApplicationPersistentStorage { @@ -238,6 +239,7 @@ model SshKey { model GitSource { id String @id @default(cuid()) name String + forPublic Boolean @default(false) type String? apiUrl String? htmlUrl String? diff --git a/apps/api/prisma/seed.js b/apps/api/prisma/seed.js index 96b55b105..9fa55136f 100644 --- a/apps/api/prisma/seed.js +++ b/apps/api/prisma/seed.js @@ -66,6 +66,34 @@ async function main() { } }); } + const github = await prisma.gitSource.findFirst({ + where: { htmlUrl: 'https://github.com', forPublic: true } + }); + const gitlab = await prisma.gitSource.findFirst({ + where: { htmlUrl: 'https://gitlab.com', forPublic: true } + }); + if (!github) { + await prisma.gitSource.create({ + data: { + apiUrl: 'https://api.github.com', + htmlUrl: 'https://github.com', + forPublic: true, + name: 'Github Public', + type: 'github' + } + }); + } + if (!gitlab) { + await prisma.gitSource.create({ + data: { + apiUrl: 'https://gitlab.com/api/v4', + htmlUrl: 'https://gitlab.com', + forPublic: true, + name: 'Gitlab Public', + type: 'gitlab' + } + }); + } } main() .catch((e) => { diff --git a/apps/api/src/lib/importers/github.ts b/apps/api/src/lib/importers/github.ts index bac460eca..afe1483a3 100644 --- a/apps/api/src/lib/importers/github.ts +++ b/apps/api/src/lib/importers/github.ts @@ -31,7 +31,6 @@ export default async function ({ const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } }); if (body.privateKey) body.privateKey = decrypt(body.privateKey); const { privateKey, appId, installationId } = body - const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, ''); const payload = { diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 5f0f2aa2d..0e2596f79 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -499,11 +499,21 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) { try { const { id } = request.params - const { gitSourceId } = request.body - await prisma.application.update({ - where: { id }, - data: { gitSource: { connect: { id: gitSourceId } } } - }); + const { gitSourceId, forPublic, type } = request.body + console.log({ id, gitSourceId, forPublic, type }) + if (forPublic) { + const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } }); + await prisma.application.update({ + where: { id }, + data: { gitSource: { connect: { id: publicGit.id } } } + }); + } else { + await prisma.application.update({ + where: { id }, + data: { gitSource: { connect: { id: gitSourceId } } } + }); + } + return reply.code(201).send() } catch ({ status, message }) { return errorHandler({ status, message }) @@ -557,7 +567,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>) export async function saveRepository(request, reply) { try { const { id } = request.params - let { repository, branch, projectId, autodeploy, webhookToken } = request.body + let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body repository = repository.toLowerCase(); branch = branch.toLowerCase(); @@ -565,17 +575,19 @@ export async function saveRepository(request, reply) { if (webhookToken) { await prisma.application.update({ where: { id }, - data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } } + data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } } }); } else { await prisma.application.update({ where: { id }, - data: { repository, branch, projectId, settings: { update: { autodeploy } } } + data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } } }); } - const isDouble = await checkDoubleBranch(branch, projectId); - if (isDouble) { - await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } }) + if (!isPublicRepository) { + const isDouble = await checkDoubleBranch(branch, projectId); + if (isDouble) { + await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } }) + } } return reply.code(201).send() } catch ({ status, message }) { @@ -607,7 +619,8 @@ export async function getBuildPack(request) { projectId: application.projectId, repository: application.repository, branch: application.branch, - apiUrl: application.gitSource.apiUrl + apiUrl: application.gitSource.apiUrl, + isPublicRepository: application.settings.isPublicRepository } } catch ({ status, message }) { return errorHandler({ status, message }) @@ -658,7 +671,7 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas throw { status: 500, message: `Secret ${name} already exists.` } } else { value = encrypt(value.trim()); - console.log({value}) + console.log({ value }) await prisma.secret.create({ data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } } }); diff --git a/apps/api/src/routes/api/v1/applications/types.ts b/apps/api/src/routes/api/v1/applications/types.ts index 1f06a37a8..f404e9bcb 100644 --- a/apps/api/src/routes/api/v1/applications/types.ts +++ b/apps/api/src/routes/api/v1/applications/types.ts @@ -50,7 +50,7 @@ export interface GetImages { Body: { buildPack: string, deploymentType: string } } export interface SaveApplicationSource extends OnlyId { - Body: { gitSourceId: string } + Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string } } export interface CheckRepository extends OnlyId { Querystring: { repository: string, branch: string } diff --git a/apps/ui/src/routes/applications/[id]/configuration/_PublicRepository.svelte b/apps/ui/src/routes/applications/[id]/configuration/_PublicRepository.svelte new file mode 100644 index 000000000..a2543e381 --- /dev/null +++ b/apps/ui/src/routes/applications/[id]/configuration/_PublicRepository.svelte @@ -0,0 +1,121 @@ +<script lang="ts"> + import { get, post } from '$lib/api'; + import { t } from '$lib/translations'; + import { page } from '$app/stores'; + + import Select from 'svelte-select'; + import Explainer from '$lib/components/Explainer.svelte'; + import { goto } from '$app/navigation'; + import { errorNotification } from '$lib/common'; + + const { id } = $page.params; + + let publicRepositoryLink: string = 'https://github.com/zekth/fastify-typescript-example'; + let projectId: number; + let repositoryName: string; + let branchName: string; + let ownerName: string; + let type: string; + let branchSelectOptions: any = []; + let loading = { + branches: false + }; + async function loadBranches() { + const protocol = publicRepositoryLink.split(':')[0]; + const gitUrl = publicRepositoryLink.replace('http://', '').replace('https://', ''); + let [host, ...path] = gitUrl.split('/'); + const [owner, repository, ...branch] = path; + ownerName = owner; + repositoryName = repository; + if (branch[0] === 'tree') { + branchName = branch[1]; + await saveRepository(); + return; + } + if (host === 'github.com') { + host = 'api.github.com'; + type = 'github'; + } + if (host === 'gitlab.com') { + host = 'gitlab.com/api/v4'; + type = 'gitlab'; + } + const apiUrl = `${protocol}://${host}`; + + const repositoryDetails = await get(`${apiUrl}/repos/${owner}/${repository}`); + projectId = repositoryDetails.id.toString(); + + let branches: any[] = []; + let page = 1; + let branchCount = 0; + loading.branches = true; + const loadedBranches = await loadBranchesByPage(apiUrl, owner, repository, page); + branches = branches.concat(loadedBranches); + branchCount = branches.length; + if (branchCount === 100) { + while (branchCount === 100) { + page = page + 1; + const nextBranches = await loadBranchesByPage(apiUrl, owner, repository, page); + branches = branches.concat(nextBranches); + branchCount = nextBranches.length; + } + } + loading.branches = false; + branchSelectOptions = branches.map((branch: any) => ({ + value: branch.name, + label: branch.name + })); + } + async function loadBranchesByPage(apiUrl: string, owner: string, repository: string, page = 1) { + return await get(`${apiUrl}/repos/${owner}/${repository}/branches?per_page=100&page=${page}`); + // console.log(publicRepositoryLink); + } + async function saveRepository(event?: any) { + try { + if (event?.detail?.value) { + branchName = event.detail.value; + } + await post(`/applications/${id}/configuration/source`, { + gitSourceId: null, + forPublic: true, + type + }); + await post(`/applications/${id}/configuration/repository`, { + repository: `${repositoryName}/${branchName}`, + branch: branchName, + projectId, + autodeploy: false, + webhookToken: null, + isPublicRepository: true + }); + return await goto(`/applications/${id}/configuration/destination`); + } catch (error) { + return errorNotification(error); + } + } +</script> + +<div class="title">Public repository link</div> +<Explainer text="Only works with Github.com and Gitlab.com" /> +<div> + <input bind:value={publicRepositoryLink} /> + <button on:click={loadBranches}>Load</button> + {#if branchSelectOptions.length > 0} + <div class="custom-select-wrapper"> + <Select + placeholder={loading.branches + ? $t('application.configuration.loading_branches') + : !publicRepositoryLink + ? $t('application.configuration.select_a_repository_first') + : $t('application.configuration.select_a_branch')} + isWaiting={loading.branches} + showIndicator={!!publicRepositoryLink && !loading.branches} + id="branches" + on:select={saveRepository} + items={branchSelectOptions} + isDisabled={loading.branches || !!!publicRepositoryLink} + isClearable={false} + /> + </div> + {/if} +</div> diff --git a/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte b/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte index e187d95bd..28e32c3d7 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte @@ -47,7 +47,8 @@ export let branch: any; export let type: any; export let application: any; - + export let isPublicRepository: boolean; + console.log(isPublicRepository) function checkPackageJSONContents({ key, json }: { key: any; json: any }) { return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key); } diff --git a/apps/ui/src/routes/applications/[id]/configuration/repository.svelte b/apps/ui/src/routes/applications/[id]/configuration/repository.svelte index edbed5916..002e1f74c 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/repository.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/repository.svelte @@ -48,3 +48,4 @@ <GitlabRepositories {application} {appId} {settings} /> {/if} </div> + diff --git a/apps/ui/src/routes/applications/[id]/configuration/source.svelte b/apps/ui/src/routes/applications/[id]/configuration/source.svelte index 030ba86fe..a8c404db3 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/source.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/source.svelte @@ -31,6 +31,7 @@ import { t } from '$lib/translations'; import { errorNotification } from '$lib/common'; import { appSession } from '$lib/store'; + import PublicRepository from './_PublicRepository.svelte'; const { id } = $page.params; const from = $page.url.searchParams.get('from'); @@ -188,3 +189,7 @@ </div> {/if} </div> + +<div class="flex flex-wrap justify-center pt-10 items-center"> + <PublicRepository /> +</div>