diff --git a/src/domain/entities/team.ts b/src/domain/entities/team.ts index 82415efd..3245bfb8 100644 --- a/src/domain/entities/team.ts +++ b/src/domain/entities/team.ts @@ -1,4 +1,4 @@ -import type { NoteInternalId } from './note.js'; +import type { NoteInternalId, NotePublicId } from './note.js'; import type User from './user.js'; export enum MemberRole { @@ -39,6 +39,11 @@ export interface TeamMember { role: MemberRole; } +/** + * Team member public entity sends to user with public id of the note + */ +export type TeamMemberPublic = Omit & { noteId: NotePublicId }; + export type Team = TeamMember[]; export type TeamMemberCreationAttributes = Omit; diff --git a/src/domain/service/note.ts b/src/domain/service/note.ts index 323ce519..0d677c32 100644 --- a/src/domain/service/note.ts +++ b/src/domain/service/note.ts @@ -310,4 +310,20 @@ export default class NoteService { throw new DomainError('Incorrect tools passed'); } } + + /** + * Get note public id by it's internal id + * Used for making entities that use NoteInternalId public + * @param id - internal id of the note + * @returns note public id + */ + public async getNotePublicIdByInternal(id: NoteInternalId): Promise { + const note = await this.noteRepository.getNoteById(id); + + if (note === null) { + throw new DomainError(`Note with id ${id} was not found`); + } + + return note.publicId; + } } diff --git a/src/domain/service/noteSettings.ts b/src/domain/service/noteSettings.ts index a0d03c40..841cc610 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, TeamMemberPublic, 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'; @@ -38,7 +38,7 @@ export default class NoteSettingsService { * @param invitationHash - hash for joining to the team * @param userId - user to add */ - public async addUserToTeamByInvitationHash(invitationHash: InvitationHash, userId: User['id']): Promise { + public async addUserToTeamByInvitationHash(invitationHash: InvitationHash, userId: User['id']): Promise { const defaultUserRole = MemberRole.Read; const noteSettings = await this.noteSettingsRepository.getNoteSettingsByInvitationHash(invitationHash); @@ -50,19 +50,29 @@ export default class NoteSettingsService { } /** - * Check if user not already in team + * Try to get team member by user and note id */ - const isUserTeamMember = await this.teamRepository.isUserInTeam(userId, noteSettings.noteId); - - if (isUserTeamMember) { - throw new DomainError(`User already in team`); + const member = await this.teamRepository.getTeamMemberByNoteAndUserId(userId, noteSettings.noteId); + + if (member !== null) { + return { + noteId: await this.shared.note.getNotePublicIdByInternal(member.noteId), + userId: member.userId, + role: member.role, + }; } - return await this.teamRepository.createTeamMembership({ + const teamMember = await this.teamRepository.createTeamMembership({ noteId: noteSettings.noteId, userId, role: defaultUserRole, }); + + return { + noteId: await this.shared.note.getNotePublicIdByInternal(teamMember.noteId), + userId: teamMember.userId, + role: teamMember.role, + }; } /** diff --git a/src/domain/service/shared/note.ts b/src/domain/service/shared/note.ts index 0c0acff4..eee962f2 100644 --- a/src/domain/service/shared/note.ts +++ b/src/domain/service/shared/note.ts @@ -1,4 +1,5 @@ import type { NoteInternalId } from '@domain/entities/note.js'; +import type { NotePublicId } from '@domain/entities/note.js'; /** * Which methods of Domain can be used by other domains @@ -11,4 +12,11 @@ export default interface NoteServiceSharedMethods { * @param noteId - id of the current note */ getParentNoteIdByNoteId(noteId: NoteInternalId): Promise; + + /** + * Get note public id by it's internal id + * Used for making entities that use NoteInternalId public + * @param id - internal id of the note + */ + getNotePublicIdByInternal(noteId: NoteInternalId): Promise; } diff --git a/src/presentation/http/router/join.test.ts b/src/presentation/http/router/join.test.ts index ee2579e8..772c6b7b 100644 --- a/src/presentation/http/router/join.test.ts +++ b/src/presentation/http/router/join.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect } from 'vitest'; describe('Join API', () => { describe('POST /join/:hash', () => { - test('Returns 406 when user is already in the team', async () => { + test('Returns 200 and teamMember when user is already in the team', async () => { const invitationHash = 'Hzh2hy4igf'; /** @@ -28,12 +28,6 @@ describe('Join API', () => { invitationHash, }); - await global.db.insertNoteTeam({ - userId: user.id, - noteId: note.id, - role: 0, - }); - const accessToken = global.auth(user.id); /** add same user to the same note team */ @@ -45,10 +39,12 @@ describe('Join API', () => { url: `/join/${invitationHash}`, }); - expect(response?.statusCode).toBe(406); + expect(response?.statusCode).toBe(200); - expect(response?.json()).toStrictEqual({ - message: 'User already in team', + expect(response?.json()).toMatchObject({ + userId: user.id, + noteId: note.publicId, + role: 1, }); }); test('Returns 406 when invitation hash is not valid', async () => { @@ -112,11 +108,9 @@ describe('Join API', () => { expect(response?.statusCode).toBe(200); expect(response?.json()).toMatchObject({ - result: { - userId: randomGuy.id, - noteId: note.id, - role: 0, - }, + userId: randomGuy.id, + noteId: note.publicId, + role: 0, }); }); }); diff --git a/src/presentation/http/router/join.ts b/src/presentation/http/router/join.ts index 3c51c336..f7e22ed5 100644 --- a/src/presentation/http/router/join.ts +++ b/src/presentation/http/router/join.ts @@ -1,6 +1,6 @@ import type { FastifyPluginCallback } from 'fastify'; import type NoteSettingsService from '@domain/service/noteSettings.js'; -import type { TeamMember } from '@domain/entities/team.js'; +import type { TeamMemberPublic } from '@domain/entities/team.js'; /** * Represents AI router options @@ -55,7 +55,7 @@ const JoinRouter: FastifyPluginCallback = (fastify, opts, don }, async (request, reply) => { const { hash } = request.params; const { userId } = request; - let result: TeamMember | null = null; + let result: TeamMemberPublic | null = null; try { result = await noteSettingsService.addUserToTeamByInvitationHash(hash, userId as number); @@ -65,7 +65,7 @@ const JoinRouter: FastifyPluginCallback = (fastify, opts, don return reply.notAcceptable(causedError.message); } - return reply.send({ result }); + return reply.send(result); }); done(); diff --git a/src/repository/storage/postgres/orm/sequelize/teams.ts b/src/repository/storage/postgres/orm/sequelize/teams.ts index ba599760..24df0d4c 100644 --- a/src/repository/storage/postgres/orm/sequelize/teams.ts +++ b/src/repository/storage/postgres/orm/sequelize/teams.ts @@ -154,20 +154,18 @@ export default class TeamsSequelizeStorage { } /** - * Check if user is note team member + * Get team member by user id and note id * @param userId - user id to check * @param noteId - note id to identify team - * @returns returns true if user is team member + * @returns return null if user is not in team, teamMember otherwhise */ - public async isUserInTeam(userId: User['id'], noteId: NoteInternalId): Promise { - const teamMemberShip = await this.model.findOne({ + public async getTeamMemberByNoteAndUserId(userId: User['id'], noteId: NoteInternalId): Promise { + return await this.model.findOne({ where: { noteId, userId, }, }); - - return teamMemberShip !== null; } /** diff --git a/src/repository/team.repository.ts b/src/repository/team.repository.ts index a3a23141..90de8673 100644 --- a/src/repository/team.repository.ts +++ b/src/repository/team.repository.ts @@ -30,13 +30,13 @@ export default class TeamRepository { } /** - * Check if user is note team member + * Get team member by user id and note id * @param userId - user id to check * @param noteId - note id to identify team - * @returns returns true if user is team member + * @returns null if user is not in team, teamMember otherwhise */ - public async isUserInTeam(userId: User['id'], noteId: NoteInternalId): Promise { - return await this.storage.isUserInTeam(userId, noteId); + public async getTeamMemberByNoteAndUserId(userId: User['id'], noteId: NoteInternalId): Promise { + return await this.storage.getTeamMemberByNoteAndUserId(userId, noteId); } /**