diff --git a/.eslintrc b/.eslintrc index c937e8c3..bd37a813 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,6 +14,7 @@ "match": false } } - ] + ], + "jsdoc/require-returns-type": "off" } } diff --git a/src/domain/entities/note.ts b/src/domain/entities/note.ts index d2994df3..30a3541c 100644 --- a/src/domain/entities/note.ts +++ b/src/domain/entities/note.ts @@ -36,10 +36,20 @@ export interface Note { * Note creator id */ creatorId: NoteCreatorId; + + /** + * When note was created + */ + createdAt: string; + + /** + * Last time note was updated + */ + updatedAt: string; } /** - * Notes creation attributes, omitting id, because it's generated by database + * Part of note entity used to create new note */ -export type NoteCreationAttributes = Omit; +export type NoteCreationAttributes = Pick; diff --git a/src/domain/service/note.ts b/src/domain/service/note.ts index b36355c5..6863b435 100644 --- a/src/domain/service/note.ts +++ b/src/domain/service/note.ts @@ -36,6 +36,24 @@ export default class NoteService { }); } + /** + * Updates note + * + * @param id - note id + * @param content - new content + * @returns updated note + * @throws { Error } if note was not updated + */ + public async updateNoteContentByPublicId(id: NotePublicId, content: Note['content']): Promise { + const updatedNote = await this.repository.updateNoteContentByPublicId(id, content); + + if (updatedNote === null) { + throw new Error(`Note with id ${id} was not updated`); + } + + return updatedNote; + } + /** * Gets note by id * diff --git a/src/presentation/http/middlewares/authRequired.ts b/src/presentation/http/middlewares/authRequired.ts index e6636c59..d0410020 100644 --- a/src/presentation/http/middlewares/authRequired.ts +++ b/src/presentation/http/middlewares/authRequired.ts @@ -40,6 +40,8 @@ export default (authService: AuthService): preHandlerHookHandler => { await authService.verifyAccessToken(token); done(); + + return reply; } catch (error) { return reply .code(StatusCodes.UNAUTHORIZED) diff --git a/src/presentation/http/middlewares/withUser.ts b/src/presentation/http/middlewares/withUser.ts index 0b5f4c4b..db438706 100644 --- a/src/presentation/http/middlewares/withUser.ts +++ b/src/presentation/http/middlewares/withUser.ts @@ -25,7 +25,7 @@ export default (authService: AuthService): preHandlerHookHandler => { if (!notEmpty(authorizationHeader)) { done(); - return; + return reply; } /** @@ -44,6 +44,8 @@ export default (authService: AuthService): preHandlerHookHandler => { }; } finally { done(); + + return reply; } }; }; diff --git a/src/presentation/http/router/note.ts b/src/presentation/http/router/note.ts index 6f078228..561010a8 100644 --- a/src/presentation/http/router/note.ts +++ b/src/presentation/http/router/note.ts @@ -27,6 +27,22 @@ interface AddNoteOptions { content: JSON; } +/** + * Payload for update note request + */ +interface UpdateNoteOptions { + /** + * Note public id + */ + id: NotePublicId; + + /** + * New content + */ + content: JSON; +} + + /** * Interface for the note router. */ @@ -185,6 +201,34 @@ const NoteRouter: FastifyPluginCallback = (fastify, opts, don }); }); + /** + * Updates note by id. + */ + fastify.patch<{ + Body: UpdateNoteOptions, + Reply: { + updatedAt: Note['updatedAt'], + } + }>('/', { + preHandler: [ + opts.middlewares.authRequired, + opts.middlewares.withUser, + ], + }, async (request, reply) => { + /** + * @todo Validate request params + * @todo Check user access right + */ + const { id, content } = request.body; + + const note = await noteService.updateNoteContentByPublicId(id, content); + + return reply.send({ + updatedAt: note.updatedAt, + }); + }); + + /** * Get note by custom hostname */ diff --git a/src/repository/note.repository.ts b/src/repository/note.repository.ts index 43d502b9..20cc1b48 100644 --- a/src/repository/note.repository.ts +++ b/src/repository/note.repository.ts @@ -33,6 +33,17 @@ export default class NoteRepository { return await this.storage.createNote(options); } + /** + * Update note content in a store using note public id + * + * @param publicId - note public id + * @param content - new content + * @returns Note on success, null on failure + */ + public async updateNoteContentByPublicId(publicId: NotePublicId, content: Note['content'] ): Promise { + return await this.storage.updateNoteContentByPublicId(publicId, content); + } + /** * Gets note by id * diff --git a/src/repository/storage/postgres/orm/sequelize/note.ts b/src/repository/storage/postgres/orm/sequelize/note.ts index 822f1996..9ad2ea00 100644 --- a/src/repository/storage/postgres/orm/sequelize/note.ts +++ b/src/repository/storage/postgres/orm/sequelize/note.ts @@ -34,6 +34,16 @@ export class NoteModel extends Model, InferCreationAt * Note creator, user identifier, who created this note */ public declare creatorId: Note['creatorId']; + + /** + * Time when note was created + */ + public declare createdAt: CreationOptional; + + /** + * Last time when note was updated + */ + public declare updatedAt: CreationOptional; } @@ -96,6 +106,8 @@ export default class NoteSequelizeStorage { key: 'id', }, }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, }, { tableName: this.tableName, sequelize: this.database, @@ -155,6 +167,30 @@ export default class NoteSequelizeStorage { return createdNote; } + /** + * Update note content by public id + * + * @param publicId - note public id + * @param content - new content + * @returns Note on success, null on failure + */ + public async updateNoteContentByPublicId(publicId: NotePublicId, content: Note['content']): Promise { + const [affectedRowsCount, affectedRows] = await this.model.update({ + content, + }, { + where: { + publicId, + }, + returning: true, + }); + + if (affectedRowsCount !== 1) { + return null; + } + + return affectedRows[0]; + } + /** * Gets note by id *