Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(parent structure): add method that return note parent structure #276

Merged
merged 32 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
df44b32
feat(parent structure): add method that return note parent structure
dependentmadani Aug 29, 2024
0551b9f
update: start doing the search in team storage
dependentmadani Sep 1, 2024
a5adcc2
update: first part of code review is done
dependentmadani Sep 4, 2024
bccf7b1
fix: finish the review modifications
dependentmadani Sep 4, 2024
42cb421
update: first part of modification after review
dependentmadani Sep 6, 2024
bb6caff
fix: build problem fixed
dependentmadani Sep 6, 2024
a11d1f8
update(note request): update the content send in get request of note …
dependentmadani Sep 7, 2024
0e3e8f0
update(tests): add different tests for the note parent structure
dependentmadani Sep 8, 2024
3adff28
update(tests): tests are now functional
dependentmadani Sep 9, 2024
d6c9ded
update (parents note): modification based on the reviews, still tests…
dependentmadani Sep 14, 2024
6bdc56a
update: fix lint
dependentmadani Sep 14, 2024
6cf3d4c
update (note parents): few modification in the return value type, add…
dependentmadani Sep 15, 2024
1809ca1
update: add a test
dependentmadani Sep 15, 2024
ca7a9a2
update (note parents): modifications based on reviews, use test.each …
dependentmadani Sep 18, 2024
e14d948
update (parent note test): modification based on previous reviews
dependentmadani Sep 19, 2024
5c6cfc0
update (parent notes): few modification done, still work to be done
dependentmadani Sep 23, 2024
5bb6be5
update (note parents): fix the issue of tests, working good
dependentmadani Sep 24, 2024
fee9bbf
update (note parents): modification based on previous review, chore m…
dependentmadani Sep 26, 2024
d65c589
update (note parents tests): the issue of test failure is fixed
dependentmadani Sep 26, 2024
14234a1
udpate (note parents): based on last review
dependentmadani Sep 27, 2024
c09ad3a
Merge branch 'feat/return-note' of github.com:codex-team/notes.api in…
dependentmadani Sep 27, 2024
9872d5e
update (test cases): the issue is fixed due to missing an id
dependentmadani Sep 28, 2024
5a9fad2
update (test case): change the naming of a test case
dependentmadani Sep 29, 2024
4ccb075
update(note parents): based on last review, still work on sql
dependentmadani Oct 13, 2024
aa93ee2
update (parent note): add sql query to retrieve all the parents note …
dependentmadani Oct 15, 2024
ed49e60
update (note parents): only include the parents note without the main…
dependentmadani Oct 15, 2024
7bfe0eb
update (note parents): modify based on last review
dependentmadani Oct 16, 2024
dd1bcdc
update (note relation): return noteId and parentId from the sql query
dependentmadani Oct 16, 2024
92c8569
update (note parents): update the search of note ids to respect the o…
dependentmadani Oct 17, 2024
7efadf5
update (note parents): add comment about the query
dependentmadani Oct 22, 2024
47680d6
update (note parents): small modification of function description and…
dependentmadani Oct 24, 2024
31aeb92
remove (note parents test): an unecessary test case has been removed
dependentmadani Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docker-compose.yml
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
version: "3.2"
version: '3.2'

services:
api:
build:
Expand Down
12 changes: 12 additions & 0 deletions src/domain/service/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +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';

/**
* Note service
Expand Down Expand Up @@ -441,4 +442,15 @@ export default class NoteService {

return noteHistoryPublic;
}

/**
* Get note parent structure recursively by note id and user id
* and check if user has access to the parent note.
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
* @param noteId - id of the note to get parent structure
* @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<NotePublic[]> {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
return await this.noteRepository.getNoteParents(noteId, userId);
}
}
229 changes: 229 additions & 0 deletions src/presentation/http/router/note.test.ts
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MemberRole } from '@domain/entities/team.js';
import { describe, test, expect, beforeEach } from 'vitest';
import type User from '@domain/entities/user.js';
import type { Note } from '@domain/entities/note.js';

describe('Note API', () => {
/**
Expand Down Expand Up @@ -180,6 +181,49 @@ describe('Note API', () => {
});

describe('GET note/:notePublicId ', () => {
let context: {
user: User;
user2: User;
parentNote: Note;
childNote: Note;
differentChildNote: Note;
grandChildNote: Note;
} = {
user: {} as User,
user2: {} as User,
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
parentNote: {} as Note,
childNote: {} as Note,
differentChildNote: {} as Note,
grandChildNote: {} as Note,
};

beforeEach(async () => {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
/** Create test user */
context.user = await global.db.insertUser();

context.user2 = await global.db.insertUser();

/** Create test note - a parent note */
context.parentNote = await global.db.insertNote({
creatorId: context.user.id,
});

/** Create test note - a child note */
context.childNote = await global.db.insertNote({
creatorId: context.user.id,
});

/** Create test note - create note with different user */
context.differentChildNote = await global.db.insertNote({
creatorId: context.user2.id,
});

/** Create test note - a grandchild note */
context.grandChildNote = await global.db.insertNote({
creatorId: context.user.id,
});
});

test.each([
/** Returns 200 if user is team member with a Read role */
{
Expand Down Expand Up @@ -518,6 +562,191 @@ describe('Note API', () => {

expect(response?.json().message).toStrictEqual(expectedMessage);
});

e11sy marked this conversation as resolved.
Show resolved Hide resolved
test.each([
/** Returns two parents in case of relation between child and parent notes with 200 status */
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
{
testCase: 1,
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
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 with 200 status */
{
testCase: 2,
numberOfNotes: 3,
childNoteCreatedByDifferentUser: false,
isPublic: true,
expectedStatusCode: 200,
},
/** Returns one parent in case where there is no note relation with 200 status */
{
testCase: 3,
numberOfNotes: 1,
childNoteCreatedByDifferentUser: false,
isPublic: true,
expectedStatusCode: 200,
},
/** Returns mutiple parents in case where user is not in the team of a note with 200 status */
{
testCase: 4,
numberOfNotes: 3,
childNoteCreatedByDifferentUser: true,
isPublic: true,
expectedStatusCode: 200,
},
/** Returns multiple parents in case when note is not public with 200 status */
{
testCase: 5,
numberOfNotes: 3,
childNoteCreatedByDifferentUser: true,
isPublic: false,
expectedStatusCode: 200,
},
/** Returns no note in case when the user is not authorized to note with 403 status */
{
testCase: 6,
numberOfNotes: 2,
childNoteCreatedByDifferentUser: true,
isPublic: false,
expectedStatusCode: 403,
},
/** Returns one note in case when the user has no access to note with 200 status */
{
testCase: 7,
numberOfNotes: 2,
childNoteCreatedByDifferentUser: true,
isPublic: true,
expectedStatusCode: 200,
},
])('Get note parents in different scenarios', async ({ testCase, numberOfNotes, childNoteCreatedByDifferentUser, isPublic, expectedStatusCode }) => {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
if (context !== undefined) {
/** Create acces token for the user */
let accessToken = global.auth(context.user.id);

if (testCase === 6 || testCase == 7) {
accessToken = global.auth(context.user2.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,
});
}

const response = await global.api?.fakeRequest({
method: 'GET',
headers: {
authorization: `Bearer ${accessToken}`,
},
url: `/note/${notePublicId}`,
});

switch (testCase) {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
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.differentChildNote.publicId,
content: context.differentChildNote.content,
},
],
});
break;
}
}
});
});

describe('PATCH note/:notePublicId ', () => {
Expand Down
10 changes: 10 additions & 0 deletions src/presentation/http/router/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
canEdit: boolean;
};
tools: EditorTool[];
parents: NotePublic[];
} | ErrorResponse;
}>('/:notePublicId', {
config: {
Expand Down Expand Up @@ -123,6 +124,12 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
$ref: 'EditorToolSchema',
},
},
parents: {
type: 'array',
items: {
$ref: 'NoteSchema',
},
},
},
},
},
Expand Down Expand Up @@ -172,11 +179,14 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
*/
const canEdit = memberRole === MemberRole.Write;

const noteParentStructure = await noteService.getNoteParentStructure(noteId, userId!);

return reply.send({
note: notePublic,
parentNote: parentNote,
accessRights: { canEdit: canEdit },
tools: noteTools,
parents: noteParentStructure,
});
});

Expand Down
2 changes: 2 additions & 0 deletions src/repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +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);

/**
* Create associations between note and relations table
*/
noteStorage.createAssociationWithNoteRelationModel(noteRelationshipStorage.model);
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
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 type { NotePublic } from '@domain/entities/notePublic.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 getNoteParents(noteId: NoteInternalId, userId: number): Promise<NotePublic[]> {
return await this.storage.getAllNoteParents(noteId, userId);
}
}
Loading
Loading