Publish app deployment record in Laconic registry on creating new deployments (#62)
* Publish record in laconic registry on creating project and deployment * Refactor publish record method * Set name for the published record * Publish application deployment request * Add README for publishing record * Add await in add project resolver method * Update meta data for deployment request record * Remove title field from deployment entity * Refactor service and registry class for publishing record * Add record data to project and deployment entity * Set record id and data as nullable in project entity --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
bd6a6b330c
commit
a58b9b255e
1
.npmrc
Normal file
1
.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/
|
55
README.md
55
README.md
@ -41,6 +41,56 @@
|
|||||||
- In "Authorization callback URL", type `http://localhost:3000/projects/create`
|
- In "Authorization callback URL", type `http://localhost:3000/projects/create`
|
||||||
- Generate a new client secret after app is created
|
- Generate a new client secret after app is created
|
||||||
|
|
||||||
|
- Run the laconicd stack following this [doc](https://git.vdb.to/cerc-io/stack-orchestrator/src/branch/main/docs/laconicd-with-console.md)
|
||||||
|
|
||||||
|
- Create the bond and set `registryConfig.bondId` in backend [config file](packages/backend/environments/local.toml)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns bond create --type aphoton --quantity 1000000000 --gas 200000 --fees 200000aphoton"
|
||||||
|
|
||||||
|
# {"bondId":"b40f1308510f799860fb6f1ede47245a2d59f336631158f25ae0eec30aabaf89"}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Export the bond id that is generated
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BOND_ID=<BOND-ID>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
|
||||||
|
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
|
||||||
|
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
|
||||||
|
```
|
||||||
|
|
||||||
|
- Get the rest and GQL endpoint of laconicd and set it to `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For registryConfig.restEndpoint
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
|
||||||
|
# 0.0.0.0:32777
|
||||||
|
|
||||||
|
# For registryConfig.gqlEndpoint
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
|
||||||
|
# 0.0.0.0:32771
|
||||||
|
```
|
||||||
|
|
||||||
|
- Reserve authority for `snowball`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority reserve snowball"
|
||||||
|
# {"success":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Set authority bond
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority bond set snowball $BOND_ID"
|
||||||
|
# {"success":true}
|
||||||
|
```
|
||||||
|
|
||||||
- Start the server
|
- Start the server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -57,13 +107,13 @@
|
|||||||
|
|
||||||
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
|
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
|
||||||
|
|
||||||
```
|
```env
|
||||||
REACT_APP_GQL_SERVER_URL = 'http://localhost:8000/graphql'
|
REACT_APP_GQL_SERVER_URL = 'http://localhost:8000/graphql'
|
||||||
```
|
```
|
||||||
|
|
||||||
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
|
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
|
||||||
|
|
||||||
```
|
```env
|
||||||
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
|
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -90,3 +140,4 @@
|
|||||||
```bash
|
```bash
|
||||||
python3 -m http.server -d build 3000
|
python3 -m http.server -d build 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -9,3 +9,14 @@
|
|||||||
[githubOauth]
|
[githubOauth]
|
||||||
clientId = ""
|
clientId = ""
|
||||||
clientSecret = ""
|
clientSecret = ""
|
||||||
|
|
||||||
|
[registryConfig]
|
||||||
|
restEndpoint = "http://localhost:1317"
|
||||||
|
gqlEndpoint = "http://localhost:9473/api"
|
||||||
|
chainId = "laconic_9000-1"
|
||||||
|
privateKey = ""
|
||||||
|
bondId = ""
|
||||||
|
[registryConfig.fee]
|
||||||
|
amount = "200000"
|
||||||
|
denom = "aphoton"
|
||||||
|
gas = "550000"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cerc-io/laconic-sdk": "^0.1.14",
|
||||||
"@graphql-tools/schema": "^10.0.2",
|
"@graphql-tools/schema": "^10.0.2",
|
||||||
"@graphql-tools/utils": "^10.0.12",
|
"@graphql-tools/utils": "^10.0.12",
|
||||||
"@octokit/oauth-app": "^6.1.0",
|
"@octokit/oauth-app": "^6.1.0",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"nanoid": "3",
|
"nanoid": "3",
|
||||||
"nanoid-dictionary": "^5.0.0-beta.1",
|
"nanoid-dictionary": "^5.0.0-beta.1",
|
||||||
"reflect-metadata": "^0.2.1",
|
"reflect-metadata": "^0.2.1",
|
||||||
|
"semver": "^7.6.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typeorm": "^0.3.19",
|
"typeorm": "^0.3.19",
|
||||||
|
@ -13,8 +13,22 @@ export interface GithubOauthConfig {
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegistryConfig {
|
||||||
|
restEndpoint: string;
|
||||||
|
gqlEndpoint: string;
|
||||||
|
chainId: string;
|
||||||
|
privateKey: string;
|
||||||
|
bondId: string;
|
||||||
|
fee: {
|
||||||
|
amount: string;
|
||||||
|
denom: string;
|
||||||
|
gas: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
server: ServerConfig;
|
server: ServerConfig;
|
||||||
database: DatabaseConfig;
|
database: DatabaseConfig;
|
||||||
githubOauth: GithubOauthConfig;
|
githubOauth: GithubOauthConfig;
|
||||||
|
registryConfig: RegistryConfig;
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,26 @@ export enum Environment {
|
|||||||
Development = 'Development',
|
Development = 'Development',
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Status {
|
export enum DeploymentStatus {
|
||||||
Building = 'Building',
|
Building = 'Building',
|
||||||
Ready = 'Ready',
|
Ready = 'Ready',
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApplicationRecord {
|
||||||
|
type: string;
|
||||||
|
version:string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
homepage: string
|
||||||
|
license: string
|
||||||
|
author: string
|
||||||
|
repository: string,
|
||||||
|
app_version: string
|
||||||
|
repository_ref: string
|
||||||
|
app_type: string
|
||||||
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Deployment {
|
export class Deployment {
|
||||||
// TODO: set custom generated id
|
// TODO: set custom generated id
|
||||||
@ -46,10 +60,13 @@ export class Deployment {
|
|||||||
commitHash!: string;
|
commitHash!: string;
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
title!: string;
|
url!: string;
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
url!: string;
|
recordId!: string;
|
||||||
|
|
||||||
|
@Column('simple-json')
|
||||||
|
recordData!: ApplicationRecord;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
enum: Environment
|
enum: Environment
|
||||||
@ -60,9 +77,9 @@ export class Deployment {
|
|||||||
isCurrent!: boolean;
|
isCurrent!: boolean;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
enum: Status
|
enum: DeploymentStatus
|
||||||
})
|
})
|
||||||
status!: Status;
|
status!: DeploymentStatus;
|
||||||
|
|
||||||
@ManyToOne(() => User)
|
@ManyToOne(() => User)
|
||||||
@JoinColumn({ name: 'createdBy' })
|
@JoinColumn({ name: 'createdBy' })
|
||||||
|
@ -15,6 +15,21 @@ import { Organization } from './Organization';
|
|||||||
import { ProjectMember } from './ProjectMember';
|
import { ProjectMember } from './ProjectMember';
|
||||||
import { Deployment } from './Deployment';
|
import { Deployment } from './Deployment';
|
||||||
|
|
||||||
|
export interface ApplicationDeploymentRequest {
|
||||||
|
type: string
|
||||||
|
version: string
|
||||||
|
name: string
|
||||||
|
application: string
|
||||||
|
config: {
|
||||||
|
env: {[key:string]: string}
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
note: string
|
||||||
|
repository: string
|
||||||
|
repository_ref: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Project {
|
export class Project {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
@ -40,9 +55,16 @@ export class Project {
|
|||||||
@Column('varchar', { length: 255, default: 'main' })
|
@Column('varchar', { length: 255, default: 'main' })
|
||||||
prodBranch!: string;
|
prodBranch!: string;
|
||||||
|
|
||||||
|
@Column('varchar', { nullable: true })
|
||||||
|
recordId!: string | null;
|
||||||
|
|
||||||
|
@Column('simple-json', { nullable: true })
|
||||||
|
recordData!: ApplicationDeploymentRequest | null;
|
||||||
|
|
||||||
@Column('text', { default: '' })
|
@Column('text', { default: '' })
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
|
// TODO: Compute template & framework in import repository
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
template!: string | null;
|
template!: string | null;
|
||||||
|
|
||||||
|
@ -12,23 +12,26 @@ import { getConfig } from './utils';
|
|||||||
import { Config } from './config';
|
import { Config } from './config';
|
||||||
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
|
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
|
||||||
import { Service } from './service';
|
import { Service } from './service';
|
||||||
|
import { Registry } from './registry';
|
||||||
|
|
||||||
const log = debug('snowball:server');
|
const log = debug('snowball:server');
|
||||||
|
const OAUTH_CLIENT_TYPE = 'oauth-app';
|
||||||
|
|
||||||
export const main = async (): Promise<void> => {
|
export const main = async (): Promise<void> => {
|
||||||
// TODO: get config path using cli
|
// TODO: get config path using cli
|
||||||
const { server, database, githubOauth } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
const { server, database, githubOauth, registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||||
|
|
||||||
// TODO: Move to Service class
|
|
||||||
const app = new OAuthApp({
|
const app = new OAuthApp({
|
||||||
clientType: 'oauth-app',
|
clientType: OAUTH_CLIENT_TYPE,
|
||||||
clientId: githubOauth.clientId,
|
clientId: githubOauth.clientId,
|
||||||
clientSecret: githubOauth.clientSecret
|
clientSecret: githubOauth.clientSecret
|
||||||
});
|
});
|
||||||
|
|
||||||
const db = new Database(database);
|
const db = new Database(database);
|
||||||
await db.init();
|
await db.init();
|
||||||
const service = new Service(db, app);
|
|
||||||
|
const registry = new Registry(registryConfig);
|
||||||
|
const service = new Service(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);
|
||||||
|
133
packages/backend/src/registry.ts
Normal file
133
packages/backend/src/registry.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import debug from 'debug';
|
||||||
|
import { inc as semverInc } from 'semver';
|
||||||
|
|
||||||
|
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
|
||||||
|
|
||||||
|
import { RegistryConfig } from './config';
|
||||||
|
import { ApplicationDeploymentRequest } from './entity/Project';
|
||||||
|
import { ApplicationRecord } from './entity/Deployment';
|
||||||
|
|
||||||
|
const log = debug('snowball:registry');
|
||||||
|
|
||||||
|
const APP_RECORD_TYPE = 'ApplicationRecord';
|
||||||
|
const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest';
|
||||||
|
const AUTHORITY_NAME = 'snowball';
|
||||||
|
|
||||||
|
export class Registry {
|
||||||
|
private registry: LaconicRegistry;
|
||||||
|
private registryConfig: RegistryConfig;
|
||||||
|
|
||||||
|
constructor (registryConfig : RegistryConfig) {
|
||||||
|
this.registryConfig = registryConfig;
|
||||||
|
this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createApplicationRecord (data: { recordName: string, appType: string }): Promise<{recordId: string, recordData: ApplicationRecord}> {
|
||||||
|
// TODO: Get record name from repo package.json name
|
||||||
|
const recordName = data.recordName;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const records = await this.registry.queryRecords({
|
||||||
|
type: APP_RECORD_TYPE,
|
||||||
|
name: recordName
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// Get next version of record
|
||||||
|
const bondRecords = records.filter((record: any) => record.bondId === this.registryConfig.bondId);
|
||||||
|
const [latestBondRecord] = bondRecords.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
|
||||||
|
const nextVersion = semverInc(latestBondRecord?.attributes.version ?? '0.0.0', 'patch');
|
||||||
|
|
||||||
|
// Create record of type ApplicationRecord and publish
|
||||||
|
const applicationRecord = {
|
||||||
|
type: APP_RECORD_TYPE,
|
||||||
|
version: nextVersion ?? '',
|
||||||
|
name: recordName,
|
||||||
|
|
||||||
|
// TODO: Get data from repo package.json
|
||||||
|
description: '',
|
||||||
|
homepage: '',
|
||||||
|
license: '',
|
||||||
|
author: '',
|
||||||
|
repository: '',
|
||||||
|
app_version: '0.1.0',
|
||||||
|
|
||||||
|
// TODO: Get latest commit hash from repo production branch / deployment
|
||||||
|
repository_ref: '10ac6678e8372a05ad5bb1c34c34',
|
||||||
|
app_type: data.appType
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationRecord,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
this.registryConfig.fee
|
||||||
|
);
|
||||||
|
|
||||||
|
log('Application record data:', applicationRecord);
|
||||||
|
|
||||||
|
// TODO: Discuss computation of crn
|
||||||
|
const crn = this.getCrn(data.recordName);
|
||||||
|
|
||||||
|
await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee);
|
||||||
|
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee);
|
||||||
|
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee);
|
||||||
|
|
||||||
|
return { recordId: result.data.id, recordData: applicationRecord };
|
||||||
|
}
|
||||||
|
|
||||||
|
async createApplicationDeploymentRequest (data: { appName: string }): Promise<{recordId: string, recordData: ApplicationDeploymentRequest}> {
|
||||||
|
const crn = this.getCrn(data.appName);
|
||||||
|
const records = await this.registry.resolveNames([crn]);
|
||||||
|
const applicationRecord = records[0];
|
||||||
|
|
||||||
|
if (!applicationRecord) {
|
||||||
|
throw new Error(`No record found for ${crn}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create record of type ApplicationDeploymentRequest and publish
|
||||||
|
const applicationDeploymentRequest = {
|
||||||
|
type: DEPLOYMENT_RECORD_TYPE,
|
||||||
|
version: '1.0.0',
|
||||||
|
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
|
||||||
|
application: `${crn}@${applicationRecord.attributes.app_version}`,
|
||||||
|
|
||||||
|
// TODO: Not set in test-progressive-web-app CI
|
||||||
|
// dns: '$CERC_REGISTRY_DEPLOYMENT_SHORT_HOSTNAME',
|
||||||
|
// deployment: '$CERC_REGISTRY_DEPLOYMENT_CRN',
|
||||||
|
|
||||||
|
config: {
|
||||||
|
env: {
|
||||||
|
CERC_WEBAPP_DEBUG: `${applicationRecord.attributes.app_version}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
note: `Added by Snowball @ ${(new Date()).toISOString()}`,
|
||||||
|
repository: applicationRecord.attributes.repository,
|
||||||
|
repository_ref: applicationRecord.attributes.repository_ref
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationDeploymentRequest,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
this.registryConfig.fee
|
||||||
|
);
|
||||||
|
log(`Application deployment request record published: ${result.data.id}`);
|
||||||
|
log('Application deployment request data:', applicationDeploymentRequest);
|
||||||
|
|
||||||
|
return { recordId: result.data.id, recordData: applicationDeploymentRequest };
|
||||||
|
}
|
||||||
|
|
||||||
|
getCrn (appName: string): string {
|
||||||
|
return `crn://${AUTHORITY_NAME}/applications/${appName}`;
|
||||||
|
}
|
||||||
|
}
|
@ -7,9 +7,8 @@ import { Domain } from './entity/Domain';
|
|||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
|
|
||||||
const log = debug('snowball:database');
|
const log = debug('snowball:resolver');
|
||||||
|
|
||||||
// TODO: Remove Database argument and refactor code to Service
|
|
||||||
export const createResolvers = async (service: Service): Promise<any> => {
|
export const createResolvers = async (service: Service): Promise<any> => {
|
||||||
return {
|
return {
|
||||||
Query: {
|
Query: {
|
||||||
@ -129,9 +128,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
|
|
||||||
addProject: async (_: any, { organizationSlug, data }: { organizationSlug: string, data: DeepPartial<Project> }, context: any) => {
|
addProject: async (_: any, { organizationSlug, data }: { organizationSlug: string, data: DeepPartial<Project> }, context: any) => {
|
||||||
try {
|
try {
|
||||||
return service.addProject(context.userId, organizationSlug, data);
|
return await service.addProject(context.userId, organizationSlug, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
|
|
||||||
redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
||||||
try {
|
try {
|
||||||
return await service.redeployToProd(context.userId, deploymentId);
|
return Boolean(await service.redeployToProd(context.userId, deploymentId));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
|
@ -90,7 +90,6 @@ type Deployment {
|
|||||||
domain: Domain
|
domain: Domain
|
||||||
branch: String!
|
branch: String!
|
||||||
commitHash: String!
|
commitHash: String!
|
||||||
title: String!
|
|
||||||
url: String!
|
url: String!
|
||||||
environment: Environment!
|
environment: Environment!
|
||||||
isCurrent: Boolean!
|
isCurrent: Boolean!
|
||||||
@ -133,6 +132,7 @@ input AddProjectInput {
|
|||||||
name: String!
|
name: String!
|
||||||
repository: String!
|
repository: String!
|
||||||
prodBranch: String!
|
prodBranch: String!
|
||||||
|
template: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateProjectInput {
|
input UpdateProjectInput {
|
||||||
|
@ -1,23 +1,30 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import debug from 'debug';
|
||||||
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
||||||
|
|
||||||
import { OAuthApp } from '@octokit/oauth-app';
|
import { OAuthApp } from '@octokit/oauth-app';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Deployment, Environment } from './entity/Deployment';
|
import { Deployment, DeploymentStatus, Environment } from './entity/Deployment';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
import { Organization } from './entity/Organization';
|
import { Organization } from './entity/Organization';
|
||||||
import { Project } from './entity/Project';
|
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';
|
||||||
|
|
||||||
|
const log = debug('snowball:service');
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
private db: Database;
|
private db: Database;
|
||||||
private app: OAuthApp;
|
private app: OAuthApp;
|
||||||
|
private registry: Registry;
|
||||||
|
|
||||||
constructor (db: Database, app: OAuthApp) {
|
constructor (db: Database, app: OAuthApp, registry: Registry) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this.registry = registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser (userId: string): Promise<User | null> {
|
async getUser (userId: string): Promise<User | null> {
|
||||||
@ -148,15 +155,21 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateDeploymentToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
async updateDeploymentToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
||||||
const deployment = await this.db.getDeployment({ where: { id: deploymentId }, relations: { project: true } });
|
const oldDeployment = await this.db.getDeployment({
|
||||||
|
where: { id: deploymentId },
|
||||||
|
relations: {
|
||||||
|
project: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!deployment) {
|
if (!oldDeployment) {
|
||||||
throw new Error('Deployment does not exist');
|
throw new Error('Deployment does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
const prodBranchDomains = await this.db.getDomainsByProjectId(deployment.project.id, { branch: deployment.project.prodBranch });
|
const prodBranchDomains = await this.db.getDomainsByProjectId(oldDeployment.project.id, { branch: oldDeployment.project.prodBranch });
|
||||||
|
|
||||||
const oldDeployment = await this.db.getDeployment({
|
// TODO: Fix unique constraint error for domain
|
||||||
|
const deploymentWithProdBranchDomain = await this.db.getDeployment({
|
||||||
where: {
|
where: {
|
||||||
domain: {
|
domain: {
|
||||||
id: prodBranchDomains[0].id
|
id: prodBranchDomains[0].id
|
||||||
@ -164,24 +177,64 @@ export class Service {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (oldDeployment) {
|
if (deploymentWithProdBranchDomain) {
|
||||||
await this.db.updateDeploymentById(oldDeployment.id, {
|
await this.db.updateDeploymentById(deploymentWithProdBranchDomain.id, {
|
||||||
domain: null,
|
domain: null,
|
||||||
isCurrent: false
|
isCurrent: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { createdAt, updatedAt, ...updatedDeployment } = deployment;
|
const oldCurrentDeployment = await this.db.getDeployment({
|
||||||
|
relations: {
|
||||||
updatedDeployment.isCurrent = true;
|
domain: true
|
||||||
updatedDeployment.environment = Environment.Production;
|
},
|
||||||
updatedDeployment.domain = prodBranchDomains[0];
|
where: {
|
||||||
updatedDeployment.createdBy = Object.assign(new User(), {
|
project: {
|
||||||
id: userId
|
id: oldDeployment.project.id
|
||||||
|
},
|
||||||
|
isCurrent: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDeployement = await this.db.addDeployement(updatedDeployment);
|
if (oldCurrentDeployment) {
|
||||||
|
await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDeployement = await this.createDeployment(userId,
|
||||||
|
{
|
||||||
|
project: oldDeployment.project,
|
||||||
|
isCurrent: true,
|
||||||
|
branch: oldDeployment.branch,
|
||||||
|
environment: Environment.Production,
|
||||||
|
domain: prodBranchDomains[0],
|
||||||
|
commitHash: oldDeployment.commitHash
|
||||||
|
});
|
||||||
|
|
||||||
|
return newDeployement;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDeployment (userId: string, data: DeepPartial<Deployment>): Promise<Deployment> {
|
||||||
|
const { recordId, recordData } = await this.registry.createApplicationRecord({
|
||||||
|
recordName: data.project?.name ?? '',
|
||||||
|
appType: data.project?.template ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const newDeployement = await this.db.addDeployement({
|
||||||
|
project: data.project,
|
||||||
|
branch: data.branch,
|
||||||
|
commitHash: data.commitHash,
|
||||||
|
environment: data.environment,
|
||||||
|
isCurrent: data.isCurrent,
|
||||||
|
status: DeploymentStatus.Building,
|
||||||
|
recordId,
|
||||||
|
recordData,
|
||||||
|
domain: data.domain,
|
||||||
|
createdBy: Object.assign(new User(), {
|
||||||
|
id: userId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
log(`Application record ${recordId} published for deployment ${newDeployement.id}`);
|
||||||
return newDeployement;
|
return newDeployement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +248,27 @@ export class Service {
|
|||||||
throw new Error('Organization does not exist');
|
throw new Error('Organization does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db.addProject(userId, organization.id, data);
|
const project = await this.db.addProject(userId, organization.id, data);
|
||||||
|
|
||||||
|
// TODO: Get repository details from github
|
||||||
|
await this.createDeployment(userId,
|
||||||
|
{
|
||||||
|
project,
|
||||||
|
isCurrent: true,
|
||||||
|
branch: project.prodBranch,
|
||||||
|
environment: Environment.Production,
|
||||||
|
// TODO: Set latest commit hash
|
||||||
|
commitHash: '',
|
||||||
|
domain: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const { recordId, recordData } = await this.registry.createApplicationDeploymentRequest({ appName: project.name });
|
||||||
|
await this.db.updateProjectById(project.id, {
|
||||||
|
recordId,
|
||||||
|
recordData
|
||||||
|
});
|
||||||
|
|
||||||
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProject (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
async updateProject (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
||||||
@ -220,8 +293,8 @@ export class Service {
|
|||||||
return this.db.deleteDomainById(domainId);
|
return this.db.deleteDomainById(domainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async redeployToProd (userId: string, deploymentId: string): Promise<Deployment| boolean> {
|
async redeployToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
||||||
const deployment = await this.db.getDeployment({
|
const oldDeployment = await this.db.getDeployment({
|
||||||
relations: {
|
relations: {
|
||||||
project: true,
|
project: true,
|
||||||
domain: true,
|
domain: true,
|
||||||
@ -232,24 +305,24 @@ export class Service {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deployment === null) {
|
if (oldDeployment === null) {
|
||||||
throw new Error('Deployment not found');
|
throw new Error('Deployment not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { createdAt, updatedAt, ...updatedDeployment } = deployment;
|
await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
|
||||||
|
|
||||||
if (updatedDeployment.environment === Environment.Production) {
|
const newDeployement = await this.createDeployment(userId,
|
||||||
// TODO: Put isCurrent field in project
|
{
|
||||||
updatedDeployment.isCurrent = true;
|
project: oldDeployment.project,
|
||||||
updatedDeployment.createdBy = Object.assign(new User(), {
|
// TODO: Put isCurrent field in project
|
||||||
id: userId
|
branch: oldDeployment.branch,
|
||||||
|
isCurrent: true,
|
||||||
|
environment: Environment.Production,
|
||||||
|
domain: oldDeployment.domain,
|
||||||
|
commitHash: oldDeployment.commitHash
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const oldDeployment = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
|
return newDeployement;
|
||||||
const newDeployement = await this.db.addDeployement(updatedDeployment);
|
|
||||||
|
|
||||||
return oldDeployment && Boolean(newDeployement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> {
|
async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> {
|
||||||
|
30
packages/backend/test/fixtures/deployments.json
vendored
30
packages/backend/test/fixtures/deployments.json
vendored
@ -4,10 +4,11 @@
|
|||||||
"domainIndex":0,
|
"domainIndex":0,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"ffhae3zq",
|
"id":"ffhae3zq",
|
||||||
"title": "nextjs-boilerplate-1",
|
|
||||||
"status": "Building",
|
"status": "Building",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
|
"recordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-ffhae3zq.snowball.xyz"
|
"url": "testProject-ffhae3zq.snowball.xyz"
|
||||||
@ -17,10 +18,11 @@
|
|||||||
"domainIndex":1,
|
"domainIndex":1,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"vehagei8",
|
"id":"vehagei8",
|
||||||
"title": "nextjs-boilerplate-2",
|
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-vehagei8.snowball.xyz"
|
"url": "testProject-vehagei8.snowball.xyz"
|
||||||
@ -30,10 +32,11 @@
|
|||||||
"domainIndex":2,
|
"domainIndex":2,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"qmgekyte",
|
"id":"qmgekyte",
|
||||||
"title": "nextjs-boilerplate-3",
|
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-qmgekyte.snowball.xyz"
|
"url": "testProject-qmgekyte.snowball.xyz"
|
||||||
@ -43,10 +46,11 @@
|
|||||||
"domainIndex": null,
|
"domainIndex": null,
|
||||||
"createdByIndex": 0,
|
"createdByIndex": 0,
|
||||||
"id":"f8wsyim6",
|
"id":"f8wsyim6",
|
||||||
"title": "nextjs-boilerplate-4",
|
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "prod",
|
"branch": "prod",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-f8wsyim6.snowball.xyz"
|
"url": "testProject-f8wsyim6.snowball.xyz"
|
||||||
@ -56,10 +60,11 @@
|
|||||||
"domainIndex":3,
|
"domainIndex":3,
|
||||||
"createdByIndex": 1,
|
"createdByIndex": 1,
|
||||||
"id":"eO8cckxk",
|
"id":"eO8cckxk",
|
||||||
"title": "nextjs-boilerplate-1",
|
|
||||||
"status": "Building",
|
"status": "Building",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
|
"recordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-2-eO8cckxk.snowball.xyz"
|
"url": "testProject-2-eO8cckxk.snowball.xyz"
|
||||||
@ -69,10 +74,11 @@
|
|||||||
"domainIndex":4,
|
"domainIndex":4,
|
||||||
"createdByIndex": 1,
|
"createdByIndex": 1,
|
||||||
"id":"yaq0t5yw",
|
"id":"yaq0t5yw",
|
||||||
"title": "nextjs-boilerplate-2",
|
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-2-yaq0t5yw.snowball.xyz"
|
"url": "testProject-2-yaq0t5yw.snowball.xyz"
|
||||||
@ -82,10 +88,11 @@
|
|||||||
"domainIndex":5,
|
"domainIndex":5,
|
||||||
"createdByIndex": 1,
|
"createdByIndex": 1,
|
||||||
"id":"hwwr6sbx",
|
"id":"hwwr6sbx",
|
||||||
"title": "nextjs-boilerplate-3",
|
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "testProject-2-hwwr6sbx.snowball.xyz"
|
"url": "testProject-2-hwwr6sbx.snowball.xyz"
|
||||||
@ -95,10 +102,11 @@
|
|||||||
"domainIndex":6,
|
"domainIndex":6,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"ndxje48a",
|
"id":"ndxje48a",
|
||||||
"title": "nextjs-boilerplate-1",
|
|
||||||
"status": "Building",
|
"status": "Building",
|
||||||
"environment": "Production",
|
"environment": "Production",
|
||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
|
"recordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "iglootools-ndxje48a.snowball.xyz"
|
"url": "iglootools-ndxje48a.snowball.xyz"
|
||||||
@ -108,10 +116,11 @@
|
|||||||
"domainIndex":7,
|
"domainIndex":7,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"gtgpgvei",
|
"id":"gtgpgvei",
|
||||||
"title": "nextjs-boilerplate-2",
|
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"environment": "Preview",
|
"environment": "Preview",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "iglootools-gtgpgvei.snowball.xyz"
|
"url": "iglootools-gtgpgvei.snowball.xyz"
|
||||||
@ -121,10 +130,11 @@
|
|||||||
"domainIndex":8,
|
"domainIndex":8,
|
||||||
"createdByIndex": 2,
|
"createdByIndex": 2,
|
||||||
"id":"b4bpthjr",
|
"id":"b4bpthjr",
|
||||||
"title": "nextjs-boilerplate-3",
|
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
"environment": "Development",
|
"environment": "Development",
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
"recordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
||||||
|
"recordData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "testXyz",
|
"commitHash": "testXyz",
|
||||||
"url": "iglootools-b4bpthjr.snowball.xyz"
|
"url": "iglootools-b4bpthjr.snowball.xyz"
|
||||||
|
10
packages/backend/test/fixtures/projects.json
vendored
10
packages/backend/test/fixtures/projects.json
vendored
@ -10,6 +10,8 @@
|
|||||||
"framework": "test",
|
"framework": "test",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
"recordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"subDomain": "testProject.snowball.xyz"
|
"subDomain": "testProject.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,6 +25,8 @@
|
|||||||
"framework": "test-2",
|
"framework": "test-2",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
"recordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"subDomain": "testProject-2.snowball.xyz"
|
"subDomain": "testProject-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -36,6 +40,8 @@
|
|||||||
"framework": "test-3",
|
"framework": "test-3",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
"recordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"subDomain": "iglootools.snowball.xyz"
|
"subDomain": "iglootools.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -49,6 +55,8 @@
|
|||||||
"framework": "test-4",
|
"framework": "test-4",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
"recordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"subDomain": "iglootools-2.snowball.xyz"
|
"subDomain": "iglootools-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -62,6 +70,8 @@
|
|||||||
"framework": "test-5",
|
"framework": "test-5",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
|
"recordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"recordData": {},
|
||||||
"subDomain": "snowball-2.snowball.xyz"
|
"subDomain": "snowball-2.snowball.xyz"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "nextjs-boilerplate-9t44zbky4dg-bygideon-projects",
|
|
||||||
"status": "Building",
|
"status": "Building",
|
||||||
"isProduction": true,
|
"isProduction": true,
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
@ -15,7 +14,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"title": "nextjs-boilerplate-9232dwky4dg-bygideon-projects",
|
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
"isProduction": false,
|
"isProduction": false,
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
@ -29,7 +27,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"title": "nextjs-boilerplate-9saa22y4dg-bygideon-projects",
|
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
"isProduction": false,
|
"isProduction": false,
|
||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
|
@ -26,6 +26,8 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
|
|||||||
name: `${repository.owner!.login}-${repository.name}`,
|
name: `${repository.owner!.login}-${repository.name}`,
|
||||||
prodBranch: repository.default_branch!,
|
prodBranch: repository.default_branch!,
|
||||||
repository: repository.full_name,
|
repository: repository.full_name,
|
||||||
|
// TODO: Compute template from repo
|
||||||
|
template: 'webapp',
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate(`import?projectId=${addProject.id}`);
|
navigate(`import?projectId=${addProject.id}`);
|
||||||
|
@ -52,6 +52,8 @@ const CreateRepo = () => {
|
|||||||
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
|
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
|
||||||
prodBranch: gitRepo.data.default_branch ?? 'main',
|
prodBranch: gitRepo.data.default_branch ?? 'main',
|
||||||
repository: gitRepo.data.full_name,
|
repository: gitRepo.data.full_name,
|
||||||
|
// TODO: Set selected template
|
||||||
|
template: 'webapp',
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate(
|
navigate(
|
||||||
|
@ -42,7 +42,6 @@ query ($projectId: String!) {
|
|||||||
branch
|
branch
|
||||||
isCurrent
|
isCurrent
|
||||||
status
|
status
|
||||||
title
|
|
||||||
updatedAt
|
updatedAt
|
||||||
commitHash
|
commitHash
|
||||||
createdAt
|
createdAt
|
||||||
@ -83,7 +82,6 @@ query ($organizationSlug: String!) {
|
|||||||
branch
|
branch
|
||||||
isCurrent
|
isCurrent
|
||||||
status
|
status
|
||||||
title
|
|
||||||
updatedAt
|
updatedAt
|
||||||
commitHash
|
commitHash
|
||||||
createdAt
|
createdAt
|
||||||
@ -127,7 +125,6 @@ query ($projectId: String!) {
|
|||||||
}
|
}
|
||||||
branch
|
branch
|
||||||
commitHash
|
commitHash
|
||||||
title
|
|
||||||
url
|
url
|
||||||
environment
|
environment
|
||||||
isCurrent
|
isCurrent
|
||||||
|
@ -62,7 +62,6 @@ export type Deployment = {
|
|||||||
domain: Domain
|
domain: Domain
|
||||||
branch: string
|
branch: string
|
||||||
commitHash: string
|
commitHash: string
|
||||||
title: string
|
|
||||||
url: string
|
url: string
|
||||||
environment: Environment
|
environment: Environment
|
||||||
isCurrent: boolean
|
isCurrent: boolean
|
||||||
@ -244,6 +243,7 @@ export type AddProjectInput = {
|
|||||||
name: string;
|
name: string;
|
||||||
repository: string;
|
repository: string;
|
||||||
prodBranch: string;
|
prodBranch: string;
|
||||||
|
template?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateProjectInput = {
|
export type UpdateProjectInput = {
|
||||||
|
Loading…
Reference in New Issue
Block a user