diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 82bed68d..70533256 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -5,7 +5,7 @@ import { FindOneOptions, FindOptionsWhere, IsNull, - Not + Not, } from 'typeorm'; import path from 'path'; import debug from 'debug'; @@ -16,7 +16,7 @@ import { lowercase, numbers } from 'nanoid-dictionary'; import { DatabaseConfig } from './config'; import { User } from './entity/User'; import { Organization } from './entity/Organization'; -import { Project } from './entity/Project'; +import { AuctionStatus, Project } from './entity/Project'; import { Deployment, DeploymentStatus } from './entity/Deployment'; import { ProjectMember } from './entity/ProjectMember'; import { EnvironmentVariable } from './entity/EnvironmentVariable'; @@ -42,7 +42,7 @@ export class Database { database: dbPath, entities: [path.join(__dirname, '/entity/*')], synchronize: true, - logging: false + logging: false, }); } @@ -54,21 +54,24 @@ export class Database { // Load an organization if none exist if (!organizations.length) { - const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); - organizations = await loadAndSaveData(Organization, this.dataSource, [orgEntities[0]]); + const orgEntities = await getEntities( + path.resolve(__dirname, ORGANIZATION_DATA_PATH), + ); + organizations = await loadAndSaveData(Organization, this.dataSource, [ + orgEntities[0], + ]); } // Hotfix for updating old DB data if (organizations[0].slug === 'snowball-tools-1') { - const [orgEntity] = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); + const [orgEntity] = await getEntities( + path.resolve(__dirname, ORGANIZATION_DATA_PATH), + ); - await this.updateOrganization( - organizations[0].id, - { - slug: orgEntity.slug as string, - name: orgEntity.name as string - } - ) + await this.updateOrganization(organizations[0].id, { + slug: orgEntity.slug as string, + name: orgEntity.name as string, + }); } } @@ -95,7 +98,7 @@ export class Database { } async getOrganizations( - options: FindManyOptions + options: FindManyOptions, ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const organizations = await organizationRepository.find(options); @@ -104,7 +107,7 @@ export class Database { } async getOrganization( - options: FindOneOptions + options: FindOneOptions, ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const organization = await organizationRepository.findOne(options); @@ -119,25 +122,34 @@ export class Database { where: { userOrganizations: { member: { - id: userId - } - } - } + id: userId, + }, + }, + }, }); return userOrgs; } - async addUserOrganization(data: DeepPartial): Promise { - const userOrganizationRepository = this.dataSource.getRepository(UserOrganization); + async addUserOrganization( + data: DeepPartial, + ): Promise { + const userOrganizationRepository = + this.dataSource.getRepository(UserOrganization); const newUserOrganization = await userOrganizationRepository.save(data); return newUserOrganization; } - async updateOrganization(organizationId: string, data: DeepPartial): Promise { + async updateOrganization( + organizationId: string, + data: DeepPartial, + ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); - const updateResult = await organizationRepository.update({ id: organizationId }, data); + const updateResult = await organizationRepository.update( + { id: organizationId }, + data, + ); assert(updateResult.affected); return updateResult.affected > 0; @@ -158,7 +170,7 @@ export class Database { .leftJoinAndSelect( 'project.deployments', 'deployments', - 'deployments.isCurrent = true AND deployments.isCanonical = true' + 'deployments.isCurrent = true AND deployments.isCanonical = true', ) .leftJoinAndSelect('deployments.createdBy', 'user') .leftJoinAndSelect('deployments.deployer', 'deployer') @@ -166,7 +178,7 @@ export class Database { .leftJoinAndSelect('project.deployers', 'deployers') .leftJoinAndSelect('project.organization', 'organization') .where('project.id = :projectId', { - projectId + projectId, }) .getOne(); @@ -174,26 +186,28 @@ export class Database { } async allProjectsWithoutDeployments(): Promise { + // Fetch all projects with auction not completed and wihout any deployments const allProjects = await this.getProjects({ where: { auctionId: Not(IsNull()), + auctionStatus: Not(AuctionStatus.Completed), }, relations: ['deployments'], withDeleted: true, }); - const projects = allProjects.filter(project => { + const activeProjectsWithoutDeployments = allProjects.filter((project) => { if (project.deletedAt !== null) return false; return project.deployments.length === 0; }); - return projects; + return activeProjectsWithoutDeployments; } async getProjectsInOrganization( userId: string, - organizationSlug: string + organizationSlug: string, ): Promise { const projectRepository = this.dataSource.getRepository(Project); @@ -202,7 +216,7 @@ export class Database { .leftJoinAndSelect( 'project.deployments', 'deployments', - 'deployments.isCurrent = true AND deployments.isCanonical = true' + 'deployments.isCurrent = true AND deployments.isCanonical = true', ) .leftJoin('project.projectMembers', 'projectMembers') .leftJoin('project.organization', 'organization') @@ -210,8 +224,8 @@ export class Database { '(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug', { userId, - organizationSlug - } + organizationSlug, + }, ) .getMany(); @@ -222,7 +236,7 @@ export class Database { * Get deployments with specified filter */ async getDeployments( - options: FindManyOptions + options: FindManyOptions, ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployments = await deploymentRepository.find(options); @@ -239,16 +253,18 @@ export class Database { }, where: { project: { - id: projectId - } + id: projectId, + }, }, order: { - createdAt: 'DESC' - } + createdAt: 'DESC', + }, }); } - async getNonCanonicalDeploymentsByProjectId(projectId: string): Promise { + async getNonCanonicalDeploymentsByProjectId( + projectId: string, + ): Promise { return this.getDeployments({ relations: { project: true, @@ -257,18 +273,18 @@ export class Database { }, where: { project: { - id: projectId + id: projectId, }, - isCanonical: false + isCanonical: false, }, order: { - createdAt: 'DESC' - } + createdAt: 'DESC', + }, }); } async getDeployment( - options: FindOneOptions + options: FindOneOptions, ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployment = await deploymentRepository.findOne(options); @@ -290,7 +306,7 @@ export class Database { const updatedData = { ...data, - id + id, }; const deployment = await deploymentRepository.save(updatedData); @@ -298,7 +314,7 @@ export class Database { } async getProjectMembersByProjectId( - projectId: string + projectId: string, ): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); @@ -306,13 +322,13 @@ export class Database { const projectMembers = await projectMemberRepository.find({ relations: { project: true, - member: true + member: true, }, where: { project: { - id: projectId - } - } + id: projectId, + }, + }, }); return projectMembers; @@ -320,7 +336,7 @@ export class Database { async getEnvironmentVariablesByProjectId( projectId: string, - filter?: FindOptionsWhere + filter?: FindOptionsWhere, ): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); @@ -328,10 +344,10 @@ export class Database { const environmentVariables = await environmentVariableRepository.find({ where: { project: { - id: projectId + id: projectId, }, - ...filter - } + ...filter, + }, }); return environmentVariables; @@ -342,7 +358,7 @@ export class Database { this.dataSource.getRepository(ProjectMember); const deleteResult = await projectMemberRepository.delete({ - id: projectMemberId + id: projectMemberId, }); if (deleteResult.affected) { @@ -354,20 +370,20 @@ export class Database { async updateProjectMemberById( projectMemberId: string, - data: DeepPartial + data: DeepPartial, ): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); const updateResult = await projectMemberRepository.update( { id: projectMemberId }, - data + data, ); return Boolean(updateResult.affected); } async addProjectMember( - data: DeepPartial + data: DeepPartial, ): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); @@ -377,7 +393,7 @@ export class Database { } async addEnvironmentVariables( - data: DeepPartial[] + data: DeepPartial[], ): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); @@ -389,25 +405,25 @@ export class Database { async updateEnvironmentVariable( environmentVariableId: string, - data: DeepPartial + data: DeepPartial, ): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); const updateResult = await environmentVariableRepository.update( { id: environmentVariableId }, - data + data, ); return Boolean(updateResult.affected); } async deleteEnvironmentVariable( - environmentVariableId: string + environmentVariableId: string, ): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); const deleteResult = await environmentVariableRepository.delete({ - id: environmentVariableId + id: environmentVariableId, }); if (deleteResult.affected) { @@ -424,13 +440,13 @@ export class Database { const projectMemberWithProject = await projectMemberRepository.find({ relations: { project: { - owner: true + owner: true, }, - member: true + member: true, }, where: { - id: projectMemberId - } + id: projectMemberId, + }, }); if (projectMemberWithProject.length === 0) { @@ -442,7 +458,7 @@ export class Database { async getProjectsBySearchText( userId: string, - searchText: string + searchText: string, ): Promise { const projectRepository = this.dataSource.getRepository(Project); @@ -454,8 +470,8 @@ export class Database { '(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText', { userId, - searchText: `%${searchText}%` - } + searchText: `%${searchText}%`, + }, ) .getMany(); @@ -464,14 +480,14 @@ export class Database { async updateDeploymentById( deploymentId: string, - data: DeepPartial + data: DeepPartial, ): Promise { return this.updateDeployment({ id: deploymentId }, data); } async updateDeployment( criteria: FindOptionsWhere, - data: DeepPartial + data: DeepPartial, ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const updateResult = await deploymentRepository.update(criteria, data); @@ -481,7 +497,7 @@ export class Database { async updateDeploymentsByProjectIds( projectIds: string[], - data: DeepPartial + data: DeepPartial, ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); @@ -499,8 +515,8 @@ export class Database { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployment = await deploymentRepository.findOneOrFail({ where: { - id: deploymentId - } + id: deploymentId, + }, }); const deleteResult = await deploymentRepository.softRemove(deployment); @@ -508,7 +524,11 @@ export class Database { return Boolean(deleteResult); } - async addProject(user: User, organizationId: string, data: DeepPartial): Promise { + async addProject( + user: User, + organizationId: string, + data: DeepPartial, + ): Promise { const projectRepository = this.dataSource.getRepository(Project); // TODO: Check if organization exists @@ -521,7 +541,7 @@ export class Database { newProject.owner = user; newProject.organization = Object.assign(new Organization(), { - id: organizationId + id: organizationId, }); return projectRepository.save(newProject); @@ -535,12 +555,12 @@ export class Database { async updateProjectById( projectId: string, - data: DeepPartial + data: DeepPartial, ): Promise { const projectRepository = this.dataSource.getRepository(Project); const updateResult = await projectRepository.update( { id: projectId }, - data + data, ); return Boolean(updateResult.affected); @@ -550,11 +570,11 @@ export class Database { const projectRepository = this.dataSource.getRepository(Project); const project = await projectRepository.findOneOrFail({ where: { - id: projectId + id: projectId, }, relations: { - projectMembers: true - } + projectMembers: true, + }, }); const deleteResult = await projectRepository.softRemove(project); @@ -590,7 +610,7 @@ export class Database { async updateDomainById( domainId: string, - data: DeepPartial + data: DeepPartial, ): Promise { const domainRepository = this.dataSource.getRepository(Domain); const updateResult = await domainRepository.update({ id: domainId }, data); @@ -600,39 +620,37 @@ export class Database { async getDomainsByProjectId( projectId: string, - filter?: FindOptionsWhere + filter?: FindOptionsWhere, ): Promise { const domainRepository = this.dataSource.getRepository(Domain); const domains = await domainRepository.find({ relations: { - redirectTo: true + redirectTo: true, }, where: { project: { - id: projectId + id: projectId, }, - ...filter - } + ...filter, + }, }); return domains; } - async getOldestDomainByProjectId( - projectId: string, - ): Promise { + async getOldestDomainByProjectId(projectId: string): Promise { const domainRepository = this.dataSource.getRepository(Domain); const domain = await domainRepository.findOne({ where: { project: { - id: projectId + id: projectId, }, }, order: { - createdAt: 'ASC' - } + createdAt: 'ASC', + }, }); return domain; @@ -651,8 +669,8 @@ export class Database { status: DeploymentStatus.Ready, }, order: { - createdAt: 'DESC' - } + createdAt: 'DESC', + }, }); if (deployment === null) { @@ -677,7 +695,9 @@ export class Database { async getDeployerByLRN(deployerLrn: string): Promise { const deployerRepository = this.dataSource.getRepository(Deployer); - const deployer = await deployerRepository.findOne({ where: { deployerLrn } }); + const deployer = await deployerRepository.findOne({ + where: { deployerLrn }, + }); return deployer; } diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index a27b0f06..a853dbe5 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -9,7 +9,7 @@ import { OneToMany, DeleteDateColumn, JoinTable, - ManyToMany + ManyToMany, } from 'typeorm'; import { User } from './User'; @@ -18,6 +18,12 @@ import { ProjectMember } from './ProjectMember'; import { Deployment } from './Deployment'; import { Deployer } from './Deployer'; +export enum AuctionStatus { + Commit = 'commit', + Reveal = 'reveal', + Completed = 'completed', +} + @Entity() export class Project { @PrimaryGeneratedColumn('uuid') @@ -49,16 +55,23 @@ export class Project { @Column('text', { default: '' }) description!: string; - @Column('varchar', { nullable: true }) - auctionId!: string | null; - // Tx hash for sending coins from snowball to deployer @Column('varchar', { nullable: true }) txHash!: string | null; - @ManyToMany(() => Deployer, (deployer) => (deployer.projects)) + @ManyToMany(() => Deployer, (deployer) => deployer.projects) @JoinTable() - deployers!: Deployer[] + deployers!: Deployer[]; + + @Column('varchar', { nullable: true }) + auctionId!: string | null; + + @Column({ + enum: AuctionStatus, + // TODO: Remove later after all projects auction status have been set + default: AuctionStatus.Completed, + }) + auctionStatus!: AuctionStatus; @Column('boolean', { default: false, nullable: true }) fundsReleased!: boolean; diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index c7d8b8e0..52cbcb9e 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -6,7 +6,13 @@ import { inc as semverInc } from 'semver'; import { DeepPartial } from 'typeorm'; import * as openpgp from 'openpgp'; -import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk'; +import { + Account, + DEFAULT_GAS_ESTIMATION_MULTIPLIER, + Registry as LaconicRegistry, + getGasPrice, + parseGasAndFees, +} from '@cerc-io/registry-sdk'; import { DeliverTxResponse, IndexedTx } from '@cosmjs/stargate'; import { RegistryConfig } from './config'; @@ -14,17 +20,30 @@ import { ApplicationRecord, Deployment, ApplicationDeploymentRequest, - ApplicationDeploymentRemovalRequest + ApplicationDeploymentRemovalRequest, } from './entity/Deployment'; -import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord, RegistryRecord } from './types'; -import { getConfig, getRepoDetails, registryTransactionWithRetry, sleep } from './utils'; +import { + AppDeploymentRecord, + AppDeploymentRemovalRecord, + AuctionParams, + DeployerRecord, + RegistryRecord, +} from './types'; +import { + getConfig, + getRepoDetails, + registryTransactionWithRetry, + sleep, +} from './utils'; +import { MsgCreateAuctionResponse } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/tx'; const log = debug('snowball:registry'); const APP_RECORD_TYPE = 'ApplicationRecord'; const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction'; const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; -const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest'; +const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = + 'ApplicationDeploymentRemovalRequest'; const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord'; const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer'; @@ -43,7 +62,7 @@ export class Registry { this.registry = new LaconicRegistry( registryConfig.gqlEndpoint, registryConfig.restEndpoint, - { chainId: registryConfig.chainId, gasPrice } + { chainId: registryConfig.chainId, gasPrice }, ); } @@ -53,7 +72,7 @@ export class Registry { commitHash, appType, }: { - octokit: Octokit + octokit: Octokit; repository: string; commitHash: string; appType: string; @@ -61,29 +80,33 @@ export class Registry { applicationRecordId: string; applicationRecordData: ApplicationRecord; }> { - const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash) + const { repo, repoUrl, packageJSON } = await getRepoDetails( + octokit, + repository, + commitHash, + ); // Use registry-sdk to publish record // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Fetch previous records const records = await this.registry.queryRecords( { type: APP_RECORD_TYPE, - name: packageJSON.name + name: packageJSON.name, }, - true + true, ); // Get next version of record const bondRecords = records.filter( - (record: any) => record.bondId === this.registryConfig.bondId + (record: any) => record.bondId === this.registryConfig.bondId, ); const [latestBondRecord] = bondRecords.sort( (a: any, b: any) => - new Date(b.createTime).getTime() - new Date(a.createTime).getTime() + new Date(b.createTime).getTime() - new Date(a.createTime).getTime(), ); const nextVersion = semverInc( latestBondRecord?.attributes.version ?? '0.0.0', - 'patch' + 'patch', ); assert(nextVersion, 'Application record version not valid'); @@ -103,9 +126,9 @@ export class Registry { author: typeof packageJSON.author === 'object' ? JSON.stringify(packageJSON.author) - : packageJSON.author + : packageJSON.author, }), - ...(packageJSON.version && { app_version: packageJSON.version }) + ...(packageJSON.version && { app_version: packageJSON.version }), }; const result = await this.publishRecord(applicationRecord); @@ -117,18 +140,9 @@ export class Registry { const lrn = this.getLrn(repo); log(`Setting name: ${lrn} for record ID: ${result.id}`); - const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees); - - await sleep(SLEEP_DURATION); - await registryTransactionWithRetry(() => - this.registry.setName( - { - cid: result.id, - lrn - }, - this.registryConfig.privateKey, - fee - ) + const fee = parseGasAndFees( + this.registryConfig.fee.gas, + this.registryConfig.fee.fees, ); await sleep(SLEEP_DURATION); @@ -136,11 +150,11 @@ export class Registry { this.registry.setName( { cid: result.id, - lrn: `${lrn}@${applicationRecord.app_version}` + lrn, }, this.registryConfig.privateKey, - fee - ) + fee, + ), ); await sleep(SLEEP_DURATION); @@ -148,16 +162,28 @@ export class Registry { this.registry.setName( { cid: result.id, - lrn: `${lrn}@${applicationRecord.repository_ref}` + lrn: `${lrn}@${applicationRecord.app_version}`, }, this.registryConfig.privateKey, - fee - ) + fee, + ), + ); + + await sleep(SLEEP_DURATION); + await registryTransactionWithRetry(() => + this.registry.setName( + { + cid: result.id, + lrn: `${lrn}@${applicationRecord.repository_ref}`, + }, + this.registryConfig.privateKey, + fee, + ), ); return { applicationRecordId: result.id, - applicationRecordData: applicationRecord + applicationRecordData: applicationRecord, }; } @@ -167,7 +193,7 @@ export class Registry { auctionParams: AuctionParams, data: DeepPartial, ): Promise<{ - applicationDeploymentAuctionId: string; + applicationDeploymentAuction: MsgCreateAuctionResponse['auction']; }> { assert(data.project?.repository, 'Project repository not found'); @@ -182,8 +208,11 @@ export class Registry { const config = await getConfig(); const auctionConfig = config.auction; - const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees); - const auctionResult = await registryTransactionWithRetry(() => + const fee = parseGasAndFees( + this.registryConfig.fee.gas, + this.registryConfig.fee.fees, + ); + const auctionResult = (await registryTransactionWithRetry(() => this.registry.createProviderAuction( { commitFee: auctionConfig.commitFee, @@ -195,9 +224,9 @@ export class Registry { numProviders: auctionParams.numProviders, }, this.registryConfig.privateKey, - fee - ) - ); + fee, + ), + )) as MsgCreateAuctionResponse; if (!auctionResult.auction) { throw new Error('Error creating auction'); @@ -217,22 +246,22 @@ export class Registry { log('Application deployment auction data:', applicationDeploymentAuction); return { - applicationDeploymentAuctionId: auctionResult.auction.id, + applicationDeploymentAuction: auctionResult.auction!, }; } async createApplicationDeploymentRequest(data: { - deployment: Deployment, - appName: string, - repository: string, - auctionId?: string | null, - lrn: string, - apiUrl: string, - environmentVariables: { [key: string]: string }, - dns: string, - requesterAddress: string, - publicKey: string, - payment?: string | null + deployment: Deployment; + appName: string; + repository: string; + auctionId?: string | null; + lrn: string; + apiUrl: string; + environmentVariables: { [key: string]: string }; + dns: string; + requesterAddress: string; + publicKey: string; + payment?: string | null; }): Promise<{ applicationDeploymentRequestId: string; applicationDeploymentRequestData: ApplicationDeploymentRequest; @@ -267,10 +296,10 @@ export class Registry { config: JSON.stringify(hash ? { ref: hash } : {}), meta: JSON.stringify({ note: `Added by Snowball @ ${DateTime.utc().toFormat( - "EEE LLL dd HH:mm:ss 'UTC' yyyy" + "EEE LLL dd HH:mm:ss 'UTC' yyyy", )}`, repository: data.repository, - repository_ref: data.deployment.commitHash + repository_ref: data.deployment.commitHash, }), deployer: data.lrn, ...(data.auctionId && { auction: data.auctionId }), @@ -286,12 +315,12 @@ export class Registry { return { applicationDeploymentRequestId: result.id, - applicationDeploymentRequestData: applicationDeploymentRequest + applicationDeploymentRequestData: applicationDeploymentRequest, }; } async getAuctionWinningDeployerRecords( - auctionId: string + auctionId: string, ): Promise { const records = await this.registry.getAuctionsByIds([auctionId]); const auctionResult = records[0]; @@ -304,7 +333,7 @@ export class Registry { paymentAddress: auctionWinner, }); - const newRecords = records.filter(record => { + const newRecords = records.filter((record) => { return record.names !== null && record.names.length > 0; }); @@ -319,18 +348,19 @@ export class Registry { return deployerRecords; } - async releaseDeployerFunds( - auctionId: string - ): Promise { - const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees); + async releaseDeployerFunds(auctionId: string): Promise { + const fee = parseGasAndFees( + this.registryConfig.fee.gas, + this.registryConfig.fee.fees, + ); const auction = await registryTransactionWithRetry(() => this.registry.releaseFunds( { - auctionId + auctionId, }, this.registryConfig.privateKey, - fee - ) + fee, + ), ); return auction; @@ -340,49 +370,54 @@ export class Registry { * Fetch ApplicationDeploymentRecords for deployments */ async getDeploymentRecords( - deployments: Deployment[] + deployments: Deployment[], ): Promise { // Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments // TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute const records = await this.registry.queryRecords( { - type: APP_DEPLOYMENT_RECORD_TYPE + type: APP_DEPLOYMENT_RECORD_TYPE, }, - true + true, ); // Filter records with ApplicationDeploymentRequestId ID return records.filter((record: AppDeploymentRecord) => deployments.some( (deployment) => - deployment.applicationDeploymentRequestId === record.attributes.request - ) + deployment.applicationDeploymentRequestId === + record.attributes.request, + ), ); } /** * Fetch WebappDeployer Records by filter */ - async getDeployerRecordsByFilter(filter: { [key: string]: any }): Promise { + async getDeployerRecordsByFilter(filter: { + [key: string]: any; + }): Promise { return this.registry.queryRecords( { type: WEBAPP_DEPLOYER_RECORD_TYPE, - ...filter + ...filter, }, - true + true, ); } /** * Fetch ApplicationDeploymentRecords by filter */ - async getDeploymentRecordsByFilter(filter: { [key: string]: any }): Promise { + async getDeploymentRecordsByFilter(filter: { + [key: string]: any; + }): Promise { return this.registry.queryRecords( { type: APP_DEPLOYMENT_RECORD_TYPE, - ...filter + ...filter, }, - true + true, ); } @@ -390,29 +425,31 @@ export class Registry { * Fetch ApplicationDeploymentRemovalRecords for deployments */ async getDeploymentRemovalRecords( - deployments: Deployment[] + deployments: Deployment[], ): Promise { // Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments const records = await this.registry.queryRecords( { - type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE + type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE, }, - true + true, ); // Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs return records.filter((record: AppDeploymentRemovalRecord) => deployments.some( (deployment) => - deployment.applicationDeploymentRemovalRequestId === record.attributes.request && - deployment.applicationDeploymentRecordId === record.attributes.deployment - ) + deployment.applicationDeploymentRemovalRequestId === + record.attributes.request && + deployment.applicationDeploymentRecordId === + record.attributes.deployment, + ), ); } /** - * Fetch record by Id - */ + * Fetch record by Id + */ async getRecordById(id: string): Promise { const [record] = await this.registry.getRecordsByIds([id]); return record ?? null; @@ -440,12 +477,18 @@ export class Registry { applicationDeploymentRemovalRequest, ); - log(`Application deployment removal request record published: ${result.id}`); - log('Application deployment removal request data:', applicationDeploymentRemovalRequest); + log( + `Application deployment removal request record published: ${result.id}`, + ); + log( + 'Application deployment removal request data:', + applicationDeploymentRemovalRequest, + ); return { applicationDeploymentRemovalRequestId: result.id, - applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest + applicationDeploymentRemovalRequestData: + applicationDeploymentRemovalRequest, }; } @@ -457,8 +500,11 @@ export class Registry { const auctions = await this.registry.getAuctionsByIds(auctionIds); const completedAuctions = auctions - .filter((auction: { id: string, status: string }) => auction.status === 'completed') - .map((auction: { id: string, status: string }) => auction.id); + .filter( + (auction: { id: string; status: string }) => + auction.status === 'completed', + ) + .map((auction: { id: string; status: string }) => auction.id); return completedAuctions; } @@ -492,27 +538,38 @@ export class Registry { return this.registry.getAuctionsByIds([auctionId]); } - async sendTokensToAccount(receiverAddress: string, amount: string): Promise { - const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees); + async sendTokensToAccount( + receiverAddress: string, + amount: string, + ): Promise { + const fee = parseGasAndFees( + this.registryConfig.fee.gas, + this.registryConfig.fee.fees, + ); const account = await this.getAccount(); const laconicClient = await this.registry.getLaconicClient(account); - const txResponse: DeliverTxResponse = - await registryTransactionWithRetry(() => - laconicClient.sendTokens(account.address, receiverAddress, + const txResponse: DeliverTxResponse = await registryTransactionWithRetry( + () => + laconicClient.sendTokens( + account.address, + receiverAddress, [ { denom: 'alnt', - amount - } + amount, + }, ], - fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER) - ); + fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER, + ), + ); return txResponse; } async getAccount(): Promise { - const account = new Account(Buffer.from(this.registryConfig.privateKey, 'hex')); + const account = new Account( + Buffer.from(this.registryConfig.privateKey, 'hex'), + ); await account.init(); return account; diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 0321df01..a5cff735 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -7,11 +7,16 @@ 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'; -import { Project } from './entity/Project'; +import { AuctionStatus, Project } from './entity/Project'; import { Permission, ProjectMember } from './entity/ProjectMember'; import { User } from './entity/User'; import { Registry } from './registry'; @@ -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, ); @@ -356,24 +369,30 @@ export class Service { async checkAuctionStatus(): Promise { const projects = await this.db.allProjectsWithoutDeployments(); - const validAuctionIds = projects.map((project) => project.auctionId) + const validAuctionIds = projects + .map((project) => project.auctionId) .filter((id): id is string => Boolean(id)); - const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(validAuctionIds); + const completedAuctionIds = + await this.laconicRegistry.getCompletedAuctionIds(validAuctionIds); const projectsToBedeployed = projects.filter((project) => - completedAuctionIds.includes(project.auctionId!) + completedAuctionIds.includes(project.auctionId!), ); for (const project of projectsToBedeployed) { - const deployerRecords = await this.laconicRegistry.getAuctionWinningDeployerRecords(project!.auctionId!); + const deployerRecords = + await this.laconicRegistry.getAuctionWinningDeployerRecords( + project!.auctionId!, + ); if (!deployerRecords) { log(`No winning deployer for auction ${project!.auctionId}`); // Return all funds to the owner - await this.returnUserFundsByProjectId(project.id, false) + await this.returnUserFundsByProjectId(project.id, false); } else { - const deployers = await this.saveDeployersByDeployerRecords(deployerRecords); + const deployers = + await this.saveDeployersByDeployerRecords(deployerRecords); for (const deployer of deployers) { log(`Creating deployment for deployer ${deployer.deployerLrn}`); await this.createDeploymentFromAuction(project, deployer); @@ -381,6 +400,10 @@ export class Service { await this.updateProjectWithDeployer(project.id, deployer); } } + + await this.updateProject(project.id, { + auctionStatus: AuctionStatus.Completed, + }); } this.auctionStatusCheckTimeout = setTimeout(() => { @@ -485,12 +508,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; } @@ -650,7 +678,7 @@ export class Service { environment: Environment.Production, commitHash: oldDeployment.commitHash, commitMessage: oldDeployment.commitMessage, - deployer: oldDeployment.deployer + deployer: oldDeployment.deployer, }); return newDeployment; @@ -660,7 +688,7 @@ export class Service { userId: string, octokit: Octokit, data: DeepPartial, - deployerLrn?: string + deployerLrn?: string, ): Promise { assert(data.project?.repository, 'Project repository not found'); log( @@ -683,34 +711,58 @@ 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 customDomain = await this.db.getOldestDomainByProjectId( + data.project!.id!, + ); // 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: customDomain?.name ?? `${canonicalDeployment.project.name}`, + 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, @@ -730,7 +782,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, { @@ -743,7 +795,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('/'); @@ -769,7 +821,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 = { @@ -781,31 +833,49 @@ 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 customDomain = await this.db.getOldestDomainByProjectId( + project!.id!, + ); // 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: customDomain?.name ?? `${canonicalDeployment.project.name}`, + auctionId: project.auctionId!, + lrn: deployerLrn, + apiUrl: deployer!.deployerApiUrl!, + requesterAddress: address, + publicKey: deployer!.publicKey!, + }); await this.db.updateDeploymentById(canonicalDeployment.id, { applicationDeploymentRequestId, @@ -825,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, { @@ -859,7 +929,7 @@ export class Service { deployer: Object.assign(new Deployer(), { deployerLrn, }), - isCanonical + isCanonical, }); log(`Created deployment ${newDeployment.id}`); @@ -869,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) { @@ -918,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'); @@ -985,8 +1062,19 @@ export class Service { commitHash: latestCommit.sha, commitMessage: latestCommit.commit.message, }; - const { applicationDeploymentAuctionId } = await this.laconicRegistry.createApplicationDeploymentAuction(repo, octokit, auctionParams!, deploymentData); - await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId }); + + const { applicationDeploymentAuction } = + await this.laconicRegistry.createApplicationDeploymentAuction( + repo, + octokit, + auctionParams!, + deploymentData, + ); + + await this.updateProject(project.id, { + auctionId: applicationDeploymentAuction!.id, + auctionStatus: applicationDeploymentAuction!.status as AuctionStatus, + }); } else { const deployer = await this.db.getDeployerByLRN(lrn!); @@ -996,11 +1084,13 @@ export class Service { } if (deployer.minimumPayment && project.txHash) { - const amountToBePaid = deployer?.minimumPayment.replace(/\D/g, '').toString(); + const amountToBePaid = deployer?.minimumPayment + .replace(/\D/g, '') + .toString(); const txResponse = await this.laconicRegistry.sendTokensToAccount( deployer?.paymentAddress!, - amountToBePaid + amountToBePaid, ); const txHash = txResponse.transactionHash; @@ -1018,12 +1108,19 @@ export class Service { domain: null, commitHash: latestCommit.sha, commitMessage: latestCommit.commit.message, - deployer + deployer, }; - const newDeployment = await this.createDeployment(user.id, octokit, deploymentData); + const newDeployment = await this.createDeployment( + user.id, + octokit, + deploymentData, + ); // Update project with deployer - await this.updateProjectWithDeployer(newDeployment.projectId, newDeployment.deployer); + await this.updateProjectWithDeployer( + newDeployment.projectId, + newDeployment.deployer, + ); } await this.createRepoHook(octokit, project); @@ -1079,7 +1176,7 @@ export class Service { where: { repository: repository.full_name }, relations: { deployers: true, - } + }, }); if (!projects.length) { @@ -1095,25 +1192,23 @@ export class Service { const deployers = project.deployers; if (!deployers) { - log(`No deployer present for project ${project.id}`) + log(`No deployer present for project ${project.id}`); return; } for (const deployer of deployers) { // Create deployment with branch and latest commit in GitHub data - await this.createDeployment(project.ownerId, octokit, - { - project, - branch, - environment: - project.prodBranch === branch - ? Environment.Production - : Environment.Preview, - commitHash: headCommit.id, - commitMessage: headCommit.message, - deployer: deployer - }, - ); + await this.createDeployment(project.ownerId, octokit, { + project, + branch, + environment: + project.prodBranch === branch + ? Environment.Production + : Environment.Preview, + commitHash: headCommit.id, + commitMessage: headCommit.message, + deployer: deployer, + }); } } } @@ -1167,19 +1262,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; @@ -1218,22 +1314,26 @@ 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); if (customDomain && applicationDeploymentRequestData) { - applicationDeploymentRequestData.dns = customDomain.name + applicationDeploymentRequestData.dns = customDomain.name; } // Create a canonical deployment for the new current deployment @@ -1249,20 +1349,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; } @@ -1285,18 +1388,20 @@ export class Service { where: { projectId: deployment.project.id, deployer: deployment.deployer, - isCanonical: true + isCanonical: true, }, relations: { project: true, deployer: true, }, - }) + }); // If the canonical deployment is not present then query the chain for the deployment record for backward compatibility if (!canonicalDeployment) { - log(`Canonical deployment for deployment with id ${deployment.id} not found, querying the chain..`); - const currentDeploymentURL = `https://${(deployment.project.name).toLowerCase()}.${deployment.deployer.baseDomain}`; + log( + `Canonical deployment for deployment with id ${deployment.id} not found, querying the chain..`, + ); + const currentDeploymentURL = `https://${deployment.project.name.toLowerCase()}.${deployment.deployer.baseDomain}`; const deploymentRecords = await this.laconicRegistry.getDeploymentRecordsByFilter({ @@ -1313,24 +1418,30 @@ export class Service { } // Multiple records are fetched, take the latest record - const latestRecord = deploymentRecords - .sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime())[0]; + const latestRecord = deploymentRecords.sort( + (a, b) => + new Date(b.createTime).getTime() - + new Date(a.createTime).getTime(), + )[0]; await this.laconicRegistry.createApplicationDeploymentRemovalRequest({ deploymentId: latestRecord.id, deployerLrn: deployment.deployer.deployerLrn, auctionId: deployment.project.auctionId, - payment: deployment.project.txHash + payment: deployment.project.txHash, }); } else { // If canonical deployment is found in the DB, then send the removal request with that deployment record Id const result = - await this.laconicRegistry.createApplicationDeploymentRemovalRequest({ - deploymentId: canonicalDeployment.applicationDeploymentRecordId!, - deployerLrn: canonicalDeployment.deployer.deployerLrn, - auctionId: canonicalDeployment.project.auctionId, - payment: canonicalDeployment.project.txHash - }); + await this.laconicRegistry.createApplicationDeploymentRemovalRequest( + { + deploymentId: + canonicalDeployment.applicationDeploymentRecordId!, + deployerLrn: canonicalDeployment.deployer.deployerLrn, + auctionId: canonicalDeployment.project.auctionId, + payment: canonicalDeployment.project.txHash, + }, + ); await this.db.updateDeploymentById(canonicalDeployment.id, { status: DeploymentStatus.Deleting, @@ -1347,7 +1458,7 @@ export class Service { deploymentId: deployment.applicationDeploymentRecordId, deployerLrn: deployment.deployer.deployerLrn, auctionId: deployment.project.auctionId, - payment: deployment.project.txHash + payment: deployment.project.txHash, }); await this.db.updateDeploymentById(deployment.id, { @@ -1483,12 +1594,11 @@ export class Service { return this.db.updateUser(user, data); } - async getEnvVariables( - projectId: string, - ): Promise<{ [key: string]: string }> { - const environmentVariables = await this.db.getEnvironmentVariablesByProjectId(projectId, { - environment: Environment.Production, - }); + async getEnvVariables(projectId: string): Promise<{ [key: string]: string }> { + const environmentVariables = + await this.db.getEnvironmentVariablesByProjectId(projectId, { + environment: Environment.Production, + }); const environmentVariablesObj = environmentVariables.reduce( (acc, env) => { @@ -1501,9 +1611,7 @@ export class Service { return environmentVariablesObj; } - async getAuctionData( - auctionId: string - ): Promise { + async getAuctionData(auctionId: string): Promise { const auctions = await this.laconicRegistry.getAuctionData(auctionId); return auctions[0]; } @@ -1512,16 +1620,19 @@ export class Service { const project = await this.db.getProjectById(projectId); if (!project || !project.auctionId) { - log(`Project ${projectId} ${!project ? 'not found' : 'does not have an auction'}`); + log( + `Project ${projectId} ${!project ? 'not found' : 'does not have an auction'}`, + ); return false; } - const auction = await this.laconicRegistry.releaseDeployerFunds(project.auctionId); + const auction = await this.laconicRegistry.releaseDeployerFunds( + project.auctionId, + ); if (auction.auction.fundsReleased) { log(`Funds released for auction ${project.auctionId}`); - await this.db.updateProjectById(projectId, { fundsReleased: true }); return true; } @@ -1531,21 +1642,29 @@ export class Service { return false; } - async returnUserFundsByProjectId(projectId: string, winningDeployersPresent: boolean) { + async returnUserFundsByProjectId( + projectId: string, + winningDeployersPresent: boolean, + ) { const project = await this.db.getProjectById(projectId); if (!project || !project.auctionId) { - log(`Project ${projectId} ${!project ? 'not found' : 'does not have an auction'}`); + log( + `Project ${projectId} ${!project ? 'not found' : 'does not have an auction'}`, + ); return false; } const auction = await this.getAuctionData(project.auctionId); - const totalAuctionPrice = Number(auction.maxPrice.quantity) * auction.numProviders; + const totalAuctionPrice = + Number(auction.maxPrice.quantity) * auction.numProviders; let amountToBeReturned; if (winningDeployersPresent) { - amountToBeReturned = totalAuctionPrice - auction.winnerAddresses.length * Number(auction.winnerPrice.quantity); + amountToBeReturned = + totalAuctionPrice - + auction.winnerAddresses.length * Number(auction.winnerPrice.quantity); } else { amountToBeReturned = totalAuctionPrice; } @@ -1553,7 +1672,7 @@ export class Service { if (amountToBeReturned !== 0) { await this.laconicRegistry.sendTokensToAccount( project.paymentAddress, - amountToBeReturned.toString() + amountToBeReturned.toString(), ); } } @@ -1572,13 +1691,16 @@ export class Service { } async updateDeployersFromRegistry(): Promise { - const deployerRecords = await this.laconicRegistry.getDeployerRecordsByFilter({}); + const deployerRecords = + await this.laconicRegistry.getDeployerRecordsByFilter({}); await this.saveDeployersByDeployerRecords(deployerRecords); return await this.db.getDeployers(); } - async saveDeployersByDeployerRecords(deployerRecords: DeployerRecord[]): Promise { + async saveDeployersByDeployerRecords( + deployerRecords: DeployerRecord[], + ): Promise { const deployers: Deployer[] = []; for (const record of deployerRecords) { @@ -1589,7 +1711,9 @@ export class Service { const minimumPayment = record.attributes.minimumPayment; const paymentAddress = record.attributes.paymentAddress; const publicKey = record.attributes.publicKey; - const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1); + const baseDomain = deployerApiUrl.substring( + deployerApiUrl.indexOf('.') + 1, + ); const deployerData = { deployerLrn, @@ -1598,7 +1722,7 @@ export class Service { baseDomain, minimumPayment, paymentAddress, - publicKey + publicKey, }; // TODO: Update deployers table in a separate job @@ -1616,25 +1740,39 @@ export class Service { return account.address; } - async verifyTx(txHash: string, amountSent: string, senderAddress: string): Promise { + async verifyTx( + txHash: string, + amountSent: string, + senderAddress: string, + ): Promise { const txResponse = await this.laconicRegistry.getTxResponse(txHash); if (!txResponse) { log('Transaction response not found'); return false; } - const transfer = txResponse.events.find(e => e.type === 'transfer' && e.attributes.some(a => a.key === 'msg_index')); + const transfer = txResponse.events.find( + (e) => + e.type === 'transfer' && + e.attributes.some((a) => a.key === 'msg_index'), + ); if (!transfer) { log('No transfer event found'); return false; } - const sender = transfer.attributes.find(a => a.key === 'sender')?.value; - const recipient = transfer.attributes.find(a => a.key === 'recipient')?.value; - const amount = transfer.attributes.find(a => a.key === 'amount')?.value; + const sender = transfer.attributes.find((a) => a.key === 'sender')?.value; + const recipient = transfer.attributes.find( + (a) => a.key === 'recipient', + )?.value; + const amount = transfer.attributes.find((a) => a.key === 'amount')?.value; const recipientAddress = await this.getAddress(); - return amount === amountSent && sender === senderAddress && recipient === recipientAddress; + return ( + amount === amountSent && + sender === senderAddress && + recipient === recipientAddress + ); } }