forked from cerc-io/snowballtools-base
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into andrehadianto/design-system-components
This commit is contained in:
commit
496404195c
26
build-webapp.sh
Executable file
26
build-webapp.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PKG_DIR="./packages/frontend"
|
||||||
|
OUTPUT_DIR="${PKG_DIR}/build"
|
||||||
|
DEST_DIR=${1:-/data}
|
||||||
|
|
||||||
|
if [[ -d "$DEST_DIR" ]]; then
|
||||||
|
echo "${DEST_DIR} already exists." 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > $PKG_DIR/.env <<EOF
|
||||||
|
REACT_APP_GQL_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_gql_url'
|
||||||
|
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
|
||||||
|
REACT_APP_GITHUB_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_templaterepo'
|
||||||
|
EOF
|
||||||
|
|
||||||
|
yarn || exit 1
|
||||||
|
yarn build || exit 1
|
||||||
|
|
||||||
|
if [[ ! -d "$OUTPUT_DIR" ]]; then
|
||||||
|
echo "Missing output directory: $OUTPUT_DIR" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$OUTPUT_DIR" "$DEST_DIR"
|
@ -25,6 +25,9 @@
|
|||||||
"allowArgumentsExplicitlyTypedAsAny": true
|
"allowArgumentsExplicitlyTypedAsAny": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }]
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ "ignoreRestSiblings": true }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export interface GitHubConfig {
|
|||||||
oAuth: {
|
oAuth: {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegistryConfig {
|
export interface RegistryConfig {
|
||||||
@ -27,7 +27,7 @@ export interface RegistryConfig {
|
|||||||
amount: string;
|
amount: string;
|
||||||
denom: string;
|
denom: string;
|
||||||
gas: string;
|
gas: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
export const DEFAULT_CONFIG_FILE_PATH = 'environments/local.toml';
|
import process from 'process';
|
||||||
|
|
||||||
|
export const DEFAULT_CONFIG_FILE_PATH =
|
||||||
|
process.env.SNOWBALL_BACKEND_CONFIG_FILE_PATH || 'environments/local.toml';
|
||||||
|
|
||||||
export const DEFAULT_GQL_PATH = '/graphql';
|
export const DEFAULT_GQL_PATH = '/graphql';
|
||||||
|
|
||||||
// Note: temporary hardcoded user, later to be derived from auth token
|
// Note: temporary hardcoded user, later to be derived from auth token
|
||||||
export const USER_ID = '59f4355d-9549-4aac-9b54-eeefceeabef0';
|
export const USER_ID =
|
||||||
|
process.env.SNOWBALL_BACKEND_USER_ID ||
|
||||||
|
'59f4355d-9549-4aac-9b54-eeefceeabef0';
|
||||||
|
|
||||||
export const PROJECT_DOMAIN = 'snowball.xyz';
|
export const PROJECT_DOMAIN =
|
||||||
|
process.env.SNOWBALL_BACKEND_PROJECT_DOMAIN || 'snowball.xyz';
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { DataSource, DeepPartial, FindManyOptions, FindOneOptions, FindOptionsWhere } from 'typeorm';
|
import {
|
||||||
|
DataSource,
|
||||||
|
DeepPartial,
|
||||||
|
FindManyOptions,
|
||||||
|
FindOneOptions,
|
||||||
|
FindOptionsWhere
|
||||||
|
} from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
@ -14,6 +20,12 @@ import { ProjectMember } from './entity/ProjectMember';
|
|||||||
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { PROJECT_DOMAIN } from './constants';
|
import { PROJECT_DOMAIN } from './constants';
|
||||||
|
import { getEntities, loadAndSaveData } from './utils';
|
||||||
|
import { UserOrganization } from './entity/UserOrganization';
|
||||||
|
|
||||||
|
const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json';
|
||||||
|
const USER_DATA_PATH = '../test/fixtures/users.json';
|
||||||
|
const USER_ORGANIZATION_DATA_PATH = '../test/fixtures/user-organizations.json';
|
||||||
|
|
||||||
const log = debug('snowball:database');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
@ -36,6 +48,40 @@ export class Database {
|
|||||||
async init (): Promise<void> {
|
async init (): Promise<void> {
|
||||||
await this.dataSource.initialize();
|
await this.dataSource.initialize();
|
||||||
log('database initialized');
|
log('database initialized');
|
||||||
|
|
||||||
|
const organizations = await this.getOrganizations({});
|
||||||
|
|
||||||
|
if (!organizations.length) {
|
||||||
|
const orgEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, ORGANIZATION_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedOrgs = await loadAndSaveData(Organization, this.dataSource, [
|
||||||
|
orgEntities[0]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// TODO: Remove user once authenticated
|
||||||
|
const userEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, USER_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedUsers = await loadAndSaveData(User, this.dataSource, [
|
||||||
|
userEntities[0]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const userOrganizationRelations = {
|
||||||
|
member: savedUsers,
|
||||||
|
organization: savedOrgs
|
||||||
|
};
|
||||||
|
|
||||||
|
const userOrgEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH)
|
||||||
|
);
|
||||||
|
await loadAndSaveData(
|
||||||
|
UserOrganization,
|
||||||
|
this.dataSource,
|
||||||
|
[userOrgEntities[0]],
|
||||||
|
userOrganizationRelations
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser (options: FindOneOptions<User>): Promise<User | null> {
|
async getUser (options: FindOneOptions<User>): Promise<User | null> {
|
||||||
@ -60,7 +106,18 @@ export class Database {
|
|||||||
return updateResult.affected > 0;
|
return updateResult.affected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrganization (options: FindOneOptions<Organization>): Promise<Organization | null> {
|
async getOrganizations (
|
||||||
|
options: FindManyOptions<Organization>
|
||||||
|
): Promise<Organization[]> {
|
||||||
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
|
const organizations = await organizationRepository.find(options);
|
||||||
|
|
||||||
|
return organizations;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrganization (
|
||||||
|
options: FindOneOptions<Organization>
|
||||||
|
): Promise<Organization | null> {
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
const organization = await organizationRepository.findOne(options);
|
const organization = await organizationRepository.findOne(options);
|
||||||
|
|
||||||
@ -95,7 +152,11 @@ export class Database {
|
|||||||
|
|
||||||
const project = await projectRepository
|
const project = await projectRepository
|
||||||
.createQueryBuilder('project')
|
.createQueryBuilder('project')
|
||||||
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
.leftJoinAndSelect(
|
||||||
|
'project.deployments',
|
||||||
|
'deployments',
|
||||||
|
'deployments.isCurrent = true'
|
||||||
|
)
|
||||||
.leftJoinAndSelect('deployments.createdBy', 'user')
|
.leftJoinAndSelect('deployments.createdBy', 'user')
|
||||||
.leftJoinAndSelect('deployments.domain', 'domain')
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoinAndSelect('project.owner', 'owner')
|
.leftJoinAndSelect('project.owner', 'owner')
|
||||||
@ -108,19 +169,29 @@ export class Database {
|
|||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization (userId: string, organizationSlug: string): Promise<Project[]> {
|
async getProjectsInOrganization (
|
||||||
|
userId: string,
|
||||||
|
organizationSlug: string
|
||||||
|
): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
const projects = await projectRepository
|
const projects = await projectRepository
|
||||||
.createQueryBuilder('project')
|
.createQueryBuilder('project')
|
||||||
.leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true')
|
.leftJoinAndSelect(
|
||||||
|
'project.deployments',
|
||||||
|
'deployments',
|
||||||
|
'deployments.isCurrent = true'
|
||||||
|
)
|
||||||
.leftJoinAndSelect('deployments.domain', 'domain')
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoin('project.projectMembers', 'projectMembers')
|
.leftJoin('project.projectMembers', 'projectMembers')
|
||||||
.leftJoin('project.organization', 'organization')
|
.leftJoin('project.organization', 'organization')
|
||||||
.where('(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug', {
|
.where(
|
||||||
|
'(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug',
|
||||||
|
{
|
||||||
userId,
|
userId,
|
||||||
organizationSlug
|
organizationSlug
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
@ -129,7 +200,9 @@ export class Database {
|
|||||||
/**
|
/**
|
||||||
* Get deployments with specified filter
|
* Get deployments with specified filter
|
||||||
*/
|
*/
|
||||||
async getDeployments (options: FindManyOptions<Deployment>): Promise<Deployment[]> {
|
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(options);
|
||||||
|
|
||||||
@ -154,7 +227,9 @@ export class Database {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeployment (options: FindOneOptions<Deployment>): Promise<Deployment | null> {
|
async getDeployment (
|
||||||
|
options: FindOneOptions<Deployment>
|
||||||
|
): Promise<Deployment | null> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const deployment = await deploymentRepository.findOne(options);
|
const deployment = await deploymentRepository.findOne(options);
|
||||||
|
|
||||||
@ -182,8 +257,11 @@ export class Database {
|
|||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectMembersByProjectId (projectId: string): Promise<ProjectMember[]> {
|
async getProjectMembersByProjectId (
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
projectId: string
|
||||||
|
): Promise<ProjectMember[]> {
|
||||||
|
const projectMemberRepository =
|
||||||
|
this.dataSource.getRepository(ProjectMember);
|
||||||
|
|
||||||
const projectMembers = await projectMemberRepository.find({
|
const projectMembers = await projectMemberRepository.find({
|
||||||
relations: {
|
relations: {
|
||||||
@ -200,8 +278,12 @@ export class Database {
|
|||||||
return projectMembers;
|
return projectMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEnvironmentVariablesByProjectId (projectId: string, filter?: FindOptionsWhere<EnvironmentVariable>): Promise<EnvironmentVariable[]> {
|
async getEnvironmentVariablesByProjectId (
|
||||||
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
projectId: string,
|
||||||
|
filter?: FindOptionsWhere<EnvironmentVariable>
|
||||||
|
): Promise<EnvironmentVariable[]> {
|
||||||
|
const environmentVariableRepository =
|
||||||
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
|
|
||||||
const environmentVariables = await environmentVariableRepository.find({
|
const environmentVariables = await environmentVariableRepository.find({
|
||||||
where: {
|
where: {
|
||||||
@ -216,9 +298,12 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeProjectMemberById (projectMemberId: string): Promise<boolean> {
|
async removeProjectMemberById (projectMemberId: string): Promise<boolean> {
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
const projectMemberRepository =
|
||||||
|
this.dataSource.getRepository(ProjectMember);
|
||||||
|
|
||||||
const deleteResult = await projectMemberRepository.delete({ id: projectMemberId });
|
const deleteResult = await projectMemberRepository.delete({
|
||||||
|
id: projectMemberId
|
||||||
|
});
|
||||||
|
|
||||||
if (deleteResult.affected) {
|
if (deleteResult.affected) {
|
||||||
return deleteResult.affected > 0;
|
return deleteResult.affected > 0;
|
||||||
@ -227,37 +312,63 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProjectMemberById (projectMemberId: string, data: DeepPartial<ProjectMember>): Promise<boolean> {
|
async updateProjectMemberById (
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
projectMemberId: string,
|
||||||
const updateResult = await projectMemberRepository.update({ id: projectMemberId }, data);
|
data: DeepPartial<ProjectMember>
|
||||||
|
): Promise<boolean> {
|
||||||
|
const projectMemberRepository =
|
||||||
|
this.dataSource.getRepository(ProjectMember);
|
||||||
|
const updateResult = await projectMemberRepository.update(
|
||||||
|
{ id: projectMemberId },
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProjectMember (data: DeepPartial<ProjectMember>): Promise<ProjectMember> {
|
async addProjectMember (
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
data: DeepPartial<ProjectMember>
|
||||||
|
): Promise<ProjectMember> {
|
||||||
|
const projectMemberRepository =
|
||||||
|
this.dataSource.getRepository(ProjectMember);
|
||||||
const newProjectMember = await projectMemberRepository.save(data);
|
const newProjectMember = await projectMemberRepository.save(data);
|
||||||
|
|
||||||
return newProjectMember;
|
return newProjectMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentVariables (data: DeepPartial<EnvironmentVariable>[]): Promise<EnvironmentVariable[]> {
|
async addEnvironmentVariables (
|
||||||
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
data: DeepPartial<EnvironmentVariable>[]
|
||||||
const savedEnvironmentVariables = await environmentVariableRepository.save(data);
|
): Promise<EnvironmentVariable[]> {
|
||||||
|
const environmentVariableRepository =
|
||||||
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
|
const savedEnvironmentVariables =
|
||||||
|
await environmentVariableRepository.save(data);
|
||||||
|
|
||||||
return savedEnvironmentVariables;
|
return savedEnvironmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEnvironmentVariable (environmentVariableId: string, data: DeepPartial<EnvironmentVariable>): Promise<boolean> {
|
async updateEnvironmentVariable (
|
||||||
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
environmentVariableId: string,
|
||||||
const updateResult = await environmentVariableRepository.update({ id: environmentVariableId }, data);
|
data: DeepPartial<EnvironmentVariable>
|
||||||
|
): Promise<boolean> {
|
||||||
|
const environmentVariableRepository =
|
||||||
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
|
const updateResult = await environmentVariableRepository.update(
|
||||||
|
{ id: environmentVariableId },
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteEnvironmentVariable (environmentVariableId: string): Promise<boolean> {
|
async deleteEnvironmentVariable (
|
||||||
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
environmentVariableId: string
|
||||||
const deleteResult = await environmentVariableRepository.delete({ id: environmentVariableId });
|
): Promise<boolean> {
|
||||||
|
const environmentVariableRepository =
|
||||||
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
|
const deleteResult = await environmentVariableRepository.delete({
|
||||||
|
id: environmentVariableId
|
||||||
|
});
|
||||||
|
|
||||||
if (deleteResult.affected) {
|
if (deleteResult.affected) {
|
||||||
return deleteResult.affected > 0;
|
return deleteResult.affected > 0;
|
||||||
@ -267,7 +378,8 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getProjectMemberById (projectMemberId: string): Promise<ProjectMember> {
|
async getProjectMemberById (projectMemberId: string): Promise<ProjectMember> {
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
const projectMemberRepository =
|
||||||
|
this.dataSource.getRepository(ProjectMember);
|
||||||
|
|
||||||
const projectMemberWithProject = await projectMemberRepository.find({
|
const projectMemberWithProject = await projectMemberRepository.find({
|
||||||
relations: {
|
relations: {
|
||||||
@ -279,8 +391,7 @@ export class Database {
|
|||||||
where: {
|
where: {
|
||||||
id: projectMemberId
|
id: projectMemberId
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (projectMemberWithProject.length === 0) {
|
if (projectMemberWithProject.length === 0) {
|
||||||
throw new Error('Member does not exist');
|
throw new Error('Member does not exist');
|
||||||
@ -289,34 +400,49 @@ export class Database {
|
|||||||
return projectMemberWithProject[0];
|
return projectMemberWithProject[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsBySearchText (userId: string, searchText: string): Promise<Project[]> {
|
async getProjectsBySearchText (
|
||||||
|
userId: string,
|
||||||
|
searchText: string
|
||||||
|
): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
const projects = await projectRepository
|
const projects = await projectRepository
|
||||||
.createQueryBuilder('project')
|
.createQueryBuilder('project')
|
||||||
.leftJoinAndSelect('project.organization', 'organization')
|
.leftJoinAndSelect('project.organization', 'organization')
|
||||||
.leftJoin('project.projectMembers', 'projectMembers')
|
.leftJoin('project.projectMembers', 'projectMembers')
|
||||||
.where('(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText', {
|
.where(
|
||||||
|
'(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText',
|
||||||
|
{
|
||||||
userId,
|
userId,
|
||||||
searchText: `%${searchText}%`
|
searchText: `%${searchText}%`
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeploymentById (deploymentId: string, data: DeepPartial<Deployment>): Promise<boolean> {
|
async updateDeploymentById (
|
||||||
|
deploymentId: string,
|
||||||
|
data: DeepPartial<Deployment>
|
||||||
|
): Promise<boolean> {
|
||||||
return this.updateDeployment({ id: deploymentId }, data);
|
return this.updateDeployment({ id: deploymentId }, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeployment (criteria: FindOptionsWhere<Deployment>, data: DeepPartial<Deployment>): Promise<boolean> {
|
async updateDeployment (
|
||||||
|
criteria: FindOptionsWhere<Deployment>,
|
||||||
|
data: DeepPartial<Deployment>
|
||||||
|
): Promise<boolean> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const updateResult = await deploymentRepository.update(criteria, data);
|
const updateResult = await deploymentRepository.update(criteria, data);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeploymentsByProjectIds (projectIds: string[], data: DeepPartial<Deployment>): Promise<boolean> {
|
async updateDeploymentsByProjectIds (
|
||||||
|
projectIds: string[],
|
||||||
|
data: DeepPartial<Deployment>
|
||||||
|
): Promise<boolean> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
|
||||||
const updateResult = await deploymentRepository
|
const updateResult = await deploymentRepository
|
||||||
@ -329,7 +455,11 @@ export class Database {
|
|||||||
return Boolean(updateResult.affected);
|
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);
|
||||||
|
|
||||||
// TODO: Check if organization exists
|
// TODO: Check if organization exists
|
||||||
@ -352,9 +482,15 @@ export class Database {
|
|||||||
return projectRepository.save(newProject);
|
return projectRepository.save(newProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProjectById (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
async updateProjectById (
|
||||||
|
projectId: string,
|
||||||
|
data: DeepPartial<Project>
|
||||||
|
): Promise<boolean> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
const updateResult = await projectRepository.update({ id: projectId }, data);
|
const updateResult = await projectRepository.update(
|
||||||
|
{ id: projectId },
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
@ -401,14 +537,20 @@ export class Database {
|
|||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDomainById (domainId: string, data: DeepPartial<Domain>): Promise<boolean> {
|
async updateDomainById (
|
||||||
|
domainId: string,
|
||||||
|
data: DeepPartial<Domain>
|
||||||
|
): Promise<boolean> {
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
const updateResult = await domainRepository.update({ id: domainId }, data);
|
const updateResult = await domainRepository.update({ id: domainId }, data);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDomainsByProjectId (projectId: string, filter?: FindOptionsWhere<Domain>): Promise<Domain[]> {
|
async getDomainsByProjectId (
|
||||||
|
projectId: string,
|
||||||
|
filter?: FindOptionsWhere<Domain>
|
||||||
|
): Promise<Domain[]> {
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
|
|
||||||
const domains = await domainRepository.find({
|
const domains = await domainRepository.find({
|
||||||
|
@ -26,18 +26,27 @@ export enum DeploymentStatus {
|
|||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApplicationDeploymentRequest {
|
||||||
|
type: string;
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
application: string;
|
||||||
|
config: string;
|
||||||
|
meta: string;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
author?: string
|
author?: string;
|
||||||
repository?: string[],
|
repository?: string[];
|
||||||
app_version?: string
|
app_version?: string;
|
||||||
repository_ref: string
|
repository_ref: string;
|
||||||
app_type: string
|
app_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@ -78,6 +87,12 @@ export class Deployment {
|
|||||||
@Column('simple-json')
|
@Column('simple-json')
|
||||||
applicationRecordData!: ApplicationRecord;
|
applicationRecordData!: ApplicationRecord;
|
||||||
|
|
||||||
|
@Column('varchar')
|
||||||
|
applicationDeploymentRequestId!: string;
|
||||||
|
|
||||||
|
@Column('simple-json')
|
||||||
|
applicationDeploymentRequestData!: ApplicationDeploymentRequest;
|
||||||
|
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
applicationDeploymentRecordId!: string | null;
|
applicationDeploymentRecordId!: string | null;
|
||||||
|
|
||||||
|
@ -27,8 +27,12 @@ export class Organization {
|
|||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
@OneToMany(() => UserOrganization, userOrganization => userOrganization.organization, {
|
@OneToMany(
|
||||||
|
() => UserOrganization,
|
||||||
|
(userOrganization) => userOrganization.organization,
|
||||||
|
{
|
||||||
cascade: ['soft-remove']
|
cascade: ['soft-remove']
|
||||||
})
|
}
|
||||||
|
)
|
||||||
userOrganizations!: UserOrganization[];
|
userOrganizations!: UserOrganization[];
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,6 @@ 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: string,
|
|
||||||
meta: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Project {
|
export class Project {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
@ -52,12 +43,6 @@ export class Project {
|
|||||||
@Column('varchar', { length: 255, default: 'main' })
|
@Column('varchar', { length: 255, default: 'main' })
|
||||||
prodBranch!: string;
|
prodBranch!: string;
|
||||||
|
|
||||||
@Column('varchar', { nullable: true })
|
|
||||||
applicationDeploymentRequestId!: string | null;
|
|
||||||
|
|
||||||
@Column('simple-json', { nullable: true })
|
|
||||||
applicationDeploymentRequestData!: ApplicationDeploymentRequest | null;
|
|
||||||
|
|
||||||
@Column('text', { default: '' })
|
@Column('text', { default: '' })
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
@ -91,7 +76,7 @@ export class Project {
|
|||||||
@OneToMany(() => Deployment, (deployment) => deployment.project)
|
@OneToMany(() => Deployment, (deployment) => deployment.project)
|
||||||
deployments!: Deployment[];
|
deployments!: Deployment[];
|
||||||
|
|
||||||
@OneToMany(() => ProjectMember, projectMember => projectMember.project, {
|
@OneToMany(() => ProjectMember, (projectMember) => projectMember.project, {
|
||||||
cascade: ['soft-remove']
|
cascade: ['soft-remove']
|
||||||
})
|
})
|
||||||
projectMembers!: ProjectMember[];
|
projectMembers!: ProjectMember[];
|
||||||
|
@ -15,7 +15,7 @@ import { User } from './User';
|
|||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
View = 'View',
|
View = 'View',
|
||||||
Edit = 'Edit'
|
Edit = 'Edit',
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@ -34,13 +34,17 @@ export class User {
|
|||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
@OneToMany(() => ProjectMember, projectMember => projectMember.project, {
|
@OneToMany(() => ProjectMember, (projectMember) => projectMember.project, {
|
||||||
cascade: ['soft-remove']
|
cascade: ['soft-remove']
|
||||||
})
|
})
|
||||||
projectMembers!: ProjectMember[];
|
projectMembers!: ProjectMember[];
|
||||||
|
|
||||||
@OneToMany(() => UserOrganization, UserOrganization => UserOrganization.member, {
|
@OneToMany(
|
||||||
|
() => UserOrganization,
|
||||||
|
(UserOrganization) => UserOrganization.member,
|
||||||
|
{
|
||||||
cascade: ['soft-remove']
|
cascade: ['soft-remove']
|
||||||
})
|
}
|
||||||
|
)
|
||||||
userOrganizations!: UserOrganization[];
|
userOrganizations!: UserOrganization[];
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ 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, gitHub, registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
const { server, database, gitHub, registryConfig } = await getConfig<Config>(
|
||||||
|
DEFAULT_CONFIG_FILE_PATH
|
||||||
|
);
|
||||||
|
|
||||||
const app = new OAuthApp({
|
const app = new OAuthApp({
|
||||||
clientType: OAUTH_CLIENT_TYPE,
|
clientType: OAUTH_CLIENT_TYPE,
|
||||||
@ -31,9 +33,16 @@ 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, registryConfig }, 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);
|
||||||
|
|
||||||
await createAndStartServer(server, typeDefs, resolvers, service);
|
await createAndStartServer(server, typeDefs, resolvers, service);
|
||||||
|
@ -6,8 +6,11 @@ import { DateTime } from 'luxon';
|
|||||||
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
|
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
|
||||||
|
|
||||||
import { RegistryConfig } from './config';
|
import { RegistryConfig } from './config';
|
||||||
import { ApplicationDeploymentRequest } from './entity/Project';
|
import {
|
||||||
import { ApplicationRecord, Deployment } from './entity/Deployment';
|
ApplicationRecord,
|
||||||
|
Deployment,
|
||||||
|
ApplicationDeploymentRequest
|
||||||
|
} from './entity/Deployment';
|
||||||
import { AppDeploymentRecord, PackageJSON } from './types';
|
import { AppDeploymentRecord, PackageJSON } from './types';
|
||||||
|
|
||||||
const log = debug('snowball:registry');
|
const log = debug('snowball:registry');
|
||||||
@ -23,33 +26,52 @@ export class Registry {
|
|||||||
|
|
||||||
constructor (registryConfig: RegistryConfig) {
|
constructor (registryConfig: RegistryConfig) {
|
||||||
this.registryConfig = registryConfig;
|
this.registryConfig = registryConfig;
|
||||||
this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
|
this.registry = new LaconicRegistry(
|
||||||
|
registryConfig.gqlEndpoint,
|
||||||
|
registryConfig.restEndpoint,
|
||||||
|
registryConfig.chainId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createApplicationRecord ({
|
async createApplicationRecord ({
|
||||||
|
appName,
|
||||||
packageJSON,
|
packageJSON,
|
||||||
commitHash,
|
commitHash,
|
||||||
appType,
|
appType,
|
||||||
repoUrl
|
repoUrl
|
||||||
}: {
|
}: {
|
||||||
packageJSON: PackageJSON
|
appName: string;
|
||||||
commitHash: string,
|
packageJSON: PackageJSON;
|
||||||
appType: string,
|
commitHash: string;
|
||||||
repoUrl: string
|
appType: string;
|
||||||
}): Promise<{applicationRecordId: string, applicationRecordData: ApplicationRecord}> {
|
repoUrl: string;
|
||||||
assert(packageJSON.name, "name field doesn't exist in package.json");
|
}): Promise<{
|
||||||
|
applicationRecordId: string;
|
||||||
|
applicationRecordData: ApplicationRecord;
|
||||||
|
}> {
|
||||||
// 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
|
||||||
const records = await this.registry.queryRecords({
|
const records = await this.registry.queryRecords(
|
||||||
|
{
|
||||||
type: APP_RECORD_TYPE,
|
type: APP_RECORD_TYPE,
|
||||||
name: packageJSON.name
|
name: packageJSON.name
|
||||||
}, true);
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// Get next version of record
|
// Get next version of record
|
||||||
const bondRecords = records.filter((record: any) => record.bondId === this.registryConfig.bondId);
|
const bondRecords = records.filter(
|
||||||
const [latestBondRecord] = bondRecords.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
|
(record: any) => record.bondId === this.registryConfig.bondId
|
||||||
const nextVersion = semverInc(latestBondRecord?.attributes.version ?? '0.0.0', 'patch');
|
);
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
|
||||||
assert(nextVersion, 'Application record version not valid');
|
assert(nextVersion, 'Application record version not valid');
|
||||||
|
|
||||||
@ -60,11 +82,16 @@ export class Registry {
|
|||||||
repository_ref: commitHash,
|
repository_ref: commitHash,
|
||||||
repository: [repoUrl],
|
repository: [repoUrl],
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
name: packageJSON.name,
|
name: appName,
|
||||||
...(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 }),
|
||||||
...(packageJSON.author && { author: typeof packageJSON.author === 'object' ? JSON.stringify(packageJSON.author) : packageJSON.author }),
|
...(packageJSON.author && {
|
||||||
|
author:
|
||||||
|
typeof packageJSON.author === 'object'
|
||||||
|
? JSON.stringify(packageJSON.author)
|
||||||
|
: packageJSON.author
|
||||||
|
}),
|
||||||
...(packageJSON.version && { app_version: packageJSON.version })
|
...(packageJSON.version && { app_version: packageJSON.version })
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,26 +108,45 @@ 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, appName);
|
||||||
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(
|
||||||
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee);
|
{ cid: result.data.id, crn },
|
||||||
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee);
|
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 { applicationRecordId: result.data.id, applicationRecordData: applicationRecord };
|
return {
|
||||||
|
applicationRecordId: result.data.id,
|
||||||
|
applicationRecordData: applicationRecord
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createApplicationDeploymentRequest (data: {
|
async createApplicationDeploymentRequest (data: {
|
||||||
appName: string,
|
appName: string;
|
||||||
commitHash: string,
|
packageJsonName: string;
|
||||||
repository: string,
|
commitHash: string;
|
||||||
environmentVariables: { [key: string]: string }
|
repository: string;
|
||||||
|
environmentVariables: { [key: string]: string };
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
applicationDeploymentRequestId: string,
|
applicationDeploymentRequestId: string;
|
||||||
applicationDeploymentRequestData: ApplicationDeploymentRequest
|
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
||||||
}> {
|
}> {
|
||||||
const crn = this.getCrn(data.appName);
|
const crn = this.getCrn(data.packageJsonName, data.appName);
|
||||||
const records = await this.registry.resolveNames([crn]);
|
const records = await this.registry.resolveNames([crn]);
|
||||||
const applicationRecord = records[0];
|
const applicationRecord = records[0];
|
||||||
|
|
||||||
@ -124,7 +170,9 @@ export class Registry {
|
|||||||
env: data.environmentVariables
|
env: data.environmentVariables
|
||||||
}),
|
}),
|
||||||
meta: JSON.stringify({
|
meta: JSON.stringify({
|
||||||
note: `Added by Snowball @ ${DateTime.utc().toFormat('EEE LLL dd HH:mm:ss \'UTC\' yyyy')}`,
|
note: `Added by Snowball @ ${DateTime.utc().toFormat(
|
||||||
|
"EEE LLL dd HH:mm:ss 'UTC' yyyy"
|
||||||
|
)}`,
|
||||||
repository: data.repository,
|
repository: data.repository,
|
||||||
repository_ref: data.commitHash
|
repository_ref: data.commitHash
|
||||||
})
|
})
|
||||||
@ -142,31 +190,40 @@ 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 { applicationDeploymentRequestId: result.data.id, applicationDeploymentRequestData: applicationDeploymentRequest };
|
return {
|
||||||
|
applicationDeploymentRequestId: result.data.id,
|
||||||
|
applicationDeploymentRequestData: applicationDeploymentRequest
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch ApplicationDeploymentRecords for deployments
|
* Fetch ApplicationDeploymentRecords for deployments
|
||||||
*/
|
*/
|
||||||
async getDeploymentRecords (deployments: Deployment[]): Promise<AppDeploymentRecord[]> {
|
async getDeploymentRecords (
|
||||||
|
deployments: Deployment[]
|
||||||
|
): Promise<AppDeploymentRecord[]> {
|
||||||
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
|
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
|
||||||
// TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute
|
// TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute
|
||||||
const records = await this.registry.queryRecords({
|
const records = await this.registry.queryRecords(
|
||||||
|
{
|
||||||
type: APP_DEPLOYMENT_RECORD_TYPE
|
type: APP_DEPLOYMENT_RECORD_TYPE
|
||||||
}, true);
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// Filter records with ApplicationRecord ids
|
// Filter records with ApplicationRecord ids
|
||||||
return records.filter((record: AppDeploymentRecord) => deployments.some(deployment => deployment.applicationRecordId === record.attributes.application));
|
return records.filter((record: AppDeploymentRecord) =>
|
||||||
|
deployments.some(
|
||||||
|
(deployment) =>
|
||||||
|
deployment.applicationRecordId === record.attributes.application
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCrn (packageJsonName: string): string {
|
getCrn (packageJsonName: string, appName: string): string {
|
||||||
const [arg1, arg2] = packageJsonName.split('/');
|
const [arg1] = packageJsonName.split('/');
|
||||||
|
|
||||||
if (arg2) {
|
|
||||||
const authority = arg1.replace('@', '');
|
const authority = arg1.replace('@', '');
|
||||||
return `crn://${authority}/applications/${arg2}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `crn://${arg1}/applications/${arg1}`;
|
return `crn://${authority}/applications/${appName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,25 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
return service.getProjectById(projectId);
|
return service.getProjectById(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => {
|
projectsInOrganization: async (
|
||||||
return service.getProjectsInOrganization(context.userId, organizationSlug);
|
_: any,
|
||||||
|
{ organizationSlug }: { organizationSlug: string },
|
||||||
|
context: any
|
||||||
|
) => {
|
||||||
|
return service.getProjectsInOrganization(
|
||||||
|
context.userId,
|
||||||
|
organizationSlug
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
return service.getDeploymentsByProjectId(projectId);
|
return service.getDeploymentsByProjectId(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
environmentVariables: async (_: any, { projectId }: { projectId: string }) => {
|
environmentVariables: async (
|
||||||
|
_: any,
|
||||||
|
{ projectId }: { projectId: string }
|
||||||
|
) => {
|
||||||
return service.getEnvironmentVariablesByProjectId(projectId);
|
return service.getEnvironmentVariablesByProjectId(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -41,32 +51,55 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
return service.getProjectMembersByProjectId(projectId);
|
return service.getProjectMembersByProjectId(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
searchProjects: async (_: any, { searchText }: { searchText: string }, context: any) => {
|
searchProjects: async (
|
||||||
|
_: any,
|
||||||
|
{ searchText }: { searchText: string },
|
||||||
|
context: any
|
||||||
|
) => {
|
||||||
return service.searchProjects(context.userId, searchText);
|
return service.searchProjects(context.userId, searchText);
|
||||||
},
|
},
|
||||||
|
|
||||||
domains: async (_:any, { projectId, filter }: { projectId: string, filter?: FindOptionsWhere<Domain> }) => {
|
domains: async (
|
||||||
|
_: any,
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
filter
|
||||||
|
}: { projectId: string; filter?: FindOptionsWhere<Domain> }
|
||||||
|
) => {
|
||||||
return service.getDomainsByProjectId(projectId, filter);
|
return service.getDomainsByProjectId(projectId, filter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Return error in GQL response
|
// TODO: Return error in GQL response
|
||||||
Mutation: {
|
Mutation: {
|
||||||
removeProjectMember: async (_: any, { projectMemberId }: { projectMemberId: string }, context: any) => {
|
removeProjectMember: async (
|
||||||
|
_: any,
|
||||||
|
{ projectMemberId }: { projectMemberId: string },
|
||||||
|
context: any
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.removeProjectMember(context.userId, projectMemberId);
|
return await service.removeProjectMember(
|
||||||
|
context.userId,
|
||||||
|
projectMemberId
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProjectMember: async (_: any, { projectMemberId, data }: {
|
updateProjectMember: async (
|
||||||
projectMemberId: string,
|
_: any,
|
||||||
|
{
|
||||||
|
projectMemberId,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
projectMemberId: string;
|
||||||
data: {
|
data: {
|
||||||
permissions: Permission[]
|
permissions: Permission[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.updateProjectMember(projectMemberId, data);
|
return await service.updateProjectMember(projectMemberId, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -75,13 +108,19 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addProjectMember: async (_: any, { projectId, data }: {
|
addProjectMember: async (
|
||||||
projectId: string,
|
_: any,
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
data: {
|
data: {
|
||||||
email: string,
|
email: string;
|
||||||
permissions: Permission[]
|
permissions: Permission[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
return Boolean(await service.addProjectMember(projectId, data));
|
return Boolean(await service.addProjectMember(projectId, data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -90,25 +129,51 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addEnvironmentVariables: async (_: any, { projectId, data }: { projectId: string, data: { environments: string[], key: string, value: string}[] }) => {
|
addEnvironmentVariables: async (
|
||||||
|
_: any,
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
data: { environments: string[]; key: string; value: string }[];
|
||||||
|
}
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return Boolean(await service.addEnvironmentVariables(projectId, data));
|
return Boolean(
|
||||||
|
await service.addEnvironmentVariables(projectId, data)
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEnvironmentVariable: async (_: any, { environmentVariableId, data }: { environmentVariableId: string, data : DeepPartial<EnvironmentVariable>}) => {
|
updateEnvironmentVariable: async (
|
||||||
|
_: any,
|
||||||
|
{
|
||||||
|
environmentVariableId,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
environmentVariableId: string;
|
||||||
|
data: DeepPartial<EnvironmentVariable>;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.updateEnvironmentVariable(environmentVariableId, data);
|
return await service.updateEnvironmentVariable(
|
||||||
|
environmentVariableId,
|
||||||
|
data
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => {
|
removeEnvironmentVariable: async (
|
||||||
|
_: any,
|
||||||
|
{ environmentVariableId }: { environmentVariableId: string }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.removeEnvironmentVariable(environmentVariableId);
|
return await service.removeEnvironmentVariable(environmentVariableId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -117,25 +182,45 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
updateDeploymentToProd: async (
|
||||||
|
_: any,
|
||||||
|
{ deploymentId }: { deploymentId: string },
|
||||||
|
context: any
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return Boolean(await service.updateDeploymentToProd(context.userId, deploymentId));
|
return Boolean(
|
||||||
|
await service.updateDeploymentToProd(context.userId, deploymentId)
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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 await service.addProject(context.userId, organizationSlug, data);
|
return await service.addProject(
|
||||||
|
context.userId,
|
||||||
|
organizationSlug,
|
||||||
|
data
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProject: async (_: any, { projectId, data }: { projectId: string, data: DeepPartial<Project> }) => {
|
updateProject: async (
|
||||||
|
_: any,
|
||||||
|
{ projectId, data }: { projectId: string; data: DeepPartial<Project> }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.updateProject(projectId, data);
|
return await service.updateProject(projectId, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -144,9 +229,15 @@ 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 Boolean(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;
|
||||||
@ -157,7 +248,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
try {
|
try {
|
||||||
return await service.deleteProject(projectId);
|
return await service.deleteProject(projectId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err); return false;
|
log(err);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -170,7 +262,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => {
|
rollbackDeployment: async (
|
||||||
|
_: any,
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
deploymentId
|
||||||
|
}: { deploymentId: string; projectId: string }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.rollbackDeployment(projectId, deploymentId);
|
return await service.rollbackDeployment(projectId, deploymentId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -179,7 +277,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addDomain: async (_: any, { projectId, data }: { projectId: string, data: { name: string } }) => {
|
addDomain: async (
|
||||||
|
_: any,
|
||||||
|
{ projectId, data }: { projectId: string; data: { name: string } }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return Boolean(await service.addDomain(projectId, data));
|
return Boolean(await service.addDomain(projectId, data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -188,7 +289,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDomain: async (_: any, { domainId, data }: { domainId: string, data: DeepPartial<Domain>}) => {
|
updateDomain: async (
|
||||||
|
_: any,
|
||||||
|
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.updateDomain(domainId, data);
|
return await service.updateDomain(domainId, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -197,7 +301,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => {
|
authenticateGitHub: async (
|
||||||
|
_: any,
|
||||||
|
{ code }: { code: string },
|
||||||
|
context: any
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
return await service.authenticateGitHub(code, context.userId);
|
return await service.authenticateGitHub(code, context.userId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -208,7 +316,9 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
|
|
||||||
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
||||||
try {
|
try {
|
||||||
return service.unauthenticateGitHub(context.userId, { gitHubToken: null });
|
return service.unauthenticateGitHub(context.userId, {
|
||||||
|
gitHubToken: null
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(err);
|
log(err);
|
||||||
return false;
|
return false;
|
||||||
|
@ -188,10 +188,19 @@ type Query {
|
|||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean!
|
addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean!
|
||||||
updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean!
|
updateProjectMember(
|
||||||
|
projectMemberId: String!
|
||||||
|
data: UpdateProjectMemberInput
|
||||||
|
): Boolean!
|
||||||
removeProjectMember(projectMemberId: String!): Boolean!
|
removeProjectMember(projectMemberId: String!): Boolean!
|
||||||
addEnvironmentVariables(projectId: String!, data: [AddEnvironmentVariableInput!]): Boolean!
|
addEnvironmentVariables(
|
||||||
updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean!
|
projectId: String!
|
||||||
|
data: [AddEnvironmentVariableInput!]
|
||||||
|
): Boolean!
|
||||||
|
updateEnvironmentVariable(
|
||||||
|
environmentVariableId: String!
|
||||||
|
data: UpdateEnvironmentVariableInput!
|
||||||
|
): Boolean!
|
||||||
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
||||||
updateDeploymentToProd(deploymentId: String!): Boolean!
|
updateDeploymentToProd(deploymentId: String!): Boolean!
|
||||||
addProject(organizationSlug: String!, data: AddProjectInput): Project!
|
addProject(organizationSlug: String!, data: AddProjectInput): Project!
|
||||||
|
@ -15,15 +15,15 @@ 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, RegistryConfig } from './config';
|
import { GitHubConfig, RegistryConfig } from './config';
|
||||||
import { AppDeploymentRecord, GitPushEventPayload } from './types';
|
import { AppDeploymentRecord, GitPushEventPayload, PackageJSON } 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
|
registryConfig: RegistryConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
@ -71,7 +71,9 @@ export class Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (deployments.length) {
|
if (deployments.length) {
|
||||||
log(`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`);
|
log(
|
||||||
|
`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch ApplicationDeploymentRecord for deployments
|
// Fetch ApplicationDeploymentRecord for deployments
|
||||||
const records = await this.registry.getDeploymentRecords(deployments);
|
const records = await this.registry.getDeploymentRecords(deployments);
|
||||||
@ -91,10 +93,12 @@ export class Service {
|
|||||||
/**
|
/**
|
||||||
* Update deployments with ApplicationDeploymentRecord data
|
* Update deployments with ApplicationDeploymentRecord data
|
||||||
*/
|
*/
|
||||||
async updateDeploymentsWithRecordData (records: AppDeploymentRecord[]): Promise<void> {
|
async updateDeploymentsWithRecordData (
|
||||||
|
records: AppDeploymentRecord[]
|
||||||
|
): Promise<void> {
|
||||||
// Get deployments for ApplicationDeploymentRecords
|
// Get deployments for ApplicationDeploymentRecords
|
||||||
const deployments = await this.db.getDeployments({
|
const deployments = await this.db.getDeployments({
|
||||||
where: records.map(record => ({
|
where: records.map((record) => ({
|
||||||
applicationRecordId: record.attributes.application
|
applicationRecordId: record.attributes.application
|
||||||
})),
|
})),
|
||||||
order: {
|
order: {
|
||||||
@ -103,38 +107,46 @@ export class Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get project IDs of deployments that are in production environment
|
// Get project IDs of deployments that are in production environment
|
||||||
const productionDeploymentProjectIds = deployments.reduce((acc, deployment): Set<string> => {
|
const productionDeploymentProjectIds = deployments.reduce(
|
||||||
|
(acc, deployment): Set<string> => {
|
||||||
if (deployment.environment === Environment.Production) {
|
if (deployment.environment === Environment.Production) {
|
||||||
acc.add(deployment.projectId);
|
acc.add(deployment.projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, new Set<string>());
|
},
|
||||||
|
new Set<string>()
|
||||||
|
);
|
||||||
|
|
||||||
// Set old deployments isCurrent to false
|
// Set old deployments isCurrent to false
|
||||||
await this.db.updateDeploymentsByProjectIds(Array.from(productionDeploymentProjectIds), { isCurrent: false });
|
await this.db.updateDeploymentsByProjectIds(
|
||||||
|
Array.from(productionDeploymentProjectIds),
|
||||||
|
{ isCurrent: false }
|
||||||
|
);
|
||||||
|
|
||||||
const recordToDeploymentsMap = deployments.reduce((acc: {[key: string]: Deployment}, deployment) => {
|
const recordToDeploymentsMap = deployments.reduce(
|
||||||
|
(acc: { [key: string]: Deployment }, deployment) => {
|
||||||
acc[deployment.applicationRecordId] = deployment;
|
acc[deployment.applicationRecordId] = deployment;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
// Update deployment data for ApplicationDeploymentRecords
|
// Update deployment data for ApplicationDeploymentRecords
|
||||||
const deploymentUpdatePromises = records.map(async (record) => {
|
const deploymentUpdatePromises = records.map(async (record) => {
|
||||||
const deployment = recordToDeploymentsMap[record.attributes.application];
|
const deployment = recordToDeploymentsMap[record.attributes.application];
|
||||||
|
|
||||||
await this.db.updateDeploymentById(
|
await this.db.updateDeploymentById(deployment.id, {
|
||||||
deployment.id,
|
|
||||||
{
|
|
||||||
applicationDeploymentRecordId: record.id,
|
applicationDeploymentRecordId: record.id,
|
||||||
applicationDeploymentRecordData: record.attributes,
|
applicationDeploymentRecordData: record.attributes,
|
||||||
url: record.attributes.url,
|
url: record.attributes.url,
|
||||||
status: DeploymentStatus.Ready,
|
status: DeploymentStatus.Ready,
|
||||||
isCurrent: deployment.environment === Environment.Production
|
isCurrent: deployment.environment === Environment.Production
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
log(`Updated deployment ${deployment.id} with URL ${record.attributes.url}`);
|
log(
|
||||||
|
`Updated deployment ${deployment.id} with URL ${record.attributes.url}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(deploymentUpdatePromises);
|
await Promise.all(deploymentUpdatePromises);
|
||||||
@ -150,7 +162,10 @@ export class Service {
|
|||||||
|
|
||||||
async getOctokit (userId: string): Promise<Octokit> {
|
async getOctokit (userId: string): Promise<Octokit> {
|
||||||
const user = await this.db.getUser({ where: { id: userId } });
|
const user = await this.db.getUser({ where: { id: userId } });
|
||||||
assert(user && user.gitHubToken, 'User needs to be authenticated with GitHub token');
|
assert(
|
||||||
|
user && user.gitHubToken,
|
||||||
|
'User needs to be authenticated with GitHub token'
|
||||||
|
);
|
||||||
|
|
||||||
return new Octokit({ auth: user.gitHubToken });
|
return new Octokit({ auth: user.gitHubToken });
|
||||||
}
|
}
|
||||||
@ -165,8 +180,14 @@ export class Service {
|
|||||||
return dbProject;
|
return dbProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization (userId:string, organizationSlug: string): Promise<Project[]> {
|
async getProjectsInOrganization (
|
||||||
const dbProjects = await this.db.getProjectsInOrganization(userId, organizationSlug);
|
userId: string,
|
||||||
|
organizationSlug: string
|
||||||
|
): Promise<Project[]> {
|
||||||
|
const dbProjects = await this.db.getProjectsInOrganization(
|
||||||
|
userId,
|
||||||
|
organizationSlug
|
||||||
|
);
|
||||||
return dbProjects;
|
return dbProjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,35 +196,52 @@ export class Service {
|
|||||||
return dbDeployments;
|
return dbDeployments;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEnvironmentVariablesByProjectId (projectId: string): Promise<EnvironmentVariable[]> {
|
async getEnvironmentVariablesByProjectId (
|
||||||
const dbEnvironmentVariables = await this.db.getEnvironmentVariablesByProjectId(projectId);
|
projectId: string
|
||||||
|
): Promise<EnvironmentVariable[]> {
|
||||||
|
const dbEnvironmentVariables =
|
||||||
|
await this.db.getEnvironmentVariablesByProjectId(projectId);
|
||||||
return dbEnvironmentVariables;
|
return dbEnvironmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectMembersByProjectId (projectId: string): Promise<ProjectMember[]> {
|
async getProjectMembersByProjectId (
|
||||||
const dbProjectMembers = await this.db.getProjectMembersByProjectId(projectId);
|
projectId: string
|
||||||
|
): Promise<ProjectMember[]> {
|
||||||
|
const dbProjectMembers =
|
||||||
|
await this.db.getProjectMembersByProjectId(projectId);
|
||||||
return dbProjectMembers;
|
return dbProjectMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchProjects (userId: string, searchText: string): Promise<Project[]> {
|
async searchProjects (userId: string, searchText: string): Promise<Project[]> {
|
||||||
const dbProjects = await this.db.getProjectsBySearchText(userId, searchText);
|
const dbProjects = await this.db.getProjectsBySearchText(
|
||||||
|
userId,
|
||||||
|
searchText
|
||||||
|
);
|
||||||
return dbProjects;
|
return dbProjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDomainsByProjectId (projectId: string, filter?: FindOptionsWhere<Domain>): Promise<Domain[]> {
|
async getDomainsByProjectId (
|
||||||
|
projectId: string,
|
||||||
|
filter?: FindOptionsWhere<Domain>
|
||||||
|
): Promise<Domain[]> {
|
||||||
const dbDomains = await this.db.getDomainsByProjectId(projectId, filter);
|
const dbDomains = await this.db.getDomainsByProjectId(projectId, filter);
|
||||||
return dbDomains;
|
return dbDomains;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProjectMember (projectMemberId: string, data: {permissions: Permission[]}): Promise<boolean> {
|
async updateProjectMember (
|
||||||
|
projectMemberId: string,
|
||||||
|
data: { permissions: Permission[] }
|
||||||
|
): Promise<boolean> {
|
||||||
return this.db.updateProjectMemberById(projectMemberId, data);
|
return this.db.updateProjectMemberById(projectMemberId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProjectMember (projectId: string,
|
async addProjectMember (
|
||||||
|
projectId: string,
|
||||||
data: {
|
data: {
|
||||||
email: string,
|
email: string;
|
||||||
permissions: Permission[]
|
permissions: Permission[];
|
||||||
}): Promise<ProjectMember> {
|
}
|
||||||
|
): Promise<ProjectMember> {
|
||||||
// TODO: Send invitation
|
// TODO: Send invitation
|
||||||
let user = await this.db.getUser({
|
let user = await this.db.getUser({
|
||||||
where: {
|
where: {
|
||||||
@ -231,7 +269,10 @@ export class Service {
|
|||||||
return newProjectMember;
|
return newProjectMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeProjectMember (userId: string, projectMemberId: string): Promise<boolean> {
|
async removeProjectMember (
|
||||||
|
userId: string,
|
||||||
|
projectMemberId: string
|
||||||
|
): Promise<boolean> {
|
||||||
const member = await this.db.getProjectMemberById(projectMemberId);
|
const member = await this.db.getProjectMemberById(projectMemberId);
|
||||||
|
|
||||||
if (String(member.member.id) === userId) {
|
if (String(member.member.id) === userId) {
|
||||||
@ -248,33 +289,48 @@ export class Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentVariables (projectId: string, data: { environments: string[], key: string, value: string}[]): Promise<EnvironmentVariable[]> {
|
async addEnvironmentVariables (
|
||||||
const formattedEnvironmentVariables = data.map((environmentVariable) => {
|
projectId: string,
|
||||||
|
data: { environments: string[]; key: string; value: string }[]
|
||||||
|
): Promise<EnvironmentVariable[]> {
|
||||||
|
const formattedEnvironmentVariables = data
|
||||||
|
.map((environmentVariable) => {
|
||||||
return environmentVariable.environments.map((environment) => {
|
return environmentVariable.environments.map((environment) => {
|
||||||
return ({
|
return {
|
||||||
key: environmentVariable.key,
|
key: environmentVariable.key,
|
||||||
value: environmentVariable.value,
|
value: environmentVariable.value,
|
||||||
environment: environment as Environment,
|
environment: environment as Environment,
|
||||||
project: Object.assign(new Project(), {
|
project: Object.assign(new Project(), {
|
||||||
id: projectId
|
id: projectId
|
||||||
})
|
})
|
||||||
|
};
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
}).flat();
|
.flat();
|
||||||
|
|
||||||
const savedEnvironmentVariables = await this.db.addEnvironmentVariables(formattedEnvironmentVariables);
|
const savedEnvironmentVariables = await this.db.addEnvironmentVariables(
|
||||||
|
formattedEnvironmentVariables
|
||||||
|
);
|
||||||
return savedEnvironmentVariables;
|
return savedEnvironmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEnvironmentVariable (environmentVariableId: string, data : DeepPartial<EnvironmentVariable>): Promise<boolean> {
|
async updateEnvironmentVariable (
|
||||||
|
environmentVariableId: string,
|
||||||
|
data: DeepPartial<EnvironmentVariable>
|
||||||
|
): Promise<boolean> {
|
||||||
return this.db.updateEnvironmentVariable(environmentVariableId, data);
|
return this.db.updateEnvironmentVariable(environmentVariableId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEnvironmentVariable (environmentVariableId: string): Promise<boolean> {
|
async removeEnvironmentVariable (
|
||||||
|
environmentVariableId: string
|
||||||
|
): Promise<boolean> {
|
||||||
return this.db.deleteEnvironmentVariable(environmentVariableId);
|
return this.db.deleteEnvironmentVariable(environmentVariableId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeploymentToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
async updateDeploymentToProd (
|
||||||
|
userId: string,
|
||||||
|
deploymentId: string
|
||||||
|
): Promise<Deployment> {
|
||||||
const oldDeployment = await this.db.getDeployment({
|
const oldDeployment = await this.db.getDeployment({
|
||||||
where: { id: deploymentId },
|
where: { id: deploymentId },
|
||||||
relations: {
|
relations: {
|
||||||
@ -286,13 +342,14 @@ export class Service {
|
|||||||
throw new Error('Deployment does not exist');
|
throw new Error('Deployment does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
const prodBranchDomains = await this.db.getDomainsByProjectId(oldDeployment.project.id, { branch: oldDeployment.project.prodBranch });
|
const prodBranchDomains = await this.db.getDomainsByProjectId(
|
||||||
|
oldDeployment.project.id,
|
||||||
|
{ branch: oldDeployment.project.prodBranch }
|
||||||
|
);
|
||||||
|
|
||||||
const octokit = await this.getOctokit(userId);
|
const octokit = await this.getOctokit(userId);
|
||||||
|
|
||||||
const newDeployment = await this.createDeployment(userId,
|
const newDeployment = await this.createDeployment(userId, octokit, {
|
||||||
octokit,
|
|
||||||
{
|
|
||||||
project: oldDeployment.project,
|
project: oldDeployment.project,
|
||||||
branch: oldDeployment.branch,
|
branch: oldDeployment.branch,
|
||||||
environment: Environment.Production,
|
environment: Environment.Production,
|
||||||
@ -311,7 +368,9 @@ export class Service {
|
|||||||
recordData: { repoUrl?: string } = {}
|
recordData: { repoUrl?: string } = {}
|
||||||
): Promise<Deployment> {
|
): Promise<Deployment> {
|
||||||
assert(data.project?.repository, 'Project repository not found');
|
assert(data.project?.repository, 'Project repository not found');
|
||||||
log(`Creating deployment in project ${data.project.name} from branch ${data.branch}`);
|
log(
|
||||||
|
`Creating deployment in project ${data.project.name} from branch ${data.branch}`
|
||||||
|
);
|
||||||
const [owner, repo] = data.project.repository.split('/');
|
const [owner, repo] = data.project.repository.split('/');
|
||||||
|
|
||||||
const { data: packageJSONData } = await octokit.rest.repos.getContent({
|
const { data: packageJSONData } = await octokit.rest.repos.getContent({
|
||||||
@ -326,28 +385,61 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(!Array.isArray(packageJSONData) && packageJSONData.type === 'file');
|
assert(!Array.isArray(packageJSONData) && packageJSONData.type === 'file');
|
||||||
const packageJSON = JSON.parse(atob(packageJSONData.content));
|
const packageJSON: PackageJSON = JSON.parse(atob(packageJSONData.content));
|
||||||
|
|
||||||
|
assert(packageJSON.name, "name field doesn't exist in package.json");
|
||||||
|
|
||||||
if (!recordData.repoUrl) {
|
if (!recordData.repoUrl) {
|
||||||
const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo });
|
const { data: repoDetails } = await octokit.rest.repos.get({
|
||||||
|
owner,
|
||||||
|
repo
|
||||||
|
});
|
||||||
recordData.repoUrl = repoDetails.html_url;
|
recordData.repoUrl = repoDetails.html_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 { applicationRecordId, applicationRecordData } = await this.registry.createApplicationRecord({
|
const { applicationRecordId, applicationRecordData } =
|
||||||
|
await this.registry.createApplicationRecord({
|
||||||
|
appName: repo,
|
||||||
packageJSON,
|
packageJSON,
|
||||||
appType: data.project!.template!,
|
appType: data.project!.template!,
|
||||||
commitHash: data.commitHash!,
|
commitHash: data.commitHash!,
|
||||||
repoUrl: recordData.repoUrl
|
repoUrl: recordData.repoUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const environmentVariables =
|
||||||
|
await this.db.getEnvironmentVariablesByProjectId(data.project.id!, {
|
||||||
|
environment: Environment.Production
|
||||||
|
});
|
||||||
|
|
||||||
|
const environmentVariablesObj = environmentVariables.reduce(
|
||||||
|
(acc, env) => {
|
||||||
|
acc[env.key] = env.value;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as { [key: string]: string }
|
||||||
|
);
|
||||||
|
|
||||||
|
const { applicationDeploymentRequestId, applicationDeploymentRequestData } =
|
||||||
|
await this.registry.createApplicationDeploymentRequest({
|
||||||
|
appName: repo,
|
||||||
|
packageJsonName: packageJSON.name,
|
||||||
|
commitHash: data.commitHash!,
|
||||||
|
repository: recordData.repoUrl,
|
||||||
|
environmentVariables: environmentVariablesObj
|
||||||
|
});
|
||||||
|
|
||||||
// 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(
|
||||||
|
{
|
||||||
domainId: data.domain?.id
|
domainId: data.domain?.id
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
domain: null
|
domain: null
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const newDeployment = await this.db.addDeployment({
|
const newDeployment = await this.db.addDeployment({
|
||||||
project: data.project,
|
project: data.project,
|
||||||
@ -358,17 +450,25 @@ export class Service {
|
|||||||
status: DeploymentStatus.Building,
|
status: DeploymentStatus.Building,
|
||||||
applicationRecordId,
|
applicationRecordId,
|
||||||
applicationRecordData,
|
applicationRecordData,
|
||||||
|
applicationDeploymentRequestId,
|
||||||
|
applicationDeploymentRequestData,
|
||||||
domain: data.domain,
|
domain: data.domain,
|
||||||
createdBy: Object.assign(new User(), {
|
createdBy: Object.assign(new User(), {
|
||||||
id: userId
|
id: userId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
log(`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`);
|
log(
|
||||||
|
`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`
|
||||||
|
);
|
||||||
return newDeployment;
|
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> {
|
||||||
const organization = await this.db.getOrganization({
|
const organization = await this.db.getOrganization({
|
||||||
where: {
|
where: {
|
||||||
slug: organizationSlug
|
slug: organizationSlug
|
||||||
@ -383,7 +483,9 @@ export class Service {
|
|||||||
const octokit = await this.getOctokit(userId);
|
const octokit = await this.getOctokit(userId);
|
||||||
const [owner, repo] = project.repository.split('/');
|
const [owner, repo] = project.repository.split('/');
|
||||||
|
|
||||||
const { data: [latestCommit] } = await octokit.rest.repos.listCommits({
|
const {
|
||||||
|
data: [latestCommit]
|
||||||
|
} = await octokit.rest.repos.listCommits({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
sha: project.prodBranch,
|
sha: project.prodBranch,
|
||||||
@ -393,7 +495,8 @@ export class Service {
|
|||||||
const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo });
|
const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo });
|
||||||
|
|
||||||
// Create deployment with prod branch and latest commit
|
// Create deployment with prod branch and latest commit
|
||||||
const newDeployment = await this.createDeployment(userId,
|
await this.createDeployment(
|
||||||
|
userId,
|
||||||
octokit,
|
octokit,
|
||||||
{
|
{
|
||||||
project,
|
project,
|
||||||
@ -408,27 +511,6 @@ export class Service {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const environmentVariables = await this.db.getEnvironmentVariablesByProjectId(project.id, { environment: Environment.Production });
|
|
||||||
|
|
||||||
const environmentVariablesObj = environmentVariables.reduce((acc, env) => {
|
|
||||||
acc[env.key] = env.value;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {} as { [key: string]: string });
|
|
||||||
|
|
||||||
const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest(
|
|
||||||
{
|
|
||||||
appName: newDeployment.applicationRecordData.name!,
|
|
||||||
commitHash: latestCommit.sha,
|
|
||||||
repository: repoDetails.html_url,
|
|
||||||
environmentVariables: environmentVariablesObj
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.db.updateProjectById(project.id, {
|
|
||||||
applicationDeploymentRequestId,
|
|
||||||
applicationDeploymentRequestData
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.createRepoHook(octokit, project);
|
await this.createRepoHook(octokit, project);
|
||||||
|
|
||||||
return project;
|
return project;
|
||||||
@ -441,7 +523,10 @@ export class Service {
|
|||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
config: {
|
config: {
|
||||||
url: new URL('api/github/webhook', this.config.gitHubConfig.webhookUrl).href,
|
url: new URL(
|
||||||
|
'api/github/webhook',
|
||||||
|
this.config.gitHubConfig.webhookUrl
|
||||||
|
).href,
|
||||||
content_type: 'json'
|
content_type: 'json'
|
||||||
},
|
},
|
||||||
events: ['push']
|
events: ['push']
|
||||||
@ -449,9 +534,13 @@ export class Service {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook--status-codes
|
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook--status-codes
|
||||||
if (
|
if (
|
||||||
!(err instanceof RequestError &&
|
!(
|
||||||
|
err instanceof RequestError &&
|
||||||
err.status === 422 &&
|
err.status === 422 &&
|
||||||
(err.response?.data as any).errors.some((err: any) => err.message === GITHUB_UNIQUE_WEBHOOK_ERROR))
|
(err.response?.data as any).errors.some(
|
||||||
|
(err: any) => err.message === GITHUB_UNIQUE_WEBHOOK_ERROR
|
||||||
|
)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -463,7 +552,9 @@ export class Service {
|
|||||||
async handleGitHubPush (data: GitPushEventPayload): Promise<void> {
|
async handleGitHubPush (data: GitPushEventPayload): Promise<void> {
|
||||||
const { repository, ref, head_commit: headCommit } = data;
|
const { repository, ref, head_commit: headCommit } = data;
|
||||||
log(`Handling GitHub push event from repository: ${repository.full_name}`);
|
log(`Handling GitHub push event from repository: ${repository.full_name}`);
|
||||||
const projects = await this.db.getProjects({ where: { repository: repository.full_name } });
|
const projects = await this.db.getProjects({
|
||||||
|
where: { repository: repository.full_name }
|
||||||
|
});
|
||||||
|
|
||||||
if (!projects.length) {
|
if (!projects.length) {
|
||||||
log(`No projects found for repository ${repository.full_name}`);
|
log(`No projects found for repository ${repository.full_name}`);
|
||||||
@ -475,15 +566,18 @@ export class Service {
|
|||||||
|
|
||||||
for await (const project of projects) {
|
for await (const project of projects) {
|
||||||
const octokit = await this.getOctokit(project.ownerId);
|
const octokit = await this.getOctokit(project.ownerId);
|
||||||
const [domain] = await this.db.getDomainsByProjectId(project.id, { branch });
|
const [domain] = await this.db.getDomainsByProjectId(project.id, {
|
||||||
|
branch
|
||||||
|
});
|
||||||
|
|
||||||
// Create deployment with branch and latest commit in GitHub data
|
// Create deployment with branch and latest commit in GitHub data
|
||||||
await this.createDeployment(project.ownerId,
|
await this.createDeployment(project.ownerId, octokit, {
|
||||||
octokit,
|
|
||||||
{
|
|
||||||
project,
|
project,
|
||||||
branch,
|
branch,
|
||||||
environment: project.prodBranch === branch ? Environment.Production : Environment.Preview,
|
environment:
|
||||||
|
project.prodBranch === branch
|
||||||
|
? Environment.Production
|
||||||
|
: Environment.Preview,
|
||||||
domain,
|
domain,
|
||||||
commitHash: headCommit.id,
|
commitHash: headCommit.id,
|
||||||
commitMessage: headCommit.message
|
commitMessage: headCommit.message
|
||||||
@ -491,7 +585,10 @@ export class Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProject (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
async updateProject (
|
||||||
|
projectId: string,
|
||||||
|
data: DeepPartial<Project>
|
||||||
|
): Promise<boolean> {
|
||||||
return this.db.updateProjectById(projectId, data);
|
return this.db.updateProjectById(projectId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,13 +605,18 @@ export class Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (domainsRedirectedFrom.length > 0) {
|
if (domainsRedirectedFrom.length > 0) {
|
||||||
throw new Error('Cannot delete domain since it has redirects from other domains');
|
throw new Error(
|
||||||
|
'Cannot delete domain since it has redirects from other domains'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db.deleteDomainById(domainId);
|
return this.db.deleteDomainById(domainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async redeployToProd (userId: string, deploymentId: string): Promise<Deployment> {
|
async redeployToProd (
|
||||||
|
userId: string,
|
||||||
|
deploymentId: string
|
||||||
|
): Promise<Deployment> {
|
||||||
const oldDeployment = await this.db.getDeployment({
|
const oldDeployment = await this.db.getDeployment({
|
||||||
relations: {
|
relations: {
|
||||||
project: true,
|
project: true,
|
||||||
@ -532,9 +634,7 @@ export class Service {
|
|||||||
|
|
||||||
const octokit = await this.getOctokit(userId);
|
const octokit = await this.getOctokit(userId);
|
||||||
|
|
||||||
const newDeployment = 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,
|
||||||
@ -547,7 +647,10 @@ export class Service {
|
|||||||
return newDeployment;
|
return newDeployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollbackDeployment (projectId: string, deploymentId: string): Promise<boolean> {
|
async rollbackDeployment (
|
||||||
|
projectId: string,
|
||||||
|
deploymentId: string
|
||||||
|
): Promise<boolean> {
|
||||||
// TODO: Implement transactions
|
// TODO: Implement transactions
|
||||||
const oldCurrentDeployment = await this.db.getDeployment({
|
const oldCurrentDeployment = await this.db.getDeployment({
|
||||||
relations: {
|
relations: {
|
||||||
@ -565,16 +668,25 @@ export class Service {
|
|||||||
throw new Error('Current deployment 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 }
|
||||||
|
);
|
||||||
|
|
||||||
const newCurrentDeploymentUpdate = await this.db.updateDeploymentById(deploymentId, { isCurrent: true, domain: oldCurrentDeployment?.domain });
|
const newCurrentDeploymentUpdate = await this.db.updateDeploymentById(
|
||||||
|
deploymentId,
|
||||||
|
{ isCurrent: true, domain: oldCurrentDeployment?.domain }
|
||||||
|
);
|
||||||
|
|
||||||
return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate;
|
return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addDomain (projectId: string, data: { name: string }): Promise<{
|
async addDomain (
|
||||||
primaryDomain: Domain,
|
projectId: string,
|
||||||
redirectedDomain: Domain
|
data: { name: string }
|
||||||
|
): Promise<{
|
||||||
|
primaryDomain: Domain;
|
||||||
|
redirectedDomain: Domain;
|
||||||
}> {
|
}> {
|
||||||
const currentProject = await this.db.getProjectById(projectId);
|
const currentProject = await this.db.getProjectById(projectId);
|
||||||
|
|
||||||
@ -599,12 +711,20 @@ export class Service {
|
|||||||
redirectTo: savedPrimaryDomain
|
redirectTo: savedPrimaryDomain
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedRedirectedDomain = await this.db.addDomain(redirectedDomainDetails);
|
const savedRedirectedDomain = await this.db.addDomain(
|
||||||
|
redirectedDomainDetails
|
||||||
|
);
|
||||||
|
|
||||||
return { primaryDomain: savedPrimaryDomain, redirectedDomain: savedRedirectedDomain };
|
return {
|
||||||
|
primaryDomain: savedPrimaryDomain,
|
||||||
|
redirectedDomain: savedRedirectedDomain
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDomain (domainId: string, data: DeepPartial<Domain>): Promise<boolean> {
|
async updateDomain (
|
||||||
|
domainId: string,
|
||||||
|
data: DeepPartial<Domain>
|
||||||
|
): Promise<boolean> {
|
||||||
const domain = await this.db.getDomain({
|
const domain = await this.db.getDomain({
|
||||||
where: {
|
where: {
|
||||||
id: domainId
|
id: domainId
|
||||||
@ -645,7 +765,9 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (redirectedDomain.redirectToId) {
|
if (redirectedDomain.redirectToId) {
|
||||||
throw new Error('Unable to redirect to the domain because it is already redirecting elsewhere. Redirects cannot be chained.');
|
throw new Error(
|
||||||
|
'Unable to redirect to the domain because it is already redirecting elsewhere. Redirects cannot be chained.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
newDomain.redirectTo = redirectedDomain;
|
newDomain.redirectTo = redirectedDomain;
|
||||||
@ -656,8 +778,13 @@ export class Service {
|
|||||||
return updateResult;
|
return updateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticateGitHub (code:string, userId: string): Promise<{token: string}> {
|
async authenticateGitHub (
|
||||||
const { authentication: { token } } = await this.oauthApp.createToken({
|
code: string,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ token: string }> {
|
||||||
|
const {
|
||||||
|
authentication: { token }
|
||||||
|
} = await this.oauthApp.createToken({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -666,7 +793,10 @@ export class Service {
|
|||||||
return { token };
|
return { token };
|
||||||
}
|
}
|
||||||
|
|
||||||
async unauthenticateGitHub (userId: string, data: DeepPartial<User>): Promise<boolean> {
|
async unauthenticateGitHub (
|
||||||
|
userId: string,
|
||||||
|
data: DeepPartial<User>
|
||||||
|
): Promise<boolean> {
|
||||||
return this.db.updateUser(userId, data);
|
return this.db.updateUser(userId, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export interface PackageJSON {
|
export interface PackageJSON {
|
||||||
name?: string;
|
name: string;
|
||||||
version?: string;
|
version: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
@ -47,5 +47,5 @@ interface RegistryRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AppDeploymentRecord extends RegistryRecord {
|
export interface AppDeploymentRecord extends RegistryRecord {
|
||||||
attributes: AppDeploymentRecordAttributes
|
attributes: AppDeploymentRecordAttributes;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import fs from 'fs-extra';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import toml from 'toml';
|
import toml from 'toml';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
const log = debug('snowball:utils');
|
const log = debug('snowball:utils');
|
||||||
|
|
||||||
@ -19,3 +20,49 @@ export const getConfig = async <ConfigType>(
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkFileExists = async (filePath: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath, fs.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getEntities = async (filePath: string): Promise<any> => {
|
||||||
|
const entitiesData = await fs.readFile(filePath, 'utf-8');
|
||||||
|
const entities = JSON.parse(entitiesData);
|
||||||
|
return entities;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadAndSaveData = async <Entity extends ObjectLiteral>(
|
||||||
|
entityType: EntityTarget<Entity>,
|
||||||
|
dataSource: DataSource,
|
||||||
|
entities: any,
|
||||||
|
relations?: any | undefined
|
||||||
|
): Promise<Entity[]> => {
|
||||||
|
const entityRepository = dataSource.getRepository(entityType);
|
||||||
|
|
||||||
|
const savedEntity: Entity[] = [];
|
||||||
|
|
||||||
|
for (const entityData of entities) {
|
||||||
|
let entity = entityRepository.create(entityData as DeepPartial<Entity>);
|
||||||
|
|
||||||
|
if (relations) {
|
||||||
|
for (const field in relations) {
|
||||||
|
const valueIndex = String(field + 'Index');
|
||||||
|
|
||||||
|
entity = {
|
||||||
|
...entity,
|
||||||
|
[field]: relations[field][entityData[valueIndex]]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dbEntity = await entityRepository.save(entity);
|
||||||
|
savedEntity.push(dbEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return savedEntity;
|
||||||
|
};
|
||||||
|
@ -18,4 +18,4 @@ const main = async () => {
|
|||||||
deleteFile(config.database.dbPath);
|
deleteFile(config.database.dbPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
main().catch(err => log(err));
|
main().catch((err) => log(err));
|
||||||
|
22
packages/backend/test/fixtures/deployments.json
vendored
22
packages/backend/test/fixtures/deployments.json
vendored
@ -9,6 +9,8 @@
|
|||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"applicationRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "xqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -24,6 +26,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "wqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -39,6 +43,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "kqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -54,6 +60,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "yqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "prod",
|
"branch": "prod",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -69,6 +77,8 @@
|
|||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"applicationRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "pqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -84,6 +94,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "tqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -99,6 +111,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
|
"applicationRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "eqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -114,6 +128,8 @@
|
|||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"applicationRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "dqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -129,6 +145,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
"applicationRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "aqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -144,6 +162,8 @@
|
|||||||
"isCurrent": false,
|
"isCurrent": false,
|
||||||
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "uqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
@ -159,6 +179,8 @@
|
|||||||
"isCurrent": true,
|
"isCurrent": true,
|
||||||
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
"applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
|
||||||
"applicationRecordData": {},
|
"applicationRecordData": {},
|
||||||
|
"applicationDeploymentRequestId": "pqbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
||||||
|
"applicationDeploymentRequestData": {},
|
||||||
"branch": "test",
|
"branch": "test",
|
||||||
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
"commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
|
||||||
"commitMessage": "subscription added",
|
"commitMessage": "subscription added",
|
||||||
|
@ -2,77 +2,55 @@
|
|||||||
{
|
{
|
||||||
"memberIndex": 1,
|
"memberIndex": 1,
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"permissions": [
|
"permissions": ["View"],
|
||||||
"View"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 2,
|
"memberIndex": 2,
|
||||||
"projectIndex": 0,
|
"projectIndex": 0,
|
||||||
"permissions": [
|
"permissions": ["View", "Edit"],
|
||||||
"View",
|
|
||||||
"Edit"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 2,
|
"memberIndex": 2,
|
||||||
"projectIndex": 1,
|
"projectIndex": 1,
|
||||||
"permissions": [
|
"permissions": ["View"],
|
||||||
"View"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 0,
|
"memberIndex": 0,
|
||||||
"projectIndex": 2,
|
"projectIndex": 2,
|
||||||
"permissions": [
|
"permissions": ["View"],
|
||||||
"View"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 1,
|
"memberIndex": 1,
|
||||||
"projectIndex": 2,
|
"projectIndex": 2,
|
||||||
"permissions": [
|
"permissions": ["View", "Edit"],
|
||||||
"View",
|
|
||||||
"Edit"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 0,
|
"memberIndex": 0,
|
||||||
"projectIndex": 3,
|
"projectIndex": 3,
|
||||||
"permissions": [
|
"permissions": ["View"],
|
||||||
"View"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 2,
|
"memberIndex": 2,
|
||||||
"projectIndex": 3,
|
"projectIndex": 3,
|
||||||
"permissions": [
|
"permissions": ["View", "Edit"],
|
||||||
"View",
|
|
||||||
"Edit"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 1,
|
"memberIndex": 1,
|
||||||
"projectIndex": 4,
|
"projectIndex": 4,
|
||||||
"permissions": [
|
"permissions": ["View"],
|
||||||
"View"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"memberIndex": 2,
|
"memberIndex": 2,
|
||||||
"projectIndex": 4,
|
"projectIndex": 4,
|
||||||
"permissions": [
|
"permissions": ["View", "Edit"],
|
||||||
"View",
|
|
||||||
"Edit"
|
|
||||||
],
|
|
||||||
"isPending": false
|
"isPending": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
10
packages/backend/test/fixtures/projects.json
vendored
10
packages/backend/test/fixtures/projects.json
vendored
@ -10,8 +10,6 @@
|
|||||||
"framework": "test",
|
"framework": "test",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"applicationDeploymentRequestId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
|
||||||
"applicationDeploymentRequestData": {},
|
|
||||||
"subDomain": "testProject.snowball.xyz"
|
"subDomain": "testProject.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -25,8 +23,6 @@
|
|||||||
"framework": "test-2",
|
"framework": "test-2",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"applicationDeploymentRequestId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
|
||||||
"applicationDeploymentRequestData": {},
|
|
||||||
"subDomain": "testProject-2.snowball.xyz"
|
"subDomain": "testProject-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,8 +36,6 @@
|
|||||||
"framework": "test-3",
|
"framework": "test-3",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"applicationDeploymentRequestId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
|
||||||
"applicationDeploymentRequestData": {},
|
|
||||||
"subDomain": "iglootools.snowball.xyz"
|
"subDomain": "iglootools.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,8 +49,6 @@
|
|||||||
"framework": "test-4",
|
"framework": "test-4",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"applicationDeploymentRequestId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
|
||||||
"applicationDeploymentRequestData": {},
|
|
||||||
"subDomain": "iglootools-2.snowball.xyz"
|
"subDomain": "iglootools-2.snowball.xyz"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,8 +62,6 @@
|
|||||||
"framework": "test-5",
|
"framework": "test-5",
|
||||||
"webhooks": [],
|
"webhooks": [],
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"applicationDeploymentRequestId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
|
|
||||||
"applicationDeploymentRequestData": {},
|
|
||||||
"subDomain": "snowball-2.snowball.xyz"
|
"subDomain": "snowball-2.snowball.xyz"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import * as fs from 'fs/promises';
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@ -11,7 +10,12 @@ import { EnvironmentVariable } from '../src/entity/EnvironmentVariable';
|
|||||||
import { Domain } from '../src/entity/Domain';
|
import { Domain } from '../src/entity/Domain';
|
||||||
import { ProjectMember } from '../src/entity/ProjectMember';
|
import { ProjectMember } from '../src/entity/ProjectMember';
|
||||||
import { Deployment } from '../src/entity/Deployment';
|
import { Deployment } from '../src/entity/Deployment';
|
||||||
import { getConfig } from '../src/utils';
|
import {
|
||||||
|
checkFileExists,
|
||||||
|
getConfig,
|
||||||
|
getEntities,
|
||||||
|
loadAndSaveData
|
||||||
|
} from '../src/utils';
|
||||||
import { Config } from '../src/config';
|
import { Config } from '../src/config';
|
||||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||||
|
|
||||||
@ -27,43 +31,35 @@ const DEPLOYMENT_DATA_PATH = './fixtures/deployments.json';
|
|||||||
const ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json';
|
const ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json';
|
||||||
const REDIRECTED_DOMAIN_DATA_PATH = './fixtures/redirected-domains.json';
|
const REDIRECTED_DOMAIN_DATA_PATH = './fixtures/redirected-domains.json';
|
||||||
|
|
||||||
const loadAndSaveData = async <Entity extends ObjectLiteral>(entityType: EntityTarget<Entity>, dataSource: DataSource, filePath: string, relations?: any | undefined) => {
|
|
||||||
const entitiesData = await fs.readFile(filePath, 'utf-8');
|
|
||||||
const entities = JSON.parse(entitiesData);
|
|
||||||
const entityRepository = dataSource.getRepository(entityType);
|
|
||||||
|
|
||||||
const savedEntity:Entity[] = [];
|
|
||||||
|
|
||||||
for (const entityData of entities) {
|
|
||||||
let entity = entityRepository.create(entityData as DeepPartial<Entity>);
|
|
||||||
|
|
||||||
if (relations) {
|
|
||||||
for (const field in relations) {
|
|
||||||
const valueIndex = String(field + 'Index');
|
|
||||||
|
|
||||||
entity = {
|
|
||||||
...entity,
|
|
||||||
[field]: relations[field][entityData[valueIndex]]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const dbEntity = await entityRepository.save(entity);
|
|
||||||
savedEntity.push(dbEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return savedEntity;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateTestData = async (dataSource: DataSource) => {
|
const generateTestData = async (dataSource: DataSource) => {
|
||||||
const savedUsers = await loadAndSaveData(User, dataSource, path.resolve(__dirname, USER_DATA_PATH));
|
const userEntities = await getEntities(
|
||||||
const savedOrgs = await loadAndSaveData(Organization, dataSource, path.resolve(__dirname, ORGANIZATION_DATA_PATH));
|
path.resolve(__dirname, USER_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedUsers = await loadAndSaveData(User, dataSource, userEntities);
|
||||||
|
|
||||||
|
const orgEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, ORGANIZATION_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedOrgs = await loadAndSaveData(
|
||||||
|
Organization,
|
||||||
|
dataSource,
|
||||||
|
orgEntities
|
||||||
|
);
|
||||||
|
|
||||||
const projectRelations = {
|
const projectRelations = {
|
||||||
owner: savedUsers,
|
owner: savedUsers,
|
||||||
organization: savedOrgs
|
organization: savedOrgs
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedProjects = await loadAndSaveData(Project, dataSource, path.resolve(__dirname, PROJECT_DATA_PATH), projectRelations);
|
const projectEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, PROJECT_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedProjects = await loadAndSaveData(
|
||||||
|
Project,
|
||||||
|
dataSource,
|
||||||
|
projectEntities,
|
||||||
|
projectRelations
|
||||||
|
);
|
||||||
|
|
||||||
const domainRepository = dataSource.getRepository(Domain);
|
const domainRepository = dataSource.getRepository(Domain);
|
||||||
|
|
||||||
@ -71,14 +67,30 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
project: savedProjects
|
project: savedProjects
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedPrimaryDomains = await loadAndSaveData(Domain, dataSource, path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH), domainPrimaryRelations);
|
const primaryDomainsEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH)
|
||||||
|
);
|
||||||
|
const savedPrimaryDomains = await loadAndSaveData(
|
||||||
|
Domain,
|
||||||
|
dataSource,
|
||||||
|
primaryDomainsEntities,
|
||||||
|
domainPrimaryRelations
|
||||||
|
);
|
||||||
|
|
||||||
const domainRedirectedRelations = {
|
const domainRedirectedRelations = {
|
||||||
project: savedProjects,
|
project: savedProjects,
|
||||||
redirectTo: savedPrimaryDomains
|
redirectTo: savedPrimaryDomains
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(Domain, dataSource, path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH), domainRedirectedRelations);
|
const redirectDomainsEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH)
|
||||||
|
);
|
||||||
|
await loadAndSaveData(
|
||||||
|
Domain,
|
||||||
|
dataSource,
|
||||||
|
redirectDomainsEntities,
|
||||||
|
domainRedirectedRelations
|
||||||
|
);
|
||||||
|
|
||||||
const savedDomains = await domainRepository.find();
|
const savedDomains = await domainRepository.find();
|
||||||
|
|
||||||
@ -87,14 +99,30 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
organization: savedOrgs
|
organization: savedOrgs
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(UserOrganization, dataSource, path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), userOrganizationRelations);
|
const userOrganizationsEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH)
|
||||||
|
);
|
||||||
|
await loadAndSaveData(
|
||||||
|
UserOrganization,
|
||||||
|
dataSource,
|
||||||
|
userOrganizationsEntities,
|
||||||
|
userOrganizationRelations
|
||||||
|
);
|
||||||
|
|
||||||
const projectMemberRelations = {
|
const projectMemberRelations = {
|
||||||
member: savedUsers,
|
member: savedUsers,
|
||||||
project: savedProjects
|
project: savedProjects
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(ProjectMember, dataSource, path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH), projectMemberRelations);
|
const projectMembersEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH)
|
||||||
|
);
|
||||||
|
await loadAndSaveData(
|
||||||
|
ProjectMember,
|
||||||
|
dataSource,
|
||||||
|
projectMembersEntities,
|
||||||
|
projectMemberRelations
|
||||||
|
);
|
||||||
|
|
||||||
const deploymentRelations = {
|
const deploymentRelations = {
|
||||||
project: savedProjects,
|
project: savedProjects,
|
||||||
@ -102,23 +130,29 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
createdBy: savedUsers
|
createdBy: savedUsers
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(Deployment, dataSource, path.resolve(__dirname, DEPLOYMENT_DATA_PATH), deploymentRelations);
|
const deploymentsEntities = await getEntities(
|
||||||
|
path.resolve(__dirname, DEPLOYMENT_DATA_PATH)
|
||||||
|
);
|
||||||
|
await loadAndSaveData(
|
||||||
|
Deployment,
|
||||||
|
dataSource,
|
||||||
|
deploymentsEntities,
|
||||||
|
deploymentRelations
|
||||||
|
);
|
||||||
|
|
||||||
const environmentVariableRelations = {
|
const environmentVariableRelations = {
|
||||||
project: savedProjects
|
project: savedProjects
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(EnvironmentVariable, dataSource, path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH), environmentVariableRelations);
|
const environmentVariablesEntities = await getEntities(
|
||||||
};
|
path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH)
|
||||||
|
);
|
||||||
const checkFileExists = async (filePath: string) => {
|
await loadAndSaveData(
|
||||||
try {
|
EnvironmentVariable,
|
||||||
await fs.access(filePath, fs.constants.F_OK);
|
dataSource,
|
||||||
return true;
|
environmentVariablesEntities,
|
||||||
} catch (err) {
|
environmentVariableRelations
|
||||||
log(err);
|
);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
@ -17,16 +17,32 @@ const AUTHORITY_NAMES = ['snowballtools', 'cerc-io'];
|
|||||||
async function main () {
|
async function main () {
|
||||||
const { registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
const { registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||||
|
|
||||||
const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
|
const registry = new Registry(
|
||||||
|
registryConfig.gqlEndpoint,
|
||||||
|
registryConfig.restEndpoint,
|
||||||
|
registryConfig.chainId
|
||||||
|
);
|
||||||
|
|
||||||
const bondId = await registry.getNextBondId(registryConfig.privateKey);
|
const bondId = await registry.getNextBondId(registryConfig.privateKey);
|
||||||
log('bondId:', bondId);
|
log('bondId:', bondId);
|
||||||
await registry.createBond({ denom: DENOM, amount: BOND_AMOUNT }, registryConfig.privateKey, registryConfig.fee);
|
await registry.createBond(
|
||||||
|
{ denom: DENOM, amount: BOND_AMOUNT },
|
||||||
|
registryConfig.privateKey,
|
||||||
|
registryConfig.fee
|
||||||
|
);
|
||||||
|
|
||||||
for await (const name of AUTHORITY_NAMES) {
|
for await (const name of AUTHORITY_NAMES) {
|
||||||
await registry.reserveAuthority({ name }, registryConfig.privateKey, registryConfig.fee);
|
await registry.reserveAuthority(
|
||||||
|
{ name },
|
||||||
|
registryConfig.privateKey,
|
||||||
|
registryConfig.fee
|
||||||
|
);
|
||||||
log('Reserved authority name:', name);
|
log('Reserved authority name:', name);
|
||||||
await registry.setAuthorityBond({ name, bondId }, registryConfig.privateKey, registryConfig.fee);
|
await registry.setAuthorityBond(
|
||||||
|
{ name, bondId },
|
||||||
|
registryConfig.privateKey,
|
||||||
|
registryConfig.fee
|
||||||
|
);
|
||||||
log(`Bond ${bondId} set for authority ${name}`);
|
log(`Bond ${bondId} set for authority ${name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,15 @@ import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
|
|||||||
const log = debug('snowball:publish-deploy-records');
|
const log = debug('snowball:publish-deploy-records');
|
||||||
|
|
||||||
async function main () {
|
async function main () {
|
||||||
const { registryConfig, database } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
const { registryConfig, database } = await getConfig<Config>(
|
||||||
|
DEFAULT_CONFIG_FILE_PATH
|
||||||
|
);
|
||||||
|
|
||||||
const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
|
const registry = new Registry(
|
||||||
|
registryConfig.gqlEndpoint,
|
||||||
|
registryConfig.restEndpoint,
|
||||||
|
registryConfig.chainId
|
||||||
|
);
|
||||||
|
|
||||||
const dataSource = new DataSource({
|
const dataSource = new DataSource({
|
||||||
type: 'better-sqlite3',
|
type: 'better-sqlite3',
|
||||||
@ -53,7 +59,7 @@ async function main () {
|
|||||||
so: '66fcfa49a1664d4cb4ce4f72c1c0e151'
|
so: '66fcfa49a1664d4cb4ce4f72c1c0e151'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
request: deployment.project.applicationDeploymentRequestId,
|
request: deployment.applicationDeploymentRequestId,
|
||||||
url
|
url
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ const DEFAULT_FILTER_VALUE: FilterValue = {
|
|||||||
searchedBranch: '',
|
searchedBranch: '',
|
||||||
status: StatusOptions.ALL_STATUS,
|
status: StatusOptions.ALL_STATUS,
|
||||||
};
|
};
|
||||||
|
const FETCH_DEPLOYMENTS_INTERVAL = 5000;
|
||||||
|
|
||||||
const DeploymentsTabPanel = () => {
|
const DeploymentsTabPanel = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -26,22 +27,30 @@ const DeploymentsTabPanel = () => {
|
|||||||
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
||||||
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);
|
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);
|
||||||
|
|
||||||
const fetchDeployments = async () => {
|
const fetchDeployments = useCallback(async () => {
|
||||||
const { deployments } = await client.getDeployments(project.id);
|
const { deployments } = await client.getDeployments(project.id);
|
||||||
setDeployments(deployments);
|
setDeployments(deployments);
|
||||||
};
|
}, [client, project]);
|
||||||
|
|
||||||
const fetchProductionBranchDomains = useCallback(async () => {
|
const fetchProductionBranchDomains = useCallback(async () => {
|
||||||
const { domains } = await client.getDomains(project.id, {
|
const { domains } = await client.getDomains(project.id, {
|
||||||
branch: project.prodBranch,
|
branch: project.prodBranch,
|
||||||
});
|
});
|
||||||
setProdBranchDomains(domains);
|
setProdBranchDomains(domains);
|
||||||
}, []);
|
}, [client, project]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDeployments();
|
|
||||||
fetchProductionBranchDomains();
|
fetchProductionBranchDomains();
|
||||||
}, []);
|
fetchDeployments();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchDeployments();
|
||||||
|
}, FETCH_DEPLOYMENTS_INTERVAL);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [fetchDeployments, fetchProductionBranchDomains]);
|
||||||
|
|
||||||
const currentDeployment = useMemo(() => {
|
const currentDeployment = useMemo(() => {
|
||||||
return deployments.find((deployment) => {
|
return deployments.find((deployment) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user