Skip to content

Commit

Permalink
Feat: route for updating parent relation (#192)
Browse files Browse the repository at this point in the history
* deleteParentNoteRelationByNoteId func

* add func updateNoteRelationById in note service

* route to update parent

* tests for new route

* remove duplicate tests

* refactor: remove delete functionality from updateNoteRelation

* refactor: PATCH /note/:noteId/parent route does not delete relations

* remove updating parent from updateNoteContent

* add new check in tests
  • Loading branch information
kloV148 authored Mar 13, 2024
1 parent b43b814 commit 50c2154
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 16 deletions.
36 changes: 24 additions & 12 deletions src/domain/service/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Note> {
public async updateNoteContentById(id: NoteInternalId, content: Note['content']): Promise<Note> {
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;
}

Expand Down Expand Up @@ -174,4 +163,27 @@ export default class NoteService {
public async getParentNoteIdByNoteId(noteId: Note['id']): Promise<number | null> {
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<boolean> {
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);
};
}
123 changes: 122 additions & 1 deletion src/presentation/http/router/note.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');
});
});
});
58 changes: 55 additions & 3 deletions src/presentation/http/router/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
},
Body: {
content: JSON;
parentId?: NotePublicId;
},
Reply: {
updatedAt: Note['updatedAt'],
Expand Down Expand Up @@ -264,15 +263,68 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (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
*/
Expand Down

0 comments on commit 50c2154

Please sign in to comment.