diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index 814a324..f53a7b8 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -25,6 +25,20 @@ export enum DeploymentStatus { Error = 'Error', } +export interface ApplicationRecord { + type: string; + version:string + name: string + description: string + homepage: string + license: string + author: string + repository: string, + app_version: string + repository_ref: string + app_type: string +} + @Entity() export class Deployment { // TODO: set custom generated id @@ -51,6 +65,9 @@ export class Deployment { @Column('varchar') recordId!: string; + @Column('simple-json') + recordData!: ApplicationRecord; + @Column({ enum: Environment }) diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index 4926319..54b4cc7 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -15,6 +15,17 @@ import { Organization } from './Organization'; import { ProjectMember } from './ProjectMember'; import { Deployment } from './Deployment'; +export interface ApplicationDeploymentRequest { + type: string + version: string + name: string + application: string + config: { + env: {[key:string]: string} + }, + meta: {[key:string]: string} +} + @Entity() export class Project { @PrimaryGeneratedColumn('uuid') @@ -43,6 +54,9 @@ export class Project { @Column('varchar') recordId!: string; + @Column('simple-json') + recordData!: ApplicationDeploymentRequest; + @Column('text', { default: '' }) description!: string; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index e904cec..275a923 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -29,7 +29,7 @@ export const main = async (): Promise => { const db = new Database(database); await db.init(); - const registry = new Registry({ registryConfig }); + const registry = new Registry(registryConfig); const service = new Service(db, app, registry); const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 6165c38..a193df6 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -4,6 +4,8 @@ import { inc as semverInc } from 'semver'; import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; import { RegistryConfig } from './config'; +import { ApplicationDeploymentRequest } from './entity/Project'; +import { ApplicationRecord } from './entity/Deployment'; const log = debug('snowball:registry'); @@ -15,13 +17,12 @@ export class Registry { private registry: LaconicRegistry; private registryConfig: RegistryConfig; - constructor ({ registryConfig }: { registryConfig: RegistryConfig }) { + constructor (registryConfig : RegistryConfig) { this.registryConfig = registryConfig; this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); } - // TODO: Create record called from create deployment - async createApplicationRecord (data: { recordName: string }): Promise { + async createApplicationRecord (data: { recordName: string, appType: string }): Promise<{recordId: string, recordData: ApplicationRecord}> { // TODO: Get record name from repo package.json name const recordName = data.recordName; @@ -41,7 +42,7 @@ export class Registry { // Create record of type ApplicationRecord and publish const applicationRecord = { type: APP_RECORD_TYPE, - version: nextVersion, + version: nextVersion ?? '', name: recordName, // TODO: Get data from repo package.json @@ -54,9 +55,7 @@ export class Registry { // TODO: Get latest commit hash from repo production branch / deployment repository_ref: '10ac6678e8372a05ad5bb1c34c34', - - // TODO: Get from project framework/template type - app_type: 'webapp' + app_type: data.appType }; const result = await this.registry.setRecord( @@ -69,22 +68,20 @@ export class Registry { this.registryConfig.fee ); - // TODO: Log deployment id - log(`Application record published for deployment: ${result.data.id}`); log('Application record data:', applicationRecord); // TODO: Discuss computation of crn - const crn = this.getCrn(AUTHORITY_NAME, data.recordName); + const crn = this.getCrn(data.recordName); await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee); - return result.data.id; + return { recordId: result.data.id, recordData: applicationRecord }; } - async createApplicationDeploymentRequest (data: { appName: string }): Promise { - const crn = this.getCrn(AUTHORITY_NAME, data.appName); + async createApplicationDeploymentRequest (data: { appName: string }): Promise<{recordId: string, recordData: ApplicationDeploymentRequest}> { + const crn = this.getCrn(data.appName); const records = await this.registry.resolveNames([crn]); const applicationRecord = records[0]; @@ -127,10 +124,10 @@ export class Registry { log(`Application deployment request record published: ${result.data.id}`); log('Application deployment request data:', applicationDeploymentRequest); - return result.data.id; + return { recordId: result.data.id, recordData: applicationDeploymentRequest }; } - getCrn (authority: string, appName: string): string { - return `crn://${authority}/applications/${appName}`; + getCrn (appName: string): string { + return `crn://${AUTHORITY_NAME}/applications/${appName}`; } } diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 64c5cf1..ce2156a 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -147,7 +147,7 @@ export const createResolvers = async (service: Service): Promise => { redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { try { - return await service.redeployToProd(context.userId, deploymentId); + return Boolean(await service.redeployToProd(context.userId, deploymentId)); } catch (err) { log(err); return false; diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 7e3e6f3..5afea1e 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -132,6 +132,7 @@ input AddProjectInput { name: String! repository: String! prodBranch: String! + template: String! } input UpdateProjectInput { diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index d1925ba..939bd35 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -12,6 +12,9 @@ import { Project } from './entity/Project'; import { Permission, ProjectMember } from './entity/ProjectMember'; import { User } from './entity/User'; import { Registry } from './registry'; +import debug from 'debug'; + +const log = debug('snowball:service'); export class Service { private db: Database; @@ -166,7 +169,7 @@ export class Service { const prodBranchDomains = await this.db.getDomainsByProjectId(oldDeployment.project.id, { branch: oldDeployment.project.prodBranch }); // TODO: Fix unique constraint error for domain - const DeploymentWithProdBranchDomain = await this.db.getDeployment({ + const deploymentWithProdBranchDomain = await this.db.getDeployment({ where: { domain: { id: prodBranchDomains[0].id @@ -174,8 +177,8 @@ export class Service { } }); - if (DeploymentWithProdBranchDomain) { - await this.db.updateDeploymentById(DeploymentWithProdBranchDomain.id, { + if (deploymentWithProdBranchDomain) { + await this.db.updateDeploymentById(deploymentWithProdBranchDomain.id, { domain: null, isCurrent: false }); @@ -197,34 +200,41 @@ export class Service { await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null }); } - const newDeployement = await this.createDeployment({ - project: oldDeployment.project, - userId, - isCurrent: true, - environment: Environment.Production, - domain: prodBranchDomains[0], - commitHash: oldDeployment.commitHash - }); + const newDeployement = await this.createDeployment(userId, + { + project: oldDeployment.project, + isCurrent: true, + branch: oldDeployment.branch, + environment: Environment.Production, + domain: prodBranchDomains[0], + commitHash: oldDeployment.commitHash + }); return newDeployement; } - async createDeployment (data: { project: Project, userId: string, isCurrent: boolean, environment: Environment, domain?: Domain | null, commitHash: string }): Promise { - const applicationRecordId = await this.registry.createApplicationRecord({ recordName: data.project.name }); + async createDeployment (userId: string, data: DeepPartial): Promise { + const { recordId, recordData } = await this.registry.createApplicationRecord({ + recordName: data.project?.name ?? '', + appType: data.project?.template ?? '' + }); const newDeployement = await this.db.addDeployement({ project: data.project, - branch: data.project.prodBranch, + branch: data.branch, commitHash: data.commitHash, environment: data.environment, isCurrent: data.isCurrent, status: DeploymentStatus.Building, - recordId: applicationRecordId, + recordId, + recordData, + domain: data.domain, createdBy: Object.assign(new User(), { - id: data.userId + id: userId }) }); + log(`Application record ${recordId} published for deployment ${newDeployement.id}`); return newDeployement; } @@ -240,22 +250,26 @@ export class Service { const project = await this.db.addProject(userId, organization.id, { ...data, - recordId: '' + recordId: '', + recordData: {} }); // TODO: Get repository details from github - await this.createDeployment({ - project, - userId, - isCurrent: true, - environment: Environment.Production, - // TODO: Set latest commit hash - commitHash: "" - }); + await this.createDeployment(userId, + { + project, + isCurrent: true, + branch: project.prodBranch, + environment: Environment.Production, + // TODO: Set latest commit hash + commitHash: '', + domain: null + }); - const applicationDeploymentRequestRecordId = await this.registry.createApplicationDeploymentRequest({ appName: project.name }); + const { recordId, recordData } = await this.registry.createApplicationDeploymentRequest({ appName: project.name }); await this.db.updateProjectById(project.id, { - recordId: applicationDeploymentRequestRecordId + recordId, + recordData }); return project; @@ -283,7 +297,7 @@ export class Service { return this.db.deleteDomainById(domainId); } - async redeployToProd (userId: string, deploymentId: string): Promise { + async redeployToProd (userId: string, deploymentId: string): Promise { const oldDeployment = await this.db.getDeployment({ relations: { project: true, @@ -299,19 +313,20 @@ export class Service { throw new Error('Deployment not found'); } - const oldDeploymentUpdated = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false }); + await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false }); - const newDeployement = await this.createDeployment({ - project: oldDeployment.project, - userId: userId, - // TODO: Put isCurrent field in project - isCurrent: true, - environment: Environment.Production, - domain: oldDeployment.domain, - commitHash: oldDeployment.commitHash - }); + const newDeployement = await this.createDeployment(userId, + { + project: oldDeployment.project, + // TODO: Put isCurrent field in project + branch: oldDeployment.branch, + isCurrent: true, + environment: Environment.Production, + domain: oldDeployment.domain, + commitHash: oldDeployment.commitHash + }); - return oldDeploymentUpdated && Boolean(newDeployement); + return newDeployement; } async rollbackDeployment (projectId: string, deploymentId: string): Promise { diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json index c85d763..c67f315 100644 --- a/packages/backend/test/fixtures/deployments.json +++ b/packages/backend/test/fixtures/deployments.json @@ -8,6 +8,7 @@ "environment": "Production", "isCurrent": true, "recordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "main", "commitHash": "testXyz", "url": "testProject-ffhae3zq.snowball.xyz" @@ -21,6 +22,7 @@ "environment": "Preview", "isCurrent": false, "recordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "testProject-vehagei8.snowball.xyz" @@ -34,6 +36,7 @@ "environment": "Development", "isCurrent": false, "recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "testProject-qmgekyte.snowball.xyz" @@ -47,6 +50,7 @@ "environment": "Production", "isCurrent": false, "recordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "prod", "commitHash": "testXyz", "url": "testProject-f8wsyim6.snowball.xyz" @@ -60,6 +64,7 @@ "environment": "Production", "isCurrent": true, "recordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "main", "commitHash": "testXyz", "url": "testProject-2-eO8cckxk.snowball.xyz" @@ -73,6 +78,7 @@ "environment": "Preview", "isCurrent": false, "recordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "testProject-2-yaq0t5yw.snowball.xyz" @@ -86,6 +92,7 @@ "environment": "Development", "isCurrent": false, "recordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "testProject-2-hwwr6sbx.snowball.xyz" @@ -99,6 +106,7 @@ "environment": "Production", "isCurrent": true, "recordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "main", "commitHash": "testXyz", "url": "iglootools-ndxje48a.snowball.xyz" @@ -112,6 +120,7 @@ "environment": "Preview", "isCurrent": false, "recordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "iglootools-gtgpgvei.snowball.xyz" @@ -125,6 +134,7 @@ "environment": "Development", "isCurrent": false, "recordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", + "recordData": {}, "branch": "test", "commitHash": "testXyz", "url": "iglootools-b4bpthjr.snowball.xyz" diff --git a/packages/backend/test/fixtures/projects.json b/packages/backend/test/fixtures/projects.json index 046d59e..7f59d26 100644 --- a/packages/backend/test/fixtures/projects.json +++ b/packages/backend/test/fixtures/projects.json @@ -11,6 +11,7 @@ "webhooks": [], "icon": "", "recordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "subDomain": "testProject.snowball.xyz" }, { @@ -25,6 +26,7 @@ "webhooks": [], "icon": "", "recordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "subDomain": "testProject-2.snowball.xyz" }, { @@ -39,6 +41,7 @@ "webhooks": [], "icon": "", "recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "subDomain": "iglootools.snowball.xyz" }, { @@ -53,6 +56,7 @@ "webhooks": [], "icon": "", "recordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "subDomain": "iglootools-2.snowball.xyz" }, { @@ -67,6 +71,7 @@ "webhooks": [], "icon": "", "recordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "recordData": {}, "subDomain": "snowball-2.snowball.xyz" } ] diff --git a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx index 3847d6b..80059af 100644 --- a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx +++ b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx @@ -26,6 +26,8 @@ const ProjectRepoCard: React.FC = ({ repository }) => { name: `${repository.owner!.login}-${repository.name}`, prodBranch: repository.default_branch!, repository: repository.full_name, + // TODO: Compute template from repo + template: 'webapp', }); navigate(`import?projectId=${addProject.id}`); diff --git a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx index 0e6fb3f..ee12c9e 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx @@ -52,6 +52,8 @@ const CreateRepo = () => { name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`, prodBranch: gitRepo.data.default_branch ?? 'main', repository: gitRepo.data.full_name, + // TODO: Set selected template + template: 'webapp', }); navigate( diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index 44c5feb..c0df5ea 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -243,6 +243,7 @@ export type AddProjectInput = { name: string; repository: string; prodBranch: string; + template: string; } export type UpdateProjectInput = {