Refactor mutation GQL methods to service class and display organization name (#53)
* Refactor mutation methods to service class * Refactor database methods to service class * Display organization name in sidebar * Handle review comments --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
parent
ac7064afa5
commit
da92ddfba3
@ -1,24 +1,20 @@
|
|||||||
import { DataSource, DeepPartial } from 'typeorm';
|
import { DataSource, DeepPartial, FindManyOptions, FindOneOptions } 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';
|
||||||
import { customAlphabet } from 'nanoid';
|
|
||||||
import { lowercase, numbers } from 'nanoid-dictionary';
|
|
||||||
|
|
||||||
import { DatabaseConfig } from './config';
|
import { DatabaseConfig } from './config';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
import { Organization } from './entity/Organization';
|
import { Organization } from './entity/Organization';
|
||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { Deployment, Environment } from './entity/Deployment';
|
import { Deployment } from './entity/Deployment';
|
||||||
import { Permission, ProjectMember } from './entity/ProjectMember';
|
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';
|
||||||
|
|
||||||
const log = debug('snowball:database');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
const nanoid = customAlphabet(lowercase + numbers, 8);
|
|
||||||
|
|
||||||
// TODO: Fix order of methods
|
// TODO: Fix order of methods
|
||||||
export class Database {
|
export class Database {
|
||||||
private dataSource: DataSource;
|
private dataSource: DataSource;
|
||||||
@ -38,11 +34,16 @@ export class Database {
|
|||||||
log('database initialized');
|
log('database initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser (userId: number): Promise<User | null> {
|
async getUser (options: FindOneOptions<User>): Promise<User | null> {
|
||||||
const userRepository = this.dataSource.getRepository(User);
|
const userRepository = this.dataSource.getRepository(User);
|
||||||
const user = await userRepository.findOneBy({
|
const user = await userRepository.findOne(options);
|
||||||
id: userId
|
|
||||||
});
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser (data: DeepPartial<User>): Promise<User> {
|
||||||
|
const userRepository = this.dataSource.getRepository(User);
|
||||||
|
const user = await userRepository.save(data);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@ -146,6 +147,27 @@ export class Database {
|
|||||||
return deployments;
|
return deployments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDeployment (options: FindOneOptions<Deployment>): Promise<Deployment | null> {
|
||||||
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
const deployment = await deploymentRepository.findOne(options);
|
||||||
|
|
||||||
|
return deployment;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDomains (options: FindManyOptions<Domain>): Promise<Domain[]> {
|
||||||
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
|
const domains = await domainRepository.find(options);
|
||||||
|
|
||||||
|
return domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDeployement (data: DeepPartial<Deployment>): Promise<Deployment> {
|
||||||
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
const deployment = await deploymentRepository.save(data);
|
||||||
|
|
||||||
|
return deployment;
|
||||||
|
}
|
||||||
|
|
||||||
async getProjectMembersByProjectId (projectId: string): Promise<ProjectMember[]> {
|
async getProjectMembersByProjectId (projectId: string): Promise<ProjectMember[]> {
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
||||||
|
|
||||||
@ -201,60 +223,18 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProjectMember (projectId: string, data: {
|
async addProjectMember (data: DeepPartial<ProjectMember>): Promise<ProjectMember> {
|
||||||
email: string,
|
|
||||||
permissions: Permission[]
|
|
||||||
}): Promise<boolean> {
|
|
||||||
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
const projectMemberRepository = this.dataSource.getRepository(ProjectMember);
|
||||||
const userRepository = this.dataSource.getRepository(User);
|
const newProjectMember = await projectMemberRepository.save(data);
|
||||||
|
|
||||||
let user = await userRepository.findOneBy({
|
return newProjectMember;
|
||||||
email: data.email
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = await userRepository.save({
|
|
||||||
email: data.email,
|
|
||||||
isVerified: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newProjectMember = await projectMemberRepository.save({
|
|
||||||
project: {
|
|
||||||
id: projectId
|
|
||||||
},
|
|
||||||
permissions: data.permissions,
|
|
||||||
isPending: true,
|
|
||||||
member: {
|
|
||||||
id: user.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Boolean(newProjectMember);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentVariablesByProjectId (projectId: string, environmentVariables: {
|
async addEnvironmentVariables (data: DeepPartial<EnvironmentVariable>[]): Promise<EnvironmentVariable[]> {
|
||||||
environments: string[];
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}[]): Promise<boolean> {
|
|
||||||
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable);
|
||||||
|
const savedEnvironmentVariables = await environmentVariableRepository.save(data);
|
||||||
|
|
||||||
const formattedEnvironmentVariables = environmentVariables.map((environmentVariable) => {
|
return savedEnvironmentVariables;
|
||||||
return environmentVariable.environments.map((environment) => {
|
|
||||||
return ({
|
|
||||||
key: environmentVariable.key,
|
|
||||||
value: environmentVariable.value,
|
|
||||||
environment: environment as Environment,
|
|
||||||
project: Object.assign(new Project(), {
|
|
||||||
id: projectId
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).flat();
|
|
||||||
|
|
||||||
const savedEnvironmentVariables = await environmentVariableRepository.save(formattedEnvironmentVariables);
|
|
||||||
return savedEnvironmentVariables.length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEnvironmentVariable (environmentVariableId: string, update: DeepPartial<EnvironmentVariable>): Promise<boolean> {
|
async updateEnvironmentVariable (environmentVariableId: string, update: DeepPartial<EnvironmentVariable>): Promise<boolean> {
|
||||||
@ -363,40 +343,6 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async redeployToProdById (userId: string, deploymentId: string): Promise<Deployment> {
|
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
|
||||||
const deployment = await deploymentRepository.findOne({
|
|
||||||
relations: {
|
|
||||||
project: true,
|
|
||||||
domain: true,
|
|
||||||
createdBy: true
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
id: deploymentId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deployment === null) {
|
|
||||||
throw new Error('Deployment not found');
|
|
||||||
}
|
|
||||||
const { createdAt, updatedAt, ...updatedDeployment } = deployment;
|
|
||||||
|
|
||||||
if (updatedDeployment.environment === Environment.Production) {
|
|
||||||
// TODO: Put isCurrent field in project
|
|
||||||
updatedDeployment.isCurrent = true;
|
|
||||||
updatedDeployment.createdBy = Object.assign(new User(), {
|
|
||||||
id: Number(userId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await deploymentRepository.update({ id: deploymentId }, { domain: null, isCurrent: false });
|
|
||||||
|
|
||||||
updatedDeployment.id = nanoid();
|
|
||||||
updatedDeployment.url = `${updatedDeployment.id}-${updatedDeployment.project.subDomain}`;
|
|
||||||
|
|
||||||
return deploymentRepository.save(updatedDeployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteProjectById (projectId: string): Promise<boolean> {
|
async deleteProjectById (projectId: string): Promise<boolean> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
const project = await projectRepository.findOneOrFail({
|
const project = await projectRepository.findOneOrFail({
|
||||||
@ -416,16 +362,6 @@ export class Database {
|
|||||||
async deleteDomainById (domainId: string): Promise<boolean> {
|
async deleteDomainById (domainId: string): Promise<boolean> {
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
|
|
||||||
const domainsRedirectedFrom = await domainRepository.find({
|
|
||||||
where: {
|
|
||||||
redirectToId: Number(domainId)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (domainsRedirectedFrom.length > 0) {
|
|
||||||
throw new Error('Cannot delete domain since it has redirects from other domains');
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteResult = await domainRepository.softDelete({ id: Number(domainId) });
|
const deleteResult = await domainRepository.softDelete({ id: Number(domainId) });
|
||||||
|
|
||||||
if (deleteResult.affected) {
|
if (deleteResult.affected) {
|
||||||
|
@ -7,10 +7,10 @@ import { OAuthApp } from '@octokit/oauth-app';
|
|||||||
import { Service } from './service';
|
import { Service } from './service';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { isUserOwner } from './utils';
|
import { isUserOwner } from './utils';
|
||||||
import { Environment } from './entity/Deployment';
|
|
||||||
import { Permission } from './entity/ProjectMember';
|
import { Permission } from './entity/ProjectMember';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
|
|
||||||
const log = debug('snowball:database');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
@ -86,12 +86,7 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv
|
|||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
return service.updateProjectMember(projectMemberId, data);
|
||||||
return await db.updateProjectMemberById(projectMemberId, data);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addProjectMember: async (_: any, { projectId, data }: {
|
addProjectMember: async (_: any, { projectId, data }: {
|
||||||
@ -101,100 +96,43 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv
|
|||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
return service.addProjectMember(projectId, data);
|
||||||
// TODO: Send invitation
|
|
||||||
return await db.addProjectMember(projectId, data);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addEnvironmentVariables: async (_: any, { projectId, environmentVariables }: { projectId: string, environmentVariables: { environments: string[], key: string, value: string}[] }) => {
|
addEnvironmentVariables: async (_: any, { projectId, data }: { projectId: string, data: { environments: string[], key: string, value: string}[] }) => {
|
||||||
try {
|
return service.addEnvironmentVariables(projectId, data);
|
||||||
return await db.addEnvironmentVariablesByProjectId(projectId, environmentVariables);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEnvironmentVariable: async (_: any, { environmentVariableId, environmentVariable }: { environmentVariableId: string, environmentVariable : {
|
updateEnvironmentVariable: async (_: any, { environmentVariableId, data }: { environmentVariableId: string, data : DeepPartial<EnvironmentVariable>}) => {
|
||||||
key: string
|
return service.updateEnvironmentVariable(environmentVariableId, data);
|
||||||
value: string
|
|
||||||
}}) => {
|
|
||||||
try {
|
|
||||||
return await db.updateEnvironmentVariable(environmentVariableId, environmentVariable);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => {
|
removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => {
|
||||||
try {
|
return service.removeEnvironmentVariable(environmentVariableId);
|
||||||
return await db.deleteEnvironmentVariable(environmentVariableId);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => {
|
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => {
|
||||||
try {
|
return service.updateDeploymentToProd(deploymentId);
|
||||||
return await db.updateDeploymentById(deploymentId, {
|
|
||||||
environment: Environment.Production
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addProject: async (_: any, { projectDetails }: { projectDetails: DeepPartial<Project> }, context: any) => {
|
addProject: async (_: any, { data }: { data: DeepPartial<Project> }, context: any) => {
|
||||||
try {
|
return service.addProject(context.userId, data);
|
||||||
await db.addProject(context.userId, projectDetails);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => {
|
updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => {
|
||||||
try {
|
return service.updateProject(projectId, projectDetails);
|
||||||
return await db.updateProjectById(projectId, projectDetails);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
||||||
try {
|
return service.redeployToProd(context.userId, deploymentId);
|
||||||
return await db.redeployToProdById(context.userId, deploymentId);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteProject: async (_: any, { projectId }: { projectId: string }) => {
|
deleteProject: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
try {
|
return service.deleteProject(projectId);
|
||||||
return await db.deleteProjectById(projectId);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteDomain: async (_: any, { domainId }: { domainId: string }) => {
|
deleteDomain: async (_: any, { domainId }: { domainId: string }) => {
|
||||||
try {
|
return service.deleteDomain(domainId);
|
||||||
return await db.deleteDomainById(domainId);
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => {
|
rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => {
|
||||||
|
@ -184,8 +184,8 @@ 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!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean!
|
addEnvironmentVariables(projectId: String!, data: [AddEnvironmentVariableInput!]): Boolean!
|
||||||
updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean!
|
updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean!
|
||||||
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
||||||
updateDeploymentToProd(deploymentId: String!): Boolean!
|
updateDeploymentToProd(deploymentId: String!): Boolean!
|
||||||
addProject(projectDetails: AddProjectInput): Boolean!
|
addProject(projectDetails: AddProjectInput): Boolean!
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
|
import debug from 'debug';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import { lowercase, numbers } from 'nanoid-dictionary';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Deployment } from './entity/Deployment';
|
import { Deployment, Environment } from './entity/Deployment';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
import { Organization } from './entity/Organization';
|
import { Organization } from './entity/Organization';
|
||||||
import { Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { ProjectMember } from './entity/ProjectMember';
|
import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
|
import { DeepPartial } from 'typeorm';
|
||||||
|
|
||||||
|
const log = debug('snowball:service');
|
||||||
|
|
||||||
|
const nanoid = customAlphabet(lowercase + numbers, 8);
|
||||||
|
|
||||||
export class Service {
|
export class Service {
|
||||||
private db: Database;
|
private db: Database;
|
||||||
@ -15,7 +24,11 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUser (userId: number): Promise<User | null> {
|
async getUser (userId: number): Promise<User | null> {
|
||||||
return this.db.getUser(userId);
|
return this.db.getUser({
|
||||||
|
where: {
|
||||||
|
id: userId
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrganizationsByUserId (userId: number): Promise<Organization[]> {
|
async getOrganizationsByUserId (userId: number): Promise<Organization[]> {
|
||||||
@ -57,4 +70,189 @@ export class Service {
|
|||||||
const dbDomains = await this.db.getDomainsByProjectId(projectId);
|
const dbDomains = await this.db.getDomainsByProjectId(projectId);
|
||||||
return dbDomains;
|
return dbDomains;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateProjectMember (projectMemberId: string, data: {permissions: Permission[]}): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.updateProjectMemberById(projectMemberId, data);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addProjectMember (projectId: string,
|
||||||
|
data: {
|
||||||
|
email: string,
|
||||||
|
permissions: Permission[]
|
||||||
|
}): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
// TODO: Send invitation
|
||||||
|
let user = await this.db.getUser({
|
||||||
|
where: {
|
||||||
|
email: data.email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
user = await this.db.createUser({
|
||||||
|
email: data.email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newProjectMember = await this.db.addProjectMember({
|
||||||
|
project: {
|
||||||
|
id: projectId
|
||||||
|
},
|
||||||
|
permissions: data.permissions,
|
||||||
|
isPending: true,
|
||||||
|
member: {
|
||||||
|
id: user.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(newProjectMember);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addEnvironmentVariables (projectId: string, data: { environments: string[], key: string, value: string}[]): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const formattedEnvironmentVariables = data.map((environmentVariable) => {
|
||||||
|
return environmentVariable.environments.map((environment) => {
|
||||||
|
return ({
|
||||||
|
key: environmentVariable.key,
|
||||||
|
value: environmentVariable.value,
|
||||||
|
environment: environment as Environment,
|
||||||
|
project: Object.assign(new Project(), {
|
||||||
|
id: projectId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).flat();
|
||||||
|
|
||||||
|
const savedEnvironmentVariables = await this.db.addEnvironmentVariables(formattedEnvironmentVariables);
|
||||||
|
return savedEnvironmentVariables.length > 0;
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateEnvironmentVariable (environmentVariableId: string, data : DeepPartial<EnvironmentVariable>): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.updateEnvironmentVariable(environmentVariableId, data);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeEnvironmentVariable (environmentVariableId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.deleteEnvironmentVariable(environmentVariableId);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateDeploymentToProd (deploymentId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.updateDeploymentById(deploymentId, {
|
||||||
|
environment: Environment.Production
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addProject (userId: string, data: DeepPartial<Project>): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await this.db.addProject(userId, data);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProject (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.updateProjectById(projectId, data);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProject (projectId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.db.deleteProjectById(projectId);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDomain (domainId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const domainsRedirectedFrom = await this.db.getDomains({
|
||||||
|
where: {
|
||||||
|
redirectToId: Number(domainId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (domainsRedirectedFrom.length > 0) {
|
||||||
|
throw new Error('Cannot delete domain since it has redirects from other domains');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.db.deleteDomainById(domainId);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async redeployToProd (userId: string, deploymentId: string): Promise<Deployment| boolean> {
|
||||||
|
try {
|
||||||
|
const deployment = await this.db.getDeployment({
|
||||||
|
relations: {
|
||||||
|
project: true,
|
||||||
|
domain: true,
|
||||||
|
createdBy: true
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: deploymentId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deployment === null) {
|
||||||
|
throw new Error('Deployment not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { createdAt, updatedAt, ...updatedDeployment } = deployment;
|
||||||
|
|
||||||
|
if (updatedDeployment.environment === Environment.Production) {
|
||||||
|
// TODO: Put isCurrent field in project
|
||||||
|
updatedDeployment.isCurrent = true;
|
||||||
|
updatedDeployment.createdBy = Object.assign(new User(), {
|
||||||
|
id: Number(userId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedDeployment.id = nanoid();
|
||||||
|
updatedDeployment.url = `${updatedDeployment.id}-${updatedDeployment.project.subDomain}`;
|
||||||
|
|
||||||
|
const oldDeployment = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
|
||||||
|
const newDeployement = await this.db.createDeployement(updatedDeployment);
|
||||||
|
|
||||||
|
return oldDeployment && Boolean(newDeployement);
|
||||||
|
} catch (err) {
|
||||||
|
log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
|
import { Organization } from 'gql-client';
|
||||||
|
|
||||||
import { Card, CardBody, Typography } from '@material-tailwind/react';
|
import { Card, CardBody, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = ({ organization }: { organization: Organization }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full p-4">
|
<div className="flex flex-col h-full p-4">
|
||||||
<div className="grow">
|
<div className="grow">
|
||||||
@ -13,8 +14,12 @@ const Sidebar = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Card className="-ml-1 my-2">
|
<Card className="-ml-1 my-2">
|
||||||
<CardBody className="p-1 py-2">
|
<CardBody className="p-1 py-2 flex gap-2">
|
||||||
<Typography>Organization</Typography>
|
<div>^</div>
|
||||||
|
<div>
|
||||||
|
<Typography>{organization.name}</Typography>
|
||||||
|
<Typography>Organization</Typography>
|
||||||
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,19 +1,42 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Organization } from 'gql-client';
|
||||||
|
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import Sidebar from '../components/Sidebar';
|
import Sidebar from '../components/Sidebar';
|
||||||
|
import { useGQLClient } from '../context/GQLClientContext';
|
||||||
|
|
||||||
|
// TODO: Implement organization switcher
|
||||||
|
// TODO: Projects get organization details through routes instead of context
|
||||||
|
const USER_ORGANIZATION_INDEX = 0;
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
const client = useGQLClient();
|
||||||
|
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||||
|
|
||||||
|
const fetchUserOrganizations = useCallback(async () => {
|
||||||
|
const { organizations } = await client.getOrganizations();
|
||||||
|
setOrganizations(organizations);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserOrganizations();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-5 h-screen bg-light-blue-50">
|
<div className="grid grid-cols-5 h-screen bg-light-blue-50">
|
||||||
<div className="h-full">
|
{organizations.length > 0 && (
|
||||||
<Sidebar />
|
<>
|
||||||
</div>
|
<div className="h-full">
|
||||||
<div className="col-span-4 h-full p-3 overflow-y-hidden">
|
<Sidebar organization={organizations[USER_ORGANIZATION_INDEX]} />
|
||||||
<div className="bg-white rounded-3xl h-full overflow-y-auto">
|
</div>
|
||||||
<Outlet />
|
<div className="col-span-4 h-full p-3 overflow-y-hidden">
|
||||||
</div>
|
<div className="bg-white rounded-3xl h-full overflow-y-auto">
|
||||||
</div>
|
<Outlet context={organizations[USER_ORGANIZATION_INDEX]} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Outlet, useNavigate } from 'react-router-dom';
|
import { Outlet, useNavigate, useOutletContext } from 'react-router-dom';
|
||||||
|
import { Organization } from 'gql-client';
|
||||||
|
|
||||||
import { IconButton, Typography } from '@material-tailwind/react';
|
import { IconButton, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ import ProjectSearchBar from '../components/projects/ProjectSearchBar';
|
|||||||
|
|
||||||
const ProjectSearch = () => {
|
const ProjectSearch = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const organization = useOutletContext<Organization>();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="sticky top-0 bg-white z-30">
|
<div className="sticky top-0 bg-white z-30">
|
||||||
@ -33,7 +34,7 @@ const ProjectSearch = () => {
|
|||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
</div>
|
</div>
|
||||||
<div className="z-0">
|
<div className="z-0">
|
||||||
<Outlet />
|
<Outlet context={organization} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useOutletContext } from 'react-router-dom';
|
||||||
|
import { Organization } from 'gql-client';
|
||||||
|
|
||||||
import { Button, Typography, Chip } from '@material-tailwind/react';
|
import { Button, Typography, Chip } from '@material-tailwind/react';
|
||||||
|
|
||||||
@ -8,16 +9,16 @@ import { useGQLClient } from '../context/GQLClientContext';
|
|||||||
import { ProjectDetails } from '../types/project';
|
import { ProjectDetails } from '../types/project';
|
||||||
import { COMMIT_DETAILS } from '../constants';
|
import { COMMIT_DETAILS } from '../constants';
|
||||||
|
|
||||||
// TODO: Implement organization switcher
|
|
||||||
const USER_ORGANIZATION_ID = '1';
|
|
||||||
|
|
||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
const organization = useOutletContext<Organization>();
|
||||||
|
|
||||||
const [projects, setProjects] = useState<ProjectDetails[]>([]);
|
const [projects, setProjects] = useState<ProjectDetails[]>([]);
|
||||||
|
|
||||||
const fetchProjects = useCallback(async () => {
|
const fetchProjects = useCallback(async () => {
|
||||||
const { projectsInOrganization } =
|
const { projectsInOrganization } = await client.getProjectsInOrganization(
|
||||||
await client.getProjectsInOrganization(USER_ORGANIZATION_ID);
|
organization.id,
|
||||||
|
);
|
||||||
|
|
||||||
const updatedProjects = projectsInOrganization.map((project) => {
|
const updatedProjects = projectsInOrganization.map((project) => {
|
||||||
return {
|
return {
|
||||||
|
@ -148,28 +148,28 @@ export class GQLClient {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentVariables (projectId: string, environmentVariables: AddEnvironmentVariableInput[]): Promise<AddEnvironmentVariablesResponse> {
|
async addEnvironmentVariables (projectId: string, data: AddEnvironmentVariableInput[]): Promise<AddEnvironmentVariablesResponse> {
|
||||||
const { data } = await this.client.mutate({
|
const result = await this.client.mutate({
|
||||||
mutation: addEnvironmentVariables,
|
mutation: addEnvironmentVariables,
|
||||||
variables: {
|
variables: {
|
||||||
projectId,
|
projectId,
|
||||||
environmentVariables
|
data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEnvironmentVariable (environmentVariableId: string, environmentVariable: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse> {
|
async updateEnvironmentVariable (environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse> {
|
||||||
const { data } = await this.client.mutate({
|
const result = await this.client.mutate({
|
||||||
mutation: updateEnvironmentVariable,
|
mutation: updateEnvironmentVariable,
|
||||||
variables: {
|
variables: {
|
||||||
environmentVariableId,
|
environmentVariableId,
|
||||||
environmentVariable
|
data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEnvironmentVariable (environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse> {
|
async removeEnvironmentVariable (environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse> {
|
||||||
|
@ -19,14 +19,14 @@ mutation ($projectId: String!, $data: AddProjectMemberInput) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const addEnvironmentVariables = gql`
|
export const addEnvironmentVariables = gql`
|
||||||
mutation ($projectId: String!, $environmentVariables: [AddEnvironmentVariableInput!]) {
|
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
|
||||||
addEnvironmentVariables(projectId: $projectId, environmentVariables: $environmentVariables)
|
addEnvironmentVariables(projectId: $projectId, data: $data)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const updateEnvironmentVariable = gql`
|
export const updateEnvironmentVariable = gql`
|
||||||
mutation ($environmentVariableId: String!, $environmentVariable: UpdateEnvironmentVariableInput!) {
|
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) {
|
||||||
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, environmentVariable: $environmentVariable)
|
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user