Skip to content

Commit

Permalink
Merge pull request #155 from codex-team/edit-team-member-role
Browse files Browse the repository at this point in the history
Feat: route for editing team member role
  • Loading branch information
e11sy authored Jan 22, 2024
2 parents 3565d37 + 09246e5 commit 727b50f
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 4 deletions.
12 changes: 12 additions & 0 deletions src/domain/service/noteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,16 @@ export default class NoteSettingsService {
public async createTeamMember(team: TeamMemberCreationAttributes): Promise<TeamMember> {
return await this.teamRepository.createTeamMembership(team);
}

/**
* Patch team member role by user and note id
*
* @param id - userId of team member
* @param noteId - note internal id
* @param role - new team member role
* @returns returns 1 if the role has been changed and 0 otherwise
*/
public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise<MemberRole | null> {
return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role);
}
}
2 changes: 1 addition & 1 deletion src/presentation/http/router/join.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('Join API', () => {

expect(response?.json()).toStrictEqual({
result: {
id: 2,
id: 3,
userId,
noteId: 1,
role: 0,
Expand Down
71 changes: 71 additions & 0 deletions src/presentation/http/router/noteSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,75 @@ describe('NoteSettings API', () => {

test.todo('Return 403 when user authorized, but not member of the team');
});

describe('PATCH /note-settings/:notePublicId/team', () => {
test('Update team member role by user id and note id, with status code 200', async () => {
// patch member role of existing team member
const response = await global.api?.fakeRequest({
method: 'PATCH',
headers: {
authorization: `Bearer ${global.auth(1)}`,
},
url: '/note-settings/Pq1T9vc23Q/team',
body: {
userId: 1,
newRole: 1,
},
});

expect(response?.statusCode).toBe(200);

// check if we changed role correctly
const team = await global.api?.fakeRequest({
method: 'GET',
headers: {
authorization: `Bearer ${global.auth(1)}`,
},
url: '/note-settings/Pq1T9vc23Q/team',
});

expect(team?.json()).toStrictEqual([
{
'id': 2,
'noteId': 2,
'userId': 1,
'role': 1,
},
]);
});

test('Returns status code 200 and new role, if role was patched (if the user already had passing a role, then behavior is the same)', async () => {
const response = await global.api?.fakeRequest({
method: 'PATCH',
headers: {
authorization: `Bearer ${global.auth(1)}`,
},
url: '/note-settings/Pq1T9vc23Q/team',
body: {
userId: 1,
newRole: 1,
},
});

expect(response?.statusCode).toBe(200);
expect(response?.body).toBe('1');
});

test('Returns status code 404 and "User does not belong to Note\'s team" message if no such a note exists', async () => {
const response = await global.api?.fakeRequest({
method: 'PATCH',
headers: {
authorization: `Bearer ${global.auth(1)}`,
},
url: '/note-settings/73NdxFZ4k7/team',
body: {
userId: 15,
newRole: 1,
},
});

expect(response?.statusCode).toBe(404);
expect(response?.json().message).toBe('User does not belong to Note\'s team');
});
});
});
47 changes: 45 additions & 2 deletions src/presentation/http/router/noteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import useNoteResolver from '../middlewares/note/useNoteResolver.js';
import type NoteService from '@domain/service/note.js';
import useNoteSettingsResolver from '../middlewares/noteSettings/useNoteSettingsResolver.js';
import type { NotePublicId } from '@domain/entities/note.js';
import type { Team } from '@domain/entities/team.js';
import type { Team, MemberRole } from '@domain/entities/team.js';
import type User from '@domain/entities/user.js';

/**
* Interface for the note settings router.
Expand Down Expand Up @@ -89,6 +90,47 @@ const NoteSettingsRouter: FastifyPluginCallback<NoteSettingsRouterOptions> = (fa
return reply.send(noteSettings);
});

/**
* Patch team member role by user and note id
*
* @todo add policy for this route to check id user have 'write' role if this team to patch someone's else role
*/
fastify.patch<{
Params: {
notePublicId: NotePublicId,
},
Body: {
userId: User['id'],
newRole: MemberRole,
},
Reply: MemberRole,
}>('/:notePublicId/team', {
config: {
policy: [
'authRequired',
],
},
schema: {
params: {
notePublicId: {
$ref: 'NoteSchema#/properties/id',
},
},
},
preHandler: [
noteResolver,
],
}, async (request, reply) => {
const noteId = request.note?.id as number;
const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, noteId, request.body.newRole);

if (newRole === null) {
return reply.notFound('User does not belong to Note\'s team');
}

return reply.send(newRole);
});

/**
* Patch noteSettings by note id
*/
Expand Down Expand Up @@ -141,7 +183,8 @@ const NoteSettingsRouter: FastifyPluginCallback<NoteSettingsRouterOptions> = (fa

/**
* Get team by note id
* TODO add policy for this route (check if user is collaborator)
*
* @todo add policy for this route (check if user is collaborator)
*/
fastify.get<{
Params: {
Expand Down
23 changes: 23 additions & 0 deletions src/repository/storage/postgres/orm/sequelize/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MemberRole } from '@domain/entities/team.js';
import type User from '@domain/entities/user.js';
import type { NoteInternalId } from '@domain/entities/note.js';


/**
* Class representing a teams model in database
*/
Expand Down Expand Up @@ -220,4 +221,26 @@ export default class TeamsSequelizeStorage {

return affectedRows > 0;
}

/**
* Patch team member role by user and note id
*
* @param userId - id of team member
* @param noteId - note internal id
* @param role - new team member role
* @returns returns 1 if the role has been changed and 0 otherwise
*/
public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise<MemberRole | null> {
const affectedRows = await this.model.update({
role: role,
}, {
where: {
userId,
noteId,
},
});

// if counter of affected rows is more than 0, then we return new role
return affectedRows[0] ? role : null;
}
}
13 changes: 12 additions & 1 deletion src/repository/team.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type TeamStorage from '@repository/storage/team.storage.js';
import type { MemberRole, Team, TeamMember, TeamMemberCreationAttributes } from '@domain/entities/team.js';
import type { Team, TeamMember, TeamMemberCreationAttributes, MemberRole } from '@domain/entities/team.js';
import type { NoteInternalId } from '@domain/entities/note';
import type User from '@domain/entities/user';

Expand Down Expand Up @@ -71,4 +71,15 @@ export default class TeamRepository {
public async removeMemberById(id: TeamMember['id']): Promise<boolean> {
return await this.storage.removeTeamMemberById(id);
}
/**
* Patch team member role by user and note id
*
* @param id - id of team member
* @param noteId - note internal id
* @param role - team member new role
* @returns returns 1 if the role has been changed and 0 otherwise
*/
public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise<MemberRole | null> {
return await this.storage.patchMemberRoleById(id, noteId, role);
}
}
6 changes: 6 additions & 0 deletions src/tests/test-data/note-teams.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"note_id": 1,
"user_id": 2,
"role": 0
},
{
"id": 2,
"note_id": 2,
"user_id": 1,
"role": 0
}
]

0 comments on commit 727b50f

Please sign in to comment.