forked from cerc-io/snowballtools-base
		
	Implement functionality to add project members (#48)
* Add mutation for adding project member * Add GQL client method for add project member * Handle review comments * Integrate add project member GQL client method * Handle cascades for project members and user organizations * Add null type to user.name and domain.redirectTo --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
		
							parent
							
								
									7e2a313012
								
							
						
					
					
						commit
						0aa35d05f4
					
				| @ -9,7 +9,7 @@ import { Organization } from './entity/Organization'; | |||||||
| import { UserOrganization } from './entity/UserOrganization'; | import { UserOrganization } from './entity/UserOrganization'; | ||||||
| import { Project } from './entity/Project'; | import { Project } from './entity/Project'; | ||||||
| import { Deployment, Environment } from './entity/Deployment'; | import { Deployment, Environment } from './entity/Deployment'; | ||||||
| import { ProjectMember } from './entity/ProjectMember'; | import { Permission, ProjectMember } from './entity/ProjectMember'; | ||||||
| import { EnvironmentVariable } from './entity/EnvironmentVariable'; | import { EnvironmentVariable } from './entity/EnvironmentVariable'; | ||||||
| import { Domain } from './entity/Domain'; | import { Domain } from './entity/Domain'; | ||||||
| 
 | 
 | ||||||
| @ -198,6 +198,38 @@ export class Database { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async addProjectMember (projectId: string, data: { | ||||||
|  |     email: string, | ||||||
|  |     permissions: Permission[] | ||||||
|  |   }): Promise<boolean> { | ||||||
|  |     const projectMemberRepository = this.dataSource.getRepository(ProjectMember); | ||||||
|  |     const userRepository = this.dataSource.getRepository(User); | ||||||
|  | 
 | ||||||
|  |     let user = await userRepository.findOneBy({ | ||||||
|  |       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 addEnvironmentVariablesByProjectId (projectId: string, environmentVariables: { | ||||||
|     environments: string[]; |     environments: string[]; | ||||||
|     key: string; |     key: string; | ||||||
| @ -338,13 +370,18 @@ export class Database { | |||||||
| 
 | 
 | ||||||
|   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 deleteResult = await projectRepository.softDelete({ id: projectId }); |     const project = await projectRepository.findOneOrFail({ | ||||||
|  |       where: { | ||||||
|  |         id: projectId | ||||||
|  |       }, | ||||||
|  |       relations: { | ||||||
|  |         projectMembers: true | ||||||
|  |       } | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     if (deleteResult.affected) { |     const deleteResult = await projectRepository.softRemove(project); | ||||||
|       return deleteResult.affected > 0; | 
 | ||||||
|     } else { |     return Boolean(deleteResult); | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async deleteDomainById (domainId: string): Promise<boolean> { |   async deleteDomainById (domainId: string): Promise<boolean> { | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ export class Domain { | |||||||
|     name!: string; |     name!: string; | ||||||
| 
 | 
 | ||||||
|   @Column('int', { nullable: true }) |   @Column('int', { nullable: true }) | ||||||
|     redirectToId!: number; |     redirectToId!: number | null; | ||||||
| 
 | 
 | ||||||
|   @ManyToOne(() => Domain) |   @ManyToOne(() => Domain) | ||||||
|   @JoinColumn({ name: 'redirectToId' }) |   @JoinColumn({ name: 'redirectToId' }) | ||||||
|  | |||||||
| @ -3,8 +3,10 @@ import { | |||||||
|   PrimaryGeneratedColumn, |   PrimaryGeneratedColumn, | ||||||
|   Column, |   Column, | ||||||
|   CreateDateColumn, |   CreateDateColumn, | ||||||
|   UpdateDateColumn |   UpdateDateColumn, | ||||||
|  |   OneToMany | ||||||
| } from 'typeorm'; | } from 'typeorm'; | ||||||
|  | import { UserOrganization } from './UserOrganization'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class Organization { | export class Organization { | ||||||
| @ -19,4 +21,9 @@ export class Organization { | |||||||
| 
 | 
 | ||||||
|   @UpdateDateColumn() |   @UpdateDateColumn() | ||||||
|     updatedAt!: Date; |     updatedAt!: Date; | ||||||
|  | 
 | ||||||
|  |   @OneToMany(() => UserOrganization, userOrganization => userOrganization.organization, { | ||||||
|  |     cascade: ['soft-remove'] | ||||||
|  |   }) | ||||||
|  |     userOrganizations!: UserOrganization[]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -63,9 +63,11 @@ export class Project { | |||||||
|   @DeleteDateColumn() |   @DeleteDateColumn() | ||||||
|     deletedAt?: Date; |     deletedAt?: Date; | ||||||
| 
 | 
 | ||||||
|   @OneToMany(() => ProjectMember, projectMember => projectMember.project) |  | ||||||
|     projectMembers!: ProjectMember[]; |  | ||||||
| 
 |  | ||||||
|   @OneToMany(() => Deployment, (deployment) => deployment.project) |   @OneToMany(() => Deployment, (deployment) => deployment.project) | ||||||
|     deployments!: Deployment[]; |     deployments!: Deployment[]; | ||||||
|  | 
 | ||||||
|  |   @OneToMany(() => ProjectMember, projectMember => projectMember.project, { | ||||||
|  |     cascade: ['soft-remove'] | ||||||
|  |   }) | ||||||
|  |     projectMembers!: ProjectMember[]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,9 @@ import { | |||||||
|   UpdateDateColumn, |   UpdateDateColumn, | ||||||
|   ManyToOne, |   ManyToOne, | ||||||
|   PrimaryGeneratedColumn, |   PrimaryGeneratedColumn, | ||||||
|   JoinColumn |   JoinColumn, | ||||||
|  |   Unique, | ||||||
|  |   DeleteDateColumn | ||||||
| } from 'typeorm'; | } from 'typeorm'; | ||||||
| 
 | 
 | ||||||
| import { Project } from './Project'; | import { Project } from './Project'; | ||||||
| @ -17,15 +19,16 @@ export enum Permission { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
|  | @Unique(['project', 'member']) | ||||||
| export class ProjectMember { | export class ProjectMember { | ||||||
|   @PrimaryGeneratedColumn() |   @PrimaryGeneratedColumn() | ||||||
|     id!: number; |     id!: number; | ||||||
| 
 | 
 | ||||||
|   @ManyToOne(() => User, (user) => user.projectMembers, { onDelete: 'CASCADE' }) |   @ManyToOne(() => User, (user) => user.projectMembers) | ||||||
|   @JoinColumn({ name: 'userId' }) |   @JoinColumn({ name: 'userId' }) | ||||||
|     member!: User; |     member!: User; | ||||||
| 
 | 
 | ||||||
|   @ManyToOne(() => Project, (project) => project.projectMembers, { onDelete: 'CASCADE' }) |   @ManyToOne(() => Project, (project) => project.projectMembers) | ||||||
|   @JoinColumn({ name: 'projectId' }) |   @JoinColumn({ name: 'projectId' }) | ||||||
|     project!: Project; |     project!: Project; | ||||||
| 
 | 
 | ||||||
| @ -34,9 +37,15 @@ export class ProjectMember { | |||||||
|   }) |   }) | ||||||
|     permissions!: Permission[]; |     permissions!: Permission[]; | ||||||
| 
 | 
 | ||||||
|  |   @Column('boolean', { default: false }) | ||||||
|  |     isPending!: boolean; | ||||||
|  | 
 | ||||||
|   @CreateDateColumn() |   @CreateDateColumn() | ||||||
|     createdAt!: Date; |     createdAt!: Date; | ||||||
| 
 | 
 | ||||||
|   @UpdateDateColumn() |   @UpdateDateColumn() | ||||||
|     updatedAt!: Date; |     updatedAt!: Date; | ||||||
|  | 
 | ||||||
|  |   @DeleteDateColumn() | ||||||
|  |     deletedAt?: Date; | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,17 +3,21 @@ import { | |||||||
|   PrimaryGeneratedColumn, |   PrimaryGeneratedColumn, | ||||||
|   Column, |   Column, | ||||||
|   CreateDateColumn, |   CreateDateColumn, | ||||||
|   OneToMany |   OneToMany, | ||||||
|  |   Unique | ||||||
| } from 'typeorm'; | } from 'typeorm'; | ||||||
|  | 
 | ||||||
| import { ProjectMember } from './ProjectMember'; | import { ProjectMember } from './ProjectMember'; | ||||||
|  | import { UserOrganization } from './UserOrganization'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
|  | @Unique(['email']) | ||||||
| export class User { | export class User { | ||||||
|   @PrimaryGeneratedColumn() |   @PrimaryGeneratedColumn() | ||||||
|     id!: number; |     id!: number; | ||||||
| 
 | 
 | ||||||
|   @Column('varchar', { length: 255 }) |   @Column('varchar', { length: 255, nullable: true }) | ||||||
|     name!: string; |     name!: string | null; | ||||||
| 
 | 
 | ||||||
|   @Column() |   @Column() | ||||||
|     email!: string; |     email!: string; | ||||||
| @ -21,12 +25,22 @@ export class User { | |||||||
|   @Column('varchar', { nullable: true }) |   @Column('varchar', { nullable: true }) | ||||||
|     gitHubToken!: string | null; |     gitHubToken!: string | null; | ||||||
| 
 | 
 | ||||||
|  |   @Column('boolean', { default: false }) | ||||||
|  |     isVerified!: boolean; | ||||||
|  | 
 | ||||||
|   @CreateDateColumn() |   @CreateDateColumn() | ||||||
|     createdAt!: Date; |     createdAt!: Date; | ||||||
| 
 | 
 | ||||||
|   @CreateDateColumn() |   @CreateDateColumn() | ||||||
|     updatedAt!: Date; |     updatedAt!: Date; | ||||||
| 
 | 
 | ||||||
|   @OneToMany(() => ProjectMember, projectMember => projectMember.project) |   @OneToMany(() => ProjectMember, projectMember => projectMember.project, { | ||||||
|  |     cascade: ['soft-remove'] | ||||||
|  |   }) | ||||||
|     projectMembers!: ProjectMember[]; |     projectMembers!: ProjectMember[]; | ||||||
|  | 
 | ||||||
|  |   @OneToMany(() => UserOrganization, UserOrganization => UserOrganization.member, { | ||||||
|  |     cascade: ['soft-remove'] | ||||||
|  |   }) | ||||||
|  |     userOrganizations!: UserOrganization[]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,8 @@ import { | |||||||
|   UpdateDateColumn, |   UpdateDateColumn, | ||||||
|   ManyToOne, |   ManyToOne, | ||||||
|   PrimaryGeneratedColumn, |   PrimaryGeneratedColumn, | ||||||
|   JoinColumn |   JoinColumn, | ||||||
|  |   DeleteDateColumn | ||||||
| } from 'typeorm'; | } from 'typeorm'; | ||||||
| 
 | 
 | ||||||
| import { User } from './User'; | import { User } from './User'; | ||||||
| @ -22,11 +23,11 @@ export class UserOrganization { | |||||||
|   @PrimaryGeneratedColumn() |   @PrimaryGeneratedColumn() | ||||||
|     id!: number; |     id!: number; | ||||||
| 
 | 
 | ||||||
|   @ManyToOne(() => User, { onDelete: 'CASCADE' }) |   @ManyToOne(() => User) | ||||||
|   @JoinColumn({ name: 'userId' }) |   @JoinColumn({ name: 'userId' }) | ||||||
|     member!: User; |     member!: User; | ||||||
| 
 | 
 | ||||||
|   @ManyToOne(() => Organization, { onDelete: 'CASCADE' }) |   @ManyToOne(() => Organization) | ||||||
|   @JoinColumn({ name: 'organizationId' }) |   @JoinColumn({ name: 'organizationId' }) | ||||||
|     organization!: Organization; |     organization!: Organization; | ||||||
| 
 | 
 | ||||||
| @ -40,4 +41,7 @@ export class UserOrganization { | |||||||
| 
 | 
 | ||||||
|   @UpdateDateColumn() |   @UpdateDateColumn() | ||||||
|     updatedAt!: Date; |     updatedAt!: Date; | ||||||
|  | 
 | ||||||
|  |   @DeleteDateColumn() | ||||||
|  |     deletedAt?: Date; | ||||||
| } | } | ||||||
|  | |||||||
| @ -111,6 +111,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|  |     // 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 { | ||||||
| @ -124,7 +125,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
|           assert(memberProject); |           assert(memberProject); | ||||||
| 
 | 
 | ||||||
|           if (isUserOwner(String(context.userId), String(memberProject.owner.id))) { |           if (isUserOwner(String(context.userId), String(memberProject.owner.id))) { | ||||||
|             return db.removeProjectMemberById(projectMemberId); |             return await db.removeProjectMemberById(projectMemberId); | ||||||
|           } else { |           } else { | ||||||
|             throw new Error('Invalid operation: not authorized'); |             throw new Error('Invalid operation: not authorized'); | ||||||
|           } |           } | ||||||
| @ -141,7 +142,23 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
|         } |         } | ||||||
|       }) => { |       }) => { | ||||||
|         try { |         try { | ||||||
|           return db.updateProjectMemberById(projectMemberId, data); |           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); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -150,7 +167,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       addEnvironmentVariables: async (_: any, { projectId, environmentVariables }: { projectId: string, environmentVariables: { environments: string[], key: string, value: string}[] }) => { |       addEnvironmentVariables: async (_: any, { projectId, environmentVariables }: { projectId: string, environmentVariables: { environments: string[], key: string, value: string}[] }) => { | ||||||
|         try { |         try { | ||||||
|           return db.addEnvironmentVariablesByProjectId(projectId, environmentVariables); |           return await db.addEnvironmentVariablesByProjectId(projectId, environmentVariables); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -162,7 +179,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
|         value: string |         value: string | ||||||
|       }}) => { |       }}) => { | ||||||
|         try { |         try { | ||||||
|           return db.updateEnvironmentVariable(environmentVariableId, environmentVariable); |           return await db.updateEnvironmentVariable(environmentVariableId, environmentVariable); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -171,7 +188,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => { |       removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => { | ||||||
|         try { |         try { | ||||||
|           return db.deleteEnvironmentVariable(environmentVariableId); |           return await db.deleteEnvironmentVariable(environmentVariableId); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -180,7 +197,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => { |       updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => { | ||||||
|         try { |         try { | ||||||
|           return db.updateDeploymentById(deploymentId, { |           return await db.updateDeploymentById(deploymentId, { | ||||||
|             environment: Environment.Production |             environment: Environment.Production | ||||||
|           }); |           }); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
| @ -200,8 +217,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { |       redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { | ||||||
|         try { |         try { | ||||||
|           await db.redeployToProdById(context.userId, deploymentId); |           return await db.redeployToProdById(context.userId, deploymentId); | ||||||
|           return true; |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -210,7 +226,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       deleteProject: async (_: any, { projectId }: { projectId: string }) => { |       deleteProject: async (_: any, { projectId }: { projectId: string }) => { | ||||||
|         try { |         try { | ||||||
|           return db.deleteProjectById(projectId); |           return await db.deleteProjectById(projectId); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -219,8 +235,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       deleteDomain: async (_: any, { domainId }: { domainId: string }) => { |       deleteDomain: async (_: any, { domainId }: { domainId: string }) => { | ||||||
|         try { |         try { | ||||||
|           await db.deleteDomainById(domainId); |           return await db.deleteDomainById(domainId); | ||||||
|           return true; |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -229,7 +244,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => { |       rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => { | ||||||
|         try { |         try { | ||||||
|           return db.rollbackDeploymentById(projectId, deploymentId); |           return await db.rollbackDeploymentById(projectId, deploymentId); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -238,8 +253,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       addDomain: async (_: any, { projectId, domainDetails }: { projectId: string, domainDetails: { name: string } }) => { |       addDomain: async (_: any, { projectId, domainDetails }: { projectId: string, domainDetails: { name: string } }) => { | ||||||
|         try { |         try { | ||||||
|           await db.addDomainByProjectId(projectId, domainDetails); |           return await db.addDomainByProjectId(projectId, domainDetails); | ||||||
|           return true; |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
| @ -248,8 +262,7 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | |||||||
| 
 | 
 | ||||||
|       updateDomain: async (_: any, { domainId, domainDetails }: { domainId: string, domainDetails: DeepPartial<Domain>}) => { |       updateDomain: async (_: any, { domainId, domainDetails }: { domainId: string, domainDetails: DeepPartial<Domain>}) => { | ||||||
|         try { |         try { | ||||||
|           await db.updateDomainById(domainId, domainDetails); |           return await db.updateDomainById(domainId, domainDetails); | ||||||
|           return true; |  | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           log(err); |           log(err); | ||||||
|           return false; |           return false; | ||||||
|  | |||||||
| @ -28,10 +28,11 @@ enum DomainStatus { | |||||||
| 
 | 
 | ||||||
| type User { | type User { | ||||||
|   id: String! |   id: String! | ||||||
|   name: String! |   name: String | ||||||
|   email: String! |   email: String! | ||||||
|   organizations: [Organization!] |   organizations: [Organization!] | ||||||
|   projects: [Project!] |   projects: [Project!] | ||||||
|  |   isVerified: Boolean! | ||||||
|   createdAt: String! |   createdAt: String! | ||||||
|   updatedAt: String! |   updatedAt: String! | ||||||
|   gitHubToken: String |   gitHubToken: String | ||||||
| @ -77,6 +78,7 @@ type ProjectMember { | |||||||
|   id: String! |   id: String! | ||||||
|   member: User! |   member: User! | ||||||
|   permissions: [Permission!]! |   permissions: [Permission!]! | ||||||
|  |   isPending: Boolean! | ||||||
|   createdAt: String! |   createdAt: String! | ||||||
|   updatedAt: String! |   updatedAt: String! | ||||||
| } | } | ||||||
| @ -134,6 +136,7 @@ type AuthResult { | |||||||
| type Mutation { | type Mutation { | ||||||
|   removeProjectMember(projectMemberId: String!): Boolean! |   removeProjectMember(projectMemberId: String!): Boolean! | ||||||
|   updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean! |   updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean! | ||||||
|  |   addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean! | ||||||
|   addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! |   addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! | ||||||
|   removeEnvironmentVariable(environmentVariableId: String!): Boolean! |   removeEnvironmentVariable(environmentVariableId: String!): Boolean! | ||||||
|   updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean! |   updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean! | ||||||
| @ -177,6 +180,11 @@ input UpdateEnvironmentVariableInput { | |||||||
|   value: String |   value: String | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | input AddProjectMemberInput { | ||||||
|  |   email: String! | ||||||
|  |   permissions: [Permission!] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| input UpdateProjectMemberInput { | input UpdateProjectMemberInput { | ||||||
|   permissions: [Permission] |   permissions: [Permission] | ||||||
| } | } | ||||||
|  | |||||||
| @ -65,6 +65,7 @@ export const projectMemberToGqlType = (dbProjectMember: ProjectMember): any => { | |||||||
|   return { |   return { | ||||||
|     id: dbProjectMember.id, |     id: dbProjectMember.id, | ||||||
|     member: dbProjectMember.member, |     member: dbProjectMember.member, | ||||||
|  |     isPending: dbProjectMember.isPending, | ||||||
|     permissions: dbProjectMember.permissions, |     permissions: dbProjectMember.permissions, | ||||||
|     createdAt: dbProjectMember.createdAt, |     createdAt: dbProjectMember.createdAt, | ||||||
|     updatedAt: dbProjectMember.updatedAt |     updatedAt: dbProjectMember.updatedAt | ||||||
|  | |||||||
| @ -2,46 +2,77 @@ | |||||||
|   { |   { | ||||||
|     "memberIndex": 1, |     "memberIndex": 1, | ||||||
|     "projectIndex": 0, |     "projectIndex": 0, | ||||||
|     "permissions": ["View"] |     "permissions": [ | ||||||
|  |       "View" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 2, |     "memberIndex": 2, | ||||||
|     "projectIndex": 0, |     "projectIndex": 0, | ||||||
|     "permissions": ["View", "Edit"] |     "permissions": [ | ||||||
|  |       "View", | ||||||
|  |       "Edit" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 2, |     "memberIndex": 2, | ||||||
|     "projectIndex": 1, |     "projectIndex": 1, | ||||||
|     "permissions": ["View"] |     "permissions": [ | ||||||
|  |       "View" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 0, |     "memberIndex": 0, | ||||||
|     "projectIndex": 2, |     "projectIndex": 2, | ||||||
|     "permissions": ["View"] |     "permissions": [ | ||||||
|  |       "View" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 1, |     "memberIndex": 1, | ||||||
|     "projectIndex": 2, |     "projectIndex": 2, | ||||||
|     "permissions": ["View", "Edit"] |     "permissions": [ | ||||||
|  |       "View", | ||||||
|  |       "Edit" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 0, |     "memberIndex": 0, | ||||||
|     "projectIndex": 3, |     "projectIndex": 3, | ||||||
|     "permissions": ["View"] |     "permissions": [ | ||||||
|  |       "View" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 2, |     "memberIndex": 2, | ||||||
|     "projectIndex": 3, |     "projectIndex": 3, | ||||||
|     "permissions": ["View", "Edit"] |     "permissions": [ | ||||||
|  |       "View", | ||||||
|  |       "Edit" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 1, |     "memberIndex": 1, | ||||||
|     "projectIndex": 4, |     "projectIndex": 4, | ||||||
|     "permissions": ["View"] |     "permissions": [ | ||||||
|  |       "View" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "memberIndex": 2, |     "memberIndex": 2, | ||||||
|     "projectIndex": 4, |     "projectIndex": 4, | ||||||
|     "permissions": ["View", "Edit"] |     "permissions": [ | ||||||
|  |       "View", | ||||||
|  |       "Edit" | ||||||
|  |     ], | ||||||
|  |     "isPending": false | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								packages/backend/test/fixtures/users.json
									
									
									
									
										vendored
									
									
								
							| @ -1,14 +1,17 @@ | |||||||
| [ | [ | ||||||
|   { |   { | ||||||
|     "name": "Saugat Yadav", |     "name": "Saugat Yadav", | ||||||
|     "email": "saugaty@airfoil.studio" |     "email": "saugaty@airfoil.studio", | ||||||
|  |     "isVerified": true | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "Gideon Low", |     "name": "Gideon Low", | ||||||
|     "email": "gideonl@airfoil.studio" |     "email": "gideonl@airfoil.studio", | ||||||
|  |     "isVerified": true | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "Sushan Yadav", |     "name": "Sushan Yadav", | ||||||
|     "email": "sushany@airfoil.studio" |     "email": "sushany@airfoil.studio", | ||||||
|  |     "isVerified": true | ||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import React, { useCallback } from 'react'; | import React, { useCallback } from 'react'; | ||||||
| import { useForm } from 'react-hook-form'; | import { useForm } from 'react-hook-form'; | ||||||
|  | import { AddProjectMemberInput, Permission } from 'gql-client'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   Button, |   Button, | ||||||
| @ -12,12 +13,10 @@ import { | |||||||
|   Checkbox, |   Checkbox, | ||||||
| } from '@material-tailwind/react'; | } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import { ProjectMember, Permission } from '../../../../types/project'; |  | ||||||
| 
 |  | ||||||
| interface AddMemberDialogProp { | interface AddMemberDialogProp { | ||||||
|   open: boolean; |   open: boolean; | ||||||
|   handleOpen: () => void; |   handleOpen: () => void; | ||||||
|   handleAddMember: (projectMember: ProjectMember) => void; |   handleAddMember: (data: AddProjectMemberInput) => Promise<void>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface formData { | interface formData { | ||||||
| @ -48,21 +47,17 @@ const AddMemberDialog = ({ | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   const submitHandler = useCallback((data: formData) => { |   const submitHandler = useCallback(async (data: formData) => { | ||||||
|     reset(); |     reset(); | ||||||
|     handleOpen(); |     handleOpen(); | ||||||
| 
 | 
 | ||||||
|     const projectMember: ProjectMember = { |     const permissions = Object.entries(data.permissions) | ||||||
|       id: Math.random().toString(), |       .filter(([, value]) => value) | ||||||
|       permissions: [], |       .map( | ||||||
|       member: { |         ([key]) => key.charAt(0).toUpperCase() + key.slice(1), | ||||||
|         name: '', |       ) as Permission[]; | ||||||
|         email: data.emailAddress, |  | ||||||
|         id: Math.random().toString(), |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     handleAddMember(projectMember); |     await handleAddMember({ email: data.emailAddress, permissions }); | ||||||
|   }, []); |   }, []); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
| @ -96,13 +91,13 @@ const AddMemberDialog = ({ | |||||||
|           </Typography> |           </Typography> | ||||||
|           <Checkbox |           <Checkbox | ||||||
|             crossOrigin={undefined} |             crossOrigin={undefined} | ||||||
|             label={Permission.VIEW} |             label={Permission.View} | ||||||
|             {...register(`permissions.view`)} |             {...register(`permissions.view`)} | ||||||
|             color="blue" |             color="blue" | ||||||
|           /> |           /> | ||||||
|           <Checkbox |           <Checkbox | ||||||
|             crossOrigin={undefined} |             crossOrigin={undefined} | ||||||
|             label={Permission.EDIT} |             label={Permission.Edit} | ||||||
|             {...register(`permissions.edit`)} |             {...register(`permissions.edit`)} | ||||||
|             color="blue" |             color="blue" | ||||||
|           /> |           /> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import React, { useCallback, useState } from 'react'; | import React, { useCallback, useState } from 'react'; | ||||||
| import { Permission } from 'gql-client'; | import { Permission, User } from 'gql-client'; | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   Select, |   Select, | ||||||
| @ -10,7 +10,6 @@ import { | |||||||
| } from '@material-tailwind/react'; | } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import ConfirmDialog from '../../../shared/ConfirmDialog'; | import ConfirmDialog from '../../../shared/ConfirmDialog'; | ||||||
| import { Member } from '../../../../types/project'; |  | ||||||
| 
 | 
 | ||||||
| const PERMISSION_OPTIONS = [ | const PERMISSION_OPTIONS = [ | ||||||
|   { |   { | ||||||
| @ -29,12 +28,11 @@ const DROPDOWN_OPTIONS = [ | |||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| interface MemberCardProps { | interface MemberCardProps { | ||||||
|   member: Member; |   member: User; | ||||||
|   isFirstCard: boolean; |   isFirstCard: boolean; | ||||||
|   isOwner: boolean; |   isOwner: boolean; | ||||||
|   isPending: boolean; |   isPending: boolean; | ||||||
|   permissions: string[]; |   permissions: string[]; | ||||||
|   handleDeletePendingMember?: (id: string) => void; |  | ||||||
|   onRemoveProjectMember?: () => Promise<void>; |   onRemoveProjectMember?: () => Promise<void>; | ||||||
|   onUpdateProjectMember?: (data: { |   onUpdateProjectMember?: (data: { | ||||||
|     permissions: Permission[]; |     permissions: Permission[]; | ||||||
| @ -47,7 +45,6 @@ const MemberCard = ({ | |||||||
|   isOwner, |   isOwner, | ||||||
|   isPending, |   isPending, | ||||||
|   permissions, |   permissions, | ||||||
|   handleDeletePendingMember, |  | ||||||
|   onRemoveProjectMember, |   onRemoveProjectMember, | ||||||
|   onUpdateProjectMember, |   onUpdateProjectMember, | ||||||
| }: MemberCardProps) => { | }: MemberCardProps) => { | ||||||
| @ -82,7 +79,7 @@ const MemberCard = ({ | |||||||
|     > |     > | ||||||
|       <div>^</div> |       <div>^</div> | ||||||
|       <div className="basis-1/2"> |       <div className="basis-1/2"> | ||||||
|         <Typography variant="small">{member.name}</Typography> |         {member.name && <Typography variant="small">{member.name}</Typography>} | ||||||
|         <Typography variant="small">{member.email}</Typography> |         <Typography variant="small">{member.email}</Typography> | ||||||
|       </div> |       </div> | ||||||
|       <div className="basis-1/2"> |       <div className="basis-1/2"> | ||||||
| @ -122,9 +119,7 @@ const MemberCard = ({ | |||||||
|                 size="sm" |                 size="sm" | ||||||
|                 className="rounded-full" |                 className="rounded-full" | ||||||
|                 onClick={() => { |                 onClick={() => { | ||||||
|                   if (handleDeletePendingMember) { |                   setRemoveMemberDialogOpen((prevVal) => !prevVal); | ||||||
|                     handleDeletePendingMember(member.id); |  | ||||||
|                   } |  | ||||||
|                 }} |                 }} | ||||||
|               > |               > | ||||||
|                 D |                 D | ||||||
|  | |||||||
| @ -1,11 +1,15 @@ | |||||||
| import React, { useCallback, useEffect, useState } from 'react'; | import React, { useCallback, useEffect, useState } from 'react'; | ||||||
| import toast, { Toaster } from 'react-hot-toast'; | import toast, { Toaster } from 'react-hot-toast'; | ||||||
| import { Permission, Project } from 'gql-client'; | import { | ||||||
|  |   Permission, | ||||||
|  |   Project, | ||||||
|  |   AddProjectMemberInput, | ||||||
|  |   ProjectMember, | ||||||
|  | } from 'gql-client'; | ||||||
| 
 | 
 | ||||||
| import { Chip, Button, Typography } from '@material-tailwind/react'; | import { Chip, Button, Typography } from '@material-tailwind/react'; | ||||||
| 
 | 
 | ||||||
| import MemberCard from './MemberCard'; | import MemberCard from './MemberCard'; | ||||||
| import { ProjectMember } from '../../../../types/project'; |  | ||||||
| import AddMemberDialog from './AddMemberDialog'; | import AddMemberDialog from './AddMemberDialog'; | ||||||
| import { useGQLClient } from '../../../../context/GQLClientContext'; | import { useGQLClient } from '../../../../context/GQLClientContext'; | ||||||
| 
 | 
 | ||||||
| @ -18,17 +22,26 @@ const MembersTabPanel = ({ project }: { project: Project }) => { | |||||||
| 
 | 
 | ||||||
|   const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]); |   const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]); | ||||||
| 
 | 
 | ||||||
|   const addMemberHandler = useCallback((projectMember: ProjectMember) => { |  | ||||||
|     setProjectMembers((val) => [...val, projectMember]); |  | ||||||
|     toast.success('Invitation sent'); |  | ||||||
|   }, []); |  | ||||||
| 
 |  | ||||||
|   const fetchProjectMembers = useCallback(async () => { |   const fetchProjectMembers = useCallback(async () => { | ||||||
|     const { projectMembers } = await client.getProjectMembers(project.id); |     const { projectMembers } = await client.getProjectMembers(project.id); | ||||||
| 
 |  | ||||||
|     setProjectMembers(projectMembers); |     setProjectMembers(projectMembers); | ||||||
|   }, [project.id]); |   }, [project.id]); | ||||||
| 
 | 
 | ||||||
|  |   const addMemberHandler = useCallback( | ||||||
|  |     async (data: AddProjectMemberInput) => { | ||||||
|  |       const { addProjectMember: isProjectMemberAdded } = | ||||||
|  |         await client.addProjectMember(project.id, data); | ||||||
|  | 
 | ||||||
|  |       if (isProjectMemberAdded) { | ||||||
|  |         await fetchProjectMembers(); | ||||||
|  |         toast.success('Invitation sent'); | ||||||
|  |       } else { | ||||||
|  |         toast.error('Invitation not sent'); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     [project], | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|   const removeMemberHandler = async (projectMemberId: string) => { |   const removeMemberHandler = async (projectMemberId: string) => { | ||||||
|     const { removeProjectMember: isMemberRemoved } = |     const { removeProjectMember: isMemberRemoved } = | ||||||
|       await client.removeProjectMember(projectMemberId); |       await client.removeProjectMember(projectMemberId); | ||||||
| @ -96,15 +109,8 @@ const MembersTabPanel = ({ project }: { project: Project }) => { | |||||||
|             key={projectMember.id} |             key={projectMember.id} | ||||||
|             isFirstCard={index === FIRST_MEMBER_CARD} |             isFirstCard={index === FIRST_MEMBER_CARD} | ||||||
|             isOwner={projectMember.member.id === project.owner.id} |             isOwner={projectMember.member.id === project.owner.id} | ||||||
|             isPending={projectMember.member.name === ''} |             isPending={projectMember.isPending} | ||||||
|             permissions={projectMember.permissions} |             permissions={projectMember.permissions} | ||||||
|             handleDeletePendingMember={(id: string) => { |  | ||||||
|               setProjectMembers( |  | ||||||
|                 projectMembers.filter( |  | ||||||
|                   (projectMember) => projectMember.member.id !== id, |  | ||||||
|                 ), |  | ||||||
|               ); |  | ||||||
|             }} |  | ||||||
|             onRemoveProjectMember={async () => |             onRemoveProjectMember={async () => | ||||||
|               await removeMemberHandler(projectMember.id) |               await removeMemberHandler(projectMember.id) | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -8,12 +8,6 @@ export interface ProjectDetails extends Project { | |||||||
|   repositoryId?: number; |   repositoryId?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ProjectMember { |  | ||||||
|   id: string; |  | ||||||
|   member: Member; |  | ||||||
|   permissions: string[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface DeploymentDetails extends Deployment { | export interface DeploymentDetails extends Deployment { | ||||||
|   commit: Commit; |   commit: Commit; | ||||||
|   author: string; |   author: string; | ||||||
| @ -72,17 +66,6 @@ export interface DomainDetails { | |||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export enum Permission { |  | ||||||
|   VIEW = 'view', |  | ||||||
|   EDIT = 'edit', |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface Member { |  | ||||||
|   name: string; |  | ||||||
|   email: string; |  | ||||||
|   id: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface ProjectSearchOutletContext { | export interface ProjectSearchOutletContext { | ||||||
|   projects: ProjectDetails[]; |   projects: ProjectDetails[]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; | import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; | ||||||
| 
 | 
 | ||||||
| import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject, getDomains, getProjectsInOrganization } from './queries'; | import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject, getDomains, getProjectsInOrganization } from './queries'; | ||||||
| import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse, UpdateDomainInput, UpdateDomainResponse, AuthenticateGitHubResponse, UnauthenticateGitHubResponse, UpdateEnvironmentVariableResponse, UpdateEnvironmentVariableInput, RemoveEnvironmentVariableResponse, UpdateProjectMemberInput, RemoveProjectMemberResponse, UpdateProjectMemberResponse, DeleteDomainResponse } from './types'; | import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse, UpdateDomainInput, UpdateDomainResponse, AuthenticateGitHubResponse, UnauthenticateGitHubResponse, UpdateEnvironmentVariableResponse, UpdateEnvironmentVariableInput, RemoveEnvironmentVariableResponse, UpdateProjectMemberInput, RemoveProjectMemberResponse, UpdateProjectMemberResponse, DeleteDomainResponse, AddProjectMemberInput, AddProjectMemberResponse } from './types'; | ||||||
| import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain } from './mutations'; | import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, addProjectMember } from './mutations'; | ||||||
| 
 | 
 | ||||||
| export interface GraphQLConfig { | export interface GraphQLConfig { | ||||||
|   gqlEndpoint: string; |   gqlEndpoint: string; | ||||||
| @ -102,6 +102,18 @@ export class GQLClient { | |||||||
|     return result.data; |     return result.data; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async addProjectMember (projectId: string, data: AddProjectMemberInput) : Promise<AddProjectMemberResponse> { | ||||||
|  |     const result = await this.client.mutate({ | ||||||
|  |       mutation: addProjectMember, | ||||||
|  |       variables: { | ||||||
|  |         projectId, | ||||||
|  |         data | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return result.data; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async updateProjectMember (projectMemberId: string, data: UpdateProjectMemberInput): Promise<UpdateProjectMemberResponse> { |   async updateProjectMember (projectMemberId: string, data: UpdateProjectMemberInput): Promise<UpdateProjectMemberResponse> { | ||||||
|     const result = await this.client.mutate({ |     const result = await this.client.mutate({ | ||||||
|       mutation: updateProjectMember, |       mutation: updateProjectMember, | ||||||
|  | |||||||
| @ -12,6 +12,12 @@ mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) { | |||||||
| } | } | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
|  | export const addProjectMember = gql` | ||||||
|  | mutation ($projectId: String!, $data: AddProjectMemberInput) { | ||||||
|  |   addProjectMember(projectId: $projectId, data: $data) | ||||||
|  | } | ||||||
|  | `;
 | ||||||
|  | 
 | ||||||
| export const addEnvironmentVariables = gql` | export const addEnvironmentVariables = gql` | ||||||
| mutation ($projectId: String!, $environmentVariables: [AddEnvironmentVariableInput!]) { | mutation ($projectId: String!, $environmentVariables: [AddEnvironmentVariableInput!]) { | ||||||
|   addEnvironmentVariables(projectId: $projectId, environmentVariables: $environmentVariables) |   addEnvironmentVariables(projectId: $projectId, environmentVariables: $environmentVariables) | ||||||
|  | |||||||
| @ -178,7 +178,9 @@ query ($projectId: String!) { | |||||||
|       id |       id | ||||||
|       name |       name | ||||||
|       email |       email | ||||||
|  |       isVerified | ||||||
|     } |     } | ||||||
|  |     isPending | ||||||
|     createdAt |     createdAt | ||||||
|     updatedAt |     updatedAt | ||||||
|     permissions |     permissions | ||||||
|  | |||||||
| @ -42,15 +42,16 @@ export type Domain = { | |||||||
|   branch: string |   branch: string | ||||||
|   name: string |   name: string | ||||||
|   status: DomainStatus |   status: DomainStatus | ||||||
|   redirectTo?: Domain |   redirectTo: Domain | null | ||||||
|   createdAt: string |   createdAt: string | ||||||
|   updatedAt: string |   updatedAt: string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type User = { | export type User = { | ||||||
|   id: string |   id: string | ||||||
|   name: string |   name: string | null | ||||||
|   email: string |   email: string | ||||||
|  |   isVerified: boolean | ||||||
|   createdAt: string |   createdAt: string | ||||||
|   updatedAt: string |   updatedAt: string | ||||||
|   gitHubToken: string | null |   gitHubToken: string | null | ||||||
| @ -82,6 +83,7 @@ export type ProjectMember = { | |||||||
|   id: string |   id: string | ||||||
|   member: User |   member: User | ||||||
|   permissions: Permission[] |   permissions: Permission[] | ||||||
|  |   isPending: boolean | ||||||
|   createdAt: string |   createdAt: string | ||||||
|   updatedAt: string |   updatedAt: string | ||||||
| } | } | ||||||
| @ -135,6 +137,10 @@ export type GetProjectMembersResponse = { | |||||||
|   projectMembers: ProjectMember[] |   projectMembers: ProjectMember[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export type AddProjectMemberResponse = { | ||||||
|  |   addProjectMember: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export type RemoveProjectMemberResponse = { | export type RemoveProjectMemberResponse = { | ||||||
|   removeProjectMember: boolean; |   removeProjectMember: boolean; | ||||||
| } | } | ||||||
| @ -194,6 +200,11 @@ export type UpdateProjectMemberInput = { | |||||||
|   permissions: Permission[]; |   permissions: Permission[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export type AddProjectMemberInput = { | ||||||
|  |   email: string; | ||||||
|  |   permissions: Permission[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export type UpdateEnvironmentVariableResponse = { | export type UpdateEnvironmentVariableResponse = { | ||||||
|   updateEnvironmentVariable: boolean; |   updateEnvironmentVariable: boolean; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user