From 02f7ebb9bd39da39287f75c6ae809715ccba2954 Mon Sep 17 00:00:00 2001 From: prathamesh0 <42446521+prathamesh0@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:01:33 +0530 Subject: [PATCH] Add checks for mutations and a mutation to add environment variables to project (#33) * Add mutation to add environment variables by project id * Add checks while removing project members --------- Co-authored-by: neeraj --- packages/backend/src/database.ts | 49 +++++++++++++++++++++++++++++++ packages/backend/src/resolvers.ts | 37 ++++++++++++++++++----- packages/backend/src/schema.gql | 7 +++++ packages/backend/src/utils.ts | 4 +++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index a9fa27e..71922c0 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -1,6 +1,7 @@ import { DataSource } from 'typeorm'; import path from 'path'; import debug from 'debug'; +import assert from 'assert'; import { DatabaseConfig } from './config'; import { User } from './entity/User'; @@ -133,6 +134,7 @@ export class Database { async removeProjectMemberByMemberId (memberId: string): Promise { // TODO: Check if user is authorized to delete members const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + const deleted = await projectMemberRepository.delete(memberId); if (deleted.affected) { @@ -141,4 +143,51 @@ export class Database { return false; } } + + async addEnvironmentVariablesByProjectId (projectId: string, environmentVariables: any[]): Promise { + const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); + const projectRepository = this.dataSource.getRepository(Project); + + const project = await projectRepository.findOneBy({ + id: projectId + }); + assert(project); + + const environmentVariablesPromises = environmentVariables.map(async environmentVariable => { + const envVar = new EnvironmentVariable(); + + envVar.key = environmentVariable.key; + envVar.value = environmentVariable.value; + envVar.environments = environmentVariable.environments; + envVar.project = project; + + return environmentVariableRepository.save(envVar); + }); + + const savedEnvironmentVariables = await Promise.all(environmentVariablesPromises); + return savedEnvironmentVariables.length > 0; + } + + async getProjectMemberByMemberId (memberId: string): Promise { + const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + + const projectMemberWithProject = await projectMemberRepository.find({ + relations: { + project: { + owner: true + }, + member: true + }, + where: { + id: Number(memberId) + } + } + ); + + if (projectMemberWithProject.length === 0) { + throw new Error('Member does not exist'); + } + + return projectMemberWithProject[0]; + } } diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 2f9da79..ff4c2f7 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -1,7 +1,8 @@ import debug from 'debug'; +import assert from 'assert'; import { Database } from './database'; -import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, environmentVariableToGqlType } from './utils'; +import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, environmentVariableToGqlType, isUserOwner } from './utils'; const log = debug('snowball:database'); @@ -19,7 +20,7 @@ export const createResolvers = async (db: Database): Promise => { const orgsWithProjectsPromises = organizations.map(async (org) => { const dbProjects = await db.getProjectsByOrganizationId(org.id); - const projectsWithPromises = dbProjects.map(async (dbProject) => { + const projectsPromises = dbProjects.map(async (dbProject) => { const dbProjectMembers = await db.getProjectMembersByProjectId(dbProject.id); const dbEnvironmentVariables = await db.getEnvironmentVariablesByProjectId(dbProject.id); @@ -34,7 +35,7 @@ export const createResolvers = async (db: Database): Promise => { return projectToGqlType(dbProject, projectMembers, environmentVariables); }); - const projects = await Promise.all(projectsWithPromises); + const projects = await Promise.all(projectsPromises); return { ...org, @@ -69,11 +70,33 @@ export const createResolvers = async (db: Database): Promise => { }, Mutation: { - removeMember: async (_: any, { memberId }:{ memberId: string }) => { + removeMember: async (_: any, { memberId }: { memberId: string }, context: any) => { try { - return await db.removeProjectMemberByMemberId(memberId); - } catch (error) { - log(error); + const member = await db.getProjectMemberByMemberId(memberId); + + 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))) { + return db.removeProjectMemberByMemberId(memberId); + } else { + throw new Error('Invalid operation: not authorized'); + } + } catch (err) { + log(err); + return false; + } + }, + + addEnvironmentVariables: async (_: any, { projectId, environmentVariables }: { projectId: string, environmentVariables: { environments: string[], key: string, value: string}[] }) => { + try { + return db.addEnvironmentVariablesByProjectId(projectId, environmentVariables); + } catch (err) { + log(err); return false; } } diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 30f1398..5125a45 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -120,4 +120,11 @@ type Query { type Mutation { removeMember(memberId: String!): Boolean! + addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! +} + +input AddEnvironmentVariableInput { + environments: [Environment!]! + key: String! + value: String! } diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index c33f585..64ea47d 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -79,3 +79,7 @@ export const environmentVariableToGqlType = (dbEnvironmentVariable: EnvironmentV updatedAt: dbEnvironmentVariable.updatedAt }; }; + +export const isUserOwner = (userId: string, projectOwnerId: string): boolean => { + return userId === projectOwnerId; +};