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:
Nabarun Gogoi 2024-02-12 11:34:01 +05:30 committed by GitHub
parent bd6a6b330c
commit a58b9b255e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1646 additions and 81 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/

View File

@ -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
``` ```

View File

@ -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"

View File

@ -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",

View File

@ -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;
} }

View File

@ -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' })

View File

@ -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;

View File

@ -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);

View 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}`;
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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> {

View File

@ -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"

View File

@ -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"
} }
] ]

View File

@ -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,

View File

@ -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}`);

View File

@ -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(

View File

@ -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

View File

@ -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 = {

1254
yarn.lock

File diff suppressed because it is too large Load Diff