diff --git a/source/api/v1/auth/AuthPreHandler.ts b/source/api/v1/auth/AuthPreHandler.ts index ff8ee98..4b6546c 100644 --- a/source/api/v1/auth/AuthPreHandler.ts +++ b/source/api/v1/auth/AuthPreHandler.ts @@ -1,13 +1,15 @@ import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; -import { extractToken } from "../utils/common/TokenExtractor"; +import { extractToken } from "../shared/utils/common/TokenExtractor"; import { extractJwtPayload } from "./jwt/PayloadExtractor"; import { validateSignature } from "./jwt/SignatureValidator"; -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; import { IAuthService } from "../services/interfaces/AuthServiceInterface"; -import { isException } from "../utils/guards/ExceptionGuard"; +import { isException } from "../shared/utils/guards/ExceptionGuard"; + +export type AuthentificationPreHandler = (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => void export const authenticationFactory = (authService: IAuthService) => -async (request: FastifyRequest, reply: FastifyReply, _done: HookHandlerDoneFunction) => { +async (request: FastifyRequest, reply: FastifyReply, _done: HookHandlerDoneFunction): Promise => { const token = extractToken(request) if (!token) { reply.code(401).send(USER_EXCEPTIONS.NotAuthorized) @@ -26,19 +28,11 @@ async (request: FastifyRequest, reply: FastifyReply, _done: HookHandlerDoneFunct return } - // try { const relevanceState = await authService.checkTokenRelevance(payload.login, token) if (isException(relevanceState)) { reply.code( relevanceState.statusCode ).send(relevanceState) return - } - // } catch (exception: any) { - // reply.code( - // exception.statusCode - // ).send(exception) - // return - // } - + } } diff --git a/source/api/v1/auth/jwt/SignatureValidator.ts b/source/api/v1/auth/jwt/SignatureValidator.ts index a3a10fc..59889cc 100644 --- a/source/api/v1/auth/jwt/SignatureValidator.ts +++ b/source/api/v1/auth/jwt/SignatureValidator.ts @@ -1,5 +1,5 @@ import * as jwt from 'jsonwebtoken' -import { CONFIG } from '../../config/ServerConfiguration' +import { CONFIG } from '../../shared/config/ServerConfiguration' export const validateSignature = (token: string): boolean => { try { diff --git a/source/api/v1/auth/jwt/TokenGenerator.ts b/source/api/v1/auth/jwt/TokenGenerator.ts index 2b757da..a3eb232 100644 --- a/source/api/v1/auth/jwt/TokenGenerator.ts +++ b/source/api/v1/auth/jwt/TokenGenerator.ts @@ -1,5 +1,5 @@ import * as jwt from 'jsonwebtoken' -import { CONFIG } from '../../config/ServerConfiguration' +import { CONFIG } from '../../shared/config/ServerConfiguration' export const generateToken = (login: string): string => { return jwt.sign({ login }, CONFIG.jwtSecret, {expiresIn: CONFIG.jwtExpiration}) diff --git a/source/api/v1/handlers/AuthHandlers.ts b/source/api/v1/handlers/AuthHandlers.ts index 5c6d13a..1815d39 100644 --- a/source/api/v1/handlers/AuthHandlers.ts +++ b/source/api/v1/handlers/AuthHandlers.ts @@ -1,75 +1,79 @@ -import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; +import { FastifyInstance } from "fastify"; import { IAuthService } from "../services/interfaces/AuthServiceInterface"; import { UserCredentials } from "../database/entities/User"; -import { AUTH_EXCEPTIONS } from "../exceptions/AuthExceptions"; +import { AUTH_EXCEPTIONS } from "../shared/exceptions/AuthExceptions"; import { AuthUserSchema, ChangePasswordSchema } from "../validation/schemas/AuthSchemas"; import { extractJwtPayload } from "../auth/jwt/PayloadExtractor"; -import { extractToken } from "../utils/common/TokenExtractor"; -import { isException } from "../utils/guards/ExceptionGuard"; -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { extractToken } from "../shared/utils/common/TokenExtractor"; +import { isException } from "../shared/utils/guards/ExceptionGuard"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; +import { Handler } from "./Handler"; +import { AuthentificationPreHandler } from "../auth/AuthPreHandler"; -export const handleAuthRoutes = ( - server: FastifyInstance, - authService: IAuthService, - authenticate: ( - request: FastifyRequest, - reply: FastifyReply, - done: HookHandlerDoneFunction - ) => void -) => { - server.post<{ - Body: UserCredentials, - Reply: { - 200: { token: string, expiresIn: string }, - 400: typeof AUTH_EXCEPTIONS.WrongCredentials, - 503: typeof AUTH_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable - } - }>("/auth", { - schema: AuthUserSchema - }, async (request, reply) => { - const credentials: UserCredentials = request.body - - const result = await authService.authorizeAndGenerateToken( - credentials.email, - credentials.password - ) - if (isException(result)) { - reply.code(result.statusCode).send(result) - return - } +export class AuthHandler extends Handler { + constructor( + server: FastifyInstance, + authentificationPreHandler: AuthentificationPreHandler, + authService: IAuthService + ) { + super(server, authentificationPreHandler, authService) + } - reply.code(200).send(result) + public override handleRoutes(): void { + this.server.post<{ + Body: UserCredentials, + Reply: { + 200: { token: string, expiresIn: string }, + 400: typeof AUTH_EXCEPTIONS.WrongCredentials, + 503: typeof AUTH_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable + } + }>("/auth", { + schema: AuthUserSchema + }, async (request, reply) => { + const credentials: UserCredentials = request.body + + const result = await this.service.authorizeAndGenerateToken( + credentials.email, + credentials.password + ) + if (isException(result)) { + reply.code(result.statusCode).send(result) + return + } - }) - - server.patch<{ - Body: { oldPassword: string, newPassword: string }, - Reply: { - 200: { success: true }, - 400: typeof AUTH_EXCEPTIONS.WrongCredentials | typeof AUTH_EXCEPTIONS.NewPasswordIsSame, - 503: typeof AUTH_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable - } - }>("/auth/password", { - schema: ChangePasswordSchema, - preHandler: authenticate - }, async (request, reply) => { - const passwords = request.body - const { login } = extractJwtPayload( - extractToken(request) - ) - - const state = await authService.changePassword( - login, - passwords.oldPassword, - passwords.newPassword - ) - - if (isException(state)) { - reply.code(state.statusCode).send(state) - return - } + reply.code(200).send(result) - reply.code(200).send(state) - - }) -} \ No newline at end of file + }) + + this.server.patch<{ + Body: { oldPassword: string, newPassword: string }, + Reply: { + 200: { success: true }, + 400: typeof AUTH_EXCEPTIONS.WrongCredentials | typeof AUTH_EXCEPTIONS.NewPasswordIsSame, + 503: typeof AUTH_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable + } + }>("/auth/password", { + schema: ChangePasswordSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const passwords = request.body + const { login } = extractJwtPayload( + extractToken(request) + ) + + const state = await this.service.changePassword( + login, + passwords.oldPassword, + passwords.newPassword + ) + + if (isException(state)) { + reply.code(state.statusCode).send(state) + return + } + + reply.code(200).send(state) + + }) + } +} diff --git a/source/api/v1/handlers/Handler.ts b/source/api/v1/handlers/Handler.ts new file mode 100644 index 0000000..d77470f --- /dev/null +++ b/source/api/v1/handlers/Handler.ts @@ -0,0 +1,12 @@ +import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; +import { AuthentificationPreHandler } from "../auth/AuthPreHandler"; + +export abstract class Handler { + constructor( + protected server: FastifyInstance, + protected authentificationPreHandler: AuthentificationPreHandler, + protected service: TService + ) {} + + public abstract handleRoutes(): void +} \ No newline at end of file diff --git a/source/api/v1/handlers/NotesHandlers.ts b/source/api/v1/handlers/NotesHandlers.ts index 22324a3..f351eb7 100644 --- a/source/api/v1/handlers/NotesHandlers.ts +++ b/source/api/v1/handlers/NotesHandlers.ts @@ -1,300 +1,303 @@ -import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; +import { FastifyInstance } from "fastify"; import { INotesService } from "../services/interfaces/NotesServiceInterface"; import { Note, NoteCollaborators, NotePreview, NoteUpdate, NoteWithoutMetadata } from "../database/entities/Note"; -import { NOTE_EXCEPTIONS } from "../exceptions/NoteExceptions"; +import { NOTE_EXCEPTIONS } from "../shared/exceptions/NoteExceptions"; import { extractJwtPayload } from "../auth/jwt/PayloadExtractor"; -import { extractToken } from "../utils/common/TokenExtractor"; +import { extractToken } from "../shared/utils/common/TokenExtractor"; import { AddCollaboratorSchema, CreateNoteSchema, DeleteNoteSchema, GetNoteCollaboratorsSchema, GetNoteSchema, GetNotesSchema, RemoveCollaboratorSchema, UpdateNoteSchema } from "../validation/schemas/NoteSchemas"; -import { isException } from "../utils/guards/ExceptionGuard"; -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { isException } from "../shared/utils/guards/ExceptionGuard"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; +import { Handler } from "./Handler"; +import { AuthentificationPreHandler } from "../auth/AuthPreHandler"; +export class NotesHandler extends Handler { + constructor( + server: FastifyInstance, + authentificationPreHandler: AuthentificationPreHandler, + notesService: INotesService + ) { + super(server, authentificationPreHandler, notesService) + } -export const handleNoteRoutes = ( - server: FastifyInstance, - notesService: INotesService, - authenticate: ( - request: FastifyRequest, - reply: FastifyReply, - done: HookHandlerDoneFunction - ) => void -) => { - server.post<{ - Body: Omit, - Reply: { - 201: Note, - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable - 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound - } - }>("/notes", { - schema: CreateNoteSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const insertData = { - ...request.body, - author: login - } - const createdNote = await notesService.createNote(insertData) - if (isException(createdNote)) { - reply.code(createdNote.statusCode).send(createdNote) - return - } - - reply.code(201).send(createdNote) - - }) - - server.get<{ - Querystring: { - limit: number, - offset: number, - sort: "ASC" | "DESC", - tags: string[] - }, - Reply: { - 200: NotePreview[], - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - } - }>("/notes/my", - { - schema: GetNotesSchema, - preHandler: authenticate - }, - async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const limit = request.query.limit - const skip = request.query.offset - const sort = request.query.sort - const tags = request.query.tags - - const notes = await notesService.getMyNotes(login, {tags, limit, skip, sort}) - if (isException(notes)) { - reply.code(notes.statusCode).send(notes) - return - } - - reply.code(200).send(notes) - - }) - - server.get<{ - Querystring: { - limit: number, - offset: number, - sort: "ASC" | "DESC", - tags: string[] - }, - Reply: { - 200: NotePreview[], - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - } - }>("/notes/collaborated", { - schema: GetNotesSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const limit = request.query.limit - const skip = request.query.offset - const sort = request.query.sort - const tags = request.query.tags - - const notes = await notesService.getCollaboratedNotes(login, {tags, limit, skip, sort}) - if (isException(notes)) { - reply.code(notes.statusCode).send(notes) - return - } - reply.code(200).send(notes) - }) - - server.get<{ - Params: { id: string }, - Reply: { - 200: Note, - 404: typeof NOTE_EXCEPTIONS.NoteNotFound, - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - } - }>("/notes/:id", - { - schema: GetNoteSchema, - preHandler: authenticate - }, - async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - - const foundNote = await notesService.getNote(id, login) - if (isException(foundNote)) { - reply.code(foundNote.statusCode).send(foundNote) - return - } - reply.code(200).send(foundNote) - - }) - - server.delete<{ - Params: { id: string }, - Reply: { - 200: { success: true }, - 404: typeof NOTE_EXCEPTIONS.NoteNotFound, - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - } - }>("/notes/:id", - { - schema: DeleteNoteSchema, - preHandler: authenticate - }, - async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - - const state = await notesService.deleteNote(id, login) - if (isException(state)) { - reply.code(state.statusCode).send(state) - return - } - reply.code(200).send(state) - - }) - - server.patch<{ - Params: { id: string }, - Reply: { - 200: Note, - 404: typeof NOTE_EXCEPTIONS.NoteNotFound, - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - }, - Body: NoteUpdate - }>("/notes/:id", { - schema: UpdateNoteSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - const updateData = request.body - - const updatedNote = await notesService.updateNote(id, login, updateData) - if (isException(updatedNote)) { - reply.code(updatedNote.statusCode).send(updatedNote) - return - } - - reply.code(200).send(updatedNote) - - }) - - server.get<{ - Params: { id: string }, - Reply: { - 200: NoteCollaborators - 404: typeof NOTE_EXCEPTIONS.NoteNotFound - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - } - }>("/notes/:id/collaborators", { - schema: GetNoteCollaboratorsSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - - const collaborators = await notesService.getCollaborators(id, login) - if (isException(collaborators)) { - reply.code(collaborators.statusCode).send(collaborators) - return - } - reply.code(200).send(collaborators) - - }) - - server.put<{ - Params: { id: string }, - Body: { - collaboratorLogin: string - }, - Reply: { - 201: {success: true} - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable, - 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound | typeof NOTE_EXCEPTIONS.NoteNotFound - 400: typeof NOTE_EXCEPTIONS.CollaboratorAlreadyInNote - 403: typeof NOTE_EXCEPTIONS.AcessRestricted - } - }>("/notes/:id/collaborators", { - schema: AddCollaboratorSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - const collaboratorLogin = request.body.collaboratorLogin - - const state = await notesService.addCollaborator( - id, - login, - collaboratorLogin - ) - if (isException(state)) { - reply.code(state.statusCode).send(state) - return - } - - reply.code(201).send(state) - }) - - server.delete<{ - Params: { id: string }, - Body: { - collaboratorLogin: string - }, - Reply: { - 200: {success: true} - 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable - 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound | typeof NOTE_EXCEPTIONS.NoteNotFound - 403: typeof NOTE_EXCEPTIONS.AcessRestricted - } - }>("/notes/:id/collaborators", { - schema: RemoveCollaboratorSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const id = request.params.id - const collaboratorLogin = request.body.collaboratorLogin - - const state = await notesService.removeCollaborator( - id, - login, - collaboratorLogin - ) - if (isException(state)) { - reply.code(state.statusCode).send(state) - return - } - - reply.code(200).send(state) - - }) -} \ No newline at end of file + public override handleRoutes(): void { + this.server.post<{ + Body: Omit, + Reply: { + 201: Note, + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable | typeof USER_EXCEPTIONS.ServiceUnavailable + 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound + } + }>("/notes", { + schema: CreateNoteSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const insertData = { + ...request.body, + author: login + } + const createdNote = await this.service.createNote(insertData) + if (isException(createdNote)) { + reply.code(createdNote.statusCode).send(createdNote) + return + } + + reply.code(201).send(createdNote) + + }) + + this.server.get<{ + Querystring: { + limit: number, + offset: number, + sort: "ASC" | "DESC", + tags: string[] + }, + Reply: { + 200: NotePreview[], + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + } + }>("/notes/my", + { + schema: GetNotesSchema, + preHandler: this.authentificationPreHandler + }, + async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const limit = request.query.limit + const skip = request.query.offset + const sort = request.query.sort + const tags = request.query.tags + + const notes = await this.service.getMyNotes(login, {tags, limit, skip, sort}) + if (isException(notes)) { + reply.code(notes.statusCode).send(notes) + return + } + + reply.code(200).send(notes) + + }) + + this.server.get<{ + Querystring: { + limit: number, + offset: number, + sort: "ASC" | "DESC", + tags: string[] + }, + Reply: { + 200: NotePreview[], + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + } + }>("/notes/collaborated", { + schema: GetNotesSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const limit = request.query.limit + const skip = request.query.offset + const sort = request.query.sort + const tags = request.query.tags + + const notes = await this.service.getCollaboratedNotes(login, {tags, limit, skip, sort}) + if (isException(notes)) { + reply.code(notes.statusCode).send(notes) + return + } + reply.code(200).send(notes) + }) + + this.server.get<{ + Params: { id: string }, + Reply: { + 200: Note, + 404: typeof NOTE_EXCEPTIONS.NoteNotFound, + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + } + }>("/notes/:id", + { + schema: GetNoteSchema, + preHandler: this.authentificationPreHandler + }, + async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + + const foundNote = await this.service.getNote(id, login) + if (isException(foundNote)) { + reply.code(foundNote.statusCode).send(foundNote) + return + } + reply.code(200).send(foundNote) + + }) + + this.server.delete<{ + Params: { id: string }, + Reply: { + 200: { success: true }, + 404: typeof NOTE_EXCEPTIONS.NoteNotFound, + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + } + }>("/notes/:id", + { + schema: DeleteNoteSchema, + preHandler: this.authentificationPreHandler + }, + async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + + const state = await this.service.deleteNote(id, login) + if (isException(state)) { + reply.code(state.statusCode).send(state) + return + } + reply.code(200).send(state) + + }) + + this.server.patch<{ + Params: { id: string }, + Reply: { + 200: Note, + 404: typeof NOTE_EXCEPTIONS.NoteNotFound, + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + }, + Body: NoteUpdate + }>("/notes/:id", { + schema: UpdateNoteSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + const updateData = request.body + + const updatedNote = await this.service.updateNote(id, login, updateData) + if (isException(updatedNote)) { + reply.code(updatedNote.statusCode).send(updatedNote) + return + } + + reply.code(200).send(updatedNote) + + }) + + this.server.get<{ + Params: { id: string }, + Reply: { + 200: NoteCollaborators + 404: typeof NOTE_EXCEPTIONS.NoteNotFound + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + } + }>("/notes/:id/collaborators", { + schema: GetNoteCollaboratorsSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + + const collaborators = await this.service.getCollaborators(id, login) + if (isException(collaborators)) { + reply.code(collaborators.statusCode).send(collaborators) + return + } + reply.code(200).send(collaborators) + + }) + + this.server.put<{ + Params: { id: string }, + Body: { + collaboratorLogin: string + }, + Reply: { + 201: {success: true} + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable, + 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound | typeof NOTE_EXCEPTIONS.NoteNotFound + 400: typeof NOTE_EXCEPTIONS.CollaboratorAlreadyInNote + 403: typeof NOTE_EXCEPTIONS.AcessRestricted + } + }>("/notes/:id/collaborators", { + schema: AddCollaboratorSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + const collaboratorLogin = request.body.collaboratorLogin + + const state = await this.service.addCollaborator( + id, + login, + collaboratorLogin + ) + if (isException(state)) { + reply.code(state.statusCode).send(state) + return + } + + reply.code(201).send(state) + }) + + this.server.delete<{ + Params: { id: string }, + Body: { + collaboratorLogin: string + }, + Reply: { + 200: {success: true} + 503: typeof NOTE_EXCEPTIONS.ServiceUnavailable + 404: typeof NOTE_EXCEPTIONS.CollaboratorNotFound | typeof NOTE_EXCEPTIONS.NoteNotFound + 403: typeof NOTE_EXCEPTIONS.AcessRestricted + } + }>("/notes/:id/collaborators", { + schema: RemoveCollaboratorSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const id = request.params.id + const collaboratorLogin = request.body.collaboratorLogin + + const state = await this.service.removeCollaborator( + id, + login, + collaboratorLogin + ) + if (isException(state)) { + reply.code(state.statusCode).send(state) + return + } + + reply.code(200).send(state) + + }) + } +} diff --git a/source/api/v1/handlers/UsersHandlers.ts b/source/api/v1/handlers/UsersHandlers.ts index 0d66cba..214d020 100644 --- a/source/api/v1/handlers/UsersHandlers.ts +++ b/source/api/v1/handlers/UsersHandlers.ts @@ -1,120 +1,124 @@ -import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; +import { FastifyInstance } from "fastify"; import { IUsersService } from "../services/interfaces/UsersServiceInterface"; import { UserUpdate, UserWithoutMetadata, UserWithoutSensetives } from "../database/entities/User"; -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; import { CreateUserSchema, GetMyProfileSchema, GetUserSchema, UpdateUserSchema } from "../validation/schemas/UserSchemas"; import { UsersService } from "../services/UsersService"; import { extractJwtPayload } from "../auth/jwt/PayloadExtractor"; -import { extractToken } from "../utils/common/TokenExtractor"; -import { isException } from "../utils/guards/ExceptionGuard"; +import { extractToken } from "../shared/utils/common/TokenExtractor"; +import { isException } from "../shared/utils/guards/ExceptionGuard"; +import { Handler } from "./Handler"; +import { AuthentificationPreHandler } from "../auth/AuthPreHandler"; -export const handleUserRoutes = ( - server: FastifyInstance, - usersService: IUsersService, - authenticate: ( - request: FastifyRequest, - reply: FastifyReply, - done: HookHandlerDoneFunction - ) => void -) => { - server.post<{ - Body: UserWithoutMetadata, - Reply: { - 201: UserWithoutSensetives, - 400: typeof USER_EXCEPTIONS.AlreadyExists, - 503: typeof USER_EXCEPTIONS.ServiceUnavailable - } - }>("/users", { schema: CreateUserSchema }, async (request, reply) => { +export class UsersHandler extends Handler { + constructor( + server: FastifyInstance, + authentificationPreHandler: AuthentificationPreHandler, + usersService: IUsersService + ) { + super(server, authentificationPreHandler, usersService) + } - const insertData: UserWithoutMetadata = request.body - let createdUser = await usersService.createUser(insertData) - - if (isException(createdUser)) { - reply.code(createdUser.statusCode).send(createdUser) - return - } - - UsersService.omitSensetiveData(createdUser) - - reply.code(201).send(createdUser) - - }) - - server.get<{ - Reply: { - 200: UserWithoutSensetives, - 404: typeof USER_EXCEPTIONS.NotFound, - 503: typeof USER_EXCEPTIONS.ServiceUnavailable, - } - }>("/users/me", { - schema: GetMyProfileSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - let user = await usersService.getUser("login", login) - if (isException(user)) { - reply.code(user.statusCode).send(user) - return - } - UsersService.omitSensetiveData(user) - - reply.code(200).send(user) - - }) - - server.patch<{ - Body: Omit, - Reply: { - 200: UserWithoutSensetives, - 404: typeof USER_EXCEPTIONS.NotFound, - 503: typeof USER_EXCEPTIONS.ServiceUnavailable - } - }>("/users/me", { - schema: UpdateUserSchema, - preHandler: authenticate - }, async (request, reply) => { - const { login } = extractJwtPayload( - extractToken(request) - ) - - const updateData = request.body - - let updatedUser = await usersService.updateUserByLogin(login, updateData) - if (isException(updatedUser)) { - reply.code(updatedUser.statusCode).send(updatedUser) - return - } - UsersService.omitSensetiveData(updatedUser) - - reply.code(200).send(updatedUser) - - }) - - server.get<{ - Params: { login: string }, - Reply: { - 200: UserWithoutSensetives, - 404: typeof USER_EXCEPTIONS.NotFound, - 503: typeof USER_EXCEPTIONS.ServiceUnavailable - } - }>("/users/:login", { - schema: GetUserSchema, - preHandler: authenticate - }, async (request, reply) => { - const login: string = request.params.login - - let user = await usersService.getUser("login", login) - if (isException(user)) { - reply.code(user.statusCode).send(user) - return - } - UsersService.omitSensetiveData(user) - - reply.code(200).send(user) - - }) -} \ No newline at end of file + public override handleRoutes(): void { + this.server.post<{ + Body: UserWithoutMetadata, + Reply: { + 201: UserWithoutSensetives, + 400: typeof USER_EXCEPTIONS.AlreadyExists, + 503: typeof USER_EXCEPTIONS.ServiceUnavailable + } + }>("/users", { schema: CreateUserSchema }, async (request, reply) => { + + const insertData: UserWithoutMetadata = request.body + let createdUser = await this.service.createUser(insertData) + + if (isException(createdUser)) { + reply.code(createdUser.statusCode).send(createdUser) + return + } + + UsersService.omitSensetiveData(createdUser) + + reply.code(201).send(createdUser) + + }) + + this.server.get<{ + Reply: { + 200: UserWithoutSensetives, + 404: typeof USER_EXCEPTIONS.NotFound, + 503: typeof USER_EXCEPTIONS.ServiceUnavailable, + } + }>("/users/me", { + schema: GetMyProfileSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + let user = await this.service.getUser("login", login) + if (isException(user)) { + reply.code(user.statusCode).send(user) + return + } + UsersService.omitSensetiveData(user) + + reply.code(200).send(user) + + }) + + this.server.patch<{ + Body: Omit, + Reply: { + 200: UserWithoutSensetives, + 404: typeof USER_EXCEPTIONS.NotFound, + 503: typeof USER_EXCEPTIONS.ServiceUnavailable + } + }>("/users/me", { + schema: UpdateUserSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const { login } = extractJwtPayload( + extractToken(request) + ) + + const updateData = request.body + + let updatedUser = await this.service.updateUserByLogin(login, updateData) + if (isException(updatedUser)) { + reply.code(updatedUser.statusCode).send(updatedUser) + return + } + UsersService.omitSensetiveData(updatedUser) + + reply.code(200).send(updatedUser) + + }) + + this.server.get<{ + Params: { login: string }, + Reply: { + 200: UserWithoutSensetives, + 404: typeof USER_EXCEPTIONS.NotFound, + 503: typeof USER_EXCEPTIONS.ServiceUnavailable + } + }>("/users/:login", { + schema: GetUserSchema, + preHandler: this.authentificationPreHandler + }, async (request, reply) => { + const login: string = request.params.login + + let user = await this.service.getUser("login", login) + if (isException(user)) { + reply.code(user.statusCode).send(user) + return + } + UsersService.omitSensetiveData(user) + + reply.code(200).send(user) + + }) + } +} diff --git a/source/api/v1/services/AuthService.ts b/source/api/v1/services/AuthService.ts index 38be236..09cd258 100644 --- a/source/api/v1/services/AuthService.ts +++ b/source/api/v1/services/AuthService.ts @@ -1,13 +1,13 @@ import { generateToken } from "../auth/jwt/TokenGenerator"; import { IUsersService } from "./interfaces/UsersServiceInterface"; -import { AUTH_EXCEPTIONS } from "../exceptions/AuthExceptions"; +import { AUTH_EXCEPTIONS } from "../shared/exceptions/AuthExceptions"; import { IAuthService } from "./interfaces/AuthServiceInterface"; -import { CONFIG } from "../config/ServerConfiguration"; +import { CONFIG } from "../shared/config/ServerConfiguration"; import bcrypt from 'bcrypt' -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; import { RedisClientType } from "redis"; -import { withExceptionCatch } from "../decorators/WithExceptionCatch"; -import { isException } from "../utils/guards/ExceptionGuard"; +import { withExceptionCatch } from "../shared/decorators/WithExceptionCatch"; +import { isException } from "../shared/utils/guards/ExceptionGuard"; export class AuthService implements IAuthService { constructor( diff --git a/source/api/v1/services/NotesService.ts b/source/api/v1/services/NotesService.ts index be2969e..1d72039 100644 --- a/source/api/v1/services/NotesService.ts +++ b/source/api/v1/services/NotesService.ts @@ -11,13 +11,13 @@ import { } from "../database/entities/User"; import {IUsersService} from "./interfaces/UsersServiceInterface"; import {INotesService, NotesSearchOptions} from "./interfaces/NotesServiceInterface"; -import {NOTE_EXCEPTIONS} from "../exceptions/NoteExceptions"; +import {NOTE_EXCEPTIONS} from "../shared/exceptions/NoteExceptions"; import {Repository} from "typeorm"; -import {transformNoteCollaborators} from "../utils/common/TransformNoteCollaborators"; +import {transformNoteCollaborators} from "../shared/utils/common/TransformNoteCollaborators"; import {excludeProperties} from "typing-assets"; -import {withExceptionCatch} from "../decorators/WithExceptionCatch"; -import {isException} from "../utils/guards/ExceptionGuard"; -import {USER_EXCEPTIONS} from "../exceptions/UserExceptions"; +import {withExceptionCatch} from "../shared/decorators/WithExceptionCatch"; +import {isException} from "../shared/utils/guards/ExceptionGuard"; +import {USER_EXCEPTIONS} from "../shared/exceptions/UserExceptions"; export class NotesService implements INotesService { private static generateNoteId(): string { diff --git a/source/api/v1/services/UsersService.ts b/source/api/v1/services/UsersService.ts index bec1246..50ff183 100644 --- a/source/api/v1/services/UsersService.ts +++ b/source/api/v1/services/UsersService.ts @@ -1,9 +1,9 @@ import { User, UserUpdate, UserWithoutMetadata, UserWithoutSensetives } from "../database/entities/User"; import { IUsersService } from "./interfaces/UsersServiceInterface"; -import { USER_EXCEPTIONS } from "../exceptions/UserExceptions"; +import { USER_EXCEPTIONS } from "../shared/exceptions/UserExceptions"; import bcrypt from 'bcrypt' import { Repository } from "typeorm"; -import { withExceptionCatch } from "../decorators/WithExceptionCatch"; +import { withExceptionCatch } from "../shared/decorators/WithExceptionCatch"; export class UsersService implements IUsersService { /** diff --git a/source/api/v1/services/interfaces/AuthServiceInterface.d.ts b/source/api/v1/services/interfaces/AuthServiceInterface.d.ts index 939ec1e..bd6313e 100644 --- a/source/api/v1/services/interfaces/AuthServiceInterface.d.ts +++ b/source/api/v1/services/interfaces/AuthServiceInterface.d.ts @@ -1,5 +1,5 @@ -import { AUTH_EXCEPTIONS } from "../../exceptions/AuthExceptions"; -import { USER_EXCEPTIONS } from "../../exceptions/UserExceptions"; +import { AUTH_EXCEPTIONS } from "../../shared/exceptions/AuthExceptions"; +import { USER_EXCEPTIONS } from "../../shared/exceptions/UserExceptions"; export declare interface IAuthService { authorizeAndGenerateToken(email: string, password: string): Promise< diff --git a/source/api/v1/services/interfaces/NotesServiceInterface.d.ts b/source/api/v1/services/interfaces/NotesServiceInterface.d.ts index 1db9a09..265d634 100644 --- a/source/api/v1/services/interfaces/NotesServiceInterface.d.ts +++ b/source/api/v1/services/interfaces/NotesServiceInterface.d.ts @@ -1,7 +1,7 @@ import { DeepOptional } from "typing-assets"; import { Note, NoteCollaborators, NotePreview, NoteUpdate, NoteWithoutMetadata } from "../../database/entities/Note"; -import { NOTE_EXCEPTIONS } from "../../exceptions/NoteExceptions"; -import { USER_EXCEPTIONS } from "../../exceptions/UserExceptions"; +import { NOTE_EXCEPTIONS } from "../../shared/exceptions/NoteExceptions"; +import { USER_EXCEPTIONS } from "../../shared/exceptions/UserExceptions"; export type NotesSearchOptions = DeepOptional<{ tags: string[] diff --git a/source/api/v1/services/interfaces/UsersServiceInterface.d.ts b/source/api/v1/services/interfaces/UsersServiceInterface.d.ts index 17e33e3..f3cfff8 100644 --- a/source/api/v1/services/interfaces/UsersServiceInterface.d.ts +++ b/source/api/v1/services/interfaces/UsersServiceInterface.d.ts @@ -1,5 +1,5 @@ import { User, UserUpdate, UserWithoutMetadata } from "../../database/entities/User"; -import { USER_EXCEPTIONS } from "../../exceptions/UserExceptions"; +import { USER_EXCEPTIONS } from "../../shared/exceptions/UserExceptions"; export declare interface IUsersService { createUser(user: UserWithoutMetadata): Promise< diff --git a/source/api/v1/config/Config.d.ts b/source/api/v1/shared/config/Config.d.ts similarity index 95% rename from source/api/v1/config/Config.d.ts rename to source/api/v1/shared/config/Config.d.ts index c414f53..3336b75 100644 --- a/source/api/v1/config/Config.d.ts +++ b/source/api/v1/shared/config/Config.d.ts @@ -1,18 +1,18 @@ -export declare interface Config { - appPort: number - - databaseHost: string - databasePort: number - databaseName: string - databaseUser: string - databasePassword: string - - redisConnectionString: string - - get databaseConnectionString(): string - - jwtSecret: string, - // in '*h' format - jwtExpiration: string, - log: () => void +export declare interface Config { + appPort: number + + databaseHost: string + databasePort: number + databaseName: string + databaseUser: string + databasePassword: string + + redisConnectionString: string + + get databaseConnectionString(): string + + jwtSecret: string, + // in '*h' format + jwtExpiration: string, + log: () => void } \ No newline at end of file diff --git a/source/api/v1/config/ServerConfiguration.ts b/source/api/v1/shared/config/ServerConfiguration.ts similarity index 100% rename from source/api/v1/config/ServerConfiguration.ts rename to source/api/v1/shared/config/ServerConfiguration.ts diff --git a/source/api/v1/decorators/WithExceptionCatch.ts b/source/api/v1/shared/decorators/WithExceptionCatch.ts similarity index 96% rename from source/api/v1/decorators/WithExceptionCatch.ts rename to source/api/v1/shared/decorators/WithExceptionCatch.ts index 54a60f1..b9fb536 100644 --- a/source/api/v1/decorators/WithExceptionCatch.ts +++ b/source/api/v1/shared/decorators/WithExceptionCatch.ts @@ -1,17 +1,17 @@ -export const withExceptionCatch = (_target: any, _key: string, descriptor: PropertyDescriptor) => { - const originalMethod = descriptor.value - - descriptor.value = async function (...args: any[]) { - try { - return await originalMethod.apply(this, args) - } catch (error) { - console.log(error) - return { - statusCode: 503, - message: "Service unavailable" - } - } - } - - return descriptor +export const withExceptionCatch = (_target: any, _key: string, descriptor: PropertyDescriptor) => { + const originalMethod = descriptor.value + + descriptor.value = async function (...args: any[]) { + try { + return await originalMethod.apply(this, args) + } catch (error) { + console.log(error) + return { + statusCode: 503, + message: "Service unavailable" + } + } + } + + return descriptor } \ No newline at end of file diff --git a/source/api/v1/exceptions/AuthExceptions.ts b/source/api/v1/shared/exceptions/AuthExceptions.ts similarity index 100% rename from source/api/v1/exceptions/AuthExceptions.ts rename to source/api/v1/shared/exceptions/AuthExceptions.ts diff --git a/source/api/v1/exceptions/NoteExceptions.ts b/source/api/v1/shared/exceptions/NoteExceptions.ts similarity index 100% rename from source/api/v1/exceptions/NoteExceptions.ts rename to source/api/v1/shared/exceptions/NoteExceptions.ts diff --git a/source/api/v1/exceptions/UserExceptions.ts b/source/api/v1/shared/exceptions/UserExceptions.ts similarity index 100% rename from source/api/v1/exceptions/UserExceptions.ts rename to source/api/v1/shared/exceptions/UserExceptions.ts diff --git a/source/api/v1/utils/common/TokenExtractor.ts b/source/api/v1/shared/utils/common/TokenExtractor.ts similarity index 100% rename from source/api/v1/utils/common/TokenExtractor.ts rename to source/api/v1/shared/utils/common/TokenExtractor.ts diff --git a/source/api/v1/utils/common/TransformNoteCollaborators.ts b/source/api/v1/shared/utils/common/TransformNoteCollaborators.ts similarity index 70% rename from source/api/v1/utils/common/TransformNoteCollaborators.ts rename to source/api/v1/shared/utils/common/TransformNoteCollaborators.ts index 854bc76..f53506d 100644 --- a/source/api/v1/utils/common/TransformNoteCollaborators.ts +++ b/source/api/v1/shared/utils/common/TransformNoteCollaborators.ts @@ -1,4 +1,4 @@ -import { NoteEntity, Note } from "../../database/entities/Note"; +import { NoteEntity, Note } from "../../../database/entities/Note"; export const transformNoteCollaborators = (note: NoteEntity.Note): Note => { return { diff --git a/source/api/v1/utils/guards/ExceptionGuard.ts b/source/api/v1/shared/utils/guards/ExceptionGuard.ts similarity index 100% rename from source/api/v1/utils/guards/ExceptionGuard.ts rename to source/api/v1/shared/utils/guards/ExceptionGuard.ts diff --git a/source/api/v1/utils/typing/Environment.d.ts b/source/api/v1/shared/utils/typing/Environment.d.ts similarity index 100% rename from source/api/v1/utils/typing/Environment.d.ts rename to source/api/v1/shared/utils/typing/Environment.d.ts diff --git a/source/api/v1/utils/typing/Exception.d.ts b/source/api/v1/shared/utils/typing/Exception.d.ts similarity index 100% rename from source/api/v1/utils/typing/Exception.d.ts rename to source/api/v1/shared/utils/typing/Exception.d.ts diff --git a/source/api/v1/utils/typing/FastifySchemaOverride.d.ts b/source/api/v1/shared/utils/typing/FastifySchemaOverride.d.ts similarity index 100% rename from source/api/v1/utils/typing/FastifySchemaOverride.d.ts rename to source/api/v1/shared/utils/typing/FastifySchemaOverride.d.ts diff --git a/source/api/v1/validation/openapi/responses/AuthResponses.ts b/source/api/v1/validation/openapi/responses/AuthResponses.ts index fd698c9..dfc7846 100644 --- a/source/api/v1/validation/openapi/responses/AuthResponses.ts +++ b/source/api/v1/validation/openapi/responses/AuthResponses.ts @@ -1,57 +1,57 @@ -import { AUTH_EXCEPTIONS } from "../../../exceptions/AuthExceptions"; -import { USER_EXCEPTIONS } from "../../../exceptions/UserExceptions"; - -export const AUTH_RESPONSES = { - Authorize: { - 200: { - type: 'object', - properties: { - token: { type: 'string' }, - expiresIn: { type: 'string' } - } - }, - 400: { - type: 'object', - properties: { - statusCode: { enum: [400] }, - message: {enum: [AUTH_EXCEPTIONS.WrongCredentials.message]} - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: {enum: [AUTH_EXCEPTIONS.ServiceUnavailable.message]} - } - }, - }, - ChangePassword: { - 200: { - type: 'object', - properties: { - success: { enum: [true] } - } - }, - 400: { - type: 'object', - properties: { - statusCode: { enum: [400] }, - message: {enum: [AUTH_EXCEPTIONS.NewPasswordIsSame.message, AUTH_EXCEPTIONS.WrongCredentials.message, "body must have required property 'PROPERTY NAME'"]} - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: {enum: [AUTH_EXCEPTIONS.ServiceUnavailable.message]} - } - }, - } +import { AUTH_EXCEPTIONS } from "../../../shared/exceptions/AuthExceptions"; +import { USER_EXCEPTIONS } from "../../../shared/exceptions/UserExceptions"; + +export const AUTH_RESPONSES = { + Authorize: { + 200: { + type: 'object', + properties: { + token: { type: 'string' }, + expiresIn: { type: 'string' } + } + }, + 400: { + type: 'object', + properties: { + statusCode: { enum: [400] }, + message: {enum: [AUTH_EXCEPTIONS.WrongCredentials.message]} + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: {enum: [AUTH_EXCEPTIONS.ServiceUnavailable.message]} + } + }, + }, + ChangePassword: { + 200: { + type: 'object', + properties: { + success: { enum: [true] } + } + }, + 400: { + type: 'object', + properties: { + statusCode: { enum: [400] }, + message: {enum: [AUTH_EXCEPTIONS.NewPasswordIsSame.message, AUTH_EXCEPTIONS.WrongCredentials.message, "body must have required property 'PROPERTY NAME'"]} + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: {enum: [AUTH_EXCEPTIONS.ServiceUnavailable.message]} + } + }, + } } as const \ No newline at end of file diff --git a/source/api/v1/validation/openapi/responses/NoteResponses.ts b/source/api/v1/validation/openapi/responses/NoteResponses.ts index 1b990ae..52e1fc1 100644 --- a/source/api/v1/validation/openapi/responses/NoteResponses.ts +++ b/source/api/v1/validation/openapi/responses/NoteResponses.ts @@ -1,263 +1,263 @@ -import { excludeProperties } from "typing-assets"; -import { NOTE_EXCEPTIONS } from "../../../exceptions/NoteExceptions"; -import { USER_EXCEPTIONS } from "../../../exceptions/UserExceptions"; -import { BaseNoteSchema } from "../../schemas/base/Note"; -import { BaseUserSchema } from "../../schemas/base/User"; - -export const NOTE_RESPONSES = { - CreateNote: { - 201: BaseNoteSchema, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message] } - } - }, - 400: { - type: 'object', - properties: { - statusCode: {enum: [400]}, - code: {enum: ["FST_ERR_VALIDATION"]}, - error: {enum: ["Bad Request"]}, - message: {enum: ["body must have required property 'PROPERTY NAME'"]} - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - GetNotes: { - 200: { - type: 'array', - items: { - type: 'object', - properties: excludeProperties({...BaseNoteSchema.properties}, "createdAt", "content", "collaborators") - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - GetNote: { - 200: BaseNoteSchema, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - DeleteNote: { - 200: { - type: 'object', - properties: { - success: {enum: [true]} - } - }, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - GetCollaborators: { - 200: { - type: 'array', - items: { - type: 'object', - properties: excludeProperties( - {...BaseUserSchema.properties}, - "password", - ) - } - }, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - UpdateNote: { - 200: BaseNoteSchema, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message] } - } - }, - 400: { - type: 'object', - properties: { - statusCode: {enum: [400]}, - code: {enum: ["FST_ERR_VALIDATION"]}, - error: {enum: ["Bad Request"]}, - message: {enum: ["body must have required property 'PROPERTY NAME'"]} - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - AddCollaborator: { - 201: { - type: 'object', - properties: { - success: {enum: [true]} - } - }, - 400: { - type: 'object', - properties: { - statusCode: { enum: [400] }, - message: { enum: [NOTE_EXCEPTIONS.CollaboratorAlreadyInNote.message] } - } - }, - 403: { - type: 'object', - properties: { - statusCode: { enum: [403] }, - message: { enum: [NOTE_EXCEPTIONS.AcessRestricted.message] } - } - }, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message, NOTE_EXCEPTIONS.NoteNotFound.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - }, - RemoveCollaborator: { - 200: { - type: 'object', - properties: { - success: {enum: [true]} - } - }, - 403: { - type: 'object', - properties: { - statusCode: { enum: [403] }, - message: { enum: [NOTE_EXCEPTIONS.AcessRestricted.message] } - } - }, - - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message, NOTE_EXCEPTIONS.NoteNotFound.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: { enum: [503] }, - message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - } - } +import { excludeProperties } from "typing-assets"; +import { NOTE_EXCEPTIONS } from "../../../shared/exceptions/NoteExceptions"; +import { USER_EXCEPTIONS } from "../../../shared/exceptions/UserExceptions"; +import { BaseNoteSchema } from "../../schemas/base/Note"; +import { BaseUserSchema } from "../../schemas/base/User"; + +export const NOTE_RESPONSES = { + CreateNote: { + 201: BaseNoteSchema, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message] } + } + }, + 400: { + type: 'object', + properties: { + statusCode: {enum: [400]}, + code: {enum: ["FST_ERR_VALIDATION"]}, + error: {enum: ["Bad Request"]}, + message: {enum: ["body must have required property 'PROPERTY NAME'"]} + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + GetNotes: { + 200: { + type: 'array', + items: { + type: 'object', + properties: excludeProperties({...BaseNoteSchema.properties}, "createdAt", "content", "collaborators") + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + GetNote: { + 200: BaseNoteSchema, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + DeleteNote: { + 200: { + type: 'object', + properties: { + success: {enum: [true]} + } + }, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + GetCollaborators: { + 200: { + type: 'array', + items: { + type: 'object', + properties: excludeProperties( + {...BaseUserSchema.properties}, + "password", + ) + } + }, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.NoteNotFound.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + UpdateNote: { + 200: BaseNoteSchema, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message] } + } + }, + 400: { + type: 'object', + properties: { + statusCode: {enum: [400]}, + code: {enum: ["FST_ERR_VALIDATION"]}, + error: {enum: ["Bad Request"]}, + message: {enum: ["body must have required property 'PROPERTY NAME'"]} + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + AddCollaborator: { + 201: { + type: 'object', + properties: { + success: {enum: [true]} + } + }, + 400: { + type: 'object', + properties: { + statusCode: { enum: [400] }, + message: { enum: [NOTE_EXCEPTIONS.CollaboratorAlreadyInNote.message] } + } + }, + 403: { + type: 'object', + properties: { + statusCode: { enum: [403] }, + message: { enum: [NOTE_EXCEPTIONS.AcessRestricted.message] } + } + }, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message, NOTE_EXCEPTIONS.NoteNotFound.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + }, + RemoveCollaborator: { + 200: { + type: 'object', + properties: { + success: {enum: [true]} + } + }, + 403: { + type: 'object', + properties: { + statusCode: { enum: [403] }, + message: { enum: [NOTE_EXCEPTIONS.AcessRestricted.message] } + } + }, + + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [NOTE_EXCEPTIONS.CollaboratorNotFound.message, NOTE_EXCEPTIONS.NoteNotFound.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: { enum: [503] }, + message: { enum: [NOTE_EXCEPTIONS.ServiceUnavailable.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + } + } } as const \ No newline at end of file diff --git a/source/api/v1/validation/openapi/responses/UserResponses.ts b/source/api/v1/validation/openapi/responses/UserResponses.ts index 5808193..eca7f57 100644 --- a/source/api/v1/validation/openapi/responses/UserResponses.ts +++ b/source/api/v1/validation/openapi/responses/UserResponses.ts @@ -1,90 +1,90 @@ -import { excludeProperties } from "typing-assets"; -import { BaseUserSchema } from "../../schemas/base/User"; -import { USER_EXCEPTIONS } from "../../../exceptions/UserExceptions"; - -export const USER_RESPONSES = { - CreateUser: { - 201: { - type: 'object', - properties: excludeProperties( - {...BaseUserSchema.properties}, - "password" - ) - }, - 400: { - type: 'object', - properties: { - statusCode: {enum: [400]}, - message: {enum: [USER_EXCEPTIONS.AlreadyExists.message, "body must have required property 'PROPERTY NAME'"]} - } - }, - 503: { - type: 'object', - properties: { - statusCode: {enum: [503]}, - message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} - } - }, - - }, - GetUser: { - 200: { - type: 'object', - properties: excludeProperties( - {...BaseUserSchema.properties}, - "password" - ) - }, - 404: { - type: 'object', - properties: { - statusCode: { enum: [404] }, - message: { enum: [USER_EXCEPTIONS.NotFound.message] } - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: {enum: [503]}, - message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} - } - }, - }, - UpdateUser: { - 200: { - type: 'object', - properties: excludeProperties( - {...BaseUserSchema.properties}, - "password" - ) - }, - 400: { - type: 'object', - properties: { - statusCode: {enum: [400]}, - message: {enum: ["body must have required property 'PROPERTY NAME'"]} - } - }, - 401: { - type: 'object', - properties: { - statusCode: { enum: [401] }, - message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } - } - }, - 503: { - type: 'object', - properties: { - statusCode: {enum: [503]}, - message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} - } - }, - } +import { excludeProperties } from "typing-assets"; +import { BaseUserSchema } from "../../schemas/base/User"; +import { USER_EXCEPTIONS } from "../../../shared/exceptions/UserExceptions"; + +export const USER_RESPONSES = { + CreateUser: { + 201: { + type: 'object', + properties: excludeProperties( + {...BaseUserSchema.properties}, + "password" + ) + }, + 400: { + type: 'object', + properties: { + statusCode: {enum: [400]}, + message: {enum: [USER_EXCEPTIONS.AlreadyExists.message, "body must have required property 'PROPERTY NAME'"]} + } + }, + 503: { + type: 'object', + properties: { + statusCode: {enum: [503]}, + message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} + } + }, + + }, + GetUser: { + 200: { + type: 'object', + properties: excludeProperties( + {...BaseUserSchema.properties}, + "password" + ) + }, + 404: { + type: 'object', + properties: { + statusCode: { enum: [404] }, + message: { enum: [USER_EXCEPTIONS.NotFound.message] } + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: {enum: [503]}, + message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} + } + }, + }, + UpdateUser: { + 200: { + type: 'object', + properties: excludeProperties( + {...BaseUserSchema.properties}, + "password" + ) + }, + 400: { + type: 'object', + properties: { + statusCode: {enum: [400]}, + message: {enum: ["body must have required property 'PROPERTY NAME'"]} + } + }, + 401: { + type: 'object', + properties: { + statusCode: { enum: [401] }, + message: { enum: [USER_EXCEPTIONS.NotAuthorized.message] } + } + }, + 503: { + type: 'object', + properties: { + statusCode: {enum: [503]}, + message: {enum: [USER_EXCEPTIONS.ServiceUnavailable.message]} + } + }, + } } as const \ No newline at end of file diff --git a/source/api/v1/validation/schemas/AuthSchemas.ts b/source/api/v1/validation/schemas/AuthSchemas.ts index ab63678..3b5e587 100644 --- a/source/api/v1/validation/schemas/AuthSchemas.ts +++ b/source/api/v1/validation/schemas/AuthSchemas.ts @@ -1,7 +1,7 @@ import { pickProperties } from "typing-assets"; import { BaseUserSchema } from "./base/User"; import { AUTH_RESPONSES } from "../openapi/responses/AuthResponses"; -import { FastifySchema } from "../../utils/typing/FastifySchemaOverride"; +import { FastifySchema } from "../../shared/utils/typing/FastifySchemaOverride"; export const AuthUserSchema: FastifySchema = { diff --git a/source/api/v1/validation/schemas/NoteSchemas.ts b/source/api/v1/validation/schemas/NoteSchemas.ts index c43d59a..ed8422e 100644 --- a/source/api/v1/validation/schemas/NoteSchemas.ts +++ b/source/api/v1/validation/schemas/NoteSchemas.ts @@ -1,7 +1,7 @@ import { excludeProperties, pickProperties } from "typing-assets" import { BaseNoteSchema, OperateNoteSchema } from "./base/Note" import { NOTE_RESPONSES } from "../openapi/responses/NoteResponses" -import { FastifySchema } from "../../utils/typing/FastifySchemaOverride" +import { FastifySchema } from "../../shared/utils/typing/FastifySchemaOverride" export const CreateNoteSchema: FastifySchema = { body: { diff --git a/source/api/v1/validation/schemas/UserSchemas.ts b/source/api/v1/validation/schemas/UserSchemas.ts index 7fa0edb..ccac790 100644 --- a/source/api/v1/validation/schemas/UserSchemas.ts +++ b/source/api/v1/validation/schemas/UserSchemas.ts @@ -1,4 +1,4 @@ -import { FastifySchema } from "../../utils/typing/FastifySchemaOverride"; +import { FastifySchema } from "../../shared/utils/typing/FastifySchemaOverride"; import { excludeProperties, pickProperties } from "typing-assets" import { BaseUserSchema } from "./base/User" import { USER_RESPONSES } from "../openapi/responses/UserResponses" diff --git a/source/api/v1/validation/schemas/base/Note.ts b/source/api/v1/validation/schemas/base/Note.ts index 895f4f2..41fff3a 100644 --- a/source/api/v1/validation/schemas/base/Note.ts +++ b/source/api/v1/validation/schemas/base/Note.ts @@ -1,4 +1,4 @@ -import { FastifySchema } from "../../../utils/typing/FastifySchemaOverride" +import { FastifySchema } from "../../../shared/utils/typing/FastifySchemaOverride" export const BaseNoteSchema = { type: 'object', diff --git a/source/main.ts b/source/main.ts index 768eb9d..c0bcab0 100644 --- a/source/main.ts +++ b/source/main.ts @@ -1,13 +1,13 @@ import fastify from 'fastify' -import { CONFIG } from './api/v1/config/ServerConfiguration' +import { CONFIG } from './api/v1/shared/config/ServerConfiguration' import { UsersService } from './api/v1/services/UsersService' -import { handleUserRoutes } from './api/v1/handlers/UsersHandlers' +import { UsersHandler } from './api/v1/handlers/UsersHandlers' import { AuthService } from './api/v1/services/AuthService' -import { handleAuthRoutes } from './api/v1/handlers/AuthHandlers' +import { AuthHandler } from './api/v1/handlers/AuthHandlers' import { logRequestMetadata } from './api/v1/hooks/onRequestLogger' import { logResponseMetadata } from './api/v1/hooks/onResponseLogger' import { authenticationFactory } from './api/v1/auth/AuthPreHandler' -import { handleNoteRoutes } from './api/v1/handlers/NotesHandlers' +import { NotesHandler } from './api/v1/handlers/NotesHandlers' import { NotesService } from './api/v1/services/NotesService' import "reflect-metadata" import { UserEntity } from './api/v1/database/entities/User' @@ -16,8 +16,6 @@ import { NoteEntity } from './api/v1/database/entities/Note' import { initSwaggerViewer } from './openapi/InitSwagger' import { connectAndGetRedisInstance } from './api/v1/cache/InitRedisInstance' - - const main = async () => { CONFIG.log() @@ -55,12 +53,16 @@ const main = async () => { const authentication = authenticationFactory(authService) const notesService = new NotesService(appDataSource.getRepository(NoteEntity.Note), usersService) - // versioning decorator which adds '/api/v' prefix to all routes + + // registering handlers with version prefix server.register((server, _, done) => { - handleUserRoutes(server, usersService, authentication) - handleAuthRoutes(server, authService, authentication) - handleNoteRoutes(server, notesService, authentication) + const usersHandler = new UsersHandler(server, authentication, usersService) + const notesHandler = new NotesHandler(server, authentication, notesService) + const authHandler = new AuthHandler(server, authentication, authService) + usersHandler.handleRoutes() + notesHandler.handleRoutes() + authHandler.handleRoutes() done() }, { prefix: "/api/v1" })