diff --git a/README.md b/README.md index 3accec13..c46cbbb3 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/packages/backend/environments/local.toml b/packages/backend/environments/local.toml index 7ca5521f..b5b5d03a 100644 --- a/packages/backend/environments/local.toml +++ b/packages/backend/environments/local.toml @@ -13,6 +13,7 @@ clientSecret = "" [registryConfig] + fetchDeploymentRecordDelay = 5000 restEndpoint = "http://localhost:1317" gqlEndpoint = "http://localhost:9473/api" chainId = "laconic_9000-1" diff --git a/packages/backend/package.json b/packages/backend/package.json index 75ba8d67..83138f1d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -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", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 50b34145..1a98a657 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -22,6 +22,7 @@ export interface RegistryConfig { chainId: string; privateKey: string; bondId: string; + fetchDeploymentRecordDelay: number; fee: { amount: string; denom: string; diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 81c9fbd3..a45c354d 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -126,10 +126,18 @@ export class Database { return projects; } - async getDeploymentsByProjectId (projectId: string): Promise { + /** + * Get deployments with specified filter + */ + async getDeployments (options: FindManyOptions): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); + const deployments = await deploymentRepository.find(options); - const deployments = await deploymentRepository.find({ + return deployments; + } + + async getDeploymentsByProjectId (projectId: string): Promise { + return this.getDeployments({ relations: { project: true, domain: true, @@ -144,8 +152,6 @@ export class Database { createdAt: 'DESC' } }); - - return deployments; } async getDeployment (options: FindOneOptions): Promise { @@ -162,16 +168,14 @@ export class Database { return domains; } - async addDeployement (data: DeepPartial): Promise { + async addDeployment (data: DeepPartial): Promise { 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): Promise { + 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): Promise { const projectRepository = this.dataSource.getRepository(Project); diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index 2ccb1c0b..62d3f38f 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -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 diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index 667d120c..86fa7b3d 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -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; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d29f3c32..7a871a28 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -31,7 +31,7 @@ export const main = async (): Promise => { 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); diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 5c17871a..108318bb 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -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 { + // 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 { diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 02602aae..f583bdab 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -30,7 +30,7 @@ export const createResolvers = async (service: Service): Promise => { }, deployments: async (_: any, { projectId }: { projectId: string }) => { - return service.getDeployementsByProjectId(projectId); + return service.getDeploymentsByProjectId(projectId); }, environmentVariables: async (_: any, { projectId }: { projectId: string }) => { diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 8d048078..620dec72 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -91,7 +91,7 @@ type Deployment { branch: String! commitHash: String! commitMessage: String! - url: String! + url: String environment: Environment! isCurrent: Boolean! status: DeploymentStatus! diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 8edb63a0..0ce326ff 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -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 { + // 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 { + // 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 => { + if (deployment.environment === Environment.Production) { + acc.add(deployment.projectId); + } + + return acc; + }, new Set()); + + // 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 { @@ -67,7 +170,7 @@ export class Service { return dbProjects; } - async getDeployementsByProjectId (projectId: string): Promise { + async getDeploymentsByProjectId (projectId: string): Promise { 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): Promise { @@ -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 { @@ -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 }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 9fb154cb..71626645 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -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 +} diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json index e0900921..5eb682a8 100644 --- a/packages/backend/test/fixtures/deployments.json +++ b/packages/backend/test/fixtures/deployments.json @@ -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", diff --git a/packages/backend/test/fixtures/organizations.json b/packages/backend/test/fixtures/organizations.json index bf934e81..ace516d3 100644 --- a/packages/backend/test/fixtures/organizations.json +++ b/packages/backend/test/fixtures/organizations.json @@ -6,7 +6,7 @@ }, { "id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb", - "name": "AirFoil", - "slug": "airfoil-2" + "name": "Laconic", + "slug": "laconic-2" } ] diff --git a/packages/backend/test/fixtures/primary-domains.json b/packages/backend/test/fixtures/primary-domains.json index b1cb0a99..23e2666d 100644 --- a/packages/backend/test/fixtures/primary-domains.json +++ b/packages/backend/test/fixtures/primary-domains.json @@ -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" }, diff --git a/packages/backend/test/fixtures/projects.json b/packages/backend/test/fixtures/projects.json index f2def3f2..55c5ee23 100644 --- a/packages/backend/test/fixtures/projects.json +++ b/packages/backend/test/fixtures/projects.json @@ -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" } ] diff --git a/packages/backend/test/fixtures/redirected-domains.json b/packages/backend/test/fixtures/redirected-domains.json index d526bde3..64fa9736 100644 --- a/packages/backend/test/fixtures/redirected-domains.json +++ b/packages/backend/test/fixtures/redirected-domains.json @@ -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" diff --git a/packages/backend/test/fixtures/user-orgnizations.json b/packages/backend/test/fixtures/user-organizations.json similarity index 100% rename from packages/backend/test/fixtures/user-orgnizations.json rename to packages/backend/test/fixtures/user-organizations.json diff --git a/packages/backend/test/fixtures/users.json b/packages/backend/test/fixtures/users.json index aacd7b6b..118beba5 100644 --- a/packages/backend/test/fixtures/users.json +++ b/packages/backend/test/fixtures/users.json @@ -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 } ] diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index fdfeeedb..0aade587 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -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'; diff --git a/packages/backend/test/publish-deploy-records.ts b/packages/backend/test/publish-deploy-records.ts new file mode 100644 index 00000000..f5636c4e --- /dev/null +++ b/packages/backend/test/publish-deploy-records.ts @@ -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(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); +}); diff --git a/packages/frontend/src/assets/domains.json b/packages/frontend/src/assets/domains.json index e17d1738..11020b61 100644 --- a/packages/frontend/src/assets/domains.json +++ b/packages/frontend/src/assets/domains.json @@ -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", diff --git a/packages/frontend/src/assets/members.json b/packages/frontend/src/assets/members.json deleted file mode 100644 index 6e38602c..00000000 --- a/packages/frontend/src/assets/members.json +++ /dev/null @@ -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 - } -] diff --git a/packages/frontend/src/assets/process-logs.json b/packages/frontend/src/assets/process-logs.json index 34c7e24b..097a0fa9 100644 --- a/packages/frontend/src/assets/process-logs.json +++ b/packages/frontend/src/assets/process-logs.json @@ -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'", diff --git a/packages/frontend/src/assets/projects.json b/packages/frontend/src/assets/projects.json deleted file mode 100644 index a344fdf7..00000000 --- a/packages/frontend/src/assets/projects.json +++ /dev/null @@ -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 - } -] diff --git a/packages/frontend/src/components/projects/create/RepositoryList.tsx b/packages/frontend/src/components/projects/create/RepositoryList.tsx index ad69e776..d0f360bb 100644 --- a/packages/frontend/src/components/projects/create/RepositoryList.tsx +++ b/packages/frontend/src/components/projects/create/RepositoryList.tsx @@ -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) => {
- {/* TODO: Fix selection of Git user at start */} - +
- {deployment.url} + {deployment.url && ( + {deployment.url} + )} ... - - ^ Visit + + ^ Visit setAssignDomainDialog(!assignDomainDialog)} diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx index 65a5a4b6..e4388bf8 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx @@ -28,9 +28,11 @@ const DeploymentDialogBodyCard = ({ color={chip.color} /> )} - - {deployment.url} - + {deployment.url && ( + + {deployment.url} + + )} ^ {deployment.branch} ^{' '} {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} diff --git a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx index 09eba5e0..4cfd4967 100644 --- a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx +++ b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx @@ -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; } @@ -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} /> diff --git a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx index 78e4aceb..b9d2866c 100644 --- a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx @@ -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; } @@ -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 ( @@ -166,9 +165,13 @@ const EditDomainDialog = ({ 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 && ( diff --git a/packages/frontend/src/context/OctokitContext.tsx b/packages/frontend/src/context/OctokitContext.tsx index 0eed37ce..21ffe246 100644 --- a/packages/frontend/src/context/OctokitContext.tsx +++ b/packages/frontend/src/context/OctokitContext.tsx @@ -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({ - octokit: null, + octokit: new Octokit(), isAuth: false, updateAuth: () => {}, }); diff --git a/packages/frontend/src/pages/org-slug/projects/create/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/index.tsx index 8658d18c..a48f4dd4 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/index.tsx @@ -24,7 +24,7 @@ const NewProject = () => { })}
Import a repository
- + ) : ( diff --git a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx index 7932f83f..958382ac 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx @@ -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; }); diff --git a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx index c5db5c34..a5f1ad73 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx @@ -24,10 +24,6 @@ const OverviewTabPanel = () => { const { project } = useOutletContext(); 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 () => { diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx index f912e8fa..24457b1a 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx @@ -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(); const [domains, setDomains] = useState([]); + const [branches, setBranches] = useState([]); + + 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} /> diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx index 0ae3932b..c0eb59b9 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx @@ -171,7 +171,8 @@ export const EnvironmentVariablesTabPanel = () => { > + Add variable -
diff --git a/packages/frontend/src/types.ts b/packages/frontend/src/types.ts index 1ece61d3..2ca22da6 100644 --- a/packages/frontend/src/types.ts +++ b/packages/frontend/src/types.ts @@ -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; diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index e75d0aba..71c44c00 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -63,7 +63,7 @@ export type Deployment = { branch: string commitHash: string commitMessage: string - url: string + url?: string environment: Environment isCurrent: boolean status: DeploymentStatus