2024-01-22 09:42:08 +00:00
|
|
|
import debug from 'debug';
|
2024-01-23 09:31:33 +00:00
|
|
|
import assert from 'assert';
|
2024-01-31 12:25:19 +00:00
|
|
|
import { DeepPartial } from 'typeorm';
|
2024-01-22 09:42:08 +00:00
|
|
|
|
2024-01-30 14:20:53 +00:00
|
|
|
import { OAuthApp } from '@octokit/oauth-app';
|
|
|
|
|
2024-01-17 05:23:01 +00:00
|
|
|
import { Database } from './database';
|
2024-01-23 09:31:33 +00:00
|
|
|
import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, environmentVariableToGqlType, isUserOwner } from './utils';
|
2024-01-25 05:47:44 +00:00
|
|
|
import { Environment } from './entity/Deployment';
|
2024-01-31 11:39:29 +00:00
|
|
|
import { Permission } from './entity/ProjectMember';
|
2024-01-31 12:25:19 +00:00
|
|
|
import { Domain } from './entity/Domain';
|
2024-02-01 08:34:07 +00:00
|
|
|
import { Project } from './entity/Project';
|
2024-01-16 09:36:35 +00:00
|
|
|
|
2024-01-22 09:42:08 +00:00
|
|
|
const log = debug('snowball:database');
|
|
|
|
|
2024-01-30 14:20:53 +00:00
|
|
|
export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> => {
|
2024-01-16 09:36:35 +00:00
|
|
|
return {
|
|
|
|
Query: {
|
2024-01-17 11:29:59 +00:00
|
|
|
// TODO: add custom type for context
|
|
|
|
user: (_: any, __: any, context: any) => {
|
2024-01-17 05:23:01 +00:00
|
|
|
return db.getUser(context.userId);
|
2024-01-17 11:29:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
organizations: async (_:any, __: any, context: any) => {
|
2024-01-18 08:36:49 +00:00
|
|
|
const organizations = await db.getOrganizationsByUserId(context.userId);
|
2024-01-17 11:29:59 +00:00
|
|
|
|
|
|
|
const orgsWithProjectsPromises = organizations.map(async (org) => {
|
2024-01-18 08:36:49 +00:00
|
|
|
const dbProjects = await db.getProjectsByOrganizationId(org.id);
|
2024-01-17 11:29:59 +00:00
|
|
|
|
2024-01-23 09:31:33 +00:00
|
|
|
const projectsPromises = dbProjects.map(async (dbProject) => {
|
2024-01-22 09:42:08 +00:00
|
|
|
const dbProjectMembers = await db.getProjectMembersByProjectId(dbProject.id);
|
2024-01-18 08:36:49 +00:00
|
|
|
const dbEnvironmentVariables = await db.getEnvironmentVariablesByProjectId(dbProject.id);
|
|
|
|
|
|
|
|
const projectMembers = dbProjectMembers.map(dbProjectMember => {
|
|
|
|
return projectMemberToGqlType(dbProjectMember);
|
|
|
|
});
|
|
|
|
|
|
|
|
const environmentVariables = dbEnvironmentVariables.map(dbEnvironmentVariable => {
|
|
|
|
return environmentVariableToGqlType(dbEnvironmentVariable);
|
|
|
|
});
|
|
|
|
|
|
|
|
return projectToGqlType(dbProject, projectMembers, environmentVariables);
|
2024-01-17 11:29:59 +00:00
|
|
|
});
|
|
|
|
|
2024-01-23 09:31:33 +00:00
|
|
|
const projects = await Promise.all(projectsPromises);
|
2024-01-18 08:36:49 +00:00
|
|
|
|
2024-01-17 11:29:59 +00:00
|
|
|
return {
|
|
|
|
...org,
|
|
|
|
projects
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-01-18 08:36:49 +00:00
|
|
|
// TODO: Add organizationMembers field when / if required
|
2024-01-17 11:29:59 +00:00
|
|
|
const orgsWithProjects = await Promise.all(orgsWithProjectsPromises);
|
|
|
|
return orgsWithProjects;
|
2024-01-18 08:36:49 +00:00
|
|
|
},
|
|
|
|
|
2024-01-25 06:28:38 +00:00
|
|
|
project: async (_: any, { projectId }: { projectId: string }) => {
|
2024-01-29 11:01:03 +00:00
|
|
|
const dbProject = await db.getProjectById(projectId);
|
2024-01-25 06:28:38 +00:00
|
|
|
|
2024-01-29 11:01:03 +00:00
|
|
|
return dbProject || null;
|
|
|
|
},
|
|
|
|
|
|
|
|
projectsInOrganization: async (_: any, { organizationId }: {organizationId: string }, context: any) => {
|
2024-01-30 09:10:04 +00:00
|
|
|
const dbProjects = await db.getProjectsInOrganization(context.userId, organizationId);
|
|
|
|
return dbProjects;
|
2024-01-25 06:28:38 +00:00
|
|
|
},
|
|
|
|
|
2024-01-18 08:36:49 +00:00
|
|
|
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
|
|
|
const dbDeployments = await db.getDeploymentsByProjectId(projectId);
|
|
|
|
|
|
|
|
const deployments = dbDeployments.map(dbDeployment => {
|
|
|
|
return deploymentToGqlType(dbDeployment);
|
|
|
|
});
|
|
|
|
|
|
|
|
return deployments;
|
2024-01-22 09:42:08 +00:00
|
|
|
},
|
|
|
|
|
2024-01-25 05:14:51 +00:00
|
|
|
environmentVariables: async (_: any, { projectId }: { projectId: string }) => {
|
|
|
|
const dbEnvironmentVariables = await db.getEnvironmentVariablesByProjectId(projectId);
|
2024-01-30 13:47:55 +00:00
|
|
|
return dbEnvironmentVariables;
|
2024-01-25 05:14:51 +00:00
|
|
|
},
|
|
|
|
|
2024-01-22 09:42:08 +00:00
|
|
|
projectMembers: async (_: any, { projectId }: { projectId: string }) => {
|
|
|
|
const dbProjectMembers = await db.getProjectMembersByProjectId(projectId);
|
|
|
|
|
|
|
|
const projectMembers = dbProjectMembers.map(dbProjectMember => {
|
|
|
|
return projectMemberToGqlType(dbProjectMember);
|
|
|
|
});
|
|
|
|
|
|
|
|
return projectMembers;
|
2024-01-24 14:47:43 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
searchProjects: async (_: any, { searchText }: { searchText: string }, context: any) => {
|
|
|
|
const dbProjects = await db.getProjectsBySearchText(context.userId, searchText);
|
|
|
|
|
|
|
|
const projects = dbProjects.map((project) => {
|
|
|
|
return projectToGqlType(project, [], []);
|
|
|
|
});
|
|
|
|
|
|
|
|
return projects;
|
2024-01-29 12:48:43 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
domains: async (_:any, { projectId }: { projectId: string }) => {
|
|
|
|
try {
|
|
|
|
return db.getDomainsByProjectId(projectId);
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-22 09:42:08 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-02-01 10:04:17 +00:00
|
|
|
// TODO: Return error in GQL response
|
2024-01-22 09:42:08 +00:00
|
|
|
Mutation: {
|
2024-01-31 11:39:29 +00:00
|
|
|
removeProjectMember: async (_: any, { projectMemberId }: { projectMemberId: string }, context: any) => {
|
2024-01-22 09:42:08 +00:00
|
|
|
try {
|
2024-01-31 11:39:29 +00:00
|
|
|
const member = await db.getProjectMemberById(projectMemberId);
|
2024-01-23 09:31:33 +00:00
|
|
|
|
|
|
|
if (member.member.id === context.userId) {
|
|
|
|
throw new Error('Invalid operation: cannot remove self');
|
|
|
|
}
|
|
|
|
|
|
|
|
const memberProject = member.project;
|
|
|
|
assert(memberProject);
|
|
|
|
|
|
|
|
if (isUserOwner(String(context.userId), String(memberProject.owner.id))) {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.removeProjectMemberById(projectMemberId);
|
2024-01-23 09:31:33 +00:00
|
|
|
} else {
|
|
|
|
throw new Error('Invalid operation: not authorized');
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-01-31 11:39:29 +00:00
|
|
|
updateProjectMember: async (_: any, { projectMemberId, data }: {
|
|
|
|
projectMemberId: string,
|
|
|
|
data: {
|
|
|
|
permissions: Permission[]
|
|
|
|
}
|
|
|
|
}) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.updateProjectMemberById(projectMemberId, data);
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addProjectMember: async (_: any, { projectId, data }: {
|
|
|
|
projectId: string,
|
|
|
|
data: {
|
|
|
|
email: string,
|
|
|
|
permissions: Permission[]
|
|
|
|
}
|
|
|
|
}) => {
|
|
|
|
try {
|
|
|
|
// TODO: Send invitation
|
|
|
|
return await db.addProjectMember(projectId, data);
|
2024-01-31 11:39:29 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-01-23 09:31:33 +00:00
|
|
|
addEnvironmentVariables: async (_: any, { projectId, environmentVariables }: { projectId: string, environmentVariables: { environments: string[], key: string, value: string}[] }) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.addEnvironmentVariablesByProjectId(projectId, environmentVariables);
|
2024-01-23 09:31:33 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
2024-01-22 09:42:08 +00:00
|
|
|
return false;
|
|
|
|
}
|
2024-01-25 05:47:44 +00:00
|
|
|
},
|
|
|
|
|
2024-01-31 05:06:22 +00:00
|
|
|
updateEnvironmentVariable: async (_: any, { environmentVariableId, environmentVariable }: { environmentVariableId: string, environmentVariable : {
|
|
|
|
key: string
|
|
|
|
value: string
|
|
|
|
}}) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.updateEnvironmentVariable(environmentVariableId, environmentVariable);
|
2024-01-31 05:06:22 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.deleteEnvironmentVariable(environmentVariableId);
|
2024-01-31 05:06:22 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-01-29 12:48:43 +00:00
|
|
|
updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => {
|
2024-01-25 05:47:44 +00:00
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.updateDeploymentById(deploymentId, {
|
2024-01-25 05:47:44 +00:00
|
|
|
environment: Environment.Production
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-25 06:28:38 +00:00
|
|
|
},
|
|
|
|
|
2024-02-01 12:40:15 +00:00
|
|
|
addProject: async (_: any, { projectDetails }: { projectDetails: DeepPartial<Project> }, context: any) => {
|
|
|
|
try {
|
|
|
|
await db.addProject(context.userId, projectDetails);
|
|
|
|
return true;
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-02-01 08:34:07 +00:00
|
|
|
updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => {
|
2024-01-25 06:28:38 +00:00
|
|
|
try {
|
2024-02-01 08:34:07 +00:00
|
|
|
return await db.updateProjectById(projectId, projectDetails);
|
2024-01-25 06:28:38 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-25 12:04:13 +00:00
|
|
|
},
|
|
|
|
|
2024-01-30 10:18:50 +00:00
|
|
|
redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => {
|
2024-01-25 12:04:13 +00:00
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.redeployToProdById(context.userId, deploymentId);
|
2024-01-25 12:04:13 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-29 11:01:03 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
deleteProject: async (_: any, { projectId }: { projectId: string }) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.deleteProjectById(projectId);
|
2024-01-29 11:01:03 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-29 12:09:51 +00:00
|
|
|
},
|
|
|
|
|
2024-02-01 03:34:24 +00:00
|
|
|
deleteDomain: async (_: any, { domainId }: { domainId: string }) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.deleteDomainById(domainId);
|
2024-02-01 03:34:24 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-01-29 12:09:51 +00:00
|
|
|
rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.rollbackDeploymentById(projectId, deploymentId);
|
2024-01-29 12:09:51 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-29 12:48:43 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
addDomain: async (_: any, { projectId, domainDetails }: { projectId: string, domainDetails: { name: string } }) => {
|
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.addDomainByProjectId(projectId, domainDetails);
|
2024-01-29 12:48:43 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-30 08:31:09 +00:00
|
|
|
},
|
|
|
|
|
2024-01-31 12:25:19 +00:00
|
|
|
updateDomain: async (_: any, { domainId, domainDetails }: { domainId: string, domainDetails: DeepPartial<Domain>}) => {
|
2024-01-30 08:31:09 +00:00
|
|
|
try {
|
2024-02-01 10:04:17 +00:00
|
|
|
return await db.updateDomainById(domainId, domainDetails);
|
2024-01-30 08:31:09 +00:00
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-30 14:20:53 +00:00
|
|
|
},
|
|
|
|
|
2024-01-31 13:21:53 +00:00
|
|
|
authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => {
|
2024-01-30 14:20:53 +00:00
|
|
|
// TOO: Move to Service class
|
|
|
|
const { authentication: { token } } = await app.createToken({
|
|
|
|
code
|
|
|
|
});
|
|
|
|
|
2024-01-31 13:21:53 +00:00
|
|
|
await db.updateUser(context.userId, { gitHubToken: token });
|
2024-01-30 14:20:53 +00:00
|
|
|
|
|
|
|
return { token };
|
2024-01-31 13:21:53 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
|
|
|
try {
|
|
|
|
return db.updateUser(context.userId, { gitHubToken: null });
|
|
|
|
} catch (err) {
|
|
|
|
log(err);
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-17 05:23:01 +00:00
|
|
|
}
|
2024-01-16 09:36:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|