mirror of
				https://github.com/snowball-tools/snowballtools-base
				synced 2025-10-31 17:14:08 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/main'
This commit is contained in:
		
							parent
							
								
									f0121605c4
								
							
						
					
					
						commit
						b5eef95d15
					
				| @ -31,7 +31,7 @@ | ||||
| - Load fixtures in database | ||||
| 
 | ||||
|   ```bash | ||||
|   yarn db:load:fixtures | ||||
|   yarn test:db:load:fixtures | ||||
|   ``` | ||||
| 
 | ||||
| - Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml) | ||||
| @ -66,7 +66,7 @@ | ||||
| - Run the script to create bond, reserve the authority and set authority bond | ||||
| 
 | ||||
|   ```bash | ||||
|   yarn registry:init | ||||
|   yarn test:registry:init | ||||
|   # snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms | ||||
|   ``` | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,7 @@ | ||||
|     clientSecret = "" | ||||
| 
 | ||||
| [registryConfig] | ||||
|   fetchDeploymentRecordDelay = 5000 | ||||
|   restEndpoint = "http://localhost:1317" | ||||
|   gqlEndpoint = "http://localhost:9473/api" | ||||
|   chainId = "laconic_9000-1" | ||||
|  | ||||
| @ -36,9 +36,10 @@ | ||||
|     "lint": "eslint .", | ||||
|     "format": "prettier --write .", | ||||
|     "format:check": "prettier --check .", | ||||
|     "registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts", | ||||
|     "db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts", | ||||
|     "db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts" | ||||
|     "test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts", | ||||
|     "test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts", | ||||
|     "test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts", | ||||
|     "test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/fs-extra": "^11.0.4", | ||||
|  | ||||
| @ -22,6 +22,7 @@ export interface RegistryConfig { | ||||
|   chainId: string; | ||||
|   privateKey: string; | ||||
|   bondId: string; | ||||
|   fetchDeploymentRecordDelay: number; | ||||
|   fee: { | ||||
|     amount: string; | ||||
|     denom: string; | ||||
|  | ||||
| @ -126,10 +126,18 @@ export class Database { | ||||
|     return projects; | ||||
|   } | ||||
| 
 | ||||
|   async getDeploymentsByProjectId (projectId: string): Promise<Deployment[]> { | ||||
|   /** | ||||
|    * Get deployments with specified filter | ||||
|    */ | ||||
|   async getDeployments (options: FindManyOptions<Deployment>): Promise<Deployment[]> { | ||||
|     const deploymentRepository = this.dataSource.getRepository(Deployment); | ||||
|     const deployments = await deploymentRepository.find(options); | ||||
| 
 | ||||
|     const deployments = await deploymentRepository.find({ | ||||
|     return deployments; | ||||
|   } | ||||
| 
 | ||||
|   async getDeploymentsByProjectId (projectId: string): Promise<Deployment[]> { | ||||
|     return this.getDeployments({ | ||||
|       relations: { | ||||
|         project: true, | ||||
|         domain: true, | ||||
| @ -144,8 +152,6 @@ export class Database { | ||||
|         createdAt: 'DESC' | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return deployments; | ||||
|   } | ||||
| 
 | ||||
|   async getDeployment (options: FindOneOptions<Deployment>): Promise<Deployment | null> { | ||||
| @ -162,16 +168,14 @@ export class Database { | ||||
|     return domains; | ||||
|   } | ||||
| 
 | ||||
|   async addDeployement (data: DeepPartial<Deployment>): Promise<Deployment> { | ||||
|   async addDeployment (data: DeepPartial<Deployment>): Promise<Deployment> { | ||||
|     const deploymentRepository = this.dataSource.getRepository(Deployment); | ||||
| 
 | ||||
|     const id = nanoid(); | ||||
|     const url = `${data.project!.name}-${id}.${PROJECT_DOMAIN}`; | ||||
| 
 | ||||
|     const updatedData = { | ||||
|       ...data, | ||||
|       id, | ||||
|       url | ||||
|       id | ||||
|     }; | ||||
|     const deployment = await deploymentRepository.save(updatedData); | ||||
| 
 | ||||
| @ -312,6 +316,19 @@ export class Database { | ||||
|     return Boolean(updateResult.affected); | ||||
|   } | ||||
| 
 | ||||
|   async updateDeploymentsByProjectIds (projectIds: string[], data: DeepPartial<Deployment>): Promise<boolean> { | ||||
|     const deploymentRepository = this.dataSource.getRepository(Deployment); | ||||
| 
 | ||||
|     const updateResult = await deploymentRepository | ||||
|       .createQueryBuilder() | ||||
|       .update(Deployment) | ||||
|       .set(data) | ||||
|       .where('projectId IN (:...projectIds)', { projectIds }) | ||||
|       .execute(); | ||||
| 
 | ||||
|     return Boolean(updateResult.affected); | ||||
|   } | ||||
| 
 | ||||
|   async addProject (userId: string, organizationId: string, data: DeepPartial<Project>): Promise<Project> { | ||||
|     const projectRepository = this.dataSource.getRepository(Project); | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,7 @@ import { | ||||
| import { Project } from './Project'; | ||||
| import { Domain } from './Domain'; | ||||
| import { User } from './User'; | ||||
| import { AppDeploymentRecordAttributes } from '../types'; | ||||
| 
 | ||||
| export enum Environment { | ||||
|   Production = 'Production', | ||||
| @ -28,7 +29,7 @@ export enum DeploymentStatus { | ||||
| export interface ApplicationRecord { | ||||
|   type: string; | ||||
|   version:string | ||||
|   name?: string | ||||
|   name: string | ||||
|   description?: string | ||||
|   homepage?: string | ||||
|   license?: string | ||||
| @ -45,6 +46,9 @@ export class Deployment { | ||||
|   @PrimaryColumn('varchar') | ||||
|     id!: string; | ||||
| 
 | ||||
|   @Column() | ||||
|     projectId!: string; | ||||
| 
 | ||||
|   @ManyToOne(() => Project, { onDelete: 'CASCADE' }) | ||||
|   @JoinColumn({ name: 'projectId' }) | ||||
|     project!: Project; | ||||
| @ -65,14 +69,20 @@ export class Deployment { | ||||
|   @Column('varchar') | ||||
|     commitMessage!: string; | ||||
| 
 | ||||
|   @Column('varchar') | ||||
|     url!: string; | ||||
|   @Column('varchar', { nullable: true }) | ||||
|     url!: string | null; | ||||
| 
 | ||||
|   @Column('varchar') | ||||
|     registryRecordId!: string; | ||||
|     applicationRecordId!: string; | ||||
| 
 | ||||
|   @Column('simple-json') | ||||
|     registryRecordData!: ApplicationRecord; | ||||
|     applicationRecordData!: ApplicationRecord; | ||||
| 
 | ||||
|   @Column('varchar', { nullable: true }) | ||||
|     applicationDeploymentRecordId!: string | null; | ||||
| 
 | ||||
|   @Column('simple-json', { nullable: true }) | ||||
|     applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null; | ||||
| 
 | ||||
|   @Column({ | ||||
|     enum: Environment | ||||
|  | ||||
| @ -53,10 +53,10 @@ export class Project { | ||||
|     prodBranch!: string; | ||||
| 
 | ||||
|   @Column('varchar', { nullable: true }) | ||||
|     registryRecordId!: string | null; | ||||
|     applicationDeploymentRequestId!: string | null; | ||||
| 
 | ||||
|   @Column('simple-json', { nullable: true }) | ||||
|     registryRecordData!: ApplicationDeploymentRequest | null; | ||||
|     applicationDeploymentRequestData!: ApplicationDeploymentRequest | null; | ||||
| 
 | ||||
|   @Column('text', { default: '' }) | ||||
|     description!: string; | ||||
|  | ||||
| @ -31,7 +31,7 @@ export const main = async (): Promise<void> => { | ||||
|   await db.init(); | ||||
| 
 | ||||
|   const registry = new Registry(registryConfig); | ||||
|   const service = new Service({ gitHubConfig: gitHub }, db, app, registry); | ||||
|   const service = new Service({ gitHubConfig: gitHub, registryConfig }, db, app, registry); | ||||
| 
 | ||||
|   const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); | ||||
|   const resolvers = await createResolvers(service); | ||||
|  | ||||
| @ -7,13 +7,14 @@ import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; | ||||
| 
 | ||||
| import { RegistryConfig } from './config'; | ||||
| import { ApplicationDeploymentRequest } from './entity/Project'; | ||||
| import { ApplicationRecord } from './entity/Deployment'; | ||||
| import { PackageJSON } from './types'; | ||||
| import { ApplicationRecord, Deployment } from './entity/Deployment'; | ||||
| import { AppDeploymentRecord, PackageJSON } from './types'; | ||||
| 
 | ||||
| const log = debug('snowball:registry'); | ||||
| 
 | ||||
| const APP_RECORD_TYPE = 'ApplicationRecord'; | ||||
| const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest'; | ||||
| const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; | ||||
| const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; | ||||
| 
 | ||||
| // TODO: Move registry code to laconic-sdk/watcher-ts
 | ||||
| export class Registry { | ||||
| @ -35,7 +36,8 @@ export class Registry { | ||||
|     commitHash: string, | ||||
|     appType: string, | ||||
|     repoUrl: string | ||||
|   }): Promise<{registryRecordId: string, registryRecordData: ApplicationRecord}> { | ||||
|   }): Promise<{applicationRecordId: string, applicationRecordData: ApplicationRecord}> { | ||||
|     assert(packageJSON.name, "name field doesn't exist in package.json"); | ||||
|     // Use laconic-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
 | ||||
| @ -58,7 +60,7 @@ export class Registry { | ||||
|       repository_ref: commitHash, | ||||
|       repository: [repoUrl], | ||||
|       app_type: appType, | ||||
|       ...(packageJSON.name && { name: packageJSON.name }), | ||||
|       name: packageJSON.name, | ||||
|       ...(packageJSON.description && { description: packageJSON.description }), | ||||
|       ...(packageJSON.homepage && { homepage: packageJSON.homepage }), | ||||
|       ...(packageJSON.license && { license: packageJSON.license }), | ||||
| @ -79,14 +81,14 @@ export class Registry { | ||||
|     log('Application record data:', applicationRecord); | ||||
| 
 | ||||
|     // TODO: Discuss computation of CRN
 | ||||
|     const crn = this.getCrn(packageJSON.name ?? ''); | ||||
|     const crn = this.getCrn(packageJSON.name); | ||||
|     log(`Setting name: ${crn} for record ID: ${result.data.id}`); | ||||
| 
 | ||||
|     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 { registryRecordId: result.data.id, registryRecordData: applicationRecord }; | ||||
|     return { applicationRecordId: result.data.id, applicationRecordData: applicationRecord }; | ||||
|   } | ||||
| 
 | ||||
|   async createApplicationDeploymentRequest (data: { | ||||
| @ -95,8 +97,8 @@ export class Registry { | ||||
|     repository: string, | ||||
|     environmentVariables: { [key: string]: string } | ||||
|   }): Promise<{ | ||||
|     registryRecordId: string, | ||||
|     registryRecordData: ApplicationDeploymentRequest | ||||
|     applicationDeploymentRequestId: string, | ||||
|     applicationDeploymentRequestData: ApplicationDeploymentRequest | ||||
|   }> { | ||||
|     const crn = this.getCrn(data.appName); | ||||
|     const records = await this.registry.resolveNames([crn]); | ||||
| @ -108,7 +110,7 @@ export class Registry { | ||||
| 
 | ||||
|     // Create record of type ApplicationDeploymentRequest and publish
 | ||||
|     const applicationDeploymentRequest = { | ||||
|       type: DEPLOYMENT_RECORD_TYPE, | ||||
|       type: APP_DEPLOYMENT_REQUEST_TYPE, | ||||
|       version: '1.0.0', | ||||
|       name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`, | ||||
|       application: `${crn}@${applicationRecord.attributes.app_version}`, | ||||
| @ -140,7 +142,21 @@ export class Registry { | ||||
|     log(`Application deployment request record published: ${result.data.id}`); | ||||
|     log('Application deployment request data:', applicationDeploymentRequest); | ||||
| 
 | ||||
|     return { registryRecordId: result.data.id, registryRecordData: applicationDeploymentRequest }; | ||||
|     return { applicationDeploymentRequestId: result.data.id, applicationDeploymentRequestData: applicationDeploymentRequest }; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Fetch ApplicationDeploymentRecords for deployments | ||||
|    */ | ||||
|   async getDeploymentRecords (deployments: Deployment[]): Promise<AppDeploymentRecord[]> { | ||||
|     // 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 | ||||
|     }, true); | ||||
| 
 | ||||
|     // Filter records with ApplicationRecord ids
 | ||||
|     return records.filter((record: AppDeploymentRecord) => deployments.some(deployment => deployment.applicationRecordId === record.attributes.application)); | ||||
|   } | ||||
| 
 | ||||
|   getCrn (packageJsonName: string): string { | ||||
|  | ||||
| @ -30,7 +30,7 @@ export const createResolvers = async (service: Service): Promise<any> => { | ||||
|       }, | ||||
| 
 | ||||
|       deployments: async (_: any, { projectId }: { projectId: string }) => { | ||||
|         return service.getDeployementsByProjectId(projectId); | ||||
|         return service.getDeploymentsByProjectId(projectId); | ||||
|       }, | ||||
| 
 | ||||
|       environmentVariables: async (_: any, { projectId }: { projectId: string }) => { | ||||
|  | ||||
| @ -91,7 +91,7 @@ type Deployment { | ||||
|   branch: String! | ||||
|   commitHash: String! | ||||
|   commitMessage: String! | ||||
|   url: String! | ||||
|   url: String | ||||
|   environment: Environment! | ||||
|   isCurrent: Boolean! | ||||
|   status: DeploymentStatus! | ||||
|  | ||||
| @ -14,14 +14,16 @@ import { Project } from './entity/Project'; | ||||
| import { Permission, ProjectMember } from './entity/ProjectMember'; | ||||
| import { User } from './entity/User'; | ||||
| import { Registry } from './registry'; | ||||
| import { GitHubConfig } from './config'; | ||||
| import { GitPushEventPayload } from './types'; | ||||
| import { GitHubConfig, RegistryConfig } from './config'; | ||||
| import { AppDeploymentRecord, GitPushEventPayload } from './types'; | ||||
| 
 | ||||
| const log = debug('snowball:service'); | ||||
| 
 | ||||
| const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository'; | ||||
| 
 | ||||
| interface Config { | ||||
|   gitHubConfig: GitHubConfig | ||||
|   registryConfig: RegistryConfig | ||||
| } | ||||
| 
 | ||||
| export class Service { | ||||
| @ -30,11 +32,112 @@ export class Service { | ||||
|   private registry: Registry; | ||||
|   private config: Config; | ||||
| 
 | ||||
|   private deployRecordCheckTimeout?: NodeJS.Timeout; | ||||
| 
 | ||||
|   constructor (config: Config, db: Database, app: OAuthApp, registry: Registry) { | ||||
|     this.db = db; | ||||
|     this.oauthApp = app; | ||||
|     this.registry = registry; | ||||
|     this.config = config; | ||||
|     this.init(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Initialize services | ||||
|    */ | ||||
|   init (): void { | ||||
|     // Start check for ApplicationDeploymentRecords asynchronously
 | ||||
|     this.checkDeployRecordsAndUpdate(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Destroy services | ||||
|    */ | ||||
|   destroy (): void { | ||||
|     clearTimeout(this.deployRecordCheckTimeout); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Checks for ApplicationDeploymentRecord and update corresponding deployments | ||||
|    * Continues check in loop after a delay of DEPLOY_RECORD_CHECK_DELAY_MS | ||||
|    */ | ||||
|   async checkDeployRecordsAndUpdate (): Promise<void> { | ||||
|     // Fetch deployments in building state
 | ||||
|     const deployments = await this.db.getDeployments({ | ||||
|       where: { | ||||
|         status: DeploymentStatus.Building | ||||
|         // TODO: Fetch and check records for recent deployments
 | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     if (deployments.length) { | ||||
|       log(`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`); | ||||
| 
 | ||||
|       // Fetch ApplicationDeploymentRecord for deployments
 | ||||
|       const records = await this.registry.getDeploymentRecords(deployments); | ||||
|       log(`Found ${records.length} ApplicationDeploymentRecords`); | ||||
| 
 | ||||
|       // Update deployments for which ApplicationDeploymentRecords were returned
 | ||||
|       if (records.length) { | ||||
|         await this.updateDeploymentsWithRecordData(records); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     this.deployRecordCheckTimeout = setTimeout(() => { | ||||
|       this.checkDeployRecordsAndUpdate(); | ||||
|     }, this.config.registryConfig.fetchDeploymentRecordDelay); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Update deployments with ApplicationDeploymentRecord data | ||||
|    */ | ||||
|   async updateDeploymentsWithRecordData (records: AppDeploymentRecord[]): Promise<void> { | ||||
|     // Get deployments for ApplicationDeploymentRecords
 | ||||
|     const deployments = await this.db.getDeployments({ | ||||
|       where: records.map(record => ({ | ||||
|         applicationRecordId: record.attributes.application | ||||
|       })), | ||||
|       order: { | ||||
|         createdAt: 'DESC' | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     // Get project IDs of deployments that are in production environment
 | ||||
|     const productionDeploymentProjectIds = deployments.reduce((acc, deployment): Set<string> => { | ||||
|       if (deployment.environment === Environment.Production) { | ||||
|         acc.add(deployment.projectId); | ||||
|       } | ||||
| 
 | ||||
|       return acc; | ||||
|     }, new Set<string>()); | ||||
| 
 | ||||
|     // Set old deployments isCurrent to false
 | ||||
|     await this.db.updateDeploymentsByProjectIds(Array.from(productionDeploymentProjectIds), { isCurrent: false }); | ||||
| 
 | ||||
|     const recordToDeploymentsMap = deployments.reduce((acc: {[key: string]: Deployment}, deployment) => { | ||||
|       acc[deployment.applicationRecordId] = deployment; | ||||
|       return acc; | ||||
|     }, {}); | ||||
| 
 | ||||
|     // Update deployment data for ApplicationDeploymentRecords
 | ||||
|     const deploymentUpdatePromises = records.map(async (record) => { | ||||
|       const deployment = recordToDeploymentsMap[record.attributes.application]; | ||||
| 
 | ||||
|       await this.db.updateDeploymentById( | ||||
|         deployment.id, | ||||
|         { | ||||
|           applicationDeploymentRecordId: record.id, | ||||
|           applicationDeploymentRecordData: record.attributes, | ||||
|           url: record.attributes.url, | ||||
|           status: DeploymentStatus.Ready, | ||||
|           isCurrent: deployment.environment === Environment.Production | ||||
|         } | ||||
|       ); | ||||
| 
 | ||||
|       log(`Updated deployment ${deployment.id} with URL ${record.attributes.url}`); | ||||
|     }); | ||||
| 
 | ||||
|     await Promise.all(deploymentUpdatePromises); | ||||
|   } | ||||
| 
 | ||||
|   async getUser (userId: string): Promise<User | null> { | ||||
| @ -67,7 +170,7 @@ export class Service { | ||||
|     return dbProjects; | ||||
|   } | ||||
| 
 | ||||
|   async getDeployementsByProjectId (projectId: string): Promise<Deployment[]> { | ||||
|   async getDeploymentsByProjectId (projectId: string): Promise<Deployment[]> { | ||||
|     const dbDeployments = await this.db.getDeploymentsByProjectId(projectId); | ||||
|     return dbDeployments; | ||||
|   } | ||||
| @ -187,11 +290,10 @@ export class Service { | ||||
| 
 | ||||
|     const octokit = await this.getOctokit(userId); | ||||
| 
 | ||||
|     const newDeployement = await this.createDeployment(userId, | ||||
|     const newDeployment = await this.createDeployment(userId, | ||||
|       octokit, | ||||
|       { | ||||
|         project: oldDeployment.project, | ||||
|         isCurrent: true, | ||||
|         branch: oldDeployment.branch, | ||||
|         environment: Environment.Production, | ||||
|         domain: prodBranchDomains[0], | ||||
| @ -199,7 +301,7 @@ export class Service { | ||||
|         commitMessage: oldDeployment.commitMessage | ||||
|       }); | ||||
| 
 | ||||
|     return newDeployement; | ||||
|     return newDeployment; | ||||
|   } | ||||
| 
 | ||||
|   async createDeployment ( | ||||
| @ -232,24 +334,13 @@ export class Service { | ||||
|     } | ||||
| 
 | ||||
|     // TODO: Set environment variables for each deployment (environment variables can`t be set in application record)
 | ||||
|     const { registryRecordId, registryRecordData } = await this.registry.createApplicationRecord({ | ||||
|     const { applicationRecordId, applicationRecordData } = await this.registry.createApplicationRecord({ | ||||
|       packageJSON, | ||||
|       appType: data.project!.template!, | ||||
|       commitHash: data.commitHash!, | ||||
|       repoUrl: recordData.repoUrl | ||||
|     }); | ||||
| 
 | ||||
|     // Check if new deployment is set to current
 | ||||
|     if (data.isCurrent) { | ||||
|       // Update previous current deployment
 | ||||
|       await this.db.updateDeployment({ | ||||
|         project: { | ||||
|           id: data.project.id | ||||
|         }, | ||||
|         isCurrent: true | ||||
|       }, { isCurrent: false }); | ||||
|     } | ||||
| 
 | ||||
|     // Update previous deployment with prod branch domain
 | ||||
|     // TODO: Fix unique constraint error for domain
 | ||||
|     await this.db.updateDeployment({ | ||||
| @ -258,24 +349,23 @@ export class Service { | ||||
|       domain: null | ||||
|     }); | ||||
| 
 | ||||
|     const newDeployement = await this.db.addDeployement({ | ||||
|     const newDeployment = await this.db.addDeployment({ | ||||
|       project: data.project, | ||||
|       branch: data.branch, | ||||
|       commitHash: data.commitHash, | ||||
|       commitMessage: data.commitMessage, | ||||
|       environment: data.environment, | ||||
|       isCurrent: data.isCurrent, | ||||
|       status: DeploymentStatus.Building, | ||||
|       registryRecordId, | ||||
|       registryRecordData, | ||||
|       applicationRecordId, | ||||
|       applicationRecordData, | ||||
|       domain: data.domain, | ||||
|       createdBy: Object.assign(new User(), { | ||||
|         id: userId | ||||
|       }) | ||||
|     }); | ||||
| 
 | ||||
|     log(`Created deployment ${newDeployement.id} and published application record ${registryRecordId}`); | ||||
|     return newDeployement; | ||||
|     log(`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`); | ||||
|     return newDeployment; | ||||
|   } | ||||
| 
 | ||||
|   async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> { | ||||
| @ -307,7 +397,6 @@ export class Service { | ||||
|       octokit, | ||||
|       { | ||||
|         project, | ||||
|         isCurrent: true, | ||||
|         branch: project.prodBranch, | ||||
|         environment: Environment.Production, | ||||
|         domain: null, | ||||
| @ -327,17 +416,17 @@ export class Service { | ||||
|       return acc; | ||||
|     }, {} as { [key: string]: string }); | ||||
| 
 | ||||
|     const { registryRecordId, registryRecordData } = await this.registry.createApplicationDeploymentRequest( | ||||
|     const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest( | ||||
|       { | ||||
|         appName: newDeployment.registryRecordData.name!, | ||||
|         appName: newDeployment.applicationRecordData.name!, | ||||
|         commitHash: latestCommit.sha, | ||||
|         repository: repoDetails.html_url, | ||||
|         environmentVariables: environmentVariablesObj | ||||
|       }); | ||||
| 
 | ||||
|     await this.db.updateProjectById(project.id, { | ||||
|       registryRecordId, | ||||
|       registryRecordData | ||||
|       applicationDeploymentRequestId, | ||||
|       applicationDeploymentRequestData | ||||
|     }); | ||||
| 
 | ||||
|     await this.createRepoHook(octokit, project); | ||||
| @ -393,7 +482,6 @@ export class Service { | ||||
|         octokit, | ||||
|         { | ||||
|           project, | ||||
|           isCurrent: project.prodBranch === branch, | ||||
|           branch, | ||||
|           environment: project.prodBranch === branch ? Environment.Production : Environment.Preview, | ||||
|           domain, | ||||
| @ -444,20 +532,19 @@ export class Service { | ||||
| 
 | ||||
|     const octokit = await this.getOctokit(userId); | ||||
| 
 | ||||
|     const newDeployement = await this.createDeployment(userId, | ||||
|     const newDeployment = await this.createDeployment(userId, | ||||
|       octokit, | ||||
|       { | ||||
|         project: oldDeployment.project, | ||||
|         // TODO: Put isCurrent field in project
 | ||||
|         branch: oldDeployment.branch, | ||||
|         isCurrent: true, | ||||
|         environment: Environment.Production, | ||||
|         domain: oldDeployment.domain, | ||||
|         commitHash: oldDeployment.commitHash, | ||||
|         commitMessage: oldDeployment.commitMessage | ||||
|       }); | ||||
| 
 | ||||
|     return newDeployement; | ||||
|     return newDeployment; | ||||
|   } | ||||
| 
 | ||||
|   async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> { | ||||
| @ -475,7 +562,7 @@ export class Service { | ||||
|     }); | ||||
| 
 | ||||
|     if (!oldCurrentDeployment) { | ||||
|       throw new Error('Current deployement doesnot exist'); | ||||
|       throw new Error('Current deployment doesnot exist'); | ||||
|     } | ||||
| 
 | ||||
|     const oldCurrentDeploymentUpdate = await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null }); | ||||
|  | ||||
| @ -25,3 +25,27 @@ export interface GitPushEventPayload { | ||||
|     message: string; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export interface AppDeploymentRecordAttributes { | ||||
|   application: string; | ||||
|   dns: string; | ||||
|   meta: string; | ||||
|   name: string; | ||||
|   request: string; | ||||
|   type: string; | ||||
|   url: string; | ||||
|   version: string; | ||||
| } | ||||
| 
 | ||||
| interface RegistryRecord { | ||||
|   id: string; | ||||
|   names: string[] | null; | ||||
|   owners: string[]; | ||||
|   bondId: string; | ||||
|   createTime: string; | ||||
|   expiryTime: string; | ||||
| } | ||||
| 
 | ||||
| export interface AppDeploymentRecord extends RegistryRecord { | ||||
|   attributes: AppDeploymentRecordAttributes | ||||
| } | ||||
|  | ||||
							
								
								
									
										58
									
								
								packages/backend/test/fixtures/deployments.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								packages/backend/test/fixtures/deployments.json
									
									
									
									
										vendored
									
									
								
							| @ -4,11 +4,11 @@ | ||||
|     "domainIndex":0, | ||||
|     "createdByIndex": 0, | ||||
|     "id":"ffhae3zq", | ||||
|     "status": "Building", | ||||
|     "status": "Ready", | ||||
|     "environment": "Production", | ||||
|     "isCurrent": true, | ||||
|     "registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "main", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -22,8 +22,8 @@ | ||||
|     "status": "Ready", | ||||
|     "environment": "Preview", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -34,11 +34,11 @@ | ||||
|     "domainIndex":2, | ||||
|     "createdByIndex": 0, | ||||
|     "id":"qmgekyte", | ||||
|     "status": "Error", | ||||
|     "status": "Ready", | ||||
|     "environment": "Development", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -52,8 +52,8 @@ | ||||
|     "status": "Ready", | ||||
|     "environment": "Production", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "prod", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -64,11 +64,11 @@ | ||||
|     "domainIndex":3, | ||||
|     "createdByIndex": 1, | ||||
|     "id":"eO8cckxk", | ||||
|     "status": "Building", | ||||
|     "status": "Ready", | ||||
|     "environment": "Production", | ||||
|     "isCurrent": true, | ||||
|     "registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "main", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -82,8 +82,8 @@ | ||||
|     "status": "Ready", | ||||
|     "environment": "Preview", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -94,11 +94,11 @@ | ||||
|     "domainIndex":5, | ||||
|     "createdByIndex": 1, | ||||
|     "id":"hwwr6sbx", | ||||
|     "status": "Error", | ||||
|     "status": "Ready", | ||||
|     "environment": "Development", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -109,11 +109,11 @@ | ||||
|     "domainIndex":9, | ||||
|     "createdByIndex": 2, | ||||
|     "id":"ndxje48a", | ||||
|     "status": "Building", | ||||
|     "status": "Ready", | ||||
|     "environment": "Production", | ||||
|     "isCurrent": true, | ||||
|     "registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "main", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -127,8 +127,8 @@ | ||||
|     "status": "Ready", | ||||
|     "environment": "Preview", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -139,11 +139,11 @@ | ||||
|     "domainIndex":8, | ||||
|     "createdByIndex": 2, | ||||
|     "id":"b4bpthjr", | ||||
|     "status": "Error", | ||||
|     "status": "Ready", | ||||
|     "environment": "Development", | ||||
|     "isCurrent": false, | ||||
|     "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
| @ -154,11 +154,11 @@ | ||||
|     "domainIndex": 6, | ||||
|     "createdByIndex": 2, | ||||
|     "id":"b4bpthjr", | ||||
|     "status": "Building", | ||||
|     "status": "Ready", | ||||
|     "environment": "Production", | ||||
|     "isCurrent": true, | ||||
|     "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", | ||||
|     "applicationRecordData": {}, | ||||
|     "branch": "test", | ||||
|     "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", | ||||
|     "commitMessage": "subscription added", | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|   }, | ||||
|   { | ||||
|     "id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb", | ||||
|     "name": "AirFoil", | ||||
|     "slug": "airfoil-2" | ||||
|     "name": "Laconic", | ||||
|     "slug": "laconic-2" | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -1,37 +1,37 @@ | ||||
| [ | ||||
|   { | ||||
|     "projectIndex": 0, | ||||
|     "name": "randomurl.snowballtools.xyz", | ||||
|     "name": "example.snowballtools.xyz", | ||||
|     "status": "Live", | ||||
|     "branch": "main" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 0, | ||||
|     "name": "saugatt.com", | ||||
|     "name": "example.org", | ||||
|     "status": "Pending", | ||||
|     "branch": "test" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 1, | ||||
|     "name": "randomurl.snowballtools.xyz", | ||||
|     "name": "example.snowballtools.xyz", | ||||
|     "status": "Live", | ||||
|     "branch": "main" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 1, | ||||
|     "name": "saugatt.com", | ||||
|     "name": "example.org", | ||||
|     "status": "Pending", | ||||
|     "branch": "test" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 2, | ||||
|     "name": "randomurl.snowballtools.xyz", | ||||
|     "name": "example.snowballtools.xyz", | ||||
|     "status": "Live", | ||||
|     "branch": "main" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 2, | ||||
|     "name": "saugatt.com", | ||||
|     "name": "example.org", | ||||
|     "status": "Pending", | ||||
|     "branch": "test" | ||||
|   }, | ||||
|  | ||||
							
								
								
									
										20
									
								
								packages/backend/test/fixtures/projects.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								packages/backend/test/fixtures/projects.json
									
									
									
									
										vendored
									
									
								
							| @ -10,8 +10,8 @@ | ||||
|     "framework": "test", | ||||
|     "webhooks": [], | ||||
|     "icon": "", | ||||
|     "registryRecordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationDeploymentRequestId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationDeploymentRequestData": {}, | ||||
|     "subDomain": "testProject.snowball.xyz" | ||||
|   }, | ||||
|   { | ||||
| @ -25,8 +25,8 @@ | ||||
|     "framework": "test-2", | ||||
|     "webhooks": [], | ||||
|     "icon": "", | ||||
|     "registryRecordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationDeploymentRequestId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationDeploymentRequestData": {}, | ||||
|     "subDomain": "testProject-2.snowball.xyz" | ||||
|   }, | ||||
|   { | ||||
| @ -40,8 +40,8 @@ | ||||
|     "framework": "test-3", | ||||
|     "webhooks": [], | ||||
|     "icon": "", | ||||
|     "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationDeploymentRequestId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationDeploymentRequestData": {}, | ||||
|     "subDomain": "iglootools.snowball.xyz" | ||||
|   }, | ||||
|   { | ||||
| @ -55,8 +55,8 @@ | ||||
|     "framework": "test-4", | ||||
|     "webhooks": [], | ||||
|     "icon": "", | ||||
|     "registryRecordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationDeploymentRequestId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationDeploymentRequestData": {}, | ||||
|     "subDomain": "iglootools-2.snowball.xyz" | ||||
|   }, | ||||
|   { | ||||
| @ -70,8 +70,8 @@ | ||||
|     "framework": "test-5", | ||||
|     "webhooks": [], | ||||
|     "icon": "", | ||||
|     "registryRecordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "registryRecordData": {}, | ||||
|     "applicationDeploymentRequestId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", | ||||
|     "applicationDeploymentRequestData": {}, | ||||
|     "subDomain": "snowball-2.snowball.xyz" | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -1,21 +1,21 @@ | ||||
| [ | ||||
|   { | ||||
|     "projectIndex": 0, | ||||
|     "name": "www.saugatt.com", | ||||
|     "name": "www.example.org", | ||||
|     "status": "Pending", | ||||
|     "redirectToIndex": 1, | ||||
|     "branch": "test" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 1, | ||||
|     "name": "www.saugatt.com", | ||||
|     "name": "www.example.org", | ||||
|     "status": "Pending", | ||||
|     "redirectToIndex": 3, | ||||
|     "branch": "test" | ||||
|   }, | ||||
|   { | ||||
|     "projectIndex": 2, | ||||
|     "name": "www.saugatt.com", | ||||
|     "name": "www.example.org", | ||||
|     "status": "Pending", | ||||
|     "redirectToIndex": 5, | ||||
|     "branch": "test" | ||||
|  | ||||
							
								
								
									
										12
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							| @ -1,20 +1,20 @@ | ||||
| [ | ||||
|   { | ||||
|     "id": "59f4355d-9549-4aac-9b54-eeefceeabef0", | ||||
|     "name": "Saugat Yadav", | ||||
|     "email": "saugaty@airfoil.studio", | ||||
|     "name": "Snowball", | ||||
|     "email": "snowball@snowballtools.xyz", | ||||
|     "isVerified": true | ||||
|   }, | ||||
|   { | ||||
|     "id": "e505b212-8da6-48b2-9614-098225dab34b", | ||||
|     "name": "Gideon Low", | ||||
|     "email": "gideonl@airfoil.studio", | ||||
|     "name": "Alice Anderson", | ||||
|     "email": "alice@snowballtools.xyz", | ||||
|     "isVerified": true | ||||
|   }, | ||||
|   { | ||||
|     "id": "cd892fad-9138-4aa2-a62c-414a32776ea7", | ||||
|     "name": "Sushan Yadav", | ||||
|     "email": "sushany@airfoil.studio", | ||||
|     "name": "Bob Banner", | ||||
|     "email": "bob@snowballtools.xyz", | ||||
|     "isVerified": true | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -20,7 +20,7 @@ const log = debug('snowball:initialize-database'); | ||||
| const USER_DATA_PATH = './fixtures/users.json'; | ||||
| const PROJECT_DATA_PATH = './fixtures/projects.json'; | ||||
| const ORGANIZATION_DATA_PATH = './fixtures/organizations.json'; | ||||
| const USER_ORGANIZATION_DATA_PATH = './fixtures/user-orgnizations.json'; | ||||
| const USER_ORGANIZATION_DATA_PATH = './fixtures/user-organizations.json'; | ||||
| const PROJECT_MEMBER_DATA_PATH = './fixtures/project-members.json'; | ||||
| const PRIMARY_DOMAIN_DATA_PATH = './fixtures/primary-domains.json'; | ||||
| const DEPLOYMENT_DATA_PATH = './fixtures/deployments.json'; | ||||
|  | ||||
							
								
								
									
										77
									
								
								packages/backend/test/publish-deploy-records.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								packages/backend/test/publish-deploy-records.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| import debug from 'debug'; | ||||
| import { DataSource } from 'typeorm'; | ||||
| import path from 'path'; | ||||
| 
 | ||||
| import { Registry } from '@cerc-io/laconic-sdk'; | ||||
| 
 | ||||
| import { Config } from '../src/config'; | ||||
| import { DEFAULT_CONFIG_FILE_PATH, PROJECT_DOMAIN } from '../src/constants'; | ||||
| import { getConfig } from '../src/utils'; | ||||
| import { Deployment, DeploymentStatus } from '../src/entity/Deployment'; | ||||
| 
 | ||||
| const log = debug('snowball:publish-deploy-records'); | ||||
| 
 | ||||
| async function main () { | ||||
|   const { registryConfig, database } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); | ||||
| 
 | ||||
|   const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); | ||||
| 
 | ||||
|   const dataSource = new DataSource({ | ||||
|     type: 'better-sqlite3', | ||||
|     database: database.dbPath, | ||||
|     synchronize: true, | ||||
|     entities: [path.join(__dirname, '../src/entity/*')] | ||||
|   }); | ||||
| 
 | ||||
|   await dataSource.initialize(); | ||||
| 
 | ||||
|   const deploymentRepository = dataSource.getRepository(Deployment); | ||||
|   const deployments = await deploymentRepository.find({ | ||||
|     relations: { | ||||
|       project: true | ||||
|     }, | ||||
|     where: { | ||||
|       status: DeploymentStatus.Building | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   for await (const deployment of deployments) { | ||||
|     const url = `${deployment.project.name}-${deployment.id}.${PROJECT_DOMAIN}`; | ||||
| 
 | ||||
|     const applicationDeploymentRecord = { | ||||
|       type: 'ApplicationDeploymentRecord', | ||||
|       version: '0.0.1', | ||||
|       name: deployment.applicationRecordData.name, | ||||
|       application: deployment.applicationRecordId, | ||||
| 
 | ||||
|       // TODO: Create DNS record
 | ||||
|       dns: 'bafyreihlymqggsgqiqawvehkpr2imt4l3u6q7um7xzjrux5rhsvwnuyewm', | ||||
| 
 | ||||
|       // Using dummy values
 | ||||
|       meta: JSON.stringify({ | ||||
|         config: 'da39a3ee5e6b4b0d3255bfef95601890afd80709', | ||||
|         so: '66fcfa49a1664d4cb4ce4f72c1c0e151' | ||||
|       }), | ||||
| 
 | ||||
|       request: deployment.project.applicationDeploymentRequestId, | ||||
|       url | ||||
|     }; | ||||
| 
 | ||||
|     const result = await registry.setRecord( | ||||
|       { | ||||
|         privateKey: registryConfig.privateKey, | ||||
|         record: applicationDeploymentRecord, | ||||
|         bondId: registryConfig.bondId | ||||
|       }, | ||||
|       '', | ||||
|       registryConfig.fee | ||||
|     ); | ||||
| 
 | ||||
|     log('Application deployment record data:', applicationDeploymentRecord); | ||||
|     log(`Application deployment record published: ${result.data.id}`); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| main().catch((err) => { | ||||
|   log(err); | ||||
| }); | ||||
| @ -2,7 +2,7 @@ | ||||
|   { | ||||
|     "id": 1, | ||||
|     "projectid": 1, | ||||
|     "name": "randomurl.snowballtools.xyz", | ||||
|     "name": "example.snowballtools.xyz", | ||||
|     "status": "live", | ||||
|     "record": null, | ||||
|     "isRedirectedto": false | ||||
| @ -10,7 +10,7 @@ | ||||
|   { | ||||
|     "id": 2, | ||||
|     "projectid": 1, | ||||
|     "name": "saugatt.com", | ||||
|     "name": "example.org", | ||||
|     "status": "pending", | ||||
|     "record": { | ||||
|       "type": "A", | ||||
| @ -22,7 +22,7 @@ | ||||
|   { | ||||
|     "id": 3, | ||||
|     "projectid": 1, | ||||
|     "name": "www.saugatt.com", | ||||
|     "name": "www.example.org", | ||||
|     "status": "pending", | ||||
|     "record": { | ||||
|       "type": "CNAME", | ||||
|  | ||||
| @ -1,17 +0,0 @@ | ||||
| [ | ||||
|   { | ||||
|     "name": "Saugat Yadav", | ||||
|     "email": "saugaty@airfoil.studio", | ||||
|     "id": 1 | ||||
|   }, | ||||
|   { | ||||
|     "name": "Gideon Low", | ||||
|     "email": "gideonl@airfoil.studio", | ||||
|     "id": 2 | ||||
|   }, | ||||
|   { | ||||
|     "name": "Sushan Yadav", | ||||
|     "email": "sushany@airfoil.studio", | ||||
|     "id": 3 | ||||
|   } | ||||
| ] | ||||
| @ -1,6 +1,6 @@ | ||||
| [ | ||||
|   "[20:50:03.502] Running build in Washington, D.C., USA (East) – iad1", | ||||
|   "[20:50:03.641] Cloning github.com/saugatyadav11/nextjs2 (Branch: main, Commit: 4a5f47f)", | ||||
|   "[20:50:03.641] Cloning github.com/cerc-io/nextjs2 (Branch: main, Commit: 4a5f47f)", | ||||
|   "[20:50:04.004] Previous build cache not available", | ||||
|   "[20:50:04.118] Cloning completed: 480.574ms", | ||||
|   "[20:50:04.382] Running 'vercel build'", | ||||
|  | ||||
| @ -1,190 +0,0 @@ | ||||
| [ | ||||
|   { | ||||
|     "id": 1, | ||||
|     "icon": "^", | ||||
|     "name": "iglootools", | ||||
|     "title": "Iglootools", | ||||
|     "domain": null, | ||||
|     "organization": "Airfoil", | ||||
|     "url": "iglootools.co", | ||||
|     "createdAt": "2023-12-07T04:20:00", | ||||
|     "createdBy": "Alice", | ||||
|     "deployment": "iglootools.snowballtools.co", | ||||
|     "source": "feature/add-remote-control", | ||||
|     "latestCommit": { | ||||
|       "message": "subscription added", | ||||
|       "createdAt": "2023-12-11T04:20:00", | ||||
|       "branch": "main" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 1, | ||||
|         "permissions": [] | ||||
|       }, | ||||
|       { | ||||
|         "id": 2, | ||||
|         "permissions": ["view", "edit"] | ||||
|       }, | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": ["view"] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 1 | ||||
|   }, | ||||
|   { | ||||
|     "id": 2, | ||||
|     "icon": "^", | ||||
|     "name": "snowball-starter-kit", | ||||
|     "title": "Snowball Starter Kit", | ||||
|     "domain": null, | ||||
|     "organization": "Snowball", | ||||
|     "url": "starterkit.snowballtools.com", | ||||
|     "createdAt": "2023-12-04T04:20:00", | ||||
|     "createdBy": "Bob", | ||||
|     "deployment": "deploy.snowballtools.com", | ||||
|     "source": "prod/add-docker-compose", | ||||
|     "latestCommit": { | ||||
|       "message": "component updates", | ||||
|       "createdAt": "2023-12-11T04:20:00", | ||||
|       "branch": "staging" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 2, | ||||
|         "permissions": [] | ||||
|       }, | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": ["view"] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 2 | ||||
|   }, | ||||
|   { | ||||
|     "id": 3, | ||||
|     "icon": "^", | ||||
|     "name": "web3-android", | ||||
|     "title": "Web3 Android", | ||||
|     "domain": null, | ||||
|     "organization": "Personal", | ||||
|     "url": "web3fordroids.com", | ||||
|     "createdAt": "2023-12-01T04:20:00", | ||||
|     "createdBy": "Charlie", | ||||
|     "deployment": "deploy.web3fordroids.com", | ||||
|     "source": "dev/style-page", | ||||
|     "latestCommit": { | ||||
|       "message": "No repo connected", | ||||
|       "createdAt": "2023-12-01T04:20:00", | ||||
|       "branch": "main" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 1, | ||||
|         "permissions": [] | ||||
|       }, | ||||
|       { | ||||
|         "id": 2, | ||||
|         "permissions": ["view", "edit"] | ||||
|       }, | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": ["view"] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 1 | ||||
|   }, | ||||
|   { | ||||
|     "id": 4, | ||||
|     "icon": "^", | ||||
|     "name": "passkeys-demo", | ||||
|     "title": "Passkeys Demo", | ||||
|     "domain": null, | ||||
|     "organization": "Airfoil", | ||||
|     "url": "passkeys.iglootools.xyz", | ||||
|     "createdAt": "2023-12-01T04:20:00", | ||||
|     "createdBy": "David", | ||||
|     "deployment": "demo.passkeys.xyz", | ||||
|     "source": "dev/style-page", | ||||
|     "latestCommit": { | ||||
|       "message": "hello world", | ||||
|       "createdAt": "2023-12-01T04:20:00", | ||||
|       "branch": "main" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 1, | ||||
|         "permissions": [] | ||||
|       }, | ||||
|       { | ||||
|         "id": 2, | ||||
|         "permissions": ["view", "edit"] | ||||
|       }, | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": ["view"] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 1 | ||||
|   }, | ||||
|   { | ||||
|     "id": 5, | ||||
|     "icon": "^", | ||||
|     "name": "iglootools", | ||||
|     "title": "Iglootools", | ||||
|     "domain": null, | ||||
|     "organization": "Airfoil", | ||||
|     "url": "iglootools.xyz", | ||||
|     "createdAt": "2023-12-11T04:20:00", | ||||
|     "createdBy": "Erin", | ||||
|     "deployment": "staging.snowballtools.com", | ||||
|     "source": "prod/fix-error", | ||||
|     "latestCommit": { | ||||
|       "message": "404 added", | ||||
|       "createdAt": "2023-12-09T04:20:00", | ||||
|       "branch": "main" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": [] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 3 | ||||
|   }, | ||||
|   { | ||||
|     "id": 6, | ||||
|     "icon": "^", | ||||
|     "name": "iglootools", | ||||
|     "title": "Iglootools", | ||||
|     "domain": null, | ||||
|     "organization": "Airfoil", | ||||
|     "url": "iglootools.xyz", | ||||
|     "createdAt": "2023-12-11T04:20:00", | ||||
|     "createdBy": "Frank", | ||||
|     "deployment": "iglootools.snowballtools.com", | ||||
|     "source": "prod/fix-error", | ||||
|     "latestCommit": { | ||||
|       "message": "design system integrated", | ||||
|       "createdAt": "2023-12-09T04:20:00", | ||||
|       "branch": "prod" | ||||
|     }, | ||||
|     "repositoryId": 1, | ||||
|     "members": [ | ||||
|       { | ||||
|         "id": 2, | ||||
|         "permissions": [] | ||||
|       }, | ||||
|       { | ||||
|         "id": 3, | ||||
|         "permissions": ["view"] | ||||
|       } | ||||
|     ], | ||||
|     "ownerId": 2 | ||||
|   } | ||||
| ] | ||||
| @ -3,11 +3,12 @@ import { Octokit } from 'octokit'; | ||||
| import assert from 'assert'; | ||||
| import { useDebounce } from 'usehooks-ts'; | ||||
| 
 | ||||
| import { Button, Typography, Option, Select } from '@material-tailwind/react'; | ||||
| import { Button, Typography, Option } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import SearchBar from '../../SearchBar'; | ||||
| import ProjectRepoCard from './ProjectRepoCard'; | ||||
| import { GitOrgDetails, GitRepositoryDetails } from '../../../types'; | ||||
| import AsyncSelect from '../../shared/AsyncSelect'; | ||||
| 
 | ||||
| const DEFAULT_SEARCHED_REPO = ''; | ||||
| const REPOS_PER_PAGE = 5; | ||||
| @ -109,8 +110,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => { | ||||
|     <div className="p-4"> | ||||
|       <div className="flex gap-2 mb-2"> | ||||
|         <div className="basis-1/3"> | ||||
|           {/* TODO: Fix selection of Git user at start */} | ||||
|           <Select | ||||
|           <AsyncSelect | ||||
|             value={selectedAccount} | ||||
|             onChange={(value) => setSelectedAccount(value!)} | ||||
|           > | ||||
| @ -119,7 +119,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => { | ||||
|                 ^ {account.login} | ||||
|               </Option> | ||||
|             ))} | ||||
|           </Select> | ||||
|           </AsyncSelect> | ||||
|         </div> | ||||
|         <div className="basis-2/3"> | ||||
|           <SearchBar | ||||
|  | ||||
| @ -90,7 +90,9 @@ const DeploymentDetailsCard = ({ | ||||
|     <div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2"> | ||||
|       <div className="col-span-2"> | ||||
|         <div className="flex"> | ||||
|           <Typography className=" basis-3/4">{deployment.url}</Typography> | ||||
|           {deployment.url && ( | ||||
|             <Typography className=" basis-3/4">{deployment.url}</Typography> | ||||
|           )} | ||||
|           <Chip | ||||
|             value={deployment.status} | ||||
|             color={STATUS_COLORS[deployment.status] ?? 'gray'} | ||||
| @ -120,12 +122,8 @@ const DeploymentDetailsCard = ({ | ||||
|             <button className="self-start">...</button> | ||||
|           </MenuHandler> | ||||
|           <MenuList> | ||||
|             <a | ||||
|               href={'https://' + deployment.url} | ||||
|               target="_blank" | ||||
|               rel="noreferrer" | ||||
|             > | ||||
|               <MenuItem>^ Visit</MenuItem> | ||||
|             <a href={deployment.url} target="_blank" rel="noreferrer"> | ||||
|               <MenuItem disabled={!Boolean(deployment.url)}>^ Visit</MenuItem> | ||||
|             </a> | ||||
|             <MenuItem | ||||
|               onClick={() => setAssignDomainDialog(!assignDomainDialog)} | ||||
|  | ||||
| @ -28,9 +28,11 @@ const DeploymentDialogBodyCard = ({ | ||||
|           color={chip.color} | ||||
|         /> | ||||
|       )} | ||||
|       <Typography variant="small" className="text-black"> | ||||
|         {deployment.url} | ||||
|       </Typography> | ||||
|       {deployment.url && ( | ||||
|         <Typography variant="small" className="text-black"> | ||||
|           {deployment.url} | ||||
|         </Typography> | ||||
|       )} | ||||
|       <Typography variant="small"> | ||||
|         ^ {deployment.branch} ^{' '} | ||||
|         {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} | ||||
|  | ||||
| @ -12,7 +12,6 @@ import { | ||||
|   Card, | ||||
| } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { RepositoryDetails } from '../../../../types'; | ||||
| import ConfirmDialog from '../../../shared/ConfirmDialog'; | ||||
| import EditDomainDialog from './EditDomainDialog'; | ||||
| import { useGQLClient } from '../../../../context/GQLClientContext'; | ||||
| @ -27,7 +26,7 @@ enum RefreshStatus { | ||||
| interface DomainCardProps { | ||||
|   domains: Domain[]; | ||||
|   domain: Domain; | ||||
|   repo: RepositoryDetails; | ||||
|   branches: string[]; | ||||
|   project: Project; | ||||
|   onUpdate: () => Promise<void>; | ||||
| } | ||||
| @ -44,7 +43,7 @@ const DOMAIN_RECORD = { | ||||
| const DomainCard = ({ | ||||
|   domains, | ||||
|   domain, | ||||
|   repo, | ||||
|   branches, | ||||
|   project, | ||||
|   onUpdate, | ||||
| }: DomainCardProps) => { | ||||
| @ -188,7 +187,7 @@ const DomainCard = ({ | ||||
|         domains={domains} | ||||
|         open={editDialogOpen} | ||||
|         domain={domain} | ||||
|         repo={repo} | ||||
|         branches={branches} | ||||
|         onUpdate={onUpdate} | ||||
|       /> | ||||
|     </> | ||||
|  | ||||
| @ -15,7 +15,6 @@ import { | ||||
|   Option, | ||||
| } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { RepositoryDetails } from '../../../../types'; | ||||
| import { useGQLClient } from '../../../../context/GQLClientContext'; | ||||
| 
 | ||||
| const DEFAULT_REDIRECT_OPTIONS = ['none']; | ||||
| @ -25,7 +24,7 @@ interface EditDomainDialogProp { | ||||
|   open: boolean; | ||||
|   handleOpen: () => void; | ||||
|   domain: Domain; | ||||
|   repo: RepositoryDetails; | ||||
|   branches: string[]; | ||||
|   onUpdate: () => Promise<void>; | ||||
| } | ||||
| 
 | ||||
| @ -40,7 +39,7 @@ const EditDomainDialog = ({ | ||||
|   open, | ||||
|   handleOpen, | ||||
|   domain, | ||||
|   repo, | ||||
|   branches, | ||||
|   onUpdate, | ||||
| }: EditDomainDialogProp) => { | ||||
|   const client = useGQLClient(); | ||||
| @ -120,7 +119,7 @@ const EditDomainDialog = ({ | ||||
|       branch: domain.branch, | ||||
|       redirectedTo: getRedirectUrl(domain), | ||||
|     }); | ||||
|   }, [domain, repo]); | ||||
|   }, [domain]); | ||||
| 
 | ||||
|   return ( | ||||
|     <Dialog open={open} handler={handleOpen}> | ||||
| @ -166,9 +165,13 @@ const EditDomainDialog = ({ | ||||
|           <Input | ||||
|             crossOrigin={undefined} | ||||
|             {...register('branch', { | ||||
|               validate: (value) => repo.branch.includes(value), | ||||
|               validate: (value) => | ||||
|                 Boolean(branches.length) ? branches.includes(value) : true, | ||||
|             })} | ||||
|             disabled={watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]} | ||||
|             disabled={ | ||||
|               !Boolean(branches.length) || | ||||
|               watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0] | ||||
|             } | ||||
|           /> | ||||
|           {!isValid && ( | ||||
|             <Typography variant="small" className="text-red-500"> | ||||
|  | ||||
| @ -14,13 +14,13 @@ import { useGQLClient } from './GQLClientContext'; | ||||
| const UNAUTHORIZED_ERROR_CODE = 401; | ||||
| 
 | ||||
| interface ContextValue { | ||||
|   octokit: Octokit | null; | ||||
|   octokit: Octokit; | ||||
|   isAuth: boolean; | ||||
|   updateAuth: () => void; | ||||
| } | ||||
| 
 | ||||
| const OctokitContext = createContext<ContextValue>({ | ||||
|   octokit: null, | ||||
|   octokit: new Octokit(), | ||||
|   isAuth: false, | ||||
|   updateAuth: () => {}, | ||||
| }); | ||||
|  | ||||
| @ -24,7 +24,7 @@ const NewProject = () => { | ||||
|         })} | ||||
|       </div> | ||||
|       <h5 className="mt-4 ml-4">Import a repository</h5> | ||||
|       <RepositoryList octokit={octokit!} /> | ||||
|       <RepositoryList octokit={octokit} /> | ||||
|     </> | ||||
|   ) : ( | ||||
|     <ConnectAccount onAuth={updateAuth} /> | ||||
|  | ||||
| @ -66,8 +66,10 @@ const DeploymentsTabPanel = () => { | ||||
| 
 | ||||
|       const dateMatch = | ||||
|         !filterValue.updateAtRange || | ||||
|         (new Date(deployment.updatedAt) >= filterValue.updateAtRange!.from! && | ||||
|           new Date(deployment.updatedAt) <= filterValue.updateAtRange!.to!); | ||||
|         (new Date(Number(deployment.createdAt)) >= | ||||
|           filterValue.updateAtRange!.from! && | ||||
|           new Date(Number(deployment.createdAt)) <= | ||||
|             filterValue.updateAtRange!.to!); | ||||
| 
 | ||||
|       return branchMatch && statusMatch && dateMatch; | ||||
|     }); | ||||
|  | ||||
| @ -24,10 +24,6 @@ const OverviewTabPanel = () => { | ||||
|   const { project } = useOutletContext<OutletContextType>(); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (!octokit) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO: Save repo commits in DB and avoid using GitHub API in frontend
 | ||||
|     // TODO: Figure out fetching latest commits for all branches
 | ||||
|     const fetchRepoActivity = async () => { | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { RequestError } from 'octokit'; | ||||
| import React, { useCallback, useEffect, useState } from 'react'; | ||||
| import { Link, useOutletContext } from 'react-router-dom'; | ||||
| import { Domain } from 'gql-client'; | ||||
| 
 | ||||
| @ -6,14 +7,41 @@ import { Button, Typography } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import DomainCard from '../../../../../components/projects/project/settings/DomainCard'; | ||||
| import { useGQLClient } from '../../../../../context/GQLClientContext'; | ||||
| import repositories from '../../../../../assets/repositories.json'; | ||||
| import { OutletContextType } from '../../../../../types'; | ||||
| import { useOctokit } from '../../../../../context/OctokitContext'; | ||||
| 
 | ||||
| const Domains = () => { | ||||
|   const client = useGQLClient(); | ||||
|   const { octokit } = useOctokit(); | ||||
|   const { project } = useOutletContext<OutletContextType>(); | ||||
| 
 | ||||
|   const [domains, setDomains] = useState<Domain[]>([]); | ||||
|   const [branches, setBranches] = useState<string[]>([]); | ||||
| 
 | ||||
|   const fetchBranches = useCallback(async () => { | ||||
|     const [owner, repo] = project.repository.split('/'); | ||||
| 
 | ||||
|     try { | ||||
|       const result = await octokit.rest.repos.listBranches({ | ||||
|         owner, | ||||
|         repo, | ||||
|       }); | ||||
| 
 | ||||
|       const branches = result.data.map((repo) => repo.name); | ||||
| 
 | ||||
|       setBranches(branches); | ||||
|     } catch (err) { | ||||
|       if (!(err instanceof RequestError && err.status === 404)) { | ||||
|         throw err; | ||||
|       } | ||||
| 
 | ||||
|       console.error(err); | ||||
|     } | ||||
|   }, []); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     fetchBranches(); | ||||
|   }, []); | ||||
| 
 | ||||
|   const fetchDomains = async () => { | ||||
|     if (project === undefined) { | ||||
| @ -46,7 +74,7 @@ const Domains = () => { | ||||
|             domain={domain} | ||||
|             key={domain.id} | ||||
|             // TODO: Use github API for getting linked repository
 | ||||
|             repo={repositories[0]!} | ||||
|             branches={branches} | ||||
|             project={project} | ||||
|             onUpdate={fetchDomains} | ||||
|           /> | ||||
|  | ||||
| @ -171,7 +171,8 @@ export const EnvironmentVariablesTabPanel = () => { | ||||
|                 > | ||||
|                   + Add variable | ||||
|                 </Button> | ||||
|                 <Button variant="outlined" size="sm"> | ||||
|                 {/* TODO: Implement import environment varible functionality */} | ||||
|                 <Button variant="outlined" size="sm" disabled> | ||||
|                   ^ Import .env | ||||
|                 </Button> | ||||
|               </div> | ||||
|  | ||||
| @ -6,15 +6,6 @@ export interface GitOrgDetails { | ||||
|   avatar_url: string; | ||||
| } | ||||
| 
 | ||||
| // TODO: Use GitRepositoryDetails
 | ||||
| export interface RepositoryDetails { | ||||
|   title: string; | ||||
|   updatedAt: string; | ||||
|   user: string; | ||||
|   private: boolean; | ||||
|   branch: string[]; | ||||
| } | ||||
| 
 | ||||
| export interface GitRepositoryDetails { | ||||
|   id: number; | ||||
|   name: string; | ||||
|  | ||||
| @ -63,7 +63,7 @@ export type Deployment = { | ||||
|   branch: string | ||||
|   commitHash: string | ||||
|   commitMessage: string | ||||
|   url: string | ||||
|   url?: string | ||||
|   environment: Environment | ||||
|   isCurrent: boolean | ||||
|   status: DeploymentStatus | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user