forked from cerc-io/snowballtools-base
Merge remote-tracking branch 'origin/main'
This commit is contained in:
parent
f0121605c4
commit
b5eef95d15
@ -31,7 +31,7 @@
|
|||||||
- Load fixtures in database
|
- Load fixtures in database
|
||||||
|
|
||||||
```bash
|
```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)
|
- 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
|
- Run the script to create bond, reserve the authority and set authority bond
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn registry:init
|
yarn test:registry:init
|
||||||
# snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms
|
# snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
clientSecret = ""
|
clientSecret = ""
|
||||||
|
|
||||||
[registryConfig]
|
[registryConfig]
|
||||||
|
fetchDeploymentRecordDelay = 5000
|
||||||
restEndpoint = "http://localhost:1317"
|
restEndpoint = "http://localhost:1317"
|
||||||
gqlEndpoint = "http://localhost:9473/api"
|
gqlEndpoint = "http://localhost:9473/api"
|
||||||
chainId = "laconic_9000-1"
|
chainId = "laconic_9000-1"
|
||||||
|
@ -36,9 +36,10 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
|
"test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
|
||||||
"db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts",
|
"test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts",
|
||||||
"db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.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": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
|
@ -22,6 +22,7 @@ export interface RegistryConfig {
|
|||||||
chainId: string;
|
chainId: string;
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
bondId: string;
|
bondId: string;
|
||||||
|
fetchDeploymentRecordDelay: number;
|
||||||
fee: {
|
fee: {
|
||||||
amount: string;
|
amount: string;
|
||||||
denom: string;
|
denom: string;
|
||||||
|
@ -126,10 +126,18 @@ export class Database {
|
|||||||
return projects;
|
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 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: {
|
relations: {
|
||||||
project: true,
|
project: true,
|
||||||
domain: true,
|
domain: true,
|
||||||
@ -144,8 +152,6 @@ export class Database {
|
|||||||
createdAt: 'DESC'
|
createdAt: 'DESC'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return deployments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeployment (options: FindOneOptions<Deployment>): Promise<Deployment | null> {
|
async getDeployment (options: FindOneOptions<Deployment>): Promise<Deployment | null> {
|
||||||
@ -162,16 +168,14 @@ export class Database {
|
|||||||
return domains;
|
return domains;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addDeployement (data: DeepPartial<Deployment>): Promise<Deployment> {
|
async addDeployment (data: DeepPartial<Deployment>): Promise<Deployment> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
const url = `${data.project!.name}-${id}.${PROJECT_DOMAIN}`;
|
|
||||||
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...data,
|
...data,
|
||||||
id,
|
id
|
||||||
url
|
|
||||||
};
|
};
|
||||||
const deployment = await deploymentRepository.save(updatedData);
|
const deployment = await deploymentRepository.save(updatedData);
|
||||||
|
|
||||||
@ -312,6 +316,19 @@ export class Database {
|
|||||||
return Boolean(updateResult.affected);
|
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> {
|
async addProject (userId: string, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
import { Project } from './Project';
|
import { Project } from './Project';
|
||||||
import { Domain } from './Domain';
|
import { Domain } from './Domain';
|
||||||
import { User } from './User';
|
import { User } from './User';
|
||||||
|
import { AppDeploymentRecordAttributes } from '../types';
|
||||||
|
|
||||||
export enum Environment {
|
export enum Environment {
|
||||||
Production = 'Production',
|
Production = 'Production',
|
||||||
@ -28,7 +29,7 @@ export enum DeploymentStatus {
|
|||||||
export interface ApplicationRecord {
|
export interface ApplicationRecord {
|
||||||
type: string;
|
type: string;
|
||||||
version:string
|
version:string
|
||||||
name?: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
homepage?: string
|
homepage?: string
|
||||||
license?: string
|
license?: string
|
||||||
@ -45,6 +46,9 @@ export class Deployment {
|
|||||||
@PrimaryColumn('varchar')
|
@PrimaryColumn('varchar')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
projectId!: string;
|
||||||
|
|
||||||
@ManyToOne(() => Project, { onDelete: 'CASCADE' })
|
@ManyToOne(() => Project, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'projectId' })
|
@JoinColumn({ name: 'projectId' })
|
||||||
project!: Project;
|
project!: Project;
|
||||||
@ -65,14 +69,20 @@ export class Deployment {
|
|||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
commitMessage!: string;
|
commitMessage!: string;
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar', { nullable: true })
|
||||||
url!: string;
|
url!: string | null;
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
registryRecordId!: string;
|
applicationRecordId!: string;
|
||||||
|
|
||||||
@Column('simple-json')
|
@Column('simple-json')
|
||||||
registryRecordData!: ApplicationRecord;
|
applicationRecordData!: ApplicationRecord;
|
||||||
|
|
||||||
|
@Column('varchar', { nullable: true })
|
||||||
|
applicationDeploymentRecordId!: string | null;
|
||||||
|
|
||||||
|
@Column('simple-json', { nullable: true })
|
||||||
|
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
enum: Environment
|
enum: Environment
|
||||||
|
@ -53,10 +53,10 @@ export class Project {
|
|||||||
prodBranch!: string;
|
prodBranch!: string;
|
||||||
|
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
registryRecordId!: string | null;
|
applicationDeploymentRequestId!: string | null;
|
||||||
|
|
||||||
@Column('simple-json', { nullable: true })
|
@Column('simple-json', { nullable: true })
|
||||||
registryRecordData!: ApplicationDeploymentRequest | null;
|
applicationDeploymentRequestData!: ApplicationDeploymentRequest | null;
|
||||||
|
|
||||||
@Column('text', { default: '' })
|
@Column('text', { default: '' })
|
||||||
description!: string;
|
description!: string;
|
||||||
|
@ -31,7 +31,7 @@ export const main = async (): Promise<void> => {
|
|||||||
await db.init();
|
await db.init();
|
||||||
|
|
||||||
const registry = new Registry(registryConfig);
|
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 typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString();
|
||||||
const resolvers = await createResolvers(service);
|
const resolvers = await createResolvers(service);
|
||||||
|
@ -7,13 +7,14 @@ import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
|
|||||||
|
|
||||||
import { RegistryConfig } from './config';
|
import { RegistryConfig } from './config';
|
||||||
import { ApplicationDeploymentRequest } from './entity/Project';
|
import { ApplicationDeploymentRequest } from './entity/Project';
|
||||||
import { ApplicationRecord } from './entity/Deployment';
|
import { ApplicationRecord, Deployment } from './entity/Deployment';
|
||||||
import { PackageJSON } from './types';
|
import { AppDeploymentRecord, PackageJSON } from './types';
|
||||||
|
|
||||||
const log = debug('snowball:registry');
|
const log = debug('snowball:registry');
|
||||||
|
|
||||||
const APP_RECORD_TYPE = 'ApplicationRecord';
|
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
|
// TODO: Move registry code to laconic-sdk/watcher-ts
|
||||||
export class Registry {
|
export class Registry {
|
||||||
@ -35,7 +36,8 @@ export class Registry {
|
|||||||
commitHash: string,
|
commitHash: string,
|
||||||
appType: string,
|
appType: string,
|
||||||
repoUrl: 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
|
// 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
|
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
|
||||||
// Fetch previous records
|
// Fetch previous records
|
||||||
@ -58,7 +60,7 @@ export class Registry {
|
|||||||
repository_ref: commitHash,
|
repository_ref: commitHash,
|
||||||
repository: [repoUrl],
|
repository: [repoUrl],
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
...(packageJSON.name && { name: packageJSON.name }),
|
name: packageJSON.name,
|
||||||
...(packageJSON.description && { description: packageJSON.description }),
|
...(packageJSON.description && { description: packageJSON.description }),
|
||||||
...(packageJSON.homepage && { homepage: packageJSON.homepage }),
|
...(packageJSON.homepage && { homepage: packageJSON.homepage }),
|
||||||
...(packageJSON.license && { license: packageJSON.license }),
|
...(packageJSON.license && { license: packageJSON.license }),
|
||||||
@ -79,14 +81,14 @@ export class Registry {
|
|||||||
log('Application record data:', applicationRecord);
|
log('Application record data:', applicationRecord);
|
||||||
|
|
||||||
// TODO: Discuss computation of CRN
|
// 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}`);
|
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 }, 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.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);
|
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: {
|
async createApplicationDeploymentRequest (data: {
|
||||||
@ -95,8 +97,8 @@ export class Registry {
|
|||||||
repository: string,
|
repository: string,
|
||||||
environmentVariables: { [key: string]: string }
|
environmentVariables: { [key: string]: string }
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
registryRecordId: string,
|
applicationDeploymentRequestId: string,
|
||||||
registryRecordData: ApplicationDeploymentRequest
|
applicationDeploymentRequestData: ApplicationDeploymentRequest
|
||||||
}> {
|
}> {
|
||||||
const crn = this.getCrn(data.appName);
|
const crn = this.getCrn(data.appName);
|
||||||
const records = await this.registry.resolveNames([crn]);
|
const records = await this.registry.resolveNames([crn]);
|
||||||
@ -108,7 +110,7 @@ export class Registry {
|
|||||||
|
|
||||||
// Create record of type ApplicationDeploymentRequest and publish
|
// Create record of type ApplicationDeploymentRequest and publish
|
||||||
const applicationDeploymentRequest = {
|
const applicationDeploymentRequest = {
|
||||||
type: DEPLOYMENT_RECORD_TYPE,
|
type: APP_DEPLOYMENT_REQUEST_TYPE,
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
|
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
|
||||||
application: `${crn}@${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 record published: ${result.data.id}`);
|
||||||
log('Application deployment request data:', applicationDeploymentRequest);
|
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 {
|
getCrn (packageJsonName: string): string {
|
||||||
|
@ -30,7 +30,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
return service.getDeployementsByProjectId(projectId);
|
return service.getDeploymentsByProjectId(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
environmentVariables: async (_: any, { projectId }: { projectId: string }) => {
|
environmentVariables: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
|
@ -91,7 +91,7 @@ type Deployment {
|
|||||||
branch: String!
|
branch: String!
|
||||||
commitHash: String!
|
commitHash: String!
|
||||||
commitMessage: String!
|
commitMessage: String!
|
||||||
url: String!
|
url: String
|
||||||
environment: Environment!
|
environment: Environment!
|
||||||
isCurrent: Boolean!
|
isCurrent: Boolean!
|
||||||
status: DeploymentStatus!
|
status: DeploymentStatus!
|
||||||
|
@ -14,14 +14,16 @@ import { Project } from './entity/Project';
|
|||||||
import { Permission, ProjectMember } from './entity/ProjectMember';
|
import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
import { Registry } from './registry';
|
import { Registry } from './registry';
|
||||||
import { GitHubConfig } from './config';
|
import { GitHubConfig, RegistryConfig } from './config';
|
||||||
import { GitPushEventPayload } from './types';
|
import { AppDeploymentRecord, GitPushEventPayload } from './types';
|
||||||
|
|
||||||
const log = debug('snowball:service');
|
const log = debug('snowball:service');
|
||||||
|
|
||||||
const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository';
|
const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository';
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
gitHubConfig: GitHubConfig
|
gitHubConfig: GitHubConfig
|
||||||
|
registryConfig: RegistryConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
@ -30,11 +32,112 @@ export class Service {
|
|||||||
private registry: Registry;
|
private registry: Registry;
|
||||||
private config: Config;
|
private config: Config;
|
||||||
|
|
||||||
|
private deployRecordCheckTimeout?: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor (config: Config, db: Database, app: OAuthApp, registry: Registry) {
|
constructor (config: Config, db: Database, app: OAuthApp, registry: Registry) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.oauthApp = app;
|
this.oauthApp = app;
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.config = config;
|
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> {
|
async getUser (userId: string): Promise<User | null> {
|
||||||
@ -67,7 +170,7 @@ export class Service {
|
|||||||
return dbProjects;
|
return dbProjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeployementsByProjectId (projectId: string): Promise<Deployment[]> {
|
async getDeploymentsByProjectId (projectId: string): Promise<Deployment[]> {
|
||||||
const dbDeployments = await this.db.getDeploymentsByProjectId(projectId);
|
const dbDeployments = await this.db.getDeploymentsByProjectId(projectId);
|
||||||
return dbDeployments;
|
return dbDeployments;
|
||||||
}
|
}
|
||||||
@ -187,11 +290,10 @@ export class Service {
|
|||||||
|
|
||||||
const octokit = await this.getOctokit(userId);
|
const octokit = await this.getOctokit(userId);
|
||||||
|
|
||||||
const newDeployement = await this.createDeployment(userId,
|
const newDeployment = await this.createDeployment(userId,
|
||||||
octokit,
|
octokit,
|
||||||
{
|
{
|
||||||
project: oldDeployment.project,
|
project: oldDeployment.project,
|
||||||
isCurrent: true,
|
|
||||||
branch: oldDeployment.branch,
|
branch: oldDeployment.branch,
|
||||||
environment: Environment.Production,
|
environment: Environment.Production,
|
||||||
domain: prodBranchDomains[0],
|
domain: prodBranchDomains[0],
|
||||||
@ -199,7 +301,7 @@ export class Service {
|
|||||||
commitMessage: oldDeployment.commitMessage
|
commitMessage: oldDeployment.commitMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
return newDeployement;
|
return newDeployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDeployment (
|
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)
|
// 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,
|
packageJSON,
|
||||||
appType: data.project!.template!,
|
appType: data.project!.template!,
|
||||||
commitHash: data.commitHash!,
|
commitHash: data.commitHash!,
|
||||||
repoUrl: recordData.repoUrl
|
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
|
// Update previous deployment with prod branch domain
|
||||||
// TODO: Fix unique constraint error for domain
|
// TODO: Fix unique constraint error for domain
|
||||||
await this.db.updateDeployment({
|
await this.db.updateDeployment({
|
||||||
@ -258,24 +349,23 @@ export class Service {
|
|||||||
domain: null
|
domain: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDeployement = await this.db.addDeployement({
|
const newDeployment = await this.db.addDeployment({
|
||||||
project: data.project,
|
project: data.project,
|
||||||
branch: data.branch,
|
branch: data.branch,
|
||||||
commitHash: data.commitHash,
|
commitHash: data.commitHash,
|
||||||
commitMessage: data.commitMessage,
|
commitMessage: data.commitMessage,
|
||||||
environment: data.environment,
|
environment: data.environment,
|
||||||
isCurrent: data.isCurrent,
|
|
||||||
status: DeploymentStatus.Building,
|
status: DeploymentStatus.Building,
|
||||||
registryRecordId,
|
applicationRecordId,
|
||||||
registryRecordData,
|
applicationRecordData,
|
||||||
domain: data.domain,
|
domain: data.domain,
|
||||||
createdBy: Object.assign(new User(), {
|
createdBy: Object.assign(new User(), {
|
||||||
id: userId
|
id: userId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
log(`Created deployment ${newDeployement.id} and published application record ${registryRecordId}`);
|
log(`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`);
|
||||||
return newDeployement;
|
return newDeployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> {
|
||||||
@ -307,7 +397,6 @@ export class Service {
|
|||||||
octokit,
|
octokit,
|
||||||
{
|
{
|
||||||
project,
|
project,
|
||||||
isCurrent: true,
|
|
||||||
branch: project.prodBranch,
|
branch: project.prodBranch,
|
||||||
environment: Environment.Production,
|
environment: Environment.Production,
|
||||||
domain: null,
|
domain: null,
|
||||||
@ -327,17 +416,17 @@ export class Service {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as { [key: string]: string });
|
}, {} 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,
|
commitHash: latestCommit.sha,
|
||||||
repository: repoDetails.html_url,
|
repository: repoDetails.html_url,
|
||||||
environmentVariables: environmentVariablesObj
|
environmentVariables: environmentVariablesObj
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.db.updateProjectById(project.id, {
|
await this.db.updateProjectById(project.id, {
|
||||||
registryRecordId,
|
applicationDeploymentRequestId,
|
||||||
registryRecordData
|
applicationDeploymentRequestData
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.createRepoHook(octokit, project);
|
await this.createRepoHook(octokit, project);
|
||||||
@ -393,7 +482,6 @@ export class Service {
|
|||||||
octokit,
|
octokit,
|
||||||
{
|
{
|
||||||
project,
|
project,
|
||||||
isCurrent: project.prodBranch === branch,
|
|
||||||
branch,
|
branch,
|
||||||
environment: project.prodBranch === branch ? Environment.Production : Environment.Preview,
|
environment: project.prodBranch === branch ? Environment.Production : Environment.Preview,
|
||||||
domain,
|
domain,
|
||||||
@ -444,20 +532,19 @@ export class Service {
|
|||||||
|
|
||||||
const octokit = await this.getOctokit(userId);
|
const octokit = await this.getOctokit(userId);
|
||||||
|
|
||||||
const newDeployement = await this.createDeployment(userId,
|
const newDeployment = await this.createDeployment(userId,
|
||||||
octokit,
|
octokit,
|
||||||
{
|
{
|
||||||
project: oldDeployment.project,
|
project: oldDeployment.project,
|
||||||
// TODO: Put isCurrent field in project
|
// TODO: Put isCurrent field in project
|
||||||
branch: oldDeployment.branch,
|
branch: oldDeployment.branch,
|
||||||
isCurrent: true,
|
|
||||||
environment: Environment.Production,
|
environment: Environment.Production,
|
||||||
domain: oldDeployment.domain,
|
domain: oldDeployment.domain,
|
||||||
commitHash: oldDeployment.commitHash,
|
commitHash: oldDeployment.commitHash,
|
||||||
commitMessage: oldDeployment.commitMessage
|
commitMessage: oldDeployment.commitMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
return newDeployement;
|
return newDeployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> {
|
async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> {
|
||||||
@ -475,7 +562,7 @@ export class Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!oldCurrentDeployment) {
|
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 });
|
const oldCurrentDeploymentUpdate = await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null });
|
||||||
|
@ -25,3 +25,27 @@ export interface GitPushEventPayload {
|
|||||||
message: string;
|
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,
|
"domainIndex":0,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"ffhae3zq",
|
"id":"ffhae3zq",
|
||||||
"status": "Building",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -22,8 +22,8 @@
|
|||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -34,11 +34,11 @@
|
|||||||
"domainIndex":2,
|
"domainIndex":2,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"qmgekyte",
|
"id":"qmgekyte",
|
||||||
"status": "Error",
|
"status": "Ready",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -52,8 +52,8 @@
|
|||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "prod",
|
"branch": "prod",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -64,11 +64,11 @@
|
|||||||
"domainIndex":3,
|
"domainIndex":3,
|
||||||
"createdByIndex": 1,
|
"createdByIndex": 1,
|
||||||
"id":"eO8cckxk",
|
"id":"eO8cckxk",
|
||||||
"status": "Building",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -82,8 +82,8 @@
|
|||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -94,11 +94,11 @@
|
|||||||
"domainIndex":5,
|
"domainIndex":5,
|
||||||
"createdByIndex": 1,
|
"createdByIndex": 1,
|
||||||
"id":"hwwr6sbx",
|
"id":"hwwr6sbx",
|
||||||
"status": "Error",
|
"status": "Ready",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
|
"applicationRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -109,11 +109,11 @@
|
|||||||
"domainIndex":9,
|
"domainIndex":9,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"ndxje48a",
|
"id":"ndxje48a",
|
||||||
"status": "Building",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -127,8 +127,8 @@
|
|||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -139,11 +139,11 @@
|
|||||||
"domainIndex":8,
|
"domainIndex":8,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"b4bpthjr",
|
"id":"b4bpthjr",
|
||||||
"status": "Error",
|
"status": "Ready",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -154,11 +154,11 @@
|
|||||||
"domainIndex": 6,
|
"domainIndex": 6,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"b4bpthjr",
|
"id":"b4bpthjr",
|
||||||
"status": "Building",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
||||||
"registryRecordData": {},
|
"applicationRecordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
|
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
|
||||||
"name": "AirFoil",
|
"name": "Laconic",
|
||||||
"slug": "airfoil-2"
|
"slug": "laconic-2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"name": "randomurl.snowballtools.xyz",
|
"name": "example.snowballtools.xyz",
|
||||||
"status": "Live",
|
"status": "Live",
|
||||||
"branch": "main"
|
"branch": "main"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"name": "saugatt.com",
|
"name": "example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"branch": "test"
|
"branch": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 1,
|
"projectIndex": 1,
|
||||||
"name": "randomurl.snowballtools.xyz",
|
"name": "example.snowballtools.xyz",
|
||||||
"status": "Live",
|
"status": "Live",
|
||||||
"branch": "main"
|
"branch": "main"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 1,
|
"projectIndex": 1,
|
||||||
"name": "saugatt.com",
|
"name": "example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"branch": "test"
|
"branch": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 2,
|
"projectIndex": 2,
|
||||||
"name": "randomurl.snowballtools.xyz",
|
"name": "example.snowballtools.xyz",
|
||||||
"status": "Live",
|
"status": "Live",
|
||||||
"branch": "main"
|
"branch": "main"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 2,
|
"projectIndex": 2,
|
||||||
"name": "saugatt.com",
|
"name": "example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"branch": "test"
|
"branch": "test"
|
||||||
},
|
},
|
||||||
|
20
packages/backend/test/fixtures/projects.json
vendored
20
packages/backend/test/fixtures/projects.json
vendored
@ -10,8 +10,8 @@
|
|||||||
"framework": "test",
|
"framework": "test",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"registryRecordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationDeploymentRequestId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationDeploymentRequestData": {},
|
||||||
"subDomain": "testProject.snowball.xyz"
|
"subDomain": "testProject.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -25,8 +25,8 @@
|
|||||||
"framework": "test-2",
|
"framework": "test-2",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"registryRecordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationDeploymentRequestId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationDeploymentRequestData": {},
|
||||||
"subDomain": "testProject-2.snowball.xyz"
|
"subDomain": "testProject-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,8 +40,8 @@
|
|||||||
"framework": "test-3",
|
"framework": "test-3",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationDeploymentRequestId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationDeploymentRequestData": {},
|
||||||
"subDomain": "iglootools.snowball.xyz"
|
"subDomain": "iglootools.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,8 +55,8 @@
|
|||||||
"framework": "test-4",
|
"framework": "test-4",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"registryRecordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationDeploymentRequestId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationDeploymentRequestData": {},
|
||||||
"subDomain": "iglootools-2.snowball.xyz"
|
"subDomain": "iglootools-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,8 +70,8 @@
|
|||||||
"framework": "test-5",
|
"framework": "test-5",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"registryRecordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationDeploymentRequestId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"registryRecordData": {},
|
"applicationDeploymentRequestData": {},
|
||||||
"subDomain": "snowball-2.snowball.xyz"
|
"subDomain": "snowball-2.snowball.xyz"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"name": "www.saugatt.com",
|
"name": "www.example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"redirectToIndex": 1,
|
"redirectToIndex": 1,
|
||||||
"branch": "test"
|
"branch": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 1,
|
"projectIndex": 1,
|
||||||
"name": "www.saugatt.com",
|
"name": "www.example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"redirectToIndex": 3,
|
"redirectToIndex": 3,
|
||||||
"branch": "test"
|
"branch": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"projectIndex": 2,
|
"projectIndex": 2,
|
||||||
"name": "www.saugatt.com",
|
"name": "www.example.org",
|
||||||
"status": "Pending",
|
"status": "Pending",
|
||||||
"redirectToIndex": 5,
|
"redirectToIndex": 5,
|
||||||
"branch": "test"
|
"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",
|
"id": "59f4355d-9549-4aac-9b54-eeefceeabef0",
|
||||||
"name": "Saugat Yadav",
|
"name": "Snowball",
|
||||||
"email": "saugaty@airfoil.studio",
|
"email": "snowball@snowballtools.xyz",
|
||||||
"isVerified": true
|
"isVerified": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "e505b212-8da6-48b2-9614-098225dab34b",
|
"id": "e505b212-8da6-48b2-9614-098225dab34b",
|
||||||
"name": "Gideon Low",
|
"name": "Alice Anderson",
|
||||||
"email": "gideonl@airfoil.studio",
|
"email": "alice@snowballtools.xyz",
|
||||||
"isVerified": true
|
"isVerified": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cd892fad-9138-4aa2-a62c-414a32776ea7",
|
"id": "cd892fad-9138-4aa2-a62c-414a32776ea7",
|
||||||
"name": "Sushan Yadav",
|
"name": "Bob Banner",
|
||||||
"email": "sushany@airfoil.studio",
|
"email": "bob@snowballtools.xyz",
|
||||||
"isVerified": true
|
"isVerified": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -20,7 +20,7 @@ const log = debug('snowball:initialize-database');
|
|||||||
const USER_DATA_PATH = './fixtures/users.json';
|
const USER_DATA_PATH = './fixtures/users.json';
|
||||||
const PROJECT_DATA_PATH = './fixtures/projects.json';
|
const PROJECT_DATA_PATH = './fixtures/projects.json';
|
||||||
const ORGANIZATION_DATA_PATH = './fixtures/organizations.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 PROJECT_MEMBER_DATA_PATH = './fixtures/project-members.json';
|
||||||
const PRIMARY_DOMAIN_DATA_PATH = './fixtures/primary-domains.json';
|
const PRIMARY_DOMAIN_DATA_PATH = './fixtures/primary-domains.json';
|
||||||
const DEPLOYMENT_DATA_PATH = './fixtures/deployments.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,
|
"id": 1,
|
||||||
"projectid": 1,
|
"projectid": 1,
|
||||||
"name": "randomurl.snowballtools.xyz",
|
"name": "example.snowballtools.xyz",
|
||||||
"status": "live",
|
"status": "live",
|
||||||
"record": null,
|
"record": null,
|
||||||
"isRedirectedto": false
|
"isRedirectedto": false
|
||||||
@ -10,7 +10,7 @@
|
|||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"projectid": 1,
|
"projectid": 1,
|
||||||
"name": "saugatt.com",
|
"name": "example.org",
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"record": {
|
"record": {
|
||||||
"type": "A",
|
"type": "A",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"projectid": 1,
|
"projectid": 1,
|
||||||
"name": "www.saugatt.com",
|
"name": "www.example.org",
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"record": {
|
"record": {
|
||||||
"type": "CNAME",
|
"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.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.004] Previous build cache not available",
|
||||||
"[20:50:04.118] Cloning completed: 480.574ms",
|
"[20:50:04.118] Cloning completed: 480.574ms",
|
||||||
"[20:50:04.382] Running 'vercel build'",
|
"[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 assert from 'assert';
|
||||||
import { useDebounce } from 'usehooks-ts';
|
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 SearchBar from '../../SearchBar';
|
||||||
import ProjectRepoCard from './ProjectRepoCard';
|
import ProjectRepoCard from './ProjectRepoCard';
|
||||||
import { GitOrgDetails, GitRepositoryDetails } from '../../../types';
|
import { GitOrgDetails, GitRepositoryDetails } from '../../../types';
|
||||||
|
import AsyncSelect from '../../shared/AsyncSelect';
|
||||||
|
|
||||||
const DEFAULT_SEARCHED_REPO = '';
|
const DEFAULT_SEARCHED_REPO = '';
|
||||||
const REPOS_PER_PAGE = 5;
|
const REPOS_PER_PAGE = 5;
|
||||||
@ -109,8 +110,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex gap-2 mb-2">
|
<div className="flex gap-2 mb-2">
|
||||||
<div className="basis-1/3">
|
<div className="basis-1/3">
|
||||||
{/* TODO: Fix selection of Git user at start */}
|
<AsyncSelect
|
||||||
<Select
|
|
||||||
value={selectedAccount}
|
value={selectedAccount}
|
||||||
onChange={(value) => setSelectedAccount(value!)}
|
onChange={(value) => setSelectedAccount(value!)}
|
||||||
>
|
>
|
||||||
@ -119,7 +119,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
|||||||
^ {account.login}
|
^ {account.login}
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</AsyncSelect>
|
||||||
</div>
|
</div>
|
||||||
<div className="basis-2/3">
|
<div className="basis-2/3">
|
||||||
<SearchBar
|
<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="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Typography className=" basis-3/4">{deployment.url}</Typography>
|
{deployment.url && (
|
||||||
|
<Typography className=" basis-3/4">{deployment.url}</Typography>
|
||||||
|
)}
|
||||||
<Chip
|
<Chip
|
||||||
value={deployment.status}
|
value={deployment.status}
|
||||||
color={STATUS_COLORS[deployment.status] ?? 'gray'}
|
color={STATUS_COLORS[deployment.status] ?? 'gray'}
|
||||||
@ -120,12 +122,8 @@ const DeploymentDetailsCard = ({
|
|||||||
<button className="self-start">...</button>
|
<button className="self-start">...</button>
|
||||||
</MenuHandler>
|
</MenuHandler>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<a
|
<a href={deployment.url} target="_blank" rel="noreferrer">
|
||||||
href={'https://' + deployment.url}
|
<MenuItem disabled={!Boolean(deployment.url)}>^ Visit</MenuItem>
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<MenuItem>^ Visit</MenuItem>
|
|
||||||
</a>
|
</a>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
|
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
|
||||||
|
@ -28,9 +28,11 @@ const DeploymentDialogBodyCard = ({
|
|||||||
color={chip.color}
|
color={chip.color}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Typography variant="small" className="text-black">
|
{deployment.url && (
|
||||||
{deployment.url}
|
<Typography variant="small" className="text-black">
|
||||||
</Typography>
|
{deployment.url}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
^ {deployment.branch} ^{' '}
|
^ {deployment.branch} ^{' '}
|
||||||
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
|
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { RepositoryDetails } from '../../../../types';
|
|
||||||
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
import ConfirmDialog from '../../../shared/ConfirmDialog';
|
||||||
import EditDomainDialog from './EditDomainDialog';
|
import EditDomainDialog from './EditDomainDialog';
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
@ -27,7 +26,7 @@ enum RefreshStatus {
|
|||||||
interface DomainCardProps {
|
interface DomainCardProps {
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
domain: Domain;
|
domain: Domain;
|
||||||
repo: RepositoryDetails;
|
branches: string[];
|
||||||
project: Project;
|
project: Project;
|
||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
}
|
}
|
||||||
@ -44,7 +43,7 @@ const DOMAIN_RECORD = {
|
|||||||
const DomainCard = ({
|
const DomainCard = ({
|
||||||
domains,
|
domains,
|
||||||
domain,
|
domain,
|
||||||
repo,
|
branches,
|
||||||
project,
|
project,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: DomainCardProps) => {
|
}: DomainCardProps) => {
|
||||||
@ -188,7 +187,7 @@ const DomainCard = ({
|
|||||||
domains={domains}
|
domains={domains}
|
||||||
open={editDialogOpen}
|
open={editDialogOpen}
|
||||||
domain={domain}
|
domain={domain}
|
||||||
repo={repo}
|
branches={branches}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
Option,
|
Option,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { RepositoryDetails } from '../../../../types';
|
|
||||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
const DEFAULT_REDIRECT_OPTIONS = ['none'];
|
const DEFAULT_REDIRECT_OPTIONS = ['none'];
|
||||||
@ -25,7 +24,7 @@ interface EditDomainDialogProp {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
domain: Domain;
|
domain: Domain;
|
||||||
repo: RepositoryDetails;
|
branches: string[];
|
||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ const EditDomainDialog = ({
|
|||||||
open,
|
open,
|
||||||
handleOpen,
|
handleOpen,
|
||||||
domain,
|
domain,
|
||||||
repo,
|
branches,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: EditDomainDialogProp) => {
|
}: EditDomainDialogProp) => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -120,7 +119,7 @@ const EditDomainDialog = ({
|
|||||||
branch: domain.branch,
|
branch: domain.branch,
|
||||||
redirectedTo: getRedirectUrl(domain),
|
redirectedTo: getRedirectUrl(domain),
|
||||||
});
|
});
|
||||||
}, [domain, repo]);
|
}, [domain]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} handler={handleOpen}>
|
<Dialog open={open} handler={handleOpen}>
|
||||||
@ -166,9 +165,13 @@ const EditDomainDialog = ({
|
|||||||
<Input
|
<Input
|
||||||
crossOrigin={undefined}
|
crossOrigin={undefined}
|
||||||
{...register('branch', {
|
{...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 && (
|
{!isValid && (
|
||||||
<Typography variant="small" className="text-red-500">
|
<Typography variant="small" className="text-red-500">
|
||||||
|
@ -14,13 +14,13 @@ import { useGQLClient } from './GQLClientContext';
|
|||||||
const UNAUTHORIZED_ERROR_CODE = 401;
|
const UNAUTHORIZED_ERROR_CODE = 401;
|
||||||
|
|
||||||
interface ContextValue {
|
interface ContextValue {
|
||||||
octokit: Octokit | null;
|
octokit: Octokit;
|
||||||
isAuth: boolean;
|
isAuth: boolean;
|
||||||
updateAuth: () => void;
|
updateAuth: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OctokitContext = createContext<ContextValue>({
|
const OctokitContext = createContext<ContextValue>({
|
||||||
octokit: null,
|
octokit: new Octokit(),
|
||||||
isAuth: false,
|
isAuth: false,
|
||||||
updateAuth: () => {},
|
updateAuth: () => {},
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ const NewProject = () => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<h5 className="mt-4 ml-4">Import a repository</h5>
|
<h5 className="mt-4 ml-4">Import a repository</h5>
|
||||||
<RepositoryList octokit={octokit!} />
|
<RepositoryList octokit={octokit} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ConnectAccount onAuth={updateAuth} />
|
<ConnectAccount onAuth={updateAuth} />
|
||||||
|
@ -66,8 +66,10 @@ const DeploymentsTabPanel = () => {
|
|||||||
|
|
||||||
const dateMatch =
|
const dateMatch =
|
||||||
!filterValue.updateAtRange ||
|
!filterValue.updateAtRange ||
|
||||||
(new Date(deployment.updatedAt) >= filterValue.updateAtRange!.from! &&
|
(new Date(Number(deployment.createdAt)) >=
|
||||||
new Date(deployment.updatedAt) <= filterValue.updateAtRange!.to!);
|
filterValue.updateAtRange!.from! &&
|
||||||
|
new Date(Number(deployment.createdAt)) <=
|
||||||
|
filterValue.updateAtRange!.to!);
|
||||||
|
|
||||||
return branchMatch && statusMatch && dateMatch;
|
return branchMatch && statusMatch && dateMatch;
|
||||||
});
|
});
|
||||||
|
@ -24,10 +24,6 @@ const OverviewTabPanel = () => {
|
|||||||
const { project } = useOutletContext<OutletContextType>();
|
const { project } = useOutletContext<OutletContextType>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!octokit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Save repo commits in DB and avoid using GitHub API in frontend
|
// TODO: Save repo commits in DB and avoid using GitHub API in frontend
|
||||||
// TODO: Figure out fetching latest commits for all branches
|
// TODO: Figure out fetching latest commits for all branches
|
||||||
const fetchRepoActivity = async () => {
|
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 { Link, useOutletContext } from 'react-router-dom';
|
||||||
import { Domain } from 'gql-client';
|
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 DomainCard from '../../../../../components/projects/project/settings/DomainCard';
|
||||||
import { useGQLClient } from '../../../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../../../context/GQLClientContext';
|
||||||
import repositories from '../../../../../assets/repositories.json';
|
|
||||||
import { OutletContextType } from '../../../../../types';
|
import { OutletContextType } from '../../../../../types';
|
||||||
|
import { useOctokit } from '../../../../../context/OctokitContext';
|
||||||
|
|
||||||
const Domains = () => {
|
const Domains = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
const { octokit } = useOctokit();
|
||||||
const { project } = useOutletContext<OutletContextType>();
|
const { project } = useOutletContext<OutletContextType>();
|
||||||
|
|
||||||
const [domains, setDomains] = useState<Domain[]>([]);
|
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 () => {
|
const fetchDomains = async () => {
|
||||||
if (project === undefined) {
|
if (project === undefined) {
|
||||||
@ -46,7 +74,7 @@ const Domains = () => {
|
|||||||
domain={domain}
|
domain={domain}
|
||||||
key={domain.id}
|
key={domain.id}
|
||||||
// TODO: Use github API for getting linked repository
|
// TODO: Use github API for getting linked repository
|
||||||
repo={repositories[0]!}
|
branches={branches}
|
||||||
project={project}
|
project={project}
|
||||||
onUpdate={fetchDomains}
|
onUpdate={fetchDomains}
|
||||||
/>
|
/>
|
||||||
|
@ -171,7 +171,8 @@ export const EnvironmentVariablesTabPanel = () => {
|
|||||||
>
|
>
|
||||||
+ Add variable
|
+ Add variable
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" size="sm">
|
{/* TODO: Implement import environment varible functionality */}
|
||||||
|
<Button variant="outlined" size="sm" disabled>
|
||||||
^ Import .env
|
^ Import .env
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,15 +6,6 @@ export interface GitOrgDetails {
|
|||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use GitRepositoryDetails
|
|
||||||
export interface RepositoryDetails {
|
|
||||||
title: string;
|
|
||||||
updatedAt: string;
|
|
||||||
user: string;
|
|
||||||
private: boolean;
|
|
||||||
branch: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GitRepositoryDetails {
|
export interface GitRepositoryDetails {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -63,7 +63,7 @@ export type Deployment = {
|
|||||||
branch: string
|
branch: string
|
||||||
commitHash: string
|
commitHash: string
|
||||||
commitMessage: string
|
commitMessage: string
|
||||||
url: string
|
url?: string
|
||||||
environment: Environment
|
environment: Environment
|
||||||
isCurrent: boolean
|
isCurrent: boolean
|
||||||
status: DeploymentStatus
|
status: DeploymentStatus
|
||||||
|
Loading…
Reference in New Issue
Block a user