From 3195898c8928f7bbb3bf9c5e16a9634469cdc5c4 Mon Sep 17 00:00:00 2001 From: e11sy Date: Mon, 8 Jan 2024 23:07:22 +0300 Subject: [PATCH 01/19] [feat]: new route for editing team member role - added route for editing team member role - added test for this route - added test data for note-teams - added function to write test data into database --- src/domain/service/noteSettings.ts | 10 ++++++ .../http/router/noteSettings.test.ts | 19 +++++++++++ src/presentation/http/router/noteSettings.ts | 33 +++++++++++++++++-- .../storage/postgres/orm/sequelize/teams.ts | 20 +++++++++++ src/repository/team.repository.ts | 9 +++++ src/tests/test-data/note-teams.json | 8 +++++ src/tests/utils/insert-data.ts | 13 ++++++++ 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/tests/test-data/note-teams.json diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 7ca79c75..f87ab6da 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -104,4 +104,14 @@ export default class NoteSettingsService { public async createTeamMember(team: TeamMemberCreationAttributes): Promise { return await this.teamRepository.create(team); } + + /** + * + * @param id - id of team member + * @param noteId - note internal id + * @param role - new team member role + */ + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); + } } diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index b09df15e..ba44e8d4 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -281,4 +281,23 @@ describe('NoteSettings API', () => { test.todo('Return 403 when user authorized, but not member of the team'); }); + + describe('PATCH /note-teams/:NotePublicId,userId', () => { + test('returns something', async () => { + const response = await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/new-role/write', + body: { + 'userId': 3, + 'noteId': 2, + }, + }); + + expect(response?.statusCode).toBe(200); + expect(response?.body).toBe('write'); + }); + }); }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 404895fb..efb2410c 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -5,8 +5,9 @@ import { isEmpty } from '@infrastructure/utils/empty.js'; 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 { NoteInternalId, NotePublicId } from '@domain/entities/note.js'; +import type { Team, TeamMember } from '@domain/entities/team.js'; +import type User from '@domain/entities/user.js'; /** * Interface for the note settings router. @@ -89,6 +90,34 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa return reply.send(noteSettings); }); + /** + * patch team member role by user and note id + */ + fastify.patch<{ + Body: { + userId: User['id']; + noteId: NoteInternalId, + } + Params: { + newRole: TeamMember['role'], + }, + Reply: TeamMember['role'], + }>('/new-role/:newRole', { + config: { + policy: [ + 'authRequired', + ], + }, + }, async (request, reply) => { + const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, request.body.noteId, request.params.newRole); + + if (newRole === null) { + return reply.notFound('user in team not found'); + } + + return reply.send(newRole); + }); + /** * Patch noteSettings by note id */ diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index d6df080a..2e421f08 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -198,4 +198,24 @@ export default class TeamsSequelizeStorage { return affectedRows > 0; } + + /** + * + * @param userId - id of team member + * @param noteId - note internal id + * @param role - new team member role + */ + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + 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; + } } diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index d486d0d9..67f895d0 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -60,4 +60,13 @@ export default class TeamRepository { public async removeMemberById(id: TeamMember['id']): Promise { return await this.storage.removeTeamMemberById(id); } + /** + * + * @param id - id of team member + * @param noteId - note internal id + * @param role - team member new role + */ + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + return await this.storage.patchMemberRoleById(id, noteId, role); + } } diff --git a/src/tests/test-data/note-teams.json b/src/tests/test-data/note-teams.json new file mode 100644 index 00000000..34163c88 --- /dev/null +++ b/src/tests/test-data/note-teams.json @@ -0,0 +1,8 @@ +[ + { + "id": 2, + "note_id": 2, + "user_id": 3, + "role": "read" + } + ] \ No newline at end of file diff --git a/src/tests/utils/insert-data.ts b/src/tests/utils/insert-data.ts index 4b819805..a8b42a88 100644 --- a/src/tests/utils/insert-data.ts +++ b/src/tests/utils/insert-data.ts @@ -3,6 +3,7 @@ import users from '../test-data/users.json'; import userSessions from '../test-data/user-sessions.json'; import notes from '../test-data/notes.json'; import noteSettings from '../test-data/notes-settings.json'; +import noteTeams from '../test-data/note-teams.json'; /** @@ -48,6 +49,17 @@ async function insertNoteSettings(db: SequelizeOrm): Promise { } } +/** + * Fills in the database with note teams data + * + * @param db - SequelizeOrm instance + */ +async function insertNoteTeams(db: SequelizeOrm): Promise { + for (const noteTeam of noteTeams) { + await db.connection.query(`INSERT INTO public.note_teams ("note_id", "user_id", "role") VALUES (${noteTeam.note_id}, ${noteTeam.user_id}, '${noteTeam.role}')`); + } +} + /** * Fills in the database with test data @@ -59,5 +71,6 @@ export async function insertData(db: SequelizeOrm): Promise { await insertUserSessions(db); await insertNotes(db); await insertNoteSettings(db); + await insertNoteTeams(db); } From cb01adb83e7bd402287d61161b6133b95e18c830 Mon Sep 17 00:00:00 2001 From: e11sy Date: Tue, 9 Jan 2024 08:16:06 +0300 Subject: [PATCH 02/19] new tests and MemberRole changes - new tests added - enum with no strings --- src/domain/entities/team.ts | 8 +-- .../http/router/noteSettings.test.ts | 54 ++++++++++++++++++- src/presentation/http/router/noteSettings.ts | 2 +- .../storage/postgres/orm/sequelize/teams.ts | 6 +-- src/tests/test-data/note-teams.json | 2 +- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/domain/entities/team.ts b/src/domain/entities/team.ts index 6cf50b31..afe00a27 100644 --- a/src/domain/entities/team.ts +++ b/src/domain/entities/team.ts @@ -3,14 +3,14 @@ import type User from './user.js'; export enum MemberRole { /** - * Team member can read and write notes + * Team member can only read notes */ - read = 'read', + read = 0, /** - * Team member can only read notes + * Team member can read and write notes */ - write = 'write', + write = 1, } /** diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index ba44e8d4..757b0711 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -282,8 +282,41 @@ describe('NoteSettings API', () => { test.todo('Return 403 when user authorized, but not member of the team'); }); - describe('PATCH /note-teams/:NotePublicId,userId', () => { - test('returns something', async () => { + describe('PATCH /note-teams/new-role/:newRole', () => { + test('if we want write role, in database it will be stored as 1', async () => { + await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/new-role/write', + body: { + 'userId': 3, + 'noteId': 2, + }, + }); + + const team = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/Pq1T9vc23Q/team', + }); + + if (team?.json() !== undefined) { + expect(team?.json()).toStrictEqual([ + { + 'id': 1, + 'noteId': 2, + 'userId': 3, + 'role': 1, + }, + ]); + } + }); + + test('returns status code 200 and new role if it was patched', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { @@ -299,5 +332,22 @@ describe('NoteSettings API', () => { expect(response?.statusCode).toBe(200); expect(response?.body).toBe('write'); }); + + test('returns 404 and User in team not found 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/new-role/write', + body: { + 'userId': 15, + 'noteId': 0, + }, + }); + + expect(response?.statusCode).toBe(404); + expect(response?.json().message).toBe('User in team not found'); + }); }); }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index efb2410c..5c55f294 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -112,7 +112,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, request.body.noteId, request.params.newRole); if (newRole === null) { - return reply.notFound('user in team not found'); + return reply.notFound('User in team not found'); } return reply.send(newRole); diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index 2e421f08..ba8f0712 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -96,7 +96,7 @@ export default class TeamsSequelizeStorage { }, }, role: { - type: DataTypes.STRING, + type: DataTypes.INTEGER, allowNull: false, defaultValue: MemberRole.read, }, @@ -205,9 +205,9 @@ export default class TeamsSequelizeStorage { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise { const affectedRows = await this.model.update({ - role: role, + role: MemberRole[role], }, { where: { userId, diff --git a/src/tests/test-data/note-teams.json b/src/tests/test-data/note-teams.json index 34163c88..8569421d 100644 --- a/src/tests/test-data/note-teams.json +++ b/src/tests/test-data/note-teams.json @@ -3,6 +3,6 @@ "id": 2, "note_id": 2, "user_id": 3, - "role": "read" + "role": 0 } ] \ No newline at end of file From 7e8722d873d10b6cdc8bc364d4e015197f72e03e Mon Sep 17 00:00:00 2001 From: e11sy Date: Tue, 9 Jan 2024 08:21:26 +0300 Subject: [PATCH 03/19] id in test data changed - changed id in test data --- src/tests/test-data/note-teams.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test-data/note-teams.json b/src/tests/test-data/note-teams.json index 8569421d..6fcda469 100644 --- a/src/tests/test-data/note-teams.json +++ b/src/tests/test-data/note-teams.json @@ -1,6 +1,6 @@ [ { - "id": 2, + "id": 1, "note_id": 2, "user_id": 3, "role": 0 From ad9fecb4ef7ce8906144896419afd7236e36774d Mon Sep 17 00:00:00 2001 From: e11sy Date: Mon, 8 Jan 2024 23:07:22 +0300 Subject: [PATCH 04/19] [feat]: new route for editing team member role - added route for editing team member role - added test for this route - added test data for note-teams - added function to write test data into database --- src/domain/service/noteSettings.ts | 10 ++++++ .../http/router/noteSettings.test.ts | 19 +++++++++++ src/presentation/http/router/noteSettings.ts | 33 +++++++++++++++++-- .../storage/postgres/orm/sequelize/teams.ts | 20 +++++++++++ src/repository/team.repository.ts | 9 +++++ src/tests/utils/insert-data.ts | 11 +++++++ 6 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 18a3c32d..bf679ad8 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -139,4 +139,14 @@ export default class NoteSettingsService { public async createTeamMember(team: TeamMemberCreationAttributes): Promise { return await this.teamRepository.createTeamMembership(team); } + + /** + * + * @param id - id of team member + * @param noteId - note internal id + * @param role - new team member role + */ + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); + } } diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index b09df15e..ba44e8d4 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -281,4 +281,23 @@ describe('NoteSettings API', () => { test.todo('Return 403 when user authorized, but not member of the team'); }); + + describe('PATCH /note-teams/:NotePublicId,userId', () => { + test('returns something', async () => { + const response = await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/new-role/write', + body: { + 'userId': 3, + 'noteId': 2, + }, + }); + + expect(response?.statusCode).toBe(200); + expect(response?.body).toBe('write'); + }); + }); }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 404895fb..efb2410c 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -5,8 +5,9 @@ import { isEmpty } from '@infrastructure/utils/empty.js'; 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 { NoteInternalId, NotePublicId } from '@domain/entities/note.js'; +import type { Team, TeamMember } from '@domain/entities/team.js'; +import type User from '@domain/entities/user.js'; /** * Interface for the note settings router. @@ -89,6 +90,34 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa return reply.send(noteSettings); }); + /** + * patch team member role by user and note id + */ + fastify.patch<{ + Body: { + userId: User['id']; + noteId: NoteInternalId, + } + Params: { + newRole: TeamMember['role'], + }, + Reply: TeamMember['role'], + }>('/new-role/:newRole', { + config: { + policy: [ + 'authRequired', + ], + }, + }, async (request, reply) => { + const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, request.body.noteId, request.params.newRole); + + if (newRole === null) { + return reply.notFound('user in team not found'); + } + + return reply.send(newRole); + }); + /** * Patch noteSettings by note id */ diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index c263b14e..f472d4a3 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -220,4 +220,24 @@ export default class TeamsSequelizeStorage { return affectedRows > 0; } + + /** + * + * @param userId - id of team member + * @param noteId - note internal id + * @param role - new team member role + */ + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + 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; + } } diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index da2fd21b..19fe5cbe 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -71,4 +71,13 @@ export default class TeamRepository { public async removeMemberById(id: TeamMember['id']): Promise { return await this.storage.removeTeamMemberById(id); } + /** + * + * @param id - id of team member + * @param noteId - note internal id + * @param role - team member new role + */ + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + return await this.storage.patchMemberRoleById(id, noteId, role); + } } diff --git a/src/tests/utils/insert-data.ts b/src/tests/utils/insert-data.ts index 39c2e2ab..f544685f 100644 --- a/src/tests/utils/insert-data.ts +++ b/src/tests/utils/insert-data.ts @@ -60,6 +60,17 @@ async function insertNoteSettings(db: SequelizeOrm): Promise { } } +/** + * Fills in the database with note teams data + * + * @param db - SequelizeOrm instance + */ +async function insertNoteTeams(db: SequelizeOrm): Promise { + for (const noteTeam of noteTeams) { + await db.connection.query(`INSERT INTO public.note_teams ("note_id", "user_id", "role") VALUES (${noteTeam.note_id}, ${noteTeam.user_id}, '${noteTeam.role}')`); + } +} + /** * Fills in the database with test data From fbd443e43cf1cfe8b02c59186e26fb79a2ceae21 Mon Sep 17 00:00:00 2001 From: e11sy Date: Tue, 9 Jan 2024 08:16:06 +0300 Subject: [PATCH 05/19] new tests and MemberRole changes - new tests added - enum with no strings --- .../http/router/noteSettings.test.ts | 54 ++++++++++++++++++- src/presentation/http/router/noteSettings.ts | 2 +- .../storage/postgres/orm/sequelize/teams.ts | 4 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index ba44e8d4..757b0711 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -282,8 +282,41 @@ describe('NoteSettings API', () => { test.todo('Return 403 when user authorized, but not member of the team'); }); - describe('PATCH /note-teams/:NotePublicId,userId', () => { - test('returns something', async () => { + describe('PATCH /note-teams/new-role/:newRole', () => { + test('if we want write role, in database it will be stored as 1', async () => { + await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/new-role/write', + body: { + 'userId': 3, + 'noteId': 2, + }, + }); + + const team = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${global.auth(1)}`, + }, + url: '/note-settings/Pq1T9vc23Q/team', + }); + + if (team?.json() !== undefined) { + expect(team?.json()).toStrictEqual([ + { + 'id': 1, + 'noteId': 2, + 'userId': 3, + 'role': 1, + }, + ]); + } + }); + + test('returns status code 200 and new role if it was patched', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { @@ -299,5 +332,22 @@ describe('NoteSettings API', () => { expect(response?.statusCode).toBe(200); expect(response?.body).toBe('write'); }); + + test('returns 404 and User in team not found 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/new-role/write', + body: { + 'userId': 15, + 'noteId': 0, + }, + }); + + expect(response?.statusCode).toBe(404); + expect(response?.json().message).toBe('User in team not found'); + }); }); }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index efb2410c..5c55f294 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -112,7 +112,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, request.body.noteId, request.params.newRole); if (newRole === null) { - return reply.notFound('user in team not found'); + return reply.notFound('User in team not found'); } return reply.send(newRole); diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index f472d4a3..f70e4db7 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -227,9 +227,9 @@ export default class TeamsSequelizeStorage { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise { const affectedRows = await this.model.update({ - role: role, + role: MemberRole[role], }, { where: { userId, From 17d311d2d778e06058a8182f6f12ed840acd8ece Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 11 Jan 2024 22:41:49 +0300 Subject: [PATCH 06/19] type error fixed - type of role changed to typeof keyof MemberRole - fixed some test data --- src/domain/service/noteSettings.ts | 12 +----------- src/presentation/http/router/join.test.ts | 2 +- src/presentation/http/router/noteSettings.test.ts | 10 +++++----- src/presentation/http/router/noteSettings.ts | 6 +++--- .../storage/postgres/orm/sequelize/teams.ts | 5 ++++- src/repository/team.repository.ts | 2 +- src/tests/test-data/note-teams.json | 6 ++++++ src/tests/utils/insert-data.ts | 11 ----------- 8 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index a8876356..7a1f6d74 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -146,17 +146,7 @@ export default class NoteSettingsService { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { - return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); - } - - /** - * - * @param id - id of team member - * @param noteId - note internal id - * @param role - new team member role - */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : keyof typeof MemberRole): Promise { return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); } } diff --git a/src/presentation/http/router/join.test.ts b/src/presentation/http/router/join.test.ts index a086ef00..3809f44e 100644 --- a/src/presentation/http/router/join.test.ts +++ b/src/presentation/http/router/join.test.ts @@ -58,7 +58,7 @@ describe('Join API', () => { expect(response?.json()).toStrictEqual({ result: { - id: 2, + id: 3, userId, noteId: 1, role: 0, diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index 757b0711..0277de7f 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -291,7 +291,7 @@ describe('NoteSettings API', () => { }, url: '/note-settings/new-role/write', body: { - 'userId': 3, + 'userId': 1, 'noteId': 2, }, }); @@ -307,9 +307,9 @@ describe('NoteSettings API', () => { if (team?.json() !== undefined) { expect(team?.json()).toStrictEqual([ { - 'id': 1, + 'id': 2, 'noteId': 2, - 'userId': 3, + 'userId': 1, 'role': 1, }, ]); @@ -324,7 +324,7 @@ describe('NoteSettings API', () => { }, url: '/note-settings/new-role/write', body: { - 'userId': 3, + 'userId': 1, 'noteId': 2, }, }); @@ -333,7 +333,7 @@ describe('NoteSettings API', () => { expect(response?.body).toBe('write'); }); - test('returns 404 and User in team not found message if no such a note exists', async () => { + test('returns 404 and User in team is not found message if no such a note exists', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 5c55f294..a15d0121 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -6,7 +6,7 @@ import useNoteResolver from '../middlewares/note/useNoteResolver.js'; import type NoteService from '@domain/service/note.js'; import useNoteSettingsResolver from '../middlewares/noteSettings/useNoteSettingsResolver.js'; import type { NoteInternalId, NotePublicId } from '@domain/entities/note.js'; -import type { Team, TeamMember } from '@domain/entities/team.js'; +import type { Team, MemberRole } from '@domain/entities/team.js';; import type User from '@domain/entities/user.js'; /** @@ -99,9 +99,9 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa noteId: NoteInternalId, } Params: { - newRole: TeamMember['role'], + newRole: keyof typeof MemberRole, }, - Reply: TeamMember['role'], + Reply: keyof typeof MemberRole, }>('/new-role/:newRole', { config: { policy: [ diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index f70e4db7..d04559d9 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -227,7 +227,10 @@ export default class TeamsSequelizeStorage { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise { + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: keyof typeof MemberRole): Promise { + console.log('======================'); + console.log(typeof role); + console.log('======================'); const affectedRows = await this.model.update({ role: MemberRole[role], }, { diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index 19fe5cbe..d3908713 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -77,7 +77,7 @@ export default class TeamRepository { * @param noteId - note internal id * @param role - team member new role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : keyof typeof MemberRole): Promise { return await this.storage.patchMemberRoleById(id, noteId, role); } } diff --git a/src/tests/test-data/note-teams.json b/src/tests/test-data/note-teams.json index 2ffb4a63..b4d3c257 100644 --- a/src/tests/test-data/note-teams.json +++ b/src/tests/test-data/note-teams.json @@ -4,5 +4,11 @@ "note_id": 1, "user_id": 2, "role": 0 + }, + { + "id": 2, + "note_id": 2, + "user_id": 1, + "role": 0 } ] diff --git a/src/tests/utils/insert-data.ts b/src/tests/utils/insert-data.ts index f544685f..39c2e2ab 100644 --- a/src/tests/utils/insert-data.ts +++ b/src/tests/utils/insert-data.ts @@ -60,17 +60,6 @@ async function insertNoteSettings(db: SequelizeOrm): Promise { } } -/** - * Fills in the database with note teams data - * - * @param db - SequelizeOrm instance - */ -async function insertNoteTeams(db: SequelizeOrm): Promise { - for (const noteTeam of noteTeams) { - await db.connection.query(`INSERT INTO public.note_teams ("note_id", "user_id", "role") VALUES (${noteTeam.note_id}, ${noteTeam.user_id}, '${noteTeam.role}')`); - } -} - /** * Fills in the database with test data From 28b599989f26d9bffc3b9139f4ec40545564361e Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 11 Jan 2024 22:48:41 +0300 Subject: [PATCH 07/19] temp output deleted - deleted unwanted output --- src/repository/storage/postgres/orm/sequelize/teams.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index d04559d9..6d18e86f 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -228,9 +228,6 @@ export default class TeamsSequelizeStorage { * @param role - new team member role */ public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: keyof typeof MemberRole): Promise { - console.log('======================'); - console.log(typeof role); - console.log('======================'); const affectedRows = await this.model.update({ role: MemberRole[role], }, { From 3ab786c8382339a8330f770ca412dad4b81fcbff Mon Sep 17 00:00:00 2001 From: e11sy Date: Fri, 12 Jan 2024 17:06:12 +0300 Subject: [PATCH 08/19] test description changed - changed tests description - deleted unwanted semicolon --- src/presentation/http/router/noteSettings.test.ts | 6 +++--- src/presentation/http/router/noteSettings.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index 0277de7f..d19b372a 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -283,7 +283,7 @@ describe('NoteSettings API', () => { }); describe('PATCH /note-teams/new-role/:newRole', () => { - test('if we want write role, in database it will be stored as 1', async () => { + test('If we want to change role to "write", in database it will be stored as number : 1', async () => { await global.api?.fakeRequest({ method: 'PATCH', headers: { @@ -316,7 +316,7 @@ describe('NoteSettings API', () => { } }); - test('returns status code 200 and new role if it was patched', async () => { + test('Returns status code 200 and new role if it was patched', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { @@ -333,7 +333,7 @@ describe('NoteSettings API', () => { expect(response?.body).toBe('write'); }); - test('returns 404 and User in team is not found message if no such a note exists', async () => { + test('Returns status code 404 and "User in team is not found" message if no such a note exists', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index a15d0121..01b1b472 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -6,7 +6,7 @@ import useNoteResolver from '../middlewares/note/useNoteResolver.js'; import type NoteService from '@domain/service/note.js'; import useNoteSettingsResolver from '../middlewares/noteSettings/useNoteSettingsResolver.js'; import type { NoteInternalId, NotePublicId } from '@domain/entities/note.js'; -import type { Team, MemberRole } from '@domain/entities/team.js';; +import type { Team, MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; /** From 2dcfc5ba69a0a8c927209c9451ef94e6b7464d71 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sun, 14 Jan 2024 12:44:29 +0300 Subject: [PATCH 09/19] changed parameters passing in request - parameters now pass in Params, not in body - now we dont use noteInternalId - added schema to validate notePublicId --- .../http/router/noteSettings.test.ts | 24 +++++------------- src/presentation/http/router/noteSettings.ts | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index d19b372a..14c7ba5f 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -283,17 +283,13 @@ describe('NoteSettings API', () => { }); describe('PATCH /note-teams/new-role/:newRole', () => { - test('If we want to change role to "write", in database it will be stored as number : 1', async () => { + test('Update team member role by user id and note id', async () => { await global.api?.fakeRequest({ method: 'PATCH', headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/write', - body: { - 'userId': 1, - 'noteId': 2, - }, + url: '/note-settings/new-role/Pq1T9vc23Q/1/write', }); const team = await global.api?.fakeRequest({ @@ -316,17 +312,13 @@ describe('NoteSettings API', () => { } }); - test('Returns status code 200 and new role if it was patched', async () => { + test('Returns status code 200 and new role if it was patched even if user already has a passing role', async () => { const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/write', - body: { - 'userId': 1, - 'noteId': 2, - }, + url: '/note-settings/new-role/Pq1T9vc23Q/1/write', }); expect(response?.statusCode).toBe(200); @@ -339,15 +331,11 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/write', - body: { - 'userId': 15, - 'noteId': 0, - }, + url: '/note-settings/new-role/73NdxFZ4k7/15/write', }); expect(response?.statusCode).toBe(404); - expect(response?.json().message).toBe('User in team not found'); + expect(response?.json().message).toBe('User does not belong to Note\'s team'); }); }); }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 01b1b472..f15c4b59 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -5,7 +5,7 @@ import { isEmpty } from '@infrastructure/utils/empty.js'; import useNoteResolver from '../middlewares/note/useNoteResolver.js'; import type NoteService from '@domain/service/note.js'; import useNoteSettingsResolver from '../middlewares/noteSettings/useNoteSettingsResolver.js'; -import type { NoteInternalId, NotePublicId } from '@domain/entities/note.js'; +import type { NotePublicId } from '@domain/entities/note.js'; import type { Team, MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; @@ -94,25 +94,34 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa * patch team member role by user and note id */ fastify.patch<{ - Body: { - userId: User['id']; - noteId: NoteInternalId, - } Params: { + notePublicId: NotePublicId, + userId: User['id']; newRole: keyof typeof MemberRole, }, Reply: keyof typeof MemberRole, - }>('/new-role/:newRole', { + }>('/new-role/:notePublicId/:userId/:newRole', { config: { policy: [ 'authRequired', ], }, + schema: { + params: { + notePublicId: { + $ref: 'NoteSchema#/properties/id', + }, + }, + }, + preHandler: [ + noteResolver, + ], }, async (request, reply) => { - const newRole = await noteSettingsService.patchMemberRoleByUserId(request.body.userId, request.body.noteId, request.params.newRole); + const noteId = request.note?.id as number; + const newRole = await noteSettingsService.patchMemberRoleByUserId(request.params.userId, noteId, request.params.newRole); if (newRole === null) { - return reply.notFound('User in team not found'); + return reply.notFound('User does not belong to Note\'s team '); } return reply.send(newRole); From 14f1e93ff85d55b5bb7cb6b500cc786e44a682e8 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sun, 14 Jan 2024 12:49:04 +0300 Subject: [PATCH 10/19] added todo - added todo for new policy for this route - deleted unwanted spaces in output --- src/presentation/http/router/noteSettings.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index f15c4b59..4611c0e7 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -92,6 +92,8 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa /** * 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: { @@ -121,7 +123,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa const newRole = await noteSettingsService.patchMemberRoleByUserId(request.params.userId, noteId, request.params.newRole); if (newRole === null) { - return reply.notFound('User does not belong to Note\'s team '); + return reply.notFound('User does not belong to Note\'s team'); } return reply.send(newRole); From cb1e631091ff6c563c800d2a1c0387a2ac51d487 Mon Sep 17 00:00:00 2001 From: e11sy Date: Mon, 15 Jan 2024 15:06:36 +0300 Subject: [PATCH 11/19] new MemberRoleKeys type - keyof typeof MemberRole now is MemberRoleKeys - comments changed --- src/domain/entities/team.ts | 2 ++ src/domain/service/noteSettings.ts | 6 ++-- .../http/router/noteSettings.test.ts | 28 +++++++++---------- src/presentation/http/router/noteSettings.ts | 3 +- .../storage/postgres/orm/sequelize/teams.ts | 13 ++++++--- src/repository/team.repository.ts | 6 ++-- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/domain/entities/team.ts b/src/domain/entities/team.ts index 47f9ac76..7407ce22 100644 --- a/src/domain/entities/team.ts +++ b/src/domain/entities/team.ts @@ -1,6 +1,8 @@ import type { NoteInternalId } from './note.js'; import type User from './user.js'; +export type MemberRoleKeys = 'read' | 'write'; + export enum MemberRole { /** * Team member can only read notes diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 7a1f6d74..55b2e2d1 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -3,7 +3,7 @@ import type { InvitationHash } from '@domain/entities/noteSettings.js'; import type NoteSettings from '@domain/entities/noteSettings.js'; import type NoteSettingsRepository from '@repository/noteSettings.repository.js'; import type TeamRepository from '@repository/team.repository.js'; -import type { Team, TeamMember, TeamMemberCreationAttributes } from '@domain/entities/team.js'; +import type { Team, TeamMember, TeamMemberCreationAttributes, MemberRoleKeys } from '@domain/entities/team.js'; import { MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; import { createInvitationHash } from '@infrastructure/utils/invitationHash.js'; @@ -107,7 +107,7 @@ export default class NoteSettingsService { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { return await this.teamRepository.getUserRoleByUserIdAndNoteId(userId, noteId); } @@ -146,7 +146,7 @@ export default class NoteSettingsService { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : keyof typeof MemberRole): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRoleKeys): Promise { return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); } } diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index 14c7ba5f..ba44ed05 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -283,8 +283,8 @@ describe('NoteSettings API', () => { }); describe('PATCH /note-teams/new-role/:newRole', () => { - test('Update team member role by user id and note id', async () => { - await global.api?.fakeRequest({ + test('Update team member role by user id and note id, with status code 200', async () => { + const response = await global.api?.fakeRequest({ method: 'PATCH', headers: { authorization: `Bearer ${global.auth(1)}`, @@ -292,6 +292,8 @@ describe('NoteSettings API', () => { url: '/note-settings/new-role/Pq1T9vc23Q/1/write', }); + expect(response?.statusCode).toBe(200); + const team = await global.api?.fakeRequest({ method: 'GET', headers: { @@ -300,19 +302,17 @@ describe('NoteSettings API', () => { url: '/note-settings/Pq1T9vc23Q/team', }); - if (team?.json() !== undefined) { - expect(team?.json()).toStrictEqual([ - { - 'id': 2, - 'noteId': 2, - 'userId': 1, - 'role': 1, - }, - ]); - } + expect(team?.json()).toStrictEqual([ + { + 'id': 2, + 'noteId': 2, + 'userId': 1, + 'role': 1, + }, + ]); }); - test('Returns status code 200 and new role if it was patched even if user already has a passing role', async () => { + 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: { @@ -325,7 +325,7 @@ describe('NoteSettings API', () => { expect(response?.body).toBe('write'); }); - test('Returns status code 404 and "User in team is not found" message if no such a note exists', async () => { + 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: { diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 4611c0e7..7056c9e8 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -181,7 +181,8 @@ const NoteSettingsRouter: FastifyPluginCallback = (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: { diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index 6d18e86f..3571a5f0 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -2,12 +2,13 @@ import type { Sequelize, InferAttributes, InferCreationAttributes, CreationOptio import { Model, DataTypes } from 'sequelize'; import type Orm from '@repository/storage/postgres/orm/sequelize/index.js'; import { NoteModel } from '@repository/storage/postgres/orm/sequelize/note.js'; -import type { Team, TeamMemberCreationAttributes, TeamMember } from '@domain/entities/team.js'; +import type { Team, TeamMemberCreationAttributes, TeamMember, MemberRoleKeys } from '@domain/entities/team.js'; import { UserModel } from './user.js'; 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 */ @@ -181,7 +182,7 @@ export default class TeamsSequelizeStorage { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { const res = await this.model.findOne({ where: { userId, @@ -189,7 +190,11 @@ export default class TeamsSequelizeStorage { }, }); - return res?.role ?? null; + if (res?.role !== null) { + return MemberRole[res?.role as number] as MemberRoleKeys; + } + + return null; } /** @@ -227,7 +232,7 @@ export default class TeamsSequelizeStorage { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: keyof typeof MemberRole): Promise { + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRoleKeys): Promise { const affectedRows = await this.model.update({ role: MemberRole[role], }, { diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index d3908713..af0a9bab 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -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 { MemberRoleKeys, Team, TeamMember, TeamMemberCreationAttributes } from '@domain/entities/team.js'; import type { NoteInternalId } from '@domain/entities/note'; import type User from '@domain/entities/user'; @@ -49,7 +49,7 @@ export default class TeamRepository { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { return await this.storage.getUserRoleByUserIdAndNoteId(userId, noteId); } @@ -77,7 +77,7 @@ export default class TeamRepository { * @param noteId - note internal id * @param role - team member new role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : keyof typeof MemberRole): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRoleKeys): Promise { return await this.storage.patchMemberRoleById(id, noteId, role); } } From 0f1e825b175d5c9ecb6193a530e85cbe38103fd4 Mon Sep 17 00:00:00 2001 From: e11sy Date: Mon, 15 Jan 2024 15:11:23 +0300 Subject: [PATCH 12/19] comment changes - todo changed --- src/presentation/http/router/noteSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 7056c9e8..cdc863f9 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -93,7 +93,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa /** * 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 + * @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: { From ae0ee5acd6652a7088112c4fc450a5ba25b0084c Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 18 Jan 2024 19:19:46 +0300 Subject: [PATCH 13/19] comments added added new comments for note teams test --- src/domain/service/noteSettings.ts | 2 +- src/presentation/http/router/noteSettings.test.ts | 2 ++ src/presentation/http/router/noteSettings.ts | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 55b2e2d1..2152eaee 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -146,7 +146,7 @@ export default class NoteSettingsService { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRoleKeys): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role: MemberRoleKeys): Promise { return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); } } diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index ba44ed05..2d9ee258 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -284,6 +284,7 @@ describe('NoteSettings API', () => { describe('PATCH /note-teams/new-role/:newRole', () => { 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: { @@ -294,6 +295,7 @@ describe('NoteSettings API', () => { expect(response?.statusCode).toBe(200); + // check if we changed role correctly const team = await global.api?.fakeRequest({ method: 'GET', headers: { diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index cdc863f9..011d830b 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -6,7 +6,7 @@ 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, MemberRole } from '@domain/entities/team.js'; +import type { Team, MemberRoleKeys } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; /** @@ -91,7 +91,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa }); /** - * patch team member role by user and note id + * 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 */ @@ -99,9 +99,9 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa Params: { notePublicId: NotePublicId, userId: User['id']; - newRole: keyof typeof MemberRole, + newRole: MemberRoleKeys, }, - Reply: keyof typeof MemberRole, + Reply: MemberRoleKeys, }>('/new-role/:notePublicId/:userId/:newRole', { config: { policy: [ From d2ac011b4f07d5e5941de5c3454177767da2442b Mon Sep 17 00:00:00 2001 From: e11sy Date: Fri, 19 Jan 2024 21:12:19 +0300 Subject: [PATCH 14/19] deleted memberRoleKeys - switched back to memberRole because newRole passing is 0 | 1 --- src/domain/entities/team.ts | 2 -- src/domain/service/noteSettings.ts | 6 +++--- src/presentation/http/router/noteSettings.test.ts | 8 ++++---- src/presentation/http/router/noteSettings.ts | 6 +++--- .../storage/postgres/orm/sequelize/teams.ts | 14 +++++--------- src/repository/team.repository.ts | 6 +++--- 6 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/domain/entities/team.ts b/src/domain/entities/team.ts index 7407ce22..47f9ac76 100644 --- a/src/domain/entities/team.ts +++ b/src/domain/entities/team.ts @@ -1,8 +1,6 @@ import type { NoteInternalId } from './note.js'; import type User from './user.js'; -export type MemberRoleKeys = 'read' | 'write'; - export enum MemberRole { /** * Team member can only read notes diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 2152eaee..fb9d7dc9 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -3,7 +3,7 @@ import type { InvitationHash } from '@domain/entities/noteSettings.js'; import type NoteSettings from '@domain/entities/noteSettings.js'; import type NoteSettingsRepository from '@repository/noteSettings.repository.js'; import type TeamRepository from '@repository/team.repository.js'; -import type { Team, TeamMember, TeamMemberCreationAttributes, MemberRoleKeys } from '@domain/entities/team.js'; +import type { Team, TeamMember, TeamMemberCreationAttributes } from '@domain/entities/team.js'; import { MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; import { createInvitationHash } from '@infrastructure/utils/invitationHash.js'; @@ -107,7 +107,7 @@ export default class NoteSettingsService { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { return await this.teamRepository.getUserRoleByUserIdAndNoteId(userId, noteId); } @@ -146,7 +146,7 @@ export default class NoteSettingsService { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role: MemberRoleKeys): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise { return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); } } diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index 2d9ee258..fbe24973 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -290,7 +290,7 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/Pq1T9vc23Q/1/write', + url: '/note-settings/new-role/Pq1T9vc23Q/1/1', }); expect(response?.statusCode).toBe(200); @@ -320,11 +320,11 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/Pq1T9vc23Q/1/write', + url: '/note-settings/new-role/Pq1T9vc23Q/1/1', }); expect(response?.statusCode).toBe(200); - expect(response?.body).toBe('write'); + 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 () => { @@ -333,7 +333,7 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/73NdxFZ4k7/15/write', + url: '/note-settings/new-role/73NdxFZ4k7/15/1', }); expect(response?.statusCode).toBe(404); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 011d830b..2b87d038 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -6,7 +6,7 @@ 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, MemberRoleKeys } from '@domain/entities/team.js'; +import type { Team, MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; /** @@ -99,9 +99,9 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa Params: { notePublicId: NotePublicId, userId: User['id']; - newRole: MemberRoleKeys, + newRole: MemberRole, }, - Reply: MemberRoleKeys, + Reply: MemberRole, }>('/new-role/:notePublicId/:userId/:newRole', { config: { policy: [ diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index 3571a5f0..045dbcb1 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -2,7 +2,7 @@ import type { Sequelize, InferAttributes, InferCreationAttributes, CreationOptio import { Model, DataTypes } from 'sequelize'; import type Orm from '@repository/storage/postgres/orm/sequelize/index.js'; import { NoteModel } from '@repository/storage/postgres/orm/sequelize/note.js'; -import type { Team, TeamMemberCreationAttributes, TeamMember, MemberRoleKeys } from '@domain/entities/team.js'; +import type { Team, TeamMemberCreationAttributes, TeamMember } from '@domain/entities/team.js'; import { UserModel } from './user.js'; import { MemberRole } from '@domain/entities/team.js'; import type User from '@domain/entities/user.js'; @@ -182,7 +182,7 @@ export default class TeamsSequelizeStorage { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { const res = await this.model.findOne({ where: { userId, @@ -190,11 +190,7 @@ export default class TeamsSequelizeStorage { }, }); - if (res?.role !== null) { - return MemberRole[res?.role as number] as MemberRoleKeys; - } - - return null; + return res?.role ?? null; } /** @@ -232,9 +228,9 @@ export default class TeamsSequelizeStorage { * @param noteId - note internal id * @param role - new team member role */ - public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRoleKeys): Promise { + public async patchMemberRoleById(userId: TeamMember['id'], noteId: NoteInternalId, role: MemberRole): Promise { const affectedRows = await this.model.update({ - role: MemberRole[role], + role: role, }, { where: { userId, diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index af0a9bab..93d1208c 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -1,5 +1,5 @@ import type TeamStorage from '@repository/storage/team.storage.js'; -import type { MemberRoleKeys, 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'; @@ -49,7 +49,7 @@ export default class TeamRepository { * @param userId - user id to check his role * @param noteId - note id where user should have role */ - public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { + public async getUserRoleByUserIdAndNoteId(userId: User['id'], noteId: NoteInternalId): Promise { return await this.storage.getUserRoleByUserIdAndNoteId(userId, noteId); } @@ -77,7 +77,7 @@ export default class TeamRepository { * @param noteId - note internal id * @param role - team member new role */ - public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRoleKeys): Promise { + public async patchMemberRoleByUserId(id: TeamMember['id'], noteId: NoteInternalId, role : MemberRole): Promise { return await this.storage.patchMemberRoleById(id, noteId, role); } } From 4ed698d84b3dc67234be8630d74273a49468ce23 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 20 Jan 2024 15:18:06 +0300 Subject: [PATCH 15/19] moved newRole to body - moved newRole to body - changed url for route - added comments --- src/domain/service/noteSettings.ts | 3 ++- src/presentation/http/router/noteSettings.test.ts | 15 ++++++++++++--- src/presentation/http/router/noteSettings.ts | 10 ++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index fb9d7dc9..4cc59bdd 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -142,9 +142,10 @@ export default class NoteSettingsService { /** * - * @param id - id of team member + * @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 { return await this.teamRepository.patchMemberRoleByUserId(id, noteId, role); diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index fbe24973..41836a52 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -290,7 +290,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/Pq1T9vc23Q/1/1', + url: '/note-settings/Pq1T9vc23Q/member/1', + body : { + newRole : 1, + }, }); expect(response?.statusCode).toBe(200); @@ -320,7 +323,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/Pq1T9vc23Q/1/1', + url: '/note-settings/Pq1T9vc23Q/member/1', + body : { + newRole : 1, + }, }); expect(response?.statusCode).toBe(200); @@ -333,7 +339,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/new-role/73NdxFZ4k7/15/1', + url: '/note-settings/73NdxFZ4k7/member/15', + body : { + newRole : 1, + }, }); expect(response?.statusCode).toBe(404); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 2b87d038..19ae5221 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -98,11 +98,13 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa fastify.patch<{ Params: { notePublicId: NotePublicId, - userId: User['id']; - newRole: MemberRole, + userId: User['id'], + }, + Body: { + newRole: MemberRole }, Reply: MemberRole, - }>('/new-role/:notePublicId/:userId/:newRole', { + }>('/:notePublicId/member/:userId', { config: { policy: [ 'authRequired', @@ -120,7 +122,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa ], }, async (request, reply) => { const noteId = request.note?.id as number; - const newRole = await noteSettingsService.patchMemberRoleByUserId(request.params.userId, noteId, request.params.newRole); + const newRole = await noteSettingsService.patchMemberRoleByUserId(request.params.userId, noteId, request.body.newRole); if (newRole === null) { return reply.notFound('User does not belong to Note\'s team'); From f084a91accdf2526fed8f946786b4fa1183b2932 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 20 Jan 2024 15:24:38 +0300 Subject: [PATCH 16/19] moved userId to body - moved userId to body in route - changed tests --- .../http/router/noteSettings.test.ts | 21 +++++++++++-------- src/presentation/http/router/noteSettings.ts | 8 +++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index 41836a52..c92e785d 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -290,9 +290,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/Pq1T9vc23Q/member/1', - body : { - newRole : 1, + url: '/note-settings/Pq1T9vc23Q/team', + body: { + userId: 1, + newRole: 1, }, }); @@ -323,9 +324,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/Pq1T9vc23Q/member/1', - body : { - newRole : 1, + url: '/note-settings/Pq1T9vc23Q/team', + body: { + userId: 1, + newRole: 1, }, }); @@ -339,9 +341,10 @@ describe('NoteSettings API', () => { headers: { authorization: `Bearer ${global.auth(1)}`, }, - url: '/note-settings/73NdxFZ4k7/member/15', - body : { - newRole : 1, + url: '/note-settings/73NdxFZ4k7/team', + body: { + userId: 15, + newRole: 1, }, }); diff --git a/src/presentation/http/router/noteSettings.ts b/src/presentation/http/router/noteSettings.ts index 19ae5221..9cd8f5ba 100644 --- a/src/presentation/http/router/noteSettings.ts +++ b/src/presentation/http/router/noteSettings.ts @@ -98,13 +98,13 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa fastify.patch<{ Params: { notePublicId: NotePublicId, - userId: User['id'], }, Body: { - newRole: MemberRole + userId: User['id'], + newRole: MemberRole, }, Reply: MemberRole, - }>('/:notePublicId/member/:userId', { + }>('/:notePublicId/team', { config: { policy: [ 'authRequired', @@ -122,7 +122,7 @@ const NoteSettingsRouter: FastifyPluginCallback = (fa ], }, async (request, reply) => { const noteId = request.note?.id as number; - const newRole = await noteSettingsService.patchMemberRoleByUserId(request.params.userId, noteId, request.body.newRole); + 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'); From e352893e781fcf07c7870f69207ece1a560b4092 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 20 Jan 2024 15:27:16 +0300 Subject: [PATCH 17/19] comments added - added comments --- src/repository/storage/postgres/orm/sequelize/teams.ts | 1 + src/repository/team.repository.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index 045dbcb1..29091d02 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -227,6 +227,7 @@ export default class TeamsSequelizeStorage { * @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 { const affectedRows = await this.model.update({ diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index 93d1208c..ca43cc34 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -76,6 +76,7 @@ export default class TeamRepository { * @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 { return await this.storage.patchMemberRoleById(id, noteId, role); From 7f53a769947305cf2df5c1d0d9c7f04210759339 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 20 Jan 2024 15:35:03 +0300 Subject: [PATCH 18/19] updatet test description - test description updated --- src/presentation/http/router/noteSettings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presentation/http/router/noteSettings.test.ts b/src/presentation/http/router/noteSettings.test.ts index c92e785d..53567931 100644 --- a/src/presentation/http/router/noteSettings.test.ts +++ b/src/presentation/http/router/noteSettings.test.ts @@ -282,7 +282,7 @@ describe('NoteSettings API', () => { test.todo('Return 403 when user authorized, but not member of the team'); }); - describe('PATCH /note-teams/new-role/:newRole', () => { + 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({ From 09246e5a7ea7c31712b33540b30bb9206ed8910d Mon Sep 17 00:00:00 2001 From: e11sy Date: Mon, 22 Jan 2024 13:59:25 +0300 Subject: [PATCH 19/19] comments added - added docs --- src/domain/service/noteSettings.ts | 1 + src/repository/storage/postgres/orm/sequelize/teams.ts | 1 + src/repository/team.repository.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index 4cc59bdd..a59442ce 100644 --- a/src/domain/service/noteSettings.ts +++ b/src/domain/service/noteSettings.ts @@ -141,6 +141,7 @@ export default class NoteSettingsService { } /** + * Patch team member role by user and note id * * @param id - userId of team member * @param noteId - note internal id diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index 29091d02..6bcc7a8e 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -223,6 +223,7 @@ export default class TeamsSequelizeStorage { } /** + * Patch team member role by user and note id * * @param userId - id of team member * @param noteId - note internal id diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index ca43cc34..b8342c63 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -72,6 +72,7 @@ export default class TeamRepository { 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