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: {
|
||||||
if (deleteResult.affected) {
|
id: projectId
|
||||||
return deleteResult.affected > 0;
|
},
|
||||||
} else {
|
relations: {
|
||||||
return false;
|
projectMembers: true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteResult = await projectRepository.softRemove(project);
|
||||||
|
|
||||||
|
return Boolean(deleteResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
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