diff --git a/src/domain/service/note.ts b/src/domain/service/note.ts index c6eddb47..fc2a464b 100644 --- a/src/domain/service/note.ts +++ b/src/domain/service/note.ts @@ -9,7 +9,7 @@ import type User from '@domain/entities/user.js'; import type { NoteList } from '@domain/entities/noteList.js'; import type NoteHistoryRepository from '@repository/noteHistory.repository.js'; import type { NoteHistoryMeta, NoteHistoryRecord, NoteHistoryPublic } from '@domain/entities/noteHistory.js'; -import type { NotePublic } from '@domain/entities/notePublic.js'; +import { definePublicNote, type NotePublic } from '@domain/entities/notePublic.js'; /** * Note service @@ -450,15 +450,10 @@ export default class NoteService { * @returns - array of notes that are parent structure of the note */ public async getNoteParents(noteId: NoteInternalId): Promise { - const noteParents = await this.noteRepository.getNoteParents(noteId); + const noteIds: NoteInternalId[] = await this.noteRelationsRepository.getNoteParentsIds(noteId); + const noteParents = await this.noteRelationsRepository.getNotesByIds(noteIds); const noteParentsPublic: NotePublic[] = noteParents.map((note) => { - return { - content: note.content, - id: note.publicId, - creatorId: note.creatorId, - createdAt: note.createdAt, - updatedAt: note.updatedAt, - }; + return definePublicNote(note); }); return noteParentsPublic; diff --git a/src/presentation/http/router/note.test.ts b/src/presentation/http/router/note.test.ts index 9842a7b5..1a794b78 100644 --- a/src/presentation/http/router/note.test.ts +++ b/src/presentation/http/router/note.test.ts @@ -565,193 +565,219 @@ describe('Note API', () => { expect(response?.json().message).toStrictEqual(expectedMessage); }); - test.each([ - /** Returns two parents in case of relation between child and parent notes */ - { - testScenario: 1, - numberOfNotes: 2, - childNoteCreatedByDifferentUser: false, - isPublic: true, - expectedStatusCode: 200, - }, - /** Returns multiple parents in case of multiple notes relations with user presence in team in each note */ - { - testScenario: 2, - numberOfNotes: 3, - childNoteCreatedByDifferentUser: false, - isPublic: true, - expectedStatusCode: 200, - }, - /** Returns one parent in case where there is no note relation */ - { - testScenario: 3, - numberOfNotes: 1, - childNoteCreatedByDifferentUser: false, + test('Returns two parents note in case of relation between child and parent note with status 200', async () => { + /** Create test user */ + const user = await global.db.insertUser(); + + /** Create acces token for the user */ + const accessToken = global.auth(user.id); + + /** Create test note - a parent note */ + const parentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note - a child note */ + const childNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note settings */ + await global.db.insertNoteSetting({ + noteId: childNote.id, isPublic: true, - expectedStatusCode: 200, - }, - /** Returns mutiple parents in case where user is not in the team of a note */ - { - testScenario: 4, - numberOfNotes: 3, - childNoteCreatedByDifferentUser: true, + }); + + /** Create test note relation */ + await global.db.insertNoteRelation({ + parentId: parentNote.id, + noteId: childNote.id, + }); + + const response = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${accessToken}`, + }, + url: `/note/${childNote.publicId}`, + }); + + expect(response?.statusCode).toBe(200); + + expect(response?.json()).toMatchObject({ + parentStructure: [ + { + noteId: parentNote.publicId, + content: parentNote.content, + }, + { + noteId: childNote.publicId, + content: childNote.content, + }, + ], + }); + }); + + test('Returns three parents note in case of relation between all notes with status 200', async () => { + /** Create test user */ + const user = await global.db.insertUser(); + + /** Create acces token for the user */ + const accessToken = global.auth(user.id); + + /** Create test note - a grand parent note */ + const grandParentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note - a parent note */ + const parentNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note - a child note */ + const childNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note settings */ + await global.db.insertNoteSetting({ + noteId: childNote.id, isPublic: true, - expectedStatusCode: 200, - }, - /** Returns multiple parents in case when note is not public */ - { - testScenario: 5, - numberOfNotes: 3, - childNoteCreatedByDifferentUser: true, - isPublic: false, - expectedStatusCode: 200, - }, - /** Returns no note in case when the user is not authorized to note */ - { - testScenario: 6, - numberOfNotes: 2, - childNoteCreatedByDifferentUser: true, - isPublic: false, - expectedStatusCode: 403, - }, - /** Returns one note in case when the user has no access to note */ - { - testScenario: 7, - numberOfNotes: 2, - childNoteCreatedByDifferentUser: true, + }); + + /** Create note relation between parent and grandParentNote */ + await global.db.insertNoteRelation({ + parentId: grandParentNote.id, + noteId: parentNote.id, + }); + + /** Create test note relation */ + await global.db.insertNoteRelation({ + parentId: parentNote.id, + noteId: childNote.id, + }); + + const response = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${accessToken}`, + }, + url: `/note/${childNote.publicId}`, + }); + + expect(response?.statusCode).toBe(200); + + expect(response?.json()).toMatchObject({ + parentStructure: [ + { + noteId: grandParentNote.publicId, + content: grandParentNote.content, + }, + { + noteId: parentNote.publicId, + content: parentNote.content, + }, + { + noteId: childNote.publicId, + content: childNote.content, + }, + ], + }); + }); + + test('Returns two parents note in case where the note is not created by the same user but share relation with status 200', async () => { + /** Create test user */ + const user = await global.db.insertUser(); + + /** Create another user */ + const anotherUser = await global.db.insertUser(); + + /** Create acces token for the user */ + const accessToken = global.auth(user.id); + + /** Create test note - a parent note */ + const parentNote = await global.db.insertNote({ + creatorId: anotherUser.id, + }); + + /** Create test note - a child note */ + const childNote = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note settings */ + await global.db.insertNoteSetting({ + noteId: childNote.id, isPublic: true, - expectedStatusCode: 200, - }, - ])('Returns note parents by note public id in different note accessibility and authorization', async ({ testScenario, numberOfNotes, childNoteCreatedByDifferentUser, isPublic, expectedStatusCode }) => { - if (context !== undefined) { - /** Create acces token for the user */ - let accessToken = global.auth(context.user.id); - - if (testScenario === 6 || testScenario == 7) { - accessToken = global.auth(context.anotherUser.id); - } - let noteId = context.parentNote.id; - let notePublicId = context.parentNote.publicId; - - if (numberOfNotes == 2 && !childNoteCreatedByDifferentUser) { - noteId = context.childNote.id; - notePublicId = context.childNote.publicId; - } else if (numberOfNotes == 2 && childNoteCreatedByDifferentUser) { - noteId = context.differentChildNote.id; - notePublicId = context.differentChildNote.publicId; - } else if (numberOfNotes == 3) { - noteId = context.grandChildNote.id; - notePublicId = context.grandChildNote.publicId; - } - - /** Create test note settings */ - await global.db.insertNoteSetting({ - noteId: noteId, - isPublic: isPublic, - }); + }); - for (let i = 0; i < numberOfNotes - 1; i++) { - /** Create test note relation */ - await global.db.insertNoteRelation({ - parentId: i == 0 ? context.parentNote.id : (childNoteCreatedByDifferentUser ? context.differentChildNote.id : context.childNote.id), - noteId: i == 0 ? (childNoteCreatedByDifferentUser ? context.differentChildNote.id : context.childNote.id) : context.grandChildNote.id, - }); - } + /** Create test note relation */ + await global.db.insertNoteRelation({ + parentId: parentNote.id, + noteId: childNote.id, + }); - const response = await global.api?.fakeRequest({ - method: 'GET', - headers: { - authorization: `Bearer ${accessToken}`, + const response = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${accessToken}`, + }, + url: `/note/${childNote.publicId}`, + }); + + expect(response?.statusCode).toBe(200); + + expect(response?.json()).toMatchObject({ + parentStructure: [ + { + noteId: parentNote.publicId, + content: parentNote.content, }, - url: `/note/${notePublicId}`, - }); + { + noteId: childNote.publicId, + content: childNote.content, + }, + ], + }); + }); - switch (testScenario) { - case (1): - expect(response?.statusCode).toBe(expectedStatusCode); - expect(response?.json()).toMatchObject({ - parents: [ - { - id: context.parentNote.publicId, - content: context.parentNote.content, - }, - { - id: context.childNote.publicId, - content: context.childNote.content, - }, - ], - }); - break; - case (2): - expect(response?.statusCode).toBe(expectedStatusCode); - expect(response?.json()).toMatchObject({ - parents: [ - { - id: context.parentNote.publicId, - content: context.parentNote.content, - }, - { - id: context.childNote.publicId, - content: context.childNote.content, - }, - { - id: context.grandChildNote.publicId, - content: context.grandChildNote.content, - }, - ], - }); - break; - case (3): - expect(response?.statusCode).toBe(expectedStatusCode); - expect(response?.json()).toMatchObject({ - parents: [ - { - id: context.parentNote.publicId, - content: context.parentNote.content, - }, - ], - }); - break; - case (4): - case (5): - expect(response?.statusCode).toBe(expectedStatusCode); - expect(response?.json()).toMatchObject({ - parents: [ - { - id: context.parentNote.publicId, - content: context.parentNote.content, - }, - { - id: context.differentChildNote.publicId, - content: context.differentChildNote.content, - }, - { - id: context.grandChildNote.publicId, - content: context.grandChildNote.content, - }, - ], - }); - break; - case (6): - expect(response?.statusCode).toBe(expectedStatusCode); - break; - case (7): - expect(response?.statusCode).toBe(expectedStatusCode); - expect(response?.json()).toMatchObject({ - parents: [ - { - id: context.parentNote.publicId, - content: context.parentNote.content, - }, - { - id: context.differentChildNote.publicId, - content: context.differentChildNote.content, - }, - ], - }); - break; - } - } + test('Returns one note in case where there is no relation exist for the note with status 200', async () => { + /** Create test user */ + const user = await global.db.insertUser(); + + /** Create acces token for the user */ + const accessToken = global.auth(user.id); + + /** Create test note - a child note */ + const note = await global.db.insertNote({ + creatorId: user.id, + }); + + /** Create test note settings */ + await global.db.insertNoteSetting({ + noteId: note.id, + isPublic: true, + }); + + const response = await global.api?.fakeRequest({ + method: 'GET', + headers: { + authorization: `Bearer ${accessToken}`, + }, + url: `/note/${note.publicId}`, + }); + + expect(response?.statusCode).toBe(200); + + expect(response?.json()).toMatchObject({ + parentStructure: [ + { + noteId: note.publicId, + content: note.content, + }, + ], + }); }); }); diff --git a/src/repository/note.repository.ts b/src/repository/note.repository.ts index eda11a92..0771f8b9 100644 --- a/src/repository/note.repository.ts +++ b/src/repository/note.repository.ts @@ -81,13 +81,4 @@ export default class NoteRepository { public async getNoteListByUserId(id: number, offset: number, limit: number): Promise { return await this.storage.getNoteListByUserId(id, offset, limit); } - - /** - * Get all note parents based on note id - * @param noteId : note id to get all its parents - * @returns an array of note parents objects containing public id and content - */ - public async getNoteParents(noteId: NoteInternalId): Promise { - return await this.storage.getAllNoteParents(noteId); - } } diff --git a/src/repository/noteRelations.repository.ts b/src/repository/noteRelations.repository.ts index f31c1a4e..9098eee8 100644 --- a/src/repository/noteRelations.repository.ts +++ b/src/repository/noteRelations.repository.ts @@ -1,4 +1,4 @@ -import type { NoteInternalId } from '@domain/entities/note.js'; +import type { Note, NoteInternalId } from '@domain/entities/note.js'; import type NoteRelationshipStorage from '@repository/storage/noteRelations.storage.js'; /** @@ -67,4 +67,22 @@ export default class NoteRelationsRepository { public async hasRelation(noteId: NoteInternalId): Promise { return await this.storage.hasRelation(noteId); } + + /** + * Get all note parents based on note id + * @param noteId : note id to get all its parents + * @returns an array of note parents ids + */ + public async getNoteParentsIds(noteId: NoteInternalId): Promise { + return await this.storage.getAllNoteParentsIds(noteId); + } + + /** + * Get all notes based on their ids + * @param noteIds : list of note ids + * @returns an array of notes + */ + public async getNotesByIds(noteIds: NoteInternalId[]): Promise { + return await this.storage.getNotesByIds(noteIds); + } } diff --git a/src/repository/storage/postgres/orm/sequelize/note.ts b/src/repository/storage/postgres/orm/sequelize/note.ts index b67227e1..7148f269 100644 --- a/src/repository/storage/postgres/orm/sequelize/note.ts +++ b/src/repository/storage/postgres/orm/sequelize/note.ts @@ -7,7 +7,6 @@ import type { NoteSettingsModel } from './noteSettings.js'; import type { NoteVisitsModel } from './noteVisits.js'; import type { NoteHistoryModel } from './noteHistory.js'; import type { NoteRelationsModel } from './noteRelations.js'; -import { notEmpty } from '@infrastructure/utils/empty.js'; /* eslint-disable @typescript-eslint/naming-convention */ @@ -334,44 +333,4 @@ export default class NoteSequelizeStorage { }, }); }; - - /** - * Get all parent notes of a note that a user has access to, - * where the user has access to. - * @param noteId - the ID of the note. - */ - public async getAllNoteParents(noteId: NoteInternalId): Promise { - if (!this.noteRelationModel) { - throw new Error('NoteStorage: Note Relation model is not defined'); - } - - const parentNotes: Note[] = []; - let currentNoteId: NoteInternalId | null = noteId; - - while (currentNoteId != null) { - // Get the note for database - const note: Note | null = await this.model.findOne({ - where: { id: currentNoteId }, - }); - - if (notEmpty(note)) { - parentNotes.push(note); - } - - // Retrieve the parent note - const noteRelation: NoteRelationsModel | null = await this.noteRelationModel.findOne({ - where: { noteId: currentNoteId }, - }); - - if (noteRelation != null) { - currentNoteId = noteRelation.parentId; - } else { - currentNoteId = null; - } - } - - parentNotes.reverse(); - - return parentNotes; - } } diff --git a/src/repository/storage/postgres/orm/sequelize/noteRelations.ts b/src/repository/storage/postgres/orm/sequelize/noteRelations.ts index 879ddb98..c0dee786 100644 --- a/src/repository/storage/postgres/orm/sequelize/noteRelations.ts +++ b/src/repository/storage/postgres/orm/sequelize/noteRelations.ts @@ -5,6 +5,7 @@ import type Orm from '@repository/storage/postgres/orm/sequelize/index.js'; import type { NoteInternalId } from '@domain/entities/note.js'; import type { Note } from '@domain/entities/note.js'; import { Model, DataTypes } from 'sequelize'; +import { notEmpty } from '@infrastructure/utils/empty.js'; /** * Class representing a note relations in database @@ -209,4 +210,68 @@ export default class NoteRelationsSequelizeStorage { return foundNote !== null; }; + + /** + * Get all parent notes of a note that a user has access to, + * where the user has access to. + * @param noteId - the ID of the note. + */ + public async getAllNoteParentsIds(noteId: NoteInternalId): Promise { + if (!this.noteModel) { + throw new Error('NoteRelationStorage: Note model is not defined'); + } + + const parentNotes: NoteInternalId[] = []; + let currentNoteId: NoteInternalId | null = noteId; + + while (currentNoteId != null) { + // Get the note for database + const note: Note | null = await this.noteModel.findOne({ + where: { id: currentNoteId }, + }); + + if (notEmpty(note)) { + parentNotes.push(note.id); + } + + // Retrieve the parent note + const noteRelation: NoteRelationsModel | null = await this.model.findOne({ + where: { noteId: currentNoteId }, + }); + + if (noteRelation != null) { + currentNoteId = noteRelation.parentId; + } else { + currentNoteId = null; + } + } + + parentNotes.reverse(); + + return parentNotes; + } + + /** + * Get all notes based on their ids + * @param noteIds - list of note ids + */ + public async getNotesByIds(noteIds: NoteInternalId[]): Promise { + if (!this.noteModel) { + throw new Error('NoteRelationStorage: Note model is not defined'); + } + + const notes: Note[] = []; + + for (const noteId of noteIds) { + const note: Note | null = await this.noteModel.findOne({ + where: { id: noteId }, + }); + + if (notEmpty(note)) { + notes.push(note); + } + } + + return notes; + } }