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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

dependentmadani
Copy link

Problem:

First task in issue #275, make a method to return recursively note parent structure.

Solution:

Return an array of note parent structure on the following form:

NoteParents : 
[
  {
    noteId: Note['NotePublicId'],
    content: Note['content']
  }
]

Making sure that the user has access to each note parent before including them in the returned array.

Copy link

github-actions bot commented Aug 29, 2024

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 85.94% (🎯 80%)
⬆️ +0.15%
8572 / 9974
🟢 Statements 85.94% (🎯 80%)
⬆️ +0.15%
8572 / 9974
🔴 Functions 79.22% (🎯 80%)
⬆️ +0.31%
267 / 337
🟢 Branches 85.42% (🎯 80%)
⬆️ +0.15%
428 / 501
File Coverage
File Stmts % Branch % Funcs % Lines Uncovered Lines
Changed Files
src/domain/service/note.ts 96.09% 85% 100% 96.09% 100-101, 127-128, 139-140, 168-169, 190-191, 278-279, 367-368, 384-385, 426-427
src/presentation/http/router/note.ts 98.06% 80.64% 100% 98.06% 152-153, 622-623, 639-645, 706-707, 763-764
src/repository/note.repository.ts 100% 100% 100% 100%
src/repository/noteRelations.repository.ts 100% 100% 100% 100%
src/repository/storage/postgres/orm/sequelize/note.ts 97.69% 77.77% 100% 97.69% 192-193, 236-237, 240-241, 296-297
src/repository/storage/postgres/orm/sequelize/noteRelations.ts 100% 94.44% 100% 100%
src/repository/storage/postgres/orm/sequelize/teams.ts 99.19% 85.71% 100% 99.19% 195-196
Generated in workflow #849

src/domain/service/note.ts Outdated Show resolved Hide resolved
src/domain/service/note.ts Outdated Show resolved Hide resolved
src/domain/service/note.ts Outdated Show resolved Hide resolved
src/domain/entities/note.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/domain/entities/note.ts Outdated Show resolved Hide resolved
src/domain/entities/note.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
@e11sy
Copy link
Contributor

e11sy commented Sep 6, 2024

you can also patch GET /note endpoint and related test in this pr on in the following one

Good job, almost done!

@dependentmadani
Copy link
Author

resolve #275

docker-compose.yml Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Show resolved Hide resolved
src/domain/entities/note.ts Outdated Show resolved Hide resolved
src/domain/service/note.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.ts Outdated Show resolved Hide resolved
src/repository/storage/postgres/orm/sequelize/teams.ts Outdated Show resolved Hide resolved
docker-compose.yml Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected change

src/domain/service/note.ts Outdated Show resolved Hide resolved
src/repository/note.repository.ts Outdated Show resolved Hide resolved
Comment on lines 173 to 178
public createAssociationWithTeamsModel(model: ModelStatic<TeamsModel>): void {
/**
* Create association with teams
*/
this.teamModel = model;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that you need to access team model here. You can return all note parents by getNoteParents.

If you want to skip notes where user has no access, you can do it on a domain level. But I'm not sure that is is a real case. Is it possible that user has an access to a particular note, but has no access to it's parent?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably if there was a nested note relation, with different team members, where there could be some missing role access for team member in some specific notes. I might be wrong, that was based on what I understand from the code. 👀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think, that such a coupling of two independent modules (note and team) is bad idea. Such domains designed to be decoupled from each other.

My suggestion is to return all note parents. The only check is that user should has and access to the target note — it can be checked on a presentation (http) level via the notePublicOrUserInTeam policy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like creation of a golem, user would have ability to see notes which he has no access to (we return note with full content)
like in default cases
privateNote -> privateNote -> userInThisNoteTeam
all privateNotes should not be returned in this case but in your suggestion user would have ability to see full note contents of them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that we can use shared domains and move this logic to domain level as trade-off between speed and architectural purity

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep it simple for the moment, as @neSpecc proposed, by returning all the note parents, when the user try to access the note, and he does not have access to, the return of content will be blocked by the policy used.

src/repository/storage/postgres/orm/sequelize/note.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
/** Returns one parent in case where there is no note relation with 200 status */
{
numberOfNotes: 1,
userNoteCreationDifferent: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming is not clear

src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
src/presentation/http/router/note.test.ts Outdated Show resolved Hide resolved
grandChildNote: {} as Note,
};

beforeEach(async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may affect all existing tests. What is the reason to add it event in tests that does not need it?

test.each([
/** Returns two parents in case of relation between child and parent notes with 200 status */
{
testCase: 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testCase — unclear variable

isPublic: true,
expectedStatusCode: 200,
},
])('Get note parents in different scenarios', async ({ testCase, numberOfNotes, childNoteCreatedByDifferentUser, isPublic, expectedStatusCode }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unclear test case name

url: `/note/${notePublicId}`,
});

switch (testCase) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its hard to read such a big and complex text. Test.each designed for simple tests. In current implementation it decreases readability

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make it clearer, by providing testCase variable, that indicates which object in test.each. testCase is like an incremental index over the array of objects in test.each.

* @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[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public async getNoteParentStructure(noteId: NoteInternalId, userId: number): Promise<NotePublic[]> {
public async getNoteParents(noteId: NoteInternalId, userId: number): Promise<NotePublic[]> {

Comment on lines 173 to 178
public createAssociationWithTeamsModel(model: ModelStatic<TeamsModel>): void {
/**
* Create association with teams
*/
this.teamModel = model;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think, that such a coupling of two independent modules (note and team) is bad idea. Such domains designed to be decoupled from each other.

My suggestion is to return all note parents. The only check is that user should has and access to the target note — it can be checked on a presentation (http) level via the notePublicOrUserInTeam policy

src/repository/storage/postgres/orm/sequelize/note.ts Outdated Show resolved Hide resolved
* @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<Note[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I'd suggest to move this method to the NoteRelationsRepository since it is responsible for working with note relations.

  2. And remove added association from Note Storage.

  3. Repository can return a list of NoteInternalId

  4. Then on Domain level (in a Note Service) you can access Note Repository for getting Notes by ids.

  5. For doing this, you can implement getNotesByIds method in Note Repository and Store.

src/presentation/http/router/note.test.ts Show resolved Hide resolved
public async getNoteParents(noteId: NoteInternalId): Promise<NotePublic[]> {
const noteParents = await this.noteRepository.getNoteParents(noteId);
const noteParentsPublic: NotePublic[] = noteParents.map((note) => {
return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using definePublicNote() util

*/
public async getNoteParents(noteId: NoteInternalId): Promise<NotePublic[]> {
const noteIds: NoteInternalId[] = await this.noteRelationsRepository.getNoteParentsIds(noteId);
const noteParents = await this.noteRelationsRepository.getNotesByIds(noteIds);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNoteByIds should belong to NoteRepository

@@ -518,6 +564,221 @@ describe('Note API', () => {

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

test('Returns two parents note in case of relation between child and parent note with status 200', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('Returns two parents note in case of relation between child and parent note with status 200', async () => {
test('Returns two parents note in case when note has two parents', async () => {

});
});

test('Returns three parents note in case of relation between all notes with status 200', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unclear case name, especially this:

in case of relation between all notes

});
});

test('Returns two parents note in case where the note is not created by the same user but share relation with status 200', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this test? is't it repeat the first one?

Copy link
Author

@dependentmadani dependentmadani Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not totally the same as the first one, as here there is one note created by user, and parent note created by anotherUser, but there is a note relation between both notes.

});
});

test('Returns one note in case where there is no relation exist for the note with status 200', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case is also unclear for me. If note has no relation, why do you return a some note?

@@ -139,6 +139,7 @@ export async function init(orm: Orm, s3Config: S3StorageConfig): Promise<Reposit
/**
* Create associations between note and relations table
*/
noteStorage.createAssociationWithNoteRelationModel(noteRelationshipStorage.model);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant association

Comment on lines 229 to 235
const note: Note | null = await this.noteModel.findOne({
where: { id: currentNoteId },
});

if (notEmpty(note)) {
parentNotes.push(note.id);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems redundant

Comment on lines 238 to 240
const noteRelation: NoteRelationsModel | null = await this.model.findOne({
where: { noteId: currentNoteId },
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, you can try to get all note ids via a singe sql query instead of many

});
});

test('Returns one parents note in case where the note is not created by the same user but share relation', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain that user is not in parent note team, that would be more clear

src/repository/note.repository.ts Show resolved Hide resolved
@@ -518,6 +518,221 @@ describe('Note API', () => {

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

test('Returns one parents note in case when note has one parents', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('Returns one parents note in case when note has one parents', async () => {
test('Returns one parents note in case when note has one parent', async () => {

});
});

test('Returns two parents note in case when note has two parents', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('Returns two parents note in case when note has two parents', async () => {
test('Returns two note parents in case when note has two parents', async () => {

});
});

test('Returns one parent\'s note when the user is not in the parent note\'s team but shares a relation', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('Returns one parent\'s note when the user is not in the parent note\'s team but shares a relation', async () => {
test('Returns one parent note when the user is not in the parent note\'s team but shares a relation', async () => {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also if note has parent, that already means that relation exists, so but shares a relation is unneded

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, i did not get the case, if one parent note is returned, then overall two notes would be returned? (actual note and parent) or only just actual note?

* @param noteId - id of the note to get parent structure
* @returns - array of notes that are parent structure of the note
*/
public async getNoteParents(noteId: NoteInternalId): Promise<NotePublic[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Domain level should return Note[]. The Presentation level is responsible for converting it to NotePublic[]

Comment on lines +563 to +570
{
id: parentNote.publicId,
content: parentNote.content,
},
{
id: childNote.publicId,
content: childNote.content,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns one parents note in case when note has one parents

so why there are two notes?

});
});

test('Returns two parents note in case when note has two parents', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test repeats the previous one? or its name is not clear. What the exact case you are testing?

});
});

test('Returns one parent\'s note when the user is not in the parent note\'s team but shares a relation', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shares a relation

unclear statement

* @returns an array of note parents ids
*/
public async getNoteParentsIds(noteId: NoteInternalId): Promise<NoteInternalId[]> {
return await this.storage.getAllNoteParentsIds(noteId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the meaning of "All" here? is it something different between regular getNoteParentsIds which is used in other places?

Comment on lines +335 to +343
for (const noteId of noteIds) {
const note: Note | null = await this.model.findOne({
where: { id: noteId },
});

if (notEmpty(note)) {
notes.push(note);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its better to use the single sql query instead of looping over them

let currentNoteId: number | null = noteId;

// get all note ids via a singe sql query instead of many
while (currentNoteId !== null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tried to resolve it by a single sql query?

/**
* Note relation content
*/
public declare notes?: NoteModel | null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems unused

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants