Skip to content

Commit

Permalink
update (parents note): modification based on the reviews, still tests…
Browse files Browse the repository at this point in the history
… to be fixed
  • Loading branch information
dependentmadani committed Sep 14, 2024
1 parent 3adff28 commit d6c9ded
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 169 deletions.
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ services:
- 127.0.0.1:1337:1337
volumes:
- ./app-config.yaml:/usr/app/app-config.yaml
- ./app-config.local.yaml:/usr/app/app-config.local.yaml
restart: unless-stopped

postgres:
Expand Down
22 changes: 0 additions & 22 deletions src/domain/entities/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,3 @@ export interface Note {
* Part of note entity used to create new note
*/
export type NoteCreationAttributes = Pick<Note, 'publicId' | 'content' | 'creatorId' | 'tools'>;

/**
* This type represents only note's content
* Used for work with just note's content in web part
* This type would be used as a part of the structure, that represents all parent notes of the current note
*/
export type NoteIdAndContent = {
/**
* Note public id
*/
noteId: Note['publicId'];

/**
* Note content
*/
content: Note['content'];
};

/**
* This type represents the structure, that represents all parent notes of the current note
*/
export type NoteParentsStructure = NoteIdAndContent[];
6 changes: 3 additions & 3 deletions src/domain/service/note.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Note, NoteInternalId, NoteParentsStructure, NotePublicId } from '@domain/entities/note.js';
import type { Note, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
import type NoteRepository from '@repository/note.repository.js';
import type NoteVisitsRepository from '@repository/noteVisits.repository.js';
import { createPublicId } from '@infrastructure/utils/id.js';
Expand Down Expand Up @@ -457,7 +457,7 @@ export default class NoteService {
* @param userId - id of the user that is requesting the parent structure
* @returns - array of notes that are parent structure of the note
*/
public async getNoteParentStructure(noteId: NoteInternalId, userId: number): Promise<NoteParentsStructure> {
return await this.teamRepository.getAllNotesParents(noteId, userId);
public async getNoteParentStructure(noteId: NoteInternalId, userId: number): Promise<NoteList> {
return await this.noteRepository.getAllNotesParents(noteId, userId);
}
}
72 changes: 39 additions & 33 deletions src/presentation/http/router/note.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ describe('Note API', () => {
expect(response?.json().message).toStrictEqual(expectedMessage);
});

test('Returns two objects of note parent structure by note public id with 200 status', async () => {
test('Returns two parents in case of relation between child and parent notes with 200 status', async () => {
/** Create test user */
const user = await global.db.insertUser();

Expand Down Expand Up @@ -559,20 +559,22 @@ describe('Note API', () => {
expect(response?.statusCode).toBe(200);

expect(response?.json()).toMatchObject({

Check failure on line 561 in src/presentation/http/router/note.test.ts

View workflow job for this annotation

GitHub Actions / tests (feat/return-note)

src/presentation/http/router/note.test.ts > Note API > GET note/:notePublicId > Returns two parents in case of relation between child and parent notes with 200 status

AssertionError: expected { …(5) } to match object { parents: { items: [ …(2) ] } } - Expected + Received Object { + "accessRights": Object { + "canEdit": true, + }, + "note": Object { + "content": Object { + "blocks": Array [ + Object { + "data": Object { + "text": "text", + }, + "id": "mJDq8YbvqO", + "type": "paragraph", + }, + Object { + "data": Object { + "level": 2, + "text": "fdgsfdgfdsg", + }, + "id": "DeL0QehzGe", + "type": "header", + }, + ], + }, + "createdAt": "2024-09-14T23:20:16.749Z", + "id": "5BExMw7KPA", + "updatedAt": "2024-09-14T23:20:16.749Z", + }, + "parentNote": Object { + "content": Object { + "blocks": Array [ + Object { + "data": Object { + "text": "text", + }, + "id": "mJDq8YbvqO", + "type": "paragraph", + }, + Object { + "data": Object { + "level": 2, + "text": "fdgsfdgfdsg", + }, + "id": "DeL0QehzGe", + "type": "header", + }, + ], + }, + "createdAt": "2024-09-14T23:20:16.746Z", + "id": "PB42lOHjBd", + "updatedAt": "2024-09-14T23:20:16.746Z", + }, "parents": Object { "items": Array [ Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "PB42lOHjBd", + "createdAt": "2024-09-14T23:20:16.746Z", + "id": "1", + "updatedAt": "2024-09-14T23:20:16.746Z", }, Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "5BExMw7KPA", + "createdAt": "2024-09-14T23:20:16.749Z", + "id": "2", + "updatedAt": "2024-09-14T23:20:16.749Z", }, ], }, + "tools": Array [ + Object { + "exportName": "Header", + "id": "1", + "isDefault": true, + "name": "header", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1/dist/header.umd.min.js", + }, + "title": "Heading", + "userId": null, + }, + Object { + "exportName": "Paragraph", + "id": "2", + "isDefault": true, + "name": "paragraph", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3/dist/paragraph.umd.min.js", + }, + "title": "Paragraph", + "userId": null, + }, + ], } ❯ src/presentation/http/router/note.test.ts:561:32
parentStructure: [
{
noteId: parentNote.publicId,
content: parentNote.content,
},
{
noteId: childNote.publicId,
content: childNote.content,
},
],
parents: {
items: [
{
id: parentNote.publicId,
content: parentNote.content,
},
{
id: childNote.publicId,
content: childNote.content,
},
],
},
});
});

test('Returns multiple objects of note parent structure by note public id with 200 status', async () => {
test('Returns multiple parents in case of multiple notes relations with user presence in team in each note with 200 status', async () => {
/** Create test user */
const user = await global.db.insertUser();

Expand Down Expand Up @@ -622,24 +624,26 @@ describe('Note API', () => {
expect(response?.statusCode).toBe(200);

expect(response?.json()).toMatchObject({

Check failure on line 626 in src/presentation/http/router/note.test.ts

View workflow job for this annotation

GitHub Actions / tests (feat/return-note)

src/presentation/http/router/note.test.ts > Note API > GET note/:notePublicId > Returns multiple parents in case of multiple notes relations with user presence in team in each note with 200 status

AssertionError: expected { …(5) } to match object { parents: { items: [ …(3) ] } } - Expected + Received Object { + "accessRights": Object { + "canEdit": true, + }, + "note": Object { + "content": Object { + "blocks": Array [ + Object { + "data": Object { + "text": "text", + }, + "id": "mJDq8YbvqO", + "type": "paragraph", + }, + Object { + "data": Object { + "level": 2, + "text": "fdgsfdgfdsg", + }, + "id": "DeL0QehzGe", + "type": "header", + }, + ], + }, + "createdAt": "2024-09-14T23:20:16.842Z", + "id": "NN3HIrB83J", + "updatedAt": "2024-09-14T23:20:16.842Z", + }, + "parentNote": Object { + "content": Object { + "blocks": Array [ + Object { + "data": Object { + "text": "text", + }, + "id": "mJDq8YbvqO", + "type": "paragraph", + }, + Object { + "data": Object { + "level": 2, + "text": "fdgsfdgfdsg", + }, + "id": "DeL0QehzGe", + "type": "header", + }, + ], + }, + "createdAt": "2024-09-14T23:20:16.838Z", + "id": "SNj4FNU22J", + "updatedAt": "2024-09-14T23:20:16.838Z", + }, "parents": Object { "items": Array [ Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "sD5gXg-uZ0", + "createdAt": "2024-09-14T23:20:16.835Z", + "id": "1", + "updatedAt": "2024-09-14T23:20:16.835Z", }, Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "SNj4FNU22J", + "createdAt": "2024-09-14T23:20:16.838Z", + "id": "2", + "updatedAt": "2024-09-14T23:20:16.838Z", }, Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "NN3HIrB83J", + "createdAt": "2024-09-14T23:20:16.842Z", + "id": "3", + "updatedAt": "2024-09-14T23:20:16.842Z", }, ], }, + "tools": Array [ + Object { + "exportName": "Header", + "id": "1", + "isDefault": true, + "name": "header", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1/dist/header.umd.min.js", + }, + "title": "Heading", + "userId": null, + }, + Object { + "exportName": "Paragraph", + "id": "2", + "isDefault": true, + "name": "paragraph", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3/dis
parentStructure: [
{
noteId: parentNote.publicId,
content: parentNote.content,
},
{
noteId: childNote.publicId,
content: childNote.content,
},
{
noteId: grandchildNote.publicId,
content: grandchildNote.content,
},
],
parents: {
items: [
{
id: parentNote.publicId,
content: parentNote.content
},
{
id: childNote.publicId,
content: childNote.content
},
{
id: grandchildNote.publicId,
content: grandchildNote.content
},
],
}
});
});

test('Returns one object in array of note parent structure by note public id with 200 status', async () => {
test('Returns one parent in case where there is no note relation with 200 status', async () => {
/** Create test user */
const user = await global.db.insertUser();

Expand Down Expand Up @@ -667,12 +671,14 @@ describe('Note API', () => {
expect(response?.statusCode).toBe(200);

expect(response?.json()).toMatchObject({

Check failure on line 673 in src/presentation/http/router/note.test.ts

View workflow job for this annotation

GitHub Actions / tests (feat/return-note)

src/presentation/http/router/note.test.ts > Note API > GET note/:notePublicId > Returns one parent in case where there is no note relation with 200 status

AssertionError: expected { …(4) } to match object { parents: { items: [ { …(2) } ] } } - Expected + Received Object { + "accessRights": Object { + "canEdit": true, + }, + "note": Object { + "content": Object { + "blocks": Array [ + Object { + "data": Object { + "text": "text", + }, + "id": "mJDq8YbvqO", + "type": "paragraph", + }, + Object { + "data": Object { + "level": 2, + "text": "fdgsfdgfdsg", + }, + "id": "DeL0QehzGe", + "type": "header", + }, + ], + }, + "createdAt": "2024-09-14T23:20:16.921Z", + "id": "jYJmW8ANj2", + "updatedAt": "2024-09-14T23:20:16.921Z", + }, "parents": Object { "items": Array [ Object { "content": Object { "blocks": Array [ Object { "data": Object { "text": "text", }, "id": "mJDq8YbvqO", "type": "paragraph", }, Object { "data": Object { "level": 2, "text": "fdgsfdgfdsg", }, "id": "DeL0QehzGe", "type": "header", }, ], }, - "id": "jYJmW8ANj2", + "createdAt": "2024-09-14T23:20:16.921Z", + "id": "1", + "updatedAt": "2024-09-14T23:20:16.921Z", }, ], }, + "tools": Array [ + Object { + "exportName": "Header", + "id": "1", + "isDefault": true, + "name": "header", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1/dist/header.umd.min.js", + }, + "title": "Heading", + "userId": null, + }, + Object { + "exportName": "Paragraph", + "id": "2", + "isDefault": true, + "name": "paragraph", + "source": Object { + "cdn": "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3/dist/paragraph.umd.min.js", + }, + "title": "Paragraph", + "userId": null, + }, + ], } ❯ src/presentation/http/router/note.test.ts:673:32
parentStructure: [
{
noteId: note.publicId,
content: note.content,
},
],
parents: {
items: [
{
id: note.publicId,
content: note.content,
},
],
}
});
});
});
Expand Down
24 changes: 11 additions & 13 deletions src/presentation/http/router/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FastifyPluginCallback } from 'fastify';
import type NoteService from '@domain/service/note.js';
import type NoteSettingsService from '@domain/service/noteSettings.js';
import type { ErrorResponse } from '@presentation/http/types/HttpResponse.js';
import type { Note, NoteParentsStructure, NotePublicId } from '@domain/entities/note.js';
import type { Note, NotePublicId } from '@domain/entities/note.js';
import useNoteResolver from '../middlewares/note/useNoteResolver.js';
import useNoteSettingsResolver from '../middlewares/noteSettings/useNoteSettingsResolver.js';
import useMemberRoleResolver from '../middlewares/noteSettings/useMemberRoleResolver.js';
Expand All @@ -12,6 +12,7 @@ import type NoteVisitsService from '@domain/service/noteVisits.js';
import type EditorToolsService from '@domain/service/editorTools.js';
import type EditorTool from '@domain/entities/editorTools.js';
import type { NoteHistoryMeta, NoteHistoryPublic, NoteHistoryRecord } from '@domain/entities/noteHistory.js';
import { NoteList } from '@domain/entities/noteList.js';

/**
* Interface for the note router.
Expand Down Expand Up @@ -85,7 +86,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
canEdit: boolean;
};
tools: EditorTool[];
parentStructure: NoteParentsStructure;
parents: NoteList;
} | ErrorResponse;
}>('/:notePublicId', {
config: {
Expand Down Expand Up @@ -124,16 +125,13 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
$ref: 'EditorToolSchema',
},
},
parentStructure: {
type: 'array',
items: {
type: 'object',
properties: {
noteId: {
$ref: 'NoteSchema#/properties/id',
},
content: {
$ref: 'NoteSchema#/properties/content',
parents: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
$ref: 'NoteSchema',
},
},
},
Expand Down Expand Up @@ -194,7 +192,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
parentNote: parentNote,
accessRights: { canEdit: canEdit },
tools: noteTools,
parentStructure: noteParentStructure,
parents: noteParentStructure,
});
});

Expand Down
3 changes: 2 additions & 1 deletion src/repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,14 @@ export async function init(orm: Orm, s3Config: S3StorageConfig): Promise<Reposit
/**
* Create associations between note and team, user and team
*/
noteStorage.createAssociationWithTeamsModel(teamStorage.model);
teamStorage.createAssociationWithNoteModel(noteStorage.model);
teamStorage.createAssociationWithUserModel(userStorage.model);
teamStorage.createAssociationWithNoteRelationsModel(noteRelationshipStorage.model);

/**
* Create associations between note and relations table
*/
noteStorage.createAssociationWithNoteRelationModel(noteRelationshipStorage.model);
noteRelationshipStorage.createAssociationWithNoteModel(noteStorage.model);

/**
Expand Down
11 changes: 11 additions & 0 deletions src/repository/note.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
import { NoteList } from '@domain/entities/noteList.js';
import type NoteStorage from '@repository/storage/note.storage.js';

/**
Expand Down Expand Up @@ -81,4 +82,14 @@ export default class NoteRepository {
public async getNoteListByUserId(id: number, offset: number, limit: number): Promise<Note[]> {
return await this.storage.getNoteListByUserId(id, offset, limit);
}

/**
* Get all notes parents based on note id and user id, by checking team access
* @param noteId : note id to get all its parents
* @param userId : user id to check access
* @returns an array of note parents objects containing public id and content
*/
public async getAllNotesParents(noteId: NoteInternalId, userId: number): Promise<NoteList> {
return await this.storage.getAllNoteParents(noteId, userId);
}
}
88 changes: 88 additions & 0 deletions src/repository/storage/postgres/orm/sequelize/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { UserModel } from '@repository/storage/postgres/orm/sequelize/user.js';
import type { NoteSettingsModel } from './noteSettings.js';
import type { NoteVisitsModel } from './noteVisits.js';
import type { NoteHistoryModel } from './noteHistory.js';
import { NoteRelationsModel } from './noteRelations.js';
import { notEmpty } from '@infrastructure/utils/empty.js';
import { NoteList } from '@domain/entities/noteList.js';
import { TeamsModel } from './teams.js';

/* eslint-disable @typescript-eslint/naming-convention */

Expand Down Expand Up @@ -75,6 +79,10 @@ export default class NoteSequelizeStorage {

public historyModel: typeof NoteHistoryModel | null = null;

public noteRelationModel: typeof NoteRelationsModel | null = null;

public teamModel: typeof TeamsModel | null = null;

/**
* Database instance
*/
Expand Down Expand Up @@ -155,6 +163,22 @@ export default class NoteSequelizeStorage {
});
};

public createAssociationWithNoteRelationModel(model: ModelStatic<NoteRelationsModel>): void {

/**
* Create association with note relations
*/
this.noteRelationModel = model;
}

public createAssociationWithTeamsModel(model: ModelStatic<TeamsModel>): void {

/**
* Create association with teams
*/
this.teamModel = model;
}

/**
* Insert note to database
* @param options - note creation options
Expand Down Expand Up @@ -323,4 +347,68 @@ export default class NoteSequelizeStorage {
},
});
};


/**
* Get all parent notes of a note that a user has access to,
* by checking the team access.
* @param noteId - the ID of the note.
* @param userId - the ID of the user.
*/
public async getAllNoteParents(noteId: NoteInternalId, userId: number): Promise<NoteList> {
if (!this.teamModel || !this.noteRelationModel) {
throw new Error(`${this.model !== null ? 'TeamStorage: Note relation model is not defined' : 'TeamStorage: Note model is not defined'}`);
}

const parentNotes: NoteList = { items: [] };
let currentNoteId: NoteInternalId | null = noteId;
/**
* Store notes that user can not access, to check the inherited team if has access
*/
let storeUnaccessibleNote: Note[] = [];

while (currentNoteId != null) {
const teamMember = await this.teamModel.findOne({
where: {
noteId: currentNoteId,
userId,
},
include: {
model: this.model,
as: this.model.tableName,
required: true,
},
});

if (teamMember && notEmpty(teamMember.notes)) {
if (storeUnaccessibleNote.length > 0) {
parentNotes.items.push(...storeUnaccessibleNote);
storeUnaccessibleNote = [];
}
parentNotes.items.push(teamMember.notes);
} else if (teamMember === null) {
const note = await this.model.findOne({
where: { id: currentNoteId },
});

if (note !== null) {
storeUnaccessibleNote.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.items.reverse();
return parentNotes;
}
}
Loading

0 comments on commit d6c9ded

Please sign in to comment.