diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 82bed68d..03455332 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -619,25 +619,6 @@ export class Database { return domains; } - async getOldestDomainByProjectId( - projectId: string, - ): Promise { - const domainRepository = this.dataSource.getRepository(Domain); - - const domain = await domainRepository.findOne({ - where: { - project: { - id: projectId - }, - }, - order: { - createdAt: 'ASC' - } - }); - - return domain; - } - async getLatestDNSRecordByProjectId( projectId: string, ): Promise { diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 4ae6a5b8..3de3f497 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -7,7 +7,12 @@ import { DateTime } from 'luxon'; import { OAuthApp } from '@octokit/oauth-app'; import { Database } from './database'; -import { ApplicationRecord, Deployment, DeploymentStatus, Environment } from './entity/Deployment'; +import { + ApplicationRecord, + Deployment, + DeploymentStatus, + Environment, +} from './entity/Deployment'; import { Domain } from './entity/Domain'; import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { Organization } from './entity/Organization'; @@ -119,7 +124,8 @@ export class Service { } // Fetch ApplicationDeploymentRecord for deployments - const records = await this.laconicRegistry.getDeploymentRecords(deployments); + const records = + await this.laconicRegistry.getDeploymentRecords(deployments); log(`Found ${records.length} ApplicationDeploymentRecords`); // Update deployments for which ApplicationDeploymentRecords were returned @@ -204,7 +210,9 @@ export class Service { return; } - const registryRecord = await this.laconicRegistry.getRecordById(record.attributes.dns); + const registryRecord = await this.laconicRegistry.getRecordById( + record.attributes.dns, + ); if (!registryRecord) { log(`DNS record not found for deployment ${deployment.id}`); @@ -219,7 +227,7 @@ export class Service { resourceType: dnsRecord.attributes.resource_type, value: dnsRecord.attributes.value, version: dnsRecord.attributes.version, - } + }; deployment.applicationDeploymentRecordId = record.id; deployment.applicationDeploymentRecordData = record.attributes; @@ -239,18 +247,21 @@ export class Service { relations: { project: true, deployer: true, - } + }, }); if (previousCanonicalDeployment) { // Send removal request for the previous canonical deployment and delete DB entry if (previousCanonicalDeployment.url !== deployment.url) { - await this.laconicRegistry.createApplicationDeploymentRemovalRequest({ - deploymentId: previousCanonicalDeployment.applicationDeploymentRecordId!, - deployerLrn: previousCanonicalDeployment.deployer.deployerLrn, - auctionId: previousCanonicalDeployment.project.auctionId, - payment: previousCanonicalDeployment.project.txHash - }); + await this.laconicRegistry.createApplicationDeploymentRemovalRequest( + { + deploymentId: + previousCanonicalDeployment.applicationDeploymentRecordId!, + deployerLrn: previousCanonicalDeployment.deployer.deployerLrn, + auctionId: previousCanonicalDeployment.project.auctionId, + payment: previousCanonicalDeployment.project.txHash, + }, + ); } await this.db.deleteDeploymentById(previousCanonicalDeployment.id); @@ -261,7 +272,9 @@ export class Service { // Release deployer funds on successful deployment if (!deployment.project.fundsReleased) { - const fundsReleased = await this.releaseDeployerFundsByProjectId(deployment.projectId); + const fundsReleased = await this.releaseDeployerFundsByProjectId( + deployment.projectId, + ); // Return remaining amount to owner await this.returnUserFundsByProjectId(deployment.projectId, true); @@ -291,7 +304,7 @@ export class Service { const oldDeployments = projectDeployments.filter( (projectDeployment) => projectDeployment.deployer.deployerLrn === - deployment.deployer.deployerLrn && + deployment.deployer.deployerLrn && projectDeployment.id !== deployment.id && projectDeployment.isCanonical == deployment.isCanonical, ); @@ -485,12 +498,17 @@ export class Service { return dbProjects; } - async getNonCanonicalDeploymentsByProjectId(projectId: string): Promise { - const nonCanonicalDeployments = await this.db.getNonCanonicalDeploymentsByProjectId(projectId); + async getNonCanonicalDeploymentsByProjectId( + projectId: string, + ): Promise { + const nonCanonicalDeployments = + await this.db.getNonCanonicalDeploymentsByProjectId(projectId); return nonCanonicalDeployments; } - async getLatestDNSRecordByProjectId(projectId: string): Promise { + async getLatestDNSRecordByProjectId( + projectId: string, + ): Promise { const dnsRecord = await this.db.getLatestDNSRecordByProjectId(projectId); return dnsRecord; } @@ -655,7 +673,7 @@ export class Service { environment: Environment.Production, commitHash: oldDeployment.commitHash, commitMessage: oldDeployment.commitMessage, - deployer: oldDeployment.deployer + deployer: oldDeployment.deployer, }); } return newDeployment; @@ -665,7 +683,7 @@ export class Service { userId: string, octokit: Octokit, data: DeepPartial, - deployerLrn?: string + deployerLrn?: string, ): Promise { assert(data.project?.repository, 'Project repository not found'); log( @@ -688,34 +706,62 @@ export class Service { deployer = data.deployer; } - const deployment = await this.createDeploymentFromData(userId, data, deployer!.deployerLrn!, applicationRecordId, applicationRecordData, false); + const deployment = await this.createDeploymentFromData( + userId, + data, + deployer!.deployerLrn!, + applicationRecordId, + applicationRecordData, + false, + ); const address = await this.getAddress(); - const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash); - const environmentVariablesObj = await this.getEnvVariables(data.project!.id!); + const { repo, repoUrl } = await getRepoDetails( + octokit, + data.project.repository, + data.commitHash, + ); + const environmentVariablesObj = await this.getEnvVariables( + data.project!.id!, + ); // To set project DNS if (data.environment === Environment.Production) { - const canonicalDeployment = await this.createDeploymentFromData(userId, data, deployer!.deployerLrn!, applicationRecordId, applicationRecordData, true); + const canonicalDeployment = await this.createDeploymentFromData( + userId, + data, + deployer!.deployerLrn!, + applicationRecordId, + applicationRecordData, + true, + ); // If a custom domain is present then use that as the DNS in the deployment request - const customDomain = await this.db.getOldestDomainByProjectId(data.project!.id!); + const customDomains = await this.db.getDomainsByProjectId( + data.project!.id!, + ); + const dns = + customDomains.length > 0 + ? customDomains.map((d) => d.name).join(',') + : `${canonicalDeployment.project.name}`; // On deleting deployment later, project canonical deployment is also deleted // So publish project canonical deployment first so that ApplicationDeploymentRecord for the same is available when deleting deployment later - const { applicationDeploymentRequestData, applicationDeploymentRequestId } = - await this.laconicRegistry.createApplicationDeploymentRequest({ - deployment: canonicalDeployment, - appName: repo, - repository: repoUrl, - environmentVariables: environmentVariablesObj, - dns: customDomain?.name ?? `${canonicalDeployment.project.name}`, - lrn: deployer!.deployerLrn!, - apiUrl: deployer!.deployerApiUrl!, - payment: data.project.txHash, - auctionId: data.project.auctionId, - requesterAddress: address, - publicKey: deployer!.publicKey! - }); + const { + applicationDeploymentRequestData, + applicationDeploymentRequestId, + } = await this.laconicRegistry.createApplicationDeploymentRequest({ + deployment: canonicalDeployment, + appName: repo, + repository: repoUrl, + environmentVariables: environmentVariablesObj, + dns, + lrn: deployer!.deployerLrn!, + apiUrl: deployer!.deployerApiUrl!, + payment: data.project.txHash, + auctionId: data.project.auctionId, + requesterAddress: address, + publicKey: deployer!.publicKey!, + }); await this.db.updateDeploymentById(canonicalDeployment.id, { applicationDeploymentRequestId, @@ -735,7 +781,7 @@ export class Service { payment: data.project.txHash, auctionId: data.project.auctionId, requesterAddress: address, - publicKey: deployer!.publicKey! + publicKey: deployer!.publicKey!, }); await this.db.updateDeploymentById(deployment.id, { @@ -748,7 +794,7 @@ export class Service { async createDeploymentFromAuction( project: DeepPartial, - deployer: Deployer + deployer: Deployer, ): Promise { const octokit = await this.getOctokit(project.ownerId!); const [owner, repo] = project.repository!.split('/'); @@ -774,7 +820,7 @@ export class Service { const applicationRecordId = record.id; const applicationRecordData = record.attributes; - const deployerLrn = deployer!.deployerLrn + const deployerLrn = deployer!.deployerLrn; // Create deployment with prod branch and latest commit const deploymentData = { @@ -786,31 +832,50 @@ export class Service { commitMessage: latestCommit.commit.message, }; - const deployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData, false); + const deployment = await this.createDeploymentFromData( + project.ownerId!, + deploymentData, + deployerLrn, + applicationRecordId, + applicationRecordData, + false, + ); const address = await this.getAddress(); const environmentVariablesObj = await this.getEnvVariables(project!.id!); // To set project DNS if (deploymentData.environment === Environment.Production) { - const canonicalDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData, true); + const canonicalDeployment = await this.createDeploymentFromData( + project.ownerId!, + deploymentData, + deployerLrn, + applicationRecordId, + applicationRecordData, + true, + ); // If a custom domain is present then use that as the DNS in the deployment request - const customDomain = await this.db.getOldestDomainByProjectId(project!.id!); - + const customDomains = await this.db.getDomainsByProjectId(project!.id!); + const dns = + customDomains.length > 0 + ? customDomains.map((d) => d.name).join(',') + : `${canonicalDeployment.project.name}`; // On deleting deployment later, project canonical deployment is also deleted // So publish project canonical deployment first so that ApplicationDeploymentRecord for the same is available when deleting deployment later - const { applicationDeploymentRequestId, applicationDeploymentRequestData } = - await this.laconicRegistry.createApplicationDeploymentRequest({ - deployment: canonicalDeployment, - appName: repo, - repository: repoUrl, - environmentVariables: environmentVariablesObj, - dns: customDomain?.name ?? `${canonicalDeployment.project.name}`, - auctionId: project.auctionId!, - lrn: deployerLrn, - apiUrl: deployer!.deployerApiUrl!, - requesterAddress: address, - publicKey: deployer!.publicKey! - }); + const { + applicationDeploymentRequestId, + applicationDeploymentRequestData, + } = await this.laconicRegistry.createApplicationDeploymentRequest({ + deployment: canonicalDeployment, + appName: repo, + repository: repoUrl, + environmentVariables: environmentVariablesObj, + dns, + auctionId: project.auctionId!, + lrn: deployerLrn, + apiUrl: deployer!.deployerApiUrl!, + requesterAddress: address, + publicKey: deployer!.publicKey!, + }); await this.db.updateDeploymentById(canonicalDeployment.id, { applicationDeploymentRequestId, @@ -830,7 +895,7 @@ export class Service { environmentVariables: environmentVariablesObj, dns: `${deployment.project.name}-${deployment.id}`, requesterAddress: address, - publicKey: deployer!.publicKey! + publicKey: deployer!.publicKey!, }); await this.db.updateDeploymentById(deployment.id, { @@ -864,7 +929,7 @@ export class Service { deployer: Object.assign(new Deployer(), { deployerLrn, }), - isCanonical + isCanonical, }); log(`Created deployment ${newDeployment.id}`); @@ -874,11 +939,11 @@ export class Service { async updateProjectWithDeployer( projectId: string, - deployer: Deployer + deployer: Deployer, ): Promise { const deploymentProject = await this.db.getProjects({ where: { id: projectId }, - relations: ['deployers'] + relations: ['deployers'], }); if (!deploymentProject[0].deployers) { @@ -923,15 +988,22 @@ export class Service { const prodBranch = createdTemplateRepo.data.default_branch ?? 'main'; - const project = await this.addProject(user, organizationSlug, { - name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`, - prodBranch, - repository: gitRepo.data.full_name, - // TODO: Set selected template - template: 'webapp', - paymentAddress: data.paymentAddress, - txHash: data.txHash - }, lrn, auctionParams, environmentVariables); + const project = await this.addProject( + user, + organizationSlug, + { + name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`, + prodBranch, + repository: gitRepo.data.full_name, + // TODO: Set selected template + template: 'webapp', + paymentAddress: data.paymentAddress, + txHash: data.txHash, + }, + lrn, + auctionParams, + environmentVariables, + ); if (!project || !project.id) { throw new Error('Failed to create project from template'); @@ -1172,19 +1244,20 @@ export class Service { let newDeployment: Deployment; if (oldDeployment.project.auctionId) { - newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployer); - } else { - newDeployment = await this.createDeployment(user.id, octokit, - { - project: oldDeployment.project, - // TODO: Put isCurrent field in project - branch: oldDeployment.branch, - environment: Environment.Production, - commitHash: oldDeployment.commitHash, - commitMessage: oldDeployment.commitMessage, - deployer: oldDeployment.deployer - } + newDeployment = await this.createDeploymentFromAuction( + oldDeployment.project, + oldDeployment.deployer, ); + } else { + newDeployment = await this.createDeployment(user.id, octokit, { + project: oldDeployment.project, + // TODO: Put isCurrent field in project + branch: oldDeployment.branch, + environment: Environment.Production, + commitHash: oldDeployment.commitHash, + commitMessage: oldDeployment.commitMessage, + deployer: oldDeployment.deployer, + }); } return newDeployment; @@ -1227,22 +1300,28 @@ export class Service { { isCurrent: true }, ); - if (!newCurrentDeploymentUpdate || !oldCurrentDeploymentUpdate){ + if (!newCurrentDeploymentUpdate || !oldCurrentDeploymentUpdate) { return false; } - const newCurrentDeployment = await this.db.getDeployment({ where: { id: deploymentId }, relations: { project: true, deployer: true } }); + const newCurrentDeployment = await this.db.getDeployment({ + where: { id: deploymentId }, + relations: { project: true, deployer: true }, + }); if (!newCurrentDeployment) { throw new Error(`Deployment with Id ${deploymentId} not found`); } - const applicationDeploymentRequestData = newCurrentDeployment.applicationDeploymentRequestData; + const applicationDeploymentRequestData = + newCurrentDeployment.applicationDeploymentRequestData; - const customDomain = await this.db.getOldestDomainByProjectId(projectId); + const customDomains = await this.db.getDomainsByProjectId(projectId); - if (customDomain && applicationDeploymentRequestData) { - applicationDeploymentRequestData.dns = customDomain.name + if (customDomains.length > 0 && applicationDeploymentRequestData) { + applicationDeploymentRequestData.dns = customDomains + .map((d) => d.name) + .join(','); } // Create a canonical deployment for the new current deployment @@ -1258,20 +1337,23 @@ export class Service { applicationDeploymentRequestData!.meta = JSON.stringify({ ...JSON.parse(applicationDeploymentRequestData!.meta), note: `Updated by Snowball @ ${DateTime.utc().toFormat( - "EEE LLL dd HH:mm:ss 'UTC' yyyy" - )}` + "EEE LLL dd HH:mm:ss 'UTC' yyyy", + )}`, }); const result = await this.laconicRegistry.publishRecord( applicationDeploymentRequestData, ); - log(`Application deployment request record published: ${result.id}`) + log(`Application deployment request record published: ${result.id}`); - const updateResult = await this.db.updateDeploymentById(canonicalDeployment.id, { - applicationDeploymentRequestId: result.id, - applicationDeploymentRequestData, - }); + const updateResult = await this.db.updateDeploymentById( + canonicalDeployment.id, + { + applicationDeploymentRequestId: result.id, + applicationDeploymentRequestData, + }, + ); return updateResult; }