Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(API): Allow user to be added to and removed from projects #12329

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions packages/cli/src/controllers/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,7 @@ export class ProjectController {
await this.projectsService.updateProject(req.body.name, req.params.projectId);
}
if (req.body.relations) {
try {
await this.projectsService.syncProjectRelations(req.params.projectId, req.body.relations);
} catch (e) {
if (e instanceof UnlicensedProjectRoleError) {
throw new BadRequestError(e.message);
}
throw e;
}
await this.syncProjectRelations(req.params.projectId, req.body.relations);

this.eventService.emit('team-project-updated', {
userId: req.user.id,
Expand All @@ -214,6 +207,20 @@ export class ProjectController {
}
}

async syncProjectRelations(
projectId: string,
relations: ProjectRequest.ProjectRelationPayload[],
) {
try {
await this.projectsService.syncProjectRelations(projectId, relations);
} catch (e) {
if (e instanceof UnlicensedProjectRoleError) {
throw new BadRequestError(e.message);
}
throw e;
}
}

@Delete('/:projectId')
@ProjectScope('project:delete')
async deleteProject(req: ProjectRequest.Delete) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { encodeNextCursor } from '../../shared/services/pagination.service';
type Create = ProjectRequest.Create;
type Update = ProjectRequest.Update;
type Delete = ProjectRequest.Delete;
type DeleteUser = ProjectRequest.DeleteUser;
type GetAll = PaginatedRequest;
type AddUsers = ProjectRequest.AddUsers;

export = {
createProject: [
Expand Down Expand Up @@ -64,4 +66,65 @@ export = {
});
},
],
deleteUserFromProject: [
isLicensed('feat:projectRole:admin'),
globalScope('project:update'),
async (req: DeleteUser, res: Response) => {
const { projectId, id: userId } = req.params;

const project = await Container.get(ProjectRepository).findOne({
where: { id: projectId },
relations: { projectRelations: true },
});

if (!project) {
return res.status(404).send({ message: 'Not found' });
}

const relations = project.projectRelations.filter((relation) => relation.userId !== userId);

await Container.get(ProjectController).syncProjectRelations(projectId, relations);

return res.status(204).send();
},
],
addUsersToProject: [
isLicensed('feat:projectRole:admin'),
globalScope('project:update'),
async (req: AddUsers, res: Response) => {
const { projectId } = req.params;
const { users } = req.body;

const project = await Container.get(ProjectRepository).findOne({
where: { id: projectId },
relations: { projectRelations: true },
});

if (!project) {
return res.status(404).send({ message: 'Not found' });
}

const existingUsers = project.projectRelations.map((relation) => ({
userId: relation.userId,
role: relation.role,
}));

// TODO:
// - What happens when the user is already in the project?
// - What happens when the user is not found on the instance?

try {
await Container.get(ProjectController).syncProjectRelations(projectId, [
...existingUsers,
...users,
]);
} catch (error) {
return res
.status(400)
.send({ message: error instanceof Error ? error.message : 'Bad request' });
}

return res.status(201).send();
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
delete:
x-eov-operation-id: deleteUserFromProject
x-eov-operation-handler: v1/handlers/projects/projects.handler
tags:
- Projects
summary: Delete a user from a project
description: Delete a user from a project from your instance.
parameters:
- name: projectId
in: path
description: The ID of the project.
required: true
schema:
type: string
- $ref: '../../../users/spec/schemas/parameters/userIdentifier.yml'
responses:
'204':
description: Operation successful.
'401':
$ref: '../../../../shared/spec/responses/unauthorized.yml'
'403':
$ref: '../../../../shared/spec/responses/forbidden.yml'
'404':
$ref: '../../../../shared/spec/responses/notFound.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
post:
x-eov-operation-id: addUsersToProject
x-eov-operation-handler: v1/handlers/projects/projects.handler
tags:
- Projects
summary: Add one or more users to a project
description: Add one or more users to a project from your instance.
parameters:
- name: projectId
in: path
description: The ID of the project.
required: true
schema:
type: string
requestBody:
description: Payload containing an array of one or more users to add to the project.
content:
application/json:
schema:
type: object
properties:
users:
type: array
description: A list of users and roles to add to the project.
items:
type: object
properties:
userId:
type: string
description: The unique identifier of the user.
example: '91765f0d-3b29-45df-adb9-35b23937eb92'
role:
type: string
description: The role assigned to the user in the project.
example: 'project:viewer'
required:
- userId
- role
required:
- users
responses:
'201':
description: Operation successful.
'401':
$ref: '../../../../shared/spec/responses/unauthorized.yml'
'403':
$ref: '../../../../shared/spec/responses/forbidden.yml'
'404':
$ref: '../../../../shared/spec/responses/notFound.yml'
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ delete:
summary: Delete a project
description: Delete a project from your instance.
parameters:
- $ref: '../schemas/parameters/projectId.yml'
- in: path
name: projectId
description: The ID of the project.
required: true
schema:
type: string
responses:
'204':
description: Operation successful.
Expand All @@ -20,9 +25,16 @@ put:
x-eov-operation-id: updateProject
x-eov-operation-handler: v1/handlers/projects/projects.handler
tags:
- Project
- Projects
summary: Update a project
description: Update a project.
parameters:
- in: path
name: projectId
description: The ID of the project.
required: true
schema:
type: string
Comment on lines +31 to +37
Copy link
Contributor Author

@MarcL MarcL Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing the projectId parameter when run through an OpenAPI schema validator.

requestBody:
description: Updated project object.
content:
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/public-api/v1/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ paths:
$ref: './handlers/projects/spec/paths/projects.yml'
/projects/{projectId}:
$ref: './handlers/projects/spec/paths/projects.projectId.yml'
/projects/{projectId}/users:
$ref: './handlers/projects/spec/paths/projects.projectId.users.yml'
/projects/{projectId}/users/{id}:
$ref: './handlers/projects/spec/paths/projects.projectId.users.id.yml'
components:
schemas:
$ref: './shared/spec/schemas/_index.yml'
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,12 @@ export declare namespace ProjectRequest {
{ name?: string; relations?: ProjectRelationPayload[] }
>;
type Delete = AuthenticatedRequest<{ projectId: string }, {}, {}, { transferId?: string }>;
type DeleteUser = AuthenticatedRequest<{ projectId: string; id: string }, {}, {}, {}>;
type AddUsers = AuthenticatedRequest<
{ projectId: string },
{},
{ users: ProjectRelationPayload[] }
>;
}

// ----------------------------------
Expand Down
Loading
Loading