diff --git a/src/domain/service/note.ts b/src/domain/service/note.ts index d637afa5..f5744ab9 100644 --- a/src/domain/service/note.ts +++ b/src/domain/service/note.ts @@ -95,25 +95,14 @@ export default class NoteService { * * @param id - note internal id * @param content - new content - * @param parentPublicId - parent note if exist */ - public async updateNoteContentById(id: NoteInternalId, content: Note['content'], parentPublicId: Note['publicId'] | undefined): Promise { + public async updateNoteContentById(id: NoteInternalId, content: Note['content']): Promise { const updatedNote = await this.noteRepository.updateNoteContentById(id, content); if (updatedNote === null) { throw new DomainError(`Note with id ${id} was not updated`); } - if (parentPublicId !== undefined) { - const parentNote = await this.getNoteByPublicId(parentPublicId); - - if (parentNote === null) { - throw new DomainError(`Incorrect parent note`); - } - - await this.noteRelationsRepository.updateNoteRelationById(updatedNote.id, parentNote.id); - } - return updatedNote; } @@ -174,4 +163,27 @@ export default class NoteService { public async getParentNoteIdByNoteId(noteId: Note['id']): Promise { return await this.noteRelationsRepository.getParentNoteIdByNoteId(noteId); } + + /** + * Update note relation + * + * @param noteId - id of the current note + * @param parentPublicId - id of the new parent note + */ + public async updateNoteRelation(noteId: NoteInternalId, parentPublicId: NotePublicId): Promise { + const parentNote = await this.noteRepository.getNoteByPublicId(parentPublicId); + + if (parentNote === null) { + throw new DomainError(`Incorrect parent note`); + } + + if (parentNote.id === noteId) { + throw new DomainError(`Parent note is the same as the child note`); + } + + /** + * @todo: add check, that new parent is not in childNotes to avoid circular references + */ + return await this.noteRelationsRepository.updateNoteRelationById(noteId, parentNote.id); + }; } diff --git a/src/presentation/http/router/note.test.ts b/src/presentation/http/router/note.test.ts index 9ab72fd5..60c32791 100644 --- a/src/presentation/http/router/note.test.ts +++ b/src/presentation/http/router/note.test.ts @@ -769,7 +769,7 @@ describe('Note API', () => { } }); - test('Returns 200 and true when note was successfully unlinked', async () => { + test('Returns 200 and isUpdated=true when note was successfully unlinked', async () => { /* create test child note */ const childNote = await global.db.insertNote({ creatorId: user.id, @@ -845,4 +845,125 @@ describe('Note API', () => { expect(response?.json().message).toStrictEqual('Note not found'); }); }); + + describe('PATCH /note/:notePublicId/relation', () => { + let accessToken = ''; + let user: User; + + beforeEach(async () => { + /** create test user */ + user = await global.db.insertUser(); + + accessToken = global.auth(user.id); + }); + test('Returns 200 and isUpdated=true when parent was successfully updated', async () => { + /* create test child note */ + const childNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /* create test parent note */ + const parentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /* create test note, that will be new parent for the child note */ + const newParentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /* create note settings for child note*/ + await global.db.insertNoteSetting({ + noteId: childNote.id, + isPublic: true, + }); + + /* create test relation */ + await global.db.insertNoteRelation({ + noteId: childNote.id, + parentId: parentNote.id, + }); + + let response = await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${accessToken}`, + }, + body: { + parentNoteId: newParentNote.publicId, + }, + url: `/note/${childNote.publicId}/relation`, + }); + + expect(response?.statusCode).toBe(200); + + expect(response?.json().isUpdated).toBe(true); + + response = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${accessToken}`, + }, + url: `/note/${childNote.publicId}`, + }); + + expect(response?.json().parentNote.id).toBe(newParentNote.publicId); + }); + + test('Returns 400 when parent is the same as child', async () => { + /* create test child note*/ + const childNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /* create test parent note*/ + const parentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /* create test note relation*/ + await global.db.insertNoteRelation({ + noteId: childNote.id, + parentId: parentNote.id, + }); + + const response = await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${accessToken}`, + }, + body: { + parentNoteId: childNote.publicId, + }, + url: `/note/${childNote.publicId}/relation`, + }); + + expect(response?.statusCode).toBe(400); + + expect(response?.json().message).toStrictEqual('Parent note is the same as the child note'); + }); + + test('Return 400 when parent note does not exist', async () => { + const nonExistentParentId = '47L43yY7dp'; + + const childNote= await global.db.insertNote({ + creatorId: user.id, + }); + + const response = await global.api?.fakeRequest({ + method: 'PATCH', + headers: { + authorization: `Bearer ${accessToken}`, + }, + body: { + parentNoteId: nonExistentParentId, + }, + url: `/note/${childNote.publicId}/relation`, + }); + + expect(response?.statusCode).toBe(400); + + expect(response?.json().message).toStrictEqual('Incorrect parent note'); + }); + }); }); diff --git a/src/presentation/http/router/note.ts b/src/presentation/http/router/note.ts index b081e1bc..ed51af19 100644 --- a/src/presentation/http/router/note.ts +++ b/src/presentation/http/router/note.ts @@ -233,7 +233,6 @@ const NoteRouter: FastifyPluginCallback = (fastify, opts, don }, Body: { content: JSON; - parentId?: NotePublicId; }, Reply: { updatedAt: Note['updatedAt'], @@ -264,15 +263,68 @@ const NoteRouter: FastifyPluginCallback = (fastify, opts, don }, async (request, reply) => { const noteId = request.note?.id as number; const content = request.body.content as JSON; - const parentId = request.body.parentId; - const note = await noteService.updateNoteContentById(noteId, content, parentId); + const note = await noteService.updateNoteContentById(noteId, content); return reply.send({ updatedAt: note.updatedAt, }); }); + /** + * Update note relation by id. + */ + fastify.patch<{ + Params: { + notePublicId: NotePublicId, + }, + Body: { + parentNoteId: NotePublicId, + }, + Reply: { + isUpdated: boolean, + } + }>('/:notePublicId/relation', { + schema: { + params: { + notePublicId: { + $ref: 'NoteSchema#/properties/id', + }, + }, + body: { + parentNoteId: { + $ref: 'NoteSchema#/properties/id', + }, + }, + response: { + '2xx': { + type: 'object', + properties: { + isUpdated: { + type: 'boolean', + }, + }, + }, + }, + }, + config: { + policy: [ + 'authRequired', + 'userCanEdit', + ], + }, + preHandler: [ + noteResolver, + ], + }, async (request, reply) => { + const noteId = request.note?.id as number; + const parentNoteId = request.body.parentNoteId; + + const isUpdated = await noteService.updateNoteRelation(noteId, parentNoteId); + + return reply.send({ isUpdated }); + }); + /** * Delete parent relation */