├── .env.example ├── .gitignore ├── nodemon.json ├── .env.test ├── appointment.png ├── tmp ├── avatar1.jpg ├── 11aa04bdd0f6f55d74f5e722a4733eb5-avatar2.jpg ├── 245ea579522cad840fadf41bf14eb75e-avatar1.jpg ├── 54a75093b464332f2b602050f1f5b685-avatar2.jpg ├── 82d339de69852b6bc5be80ef44a0611d-avatar2.jpg ├── 8626a83e684de9d2da0f624019ce1c92-avatar2.jpg └── 8a4c7d38960e9d4748832b079dc0de62-avatar1.jpg ├── coverage ├── favicon.png ├── sort-arrow-sprite.png └── prettify.css ├── dist └── src │ ├── infra │ ├── shared │ │ ├── token │ │ │ ├── token.js │ │ │ └── jwt.token.js │ │ ├── crypto │ │ │ ├── password.crypto.js │ │ │ └── password.bcrypt.js │ │ ├── validator │ │ │ └── zod.js │ │ └── http │ │ │ └── middleware │ │ │ ├── ensure-admin.middleware.js │ │ │ └── ensure-authenticate.middleware.js │ ├── providers │ │ └── mail │ │ │ ├── mail.provider.js │ │ │ └── implementations │ │ │ └── ethereal.mail.provider.js │ └── databases │ │ ├── prisma.config.js │ │ └── seed │ │ └── create-admin.js │ ├── modules │ ├── doctor │ │ ├── dto │ │ │ └── doctor.dto.js │ │ ├── repositories │ │ │ ├── doctor.repository.js │ │ │ ├── doctor-info.repository.js │ │ │ ├── doctor-schedule.repository.js │ │ │ └── implementations │ │ │ │ ├── in-memory │ │ │ │ ├── doctor-info-memory.repository.js │ │ │ │ └── doctor-memory.repository.js │ │ │ │ └── prisma │ │ │ │ └── doctor-info.prisma.repository.js │ │ ├── mapper │ │ │ ├── doctor-info.map.js │ │ │ ├── doctor.map.js │ │ │ └── doctor-schedule.map.js │ │ ├── useCases │ │ │ ├── create-doctor-info │ │ │ │ ├── index.js │ │ │ │ ├── create-doctor-info.controller.js │ │ │ │ └── create-doctor-info.usecase.js │ │ │ ├── create-doctor-schedule │ │ │ │ ├── index.js │ │ │ │ ├── create-doctor-schedule.usecase.js │ │ │ │ └── create-doctor-schedule.controller.js │ │ │ └── create-doctor │ │ │ │ └── index.js │ │ └── entities │ │ │ ├── doctor-info.entity.js │ │ │ ├── doctor.entity.js │ │ │ ├── tests │ │ │ └── doctor.entity.test.js │ │ │ └── doctor-schedule.entity.js │ ├── patient │ │ ├── dto │ │ │ └── patient.dto.js │ │ ├── repositories │ │ │ ├── patient.repository.js │ │ │ └── in-memory │ │ │ │ └── patient.in-memory.repository.js │ │ ├── useCases │ │ │ └── create-patient │ │ │ │ ├── index.js │ │ │ │ ├── create-patient.controller.js │ │ │ │ └── create-patient.usecase.js │ │ ├── entities │ │ │ └── patient.entity.js │ │ └── mapper │ │ │ └── patient.map.js │ ├── users │ │ ├── repositories │ │ │ ├── user.repository.js │ │ │ └── implementations │ │ │ │ ├── user.memory.repository.js │ │ │ │ └── user.prisma.repository.js │ │ ├── useCases │ │ │ ├── create-user │ │ │ │ ├── index.js │ │ │ │ ├── create-user.controller.js │ │ │ │ └── create-user.usecase.js │ │ │ └── authenticate-user │ │ │ │ ├── index.js │ │ │ │ ├── authenticate-user.controller.js │ │ │ │ └── authenticate-user.usecase.js │ │ └── entities │ │ │ ├── tests │ │ │ └── user.entity.test.js │ │ │ └── user.entity.js │ ├── appointments │ │ ├── repositories │ │ │ └── appointment.repository.js │ │ ├── entities │ │ │ └── appointment.entity.js │ │ └── useCases │ │ │ ├── free-schedules │ │ │ ├── index.js │ │ │ └── free-schedules.controller.js │ │ │ └── create-appointment │ │ │ ├── tests │ │ │ └── create-appointment.usecase.test.js │ │ │ ├── index.js │ │ │ └── create-appointment.controller.js │ └── speciality │ │ ├── repositories │ │ ├── speciality.repository.js │ │ └── implementations │ │ │ ├── speciality.memory.repository.js │ │ │ └── speciality.prisma.repository.js │ │ ├── useCases │ │ └── create-speciality │ │ │ ├── index.js │ │ │ ├── create-speciality.controller.js │ │ │ └── create-speciality.usecase.js │ │ └── entities │ │ └── speciality.entity.js │ ├── utils │ ├── generateUUID.js │ ├── logger.js │ └── date.js │ ├── errors │ ├── custom.error.js │ ├── parameter-required.error.js │ └── validation-schema.error.js │ ├── logs │ └── validation.js │ ├── server.js │ └── routes │ ├── index.js │ ├── doctor.routes.js │ ├── patient.routes.js │ ├── doctor-info.routes.js │ ├── doctor-schedule.routes.js │ ├── user.routes.js │ ├── speciality.routes.js │ └── appointment.routes.js ├── src ├── utils │ ├── generateUUID.ts │ ├── logger.ts │ └── date.ts ├── server.ts ├── @types │ └── express │ │ └── index.d.ts ├── infra │ ├── shared │ │ ├── crypto │ │ │ ├── password.crypto.ts │ │ │ └── password.bcrypt.ts │ │ ├── token │ │ │ ├── token.ts │ │ │ └── jwt.token.ts │ │ ├── validator │ │ │ └── zod.ts │ │ └── http │ │ │ └── middleware │ │ │ ├── ensure-admin.middleware.ts │ │ │ └── ensure-authenticate.middleware.ts │ ├── databases │ │ ├── prisma.config.ts │ │ └── seed │ │ │ └── create-admin.ts │ ├── providers │ │ ├── mail │ │ │ ├── mail.provider.ts │ │ │ └── implementations │ │ │ │ └── ethereal.mail.provider.ts │ │ └── redis │ │ │ └── index.ts │ ├── queue │ │ └── notification-appointment │ │ │ ├── notification-appointment.queue.ts │ │ │ └── notification-appointment.worker.ts │ └── cron │ │ └── notification-appointments-day.cron.ts ├── modules │ ├── patient │ │ ├── dto │ │ │ └── patient.dto.ts │ │ ├── repositories │ │ │ ├── patient.repository.ts │ │ │ ├── in-memory │ │ │ │ └── patient.in-memory.repository.ts │ │ │ └── prisma │ │ │ │ └── patient.prisma.repository.ts │ │ ├── useCases │ │ │ └── create-patient │ │ │ │ ├── index.ts │ │ │ │ ├── create-patient.controller.ts │ │ │ │ └── create-patient.usecase.ts │ │ ├── entities │ │ │ └── patient.entity.ts │ │ └── mapper │ │ │ └── patient.map.ts │ ├── doctor │ │ ├── dto │ │ │ └── doctor.dto.ts │ │ ├── repositories │ │ │ ├── doctor-info.repository.ts │ │ │ ├── doctor.repository.ts │ │ │ ├── doctor-schedule.repository.ts │ │ │ └── implementations │ │ │ │ ├── in-memory │ │ │ │ ├── doctor-info-memory.repository.ts │ │ │ │ └── doctor-memory.repository.ts │ │ │ │ └── prisma │ │ │ │ ├── doctor-info.prisma.repository.ts │ │ │ │ ├── doctor-schedule.prisma.repository.ts │ │ │ │ └── doctor.prisma.repository.ts │ │ ├── mapper │ │ │ ├── doctor-info.map.ts │ │ │ ├── doctor.map.ts │ │ │ └── doctor-schedule.map.ts │ │ ├── useCases │ │ │ ├── create-doctor-info │ │ │ │ ├── index.ts │ │ │ │ ├── create-doctor-info.controller.ts │ │ │ │ └── create-doctor-info.usecase.ts │ │ │ ├── create-doctor-schedule │ │ │ │ ├── index.ts │ │ │ │ ├── create-doctor-schedule.controller.ts │ │ │ │ └── create-doctor-schedule.usecase.ts │ │ │ └── create-doctor │ │ │ │ ├── index.ts │ │ │ │ ├── create-doctor.controller.ts │ │ │ │ └── create-doctor.usecase.ts │ │ └── entities │ │ │ ├── doctor-info.entity.ts │ │ │ ├── doctor.entity.ts │ │ │ ├── tests │ │ │ └── doctor.entity.test.ts │ │ │ └── doctor-schedule.entity.ts │ ├── users │ │ ├── repositories │ │ │ ├── user.repository.ts │ │ │ └── implementations │ │ │ │ ├── user.memory.repository.ts │ │ │ │ └── user.prisma.repository.ts │ │ ├── useCases │ │ │ ├── create-user │ │ │ │ ├── index.ts │ │ │ │ ├── create-user.controller.ts │ │ │ │ └── create-user.usecase.ts │ │ │ ├── refresh-token │ │ │ │ ├── index.ts │ │ │ │ ├── refresh-token.controller.ts │ │ │ │ └── refresh-token.usecase.ts │ │ │ └── authenticate-user │ │ │ │ ├── index.ts │ │ │ │ ├── authenticate-user.controller.ts │ │ │ │ └── authenticate-user.usecase.ts │ │ └── entities │ │ │ ├── tests │ │ │ └── user.entity.test.ts │ │ │ └── user.entity.ts │ ├── speciality │ │ ├── repositories │ │ │ ├── speciality.repository.ts │ │ │ └── implementations │ │ │ │ ├── speciality.memory.repository.ts │ │ │ │ └── speciality.prisma.repository.ts │ │ ├── useCases │ │ │ └── create-speciality │ │ │ │ ├── index.ts │ │ │ │ ├── create-speciality.controller.ts │ │ │ │ └── create-speciality.usecase.ts │ │ └── entities │ │ │ └── speciality.entity.ts │ └── appointments │ │ ├── useCases │ │ ├── create-appointment │ │ │ ├── tests │ │ │ │ └── create-appointment.usecase.test.ts │ │ │ ├── index.ts │ │ │ └── create-appointment.controller.ts │ │ ├── free-schedules │ │ │ ├── index.ts │ │ │ └── free-schedules.controller.ts │ │ └── create-notification-appointmnet │ │ │ └── create-notification-appointment.usecase.ts │ │ ├── entities │ │ └── appointment.entity.ts │ │ └── repositories │ │ ├── appointment.repository.ts │ │ └── prisma │ │ └── appointment.prisma.repository.ts ├── errors │ ├── token.error.ts │ ├── custom.error.ts │ ├── parameter-required.error.ts │ └── validation-schema.error.ts ├── routes │ ├── doctor.routes.ts │ ├── doctor-info.routes.ts │ ├── doctor-schedule.routes.ts │ ├── patient.routes.ts │ ├── speciality.routes.ts │ ├── tests │ │ └── patient.routes.e2e-spec.ts │ ├── index.ts │ ├── user.routes.ts │ └── appointment.routes.ts ├── app.ts └── config │ └── upload.config.ts ├── prisma ├── migrations │ ├── 20230205230049_alter_table_user_add_avatar │ │ └── migration.sql │ ├── 20221220030519_alter_table_appointment_note_nullable │ │ └── migration.sql │ ├── migration_lock.toml │ ├── 20221026012250_create_speciality │ │ └── migration.sql │ ├── 20221012204410_create_users │ │ └── migration.sql │ ├── 20221126000614_create_doctor_info │ │ └── migration.sql │ ├── 20221126211707_create_patient │ │ └── migration.sql │ ├── 20221217180033_create_appointments │ │ └── migration.sql │ ├── 20221217164829_create_doctor_schedules_alter_doctor_info │ │ └── migration.sql │ └── 20221106182153_create_doctor │ │ └── migration.sql └── schema.prisma ├── vite.config.ts ├── test ├── test.e2e.config.ts └── setup.ts ├── logs └── validation.js ├── package.json └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .env -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "execMap": { 3 | "ts": "ts-node" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgresql://admin:admin@localhost:5432/medical_appointment_test" -------------------------------------------------------------------------------- /appointment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/appointment.png -------------------------------------------------------------------------------- /tmp/avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/avatar1.jpg -------------------------------------------------------------------------------- /coverage/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/coverage/favicon.png -------------------------------------------------------------------------------- /dist/src/infra/shared/token/token.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /coverage/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /dist/src/modules/doctor/dto/doctor.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/patient/dto/patient.dto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/infra/providers/mail/mail.provider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/infra/shared/crypto/password.crypto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/doctor.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/users/repositories/user.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /src/utils/generateUUID.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto' 2 | 3 | export const generateUUID = () => { 4 | return randomUUID() 5 | } 6 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/doctor-info.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/patient/repositories/patient.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /prisma/migrations/20230205230049_alter_table_user_add_avatar/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "avatar" TEXT; 3 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/repositories/appointment.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/doctor-schedule.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/repositories/speciality.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /tmp/11aa04bdd0f6f55d74f5e722a4733eb5-avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/11aa04bdd0f6f55d74f5e722a4733eb5-avatar2.jpg -------------------------------------------------------------------------------- /tmp/245ea579522cad840fadf41bf14eb75e-avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/245ea579522cad840fadf41bf14eb75e-avatar1.jpg -------------------------------------------------------------------------------- /tmp/54a75093b464332f2b602050f1f5b685-avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/54a75093b464332f2b602050f1f5b685-avatar2.jpg -------------------------------------------------------------------------------- /tmp/82d339de69852b6bc5be80ef44a0611d-avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/82d339de69852b6bc5be80ef44a0611d-avatar2.jpg -------------------------------------------------------------------------------- /tmp/8626a83e684de9d2da0f624019ce1c92-avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/8626a83e684de9d2da0f624019ce1c92-avatar2.jpg -------------------------------------------------------------------------------- /tmp/8a4c7d38960e9d4748832b079dc0de62-avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danileao/medical_appointment/HEAD/tmp/8a4c7d38960e9d4748832b079dc0de62-avatar1.jpg -------------------------------------------------------------------------------- /prisma/migrations/20221220030519_alter_table_appointment_note_nullable/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "appointments" ALTER COLUMN "note" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { app } from './app' 3 | const port = process.env.port || 3000 4 | 5 | app.listen(port, () => console.log(`Server is running on PORT ${port}`)) 6 | -------------------------------------------------------------------------------- /src/@types/express/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express' 2 | 3 | declare global { 4 | namespace Express { 5 | interface Request { 6 | userId: string 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/infra/shared/crypto/password.crypto.ts: -------------------------------------------------------------------------------- 1 | export interface IPasswordCrypto { 2 | hash(password: string): Promise 3 | compare(password: string, passwordHash: string): Promise 4 | } 5 | -------------------------------------------------------------------------------- /src/infra/databases/prisma.config.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prismaClient = new PrismaClient({ 4 | log: ['error', 'info', 'query'], 5 | }) 6 | 7 | export { prismaClient } 8 | -------------------------------------------------------------------------------- /src/modules/patient/dto/patient.dto.ts: -------------------------------------------------------------------------------- 1 | export type PatientWithUserDTO = { 2 | document: string 3 | email: string 4 | id: string 5 | userId: string 6 | user: { 7 | name: string 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | provider: 'istanbul', 7 | all: true, 8 | }, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /src/modules/doctor/dto/doctor.dto.ts: -------------------------------------------------------------------------------- 1 | export type DoctorWithUserDTO = { 2 | id: string 3 | crm: string 4 | email: string 5 | userId: string 6 | specialityId: string 7 | user: { 8 | name: string 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/doctor-info.repository.ts: -------------------------------------------------------------------------------- 1 | import { DoctorInfo } from '../entities/doctor-info.entity' 2 | 3 | export interface IDoctorInfoRepository { 4 | saveOrUpdate(data: DoctorInfo): Promise 5 | } 6 | -------------------------------------------------------------------------------- /src/errors/token.error.ts: -------------------------------------------------------------------------------- 1 | export class TokenError extends Error { 2 | statusCode?: number 3 | 4 | constructor(message: string, statusCode?: number) { 5 | super(message) 6 | this.name = 'TOKEN_ERROR' 7 | this.statusCode = statusCode 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/infra/providers/mail/mail.provider.ts: -------------------------------------------------------------------------------- 1 | export type MailDTO = { 2 | from: string 3 | to: string 4 | text?: string 5 | html?: string 6 | subject: string 7 | } 8 | 9 | export interface IMailProvider { 10 | sendMail(data: MailDTO): Promise 11 | } 12 | -------------------------------------------------------------------------------- /src/errors/custom.error.ts: -------------------------------------------------------------------------------- 1 | export class CustomError extends Error { 2 | statusCode?: number 3 | name: string 4 | 5 | constructor(message: string, statusCode = 500, name = '') { 6 | super(message) 7 | this.name = name 8 | this.statusCode = statusCode 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/infra/shared/token/token.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../../modules/users/entities/user.entity' 2 | 3 | export type TokenUser = { 4 | sub: string 5 | } 6 | 7 | export interface IToken { 8 | create(user: User): string 9 | validate(token: string): TokenUser | null 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/users/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entities/user.entity' 2 | 3 | export interface IUserRespository { 4 | findByUsername(username: string): Promise 5 | save(data: User): Promise 6 | findById(id: string): Promise 7 | } 8 | -------------------------------------------------------------------------------- /dist/src/utils/generateUUID.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.generateUUID = void 0; 4 | const crypto_1 = require("crypto"); 5 | const generateUUID = () => { 6 | return (0, crypto_1.randomUUID)(); 7 | }; 8 | exports.generateUUID = generateUUID; 9 | -------------------------------------------------------------------------------- /src/errors/parameter-required.error.ts: -------------------------------------------------------------------------------- 1 | export class ParameterRequiredError extends Error { 2 | statusCode?: number 3 | 4 | constructor(message: string, statusCode?: number) { 5 | super(message) 6 | this.name = 'PARAMETER_REQUIRED_ERROR' 7 | this.statusCode = statusCode 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/speciality/repositories/speciality.repository.ts: -------------------------------------------------------------------------------- 1 | import { Speciality } from '../entities/speciality.entity' 2 | 3 | export interface ISpecialityRepository { 4 | save(data: Speciality): Promise 5 | findByName(name: string): Promise 6 | findById(id: string): Promise 7 | } 8 | -------------------------------------------------------------------------------- /dist/src/infra/databases/prisma.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.prismaClient = void 0; 4 | const client_1 = require("@prisma/client"); 5 | const prismaClient = new client_1.PrismaClient({ 6 | log: ['error', 'info', 'query'], 7 | }); 8 | exports.prismaClient = prismaClient; 9 | -------------------------------------------------------------------------------- /src/routes/doctor.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { createDoctorController } from '../modules/doctor/useCases/create-doctor' 3 | 4 | const doctorRouter = Router() 5 | 6 | doctorRouter.post('/doctors', async (request, response) => { 7 | await createDoctorController.handle(request, response) 8 | }) 9 | 10 | export { doctorRouter } 11 | -------------------------------------------------------------------------------- /dist/src/errors/custom.error.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CustomError = void 0; 4 | class CustomError extends Error { 5 | constructor(message, statusCode = 500, name = '') { 6 | super(message); 7 | this.name = name; 8 | this.statusCode = statusCode; 9 | } 10 | } 11 | exports.CustomError = CustomError; 12 | -------------------------------------------------------------------------------- /src/errors/validation-schema.error.ts: -------------------------------------------------------------------------------- 1 | import { ErrorSchema } from '../infra/shared/validator/zod' 2 | 3 | export class ValidationSchemaError extends Error { 4 | statusCode: number 5 | errors: ErrorSchema[] 6 | 7 | constructor(message: string, errors: ErrorSchema[]) { 8 | super(message) 9 | this.name = 'VALIDATION_SCHEMA_ERROR' 10 | this.statusCode = 422 11 | this.errors = errors 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/doctor.repository.ts: -------------------------------------------------------------------------------- 1 | import { DoctorWithUserDTO } from '../dto/doctor.dto' 2 | import { Doctor } from '../entities/doctor.entity' 3 | 4 | export interface IDoctorRepository { 5 | save(data: Doctor): Promise 6 | findByCRM(crm: string): Promise 7 | findById(id: string): Promise 8 | findByUserID(userID: string): Promise 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/doctor-schedule.repository.ts: -------------------------------------------------------------------------------- 1 | import { DoctorSchedule } from '../entities/doctor-schedule.entity' 2 | import { DoctorScheduleWeek } from '../mapper/doctor-schedule.map' 3 | 4 | export interface IDoctorScheduleRepository { 5 | save(data: DoctorSchedule): Promise 6 | findByDoctorIdAndDayOfWeek( 7 | doctorId: string, 8 | dayOfWeek: number 9 | ): Promise 10 | } 11 | -------------------------------------------------------------------------------- /prisma/migrations/20221026012250_create_speciality/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "specialities" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "description" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | 8 | CONSTRAINT "specialities_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "specialities_name_key" ON "specialities"("name"); 13 | -------------------------------------------------------------------------------- /src/infra/shared/crypto/password.bcrypt.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | 3 | import { IPasswordCrypto } from './password.crypto' 4 | 5 | export class PasswordBcrypt implements IPasswordCrypto { 6 | hash(password: string): Promise { 7 | return bcrypt.hash(password, 10) 8 | } 9 | 10 | async compare(password: string, passwordHash: string): Promise { 11 | return bcrypt.compare(password, passwordHash) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dist/src/errors/parameter-required.error.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ParameterRequiredError = void 0; 4 | class ParameterRequiredError extends Error { 5 | constructor(message, statusCode) { 6 | super(message); 7 | this.name = 'PARAMETER_REQUIRED_ERROR'; 8 | this.statusCode = statusCode; 9 | } 10 | } 11 | exports.ParameterRequiredError = ParameterRequiredError; 12 | -------------------------------------------------------------------------------- /src/infra/databases/seed/create-admin.ts: -------------------------------------------------------------------------------- 1 | import { PasswordBcrypt } from '../../shared/crypto/password.bcrypt' 2 | import { prismaClient } from '../prisma.config' 3 | 4 | async function main() { 5 | const password = await new PasswordBcrypt().hash('admin') 6 | await prismaClient.user.create({ 7 | data: { 8 | name: 'admin', 9 | password, 10 | username: 'admin', 11 | isAdmin: true, 12 | }, 13 | }) 14 | } 15 | 16 | main() 17 | -------------------------------------------------------------------------------- /src/infra/queue/notification-appointment/notification-appointment.queue.ts: -------------------------------------------------------------------------------- 1 | import type { queueAsPromised } from 'fastq' 2 | import * as fastq from 'fastq' 3 | import { 4 | notificationAppointmentWorker, 5 | NotificationTask, 6 | } from './notification-appointment.worker' 7 | 8 | const queueAppointmentNotification: queueAsPromised = 9 | fastq.promise(notificationAppointmentWorker, 1) 10 | 11 | export { queueAppointmentNotification } 12 | -------------------------------------------------------------------------------- /src/modules/doctor/mapper/doctor-info.map.ts: -------------------------------------------------------------------------------- 1 | import { DoctorInfo } from '../entities/doctor-info.entity' 2 | import { DoctorInfo as DoctorInfoPrisma } from '@prisma/client' 3 | 4 | export class DoctorInfoMapper { 5 | static prismaToEntityDoctorInfo = (data: DoctorInfoPrisma): DoctorInfo => { 6 | return { 7 | doctorId: data.doctor_id, 8 | id: data.id, 9 | duration: data.duration, 10 | price: Number(data.price), 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/users/useCases/create-user/index.ts: -------------------------------------------------------------------------------- 1 | import { PasswordBcrypt } from '../../../../infra/shared/crypto/password.bcrypt' 2 | import { UserPrismaRepository } from '../../repositories/implementations/user.prisma.repository' 3 | import { CreateUserController } from './create-user.controller' 4 | 5 | const userPrismaRepository = new UserPrismaRepository() 6 | const createUserController = new CreateUserController(userPrismaRepository) 7 | 8 | export { createUserController } 9 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import express from 'express' 3 | 4 | import swaggerUI from 'swagger-ui-express' 5 | 6 | import swaggerDocument from '../swagger.json' 7 | import { router } from './routes' 8 | 9 | import './infra/cron/notification-appointments-day.cron' 10 | 11 | const app = express() 12 | 13 | app.use(express.json()) 14 | 15 | app.use(router) 16 | 17 | app.use('/docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument)) 18 | 19 | export { app } 20 | -------------------------------------------------------------------------------- /dist/src/errors/validation-schema.error.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ValidationSchemaError = void 0; 4 | class ValidationSchemaError extends Error { 5 | constructor(message, errors) { 6 | super(message); 7 | this.name = 'VALIDATION_SCHEMA_ERROR'; 8 | this.statusCode = 422; 9 | this.errors = errors; 10 | } 11 | } 12 | exports.ValidationSchemaError = ValidationSchemaError; 13 | -------------------------------------------------------------------------------- /src/modules/speciality/useCases/create-speciality/index.ts: -------------------------------------------------------------------------------- 1 | import { SpecialityPrismaRepository } from '../../repositories/implementations/speciality.prisma.repository' 2 | import { CreateSpecialityController } from './create-speciality.controller' 3 | 4 | const specialityPrismaRepository = new SpecialityPrismaRepository() 5 | const createSpecialityController = new CreateSpecialityController( 6 | specialityPrismaRepository 7 | ) 8 | 9 | export { createSpecialityController } 10 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/mapper/doctor-info.map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DoctorInfoMapper = void 0; 4 | class DoctorInfoMapper { 5 | } 6 | exports.DoctorInfoMapper = DoctorInfoMapper; 7 | DoctorInfoMapper.prismaToEntityDoctorInfo = (data) => { 8 | return { 9 | doctorId: data.doctor_id, 10 | id: data.id, 11 | duration: data.duration, 12 | price: Number(data.price), 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /test/test.e2e.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import dotenv from 'dotenv' 3 | 4 | dotenv.config({ path: '.env.test' }) 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | { 9 | name: 'setup-config', 10 | config: () => ({ 11 | test: { 12 | setupFiles: ['./test/setup.ts'], 13 | }, 14 | }), 15 | }, 16 | ], 17 | test: { 18 | include: ['**/*.e2e-spec.ts'], 19 | exclude: ['**/*.test.ts'], 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /prisma/migrations/20221012204410_create_users/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "username" TEXT NOT NULL, 6 | "password" TEXT NOT NULL, 7 | "isAdmin" BOOLEAN NOT NULL DEFAULT false, 8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | 10 | CONSTRAINT "users_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "users_username_key" ON "users"("username"); 15 | -------------------------------------------------------------------------------- /src/modules/patient/repositories/patient.repository.ts: -------------------------------------------------------------------------------- 1 | import { PatientWithUserDTO } from '../dto/patient.dto' 2 | import { Patient } from '../entities/patient.entity' 3 | 4 | export interface IPatientRepository { 5 | save(data: Patient): Promise 6 | findByDocumentOrEmail( 7 | document: string, 8 | email: string 9 | ): Promise 10 | findById(id: string): Promise 11 | 12 | findByUserId(userId: string): Promise 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/create-appointment/tests/create-appointment.usecase.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest' 2 | 3 | describe('Create Appointment', () => { 4 | test('Should not be able to create an appointment without a patient or with an invalid patient', async () => { 5 | expect(true).toBe(true) 6 | }) 7 | 8 | test('Should not be able to create an appointment without a doctor or with an invalid doctor', async () => { 9 | expect(true).toBe(true) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston' 2 | 3 | const logger = winston.createLogger({ 4 | format: winston.format.combine( 5 | winston.format.timestamp({ format: 'DD/MM/YYYY HH:mm:ss' }), 6 | winston.format.json() 7 | ), 8 | transports: [ 9 | new winston.transports.File({ 10 | filename: 'logs/app.log', 11 | }), 12 | new winston.transports.File({ 13 | filename: 'logs/error.log', 14 | level: 'error', 15 | }), 16 | ], 17 | }) 18 | 19 | export { logger } 20 | -------------------------------------------------------------------------------- /logs/validation.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs') 2 | 3 | const startAt = ' 09:12' 4 | const endAt = ' 08:12' 5 | 6 | // console.log(validateTime(startAt)) 7 | 8 | function validateTime(hour) { 9 | return dayjs(formatDateHour(hour)).isValid() 10 | } 11 | 12 | function formatDateHour(hour) { 13 | const date = dayjs().format('YYYY-MM-DD ') 14 | const dateTimeFormat = new Date(`${date} ${hour}`) 15 | return dayjs(dateTimeFormat) 16 | } 17 | 18 | console.log(formatDateHour(endAt).isAfter(formatDateHour(startAt))) 19 | -------------------------------------------------------------------------------- /dist/src/logs/validation.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs') 2 | 3 | const startAt = ' 09:12' 4 | const endAt = ' 08:12' 5 | 6 | // console.log(validateTime(startAt)) 7 | 8 | function validateTime(hour) { 9 | return dayjs(formatDateHour(hour)).isValid() 10 | } 11 | 12 | function formatDateHour(hour) { 13 | const date = dayjs().format('YYYY-MM-DD ') 14 | const dateTimeFormat = new Date(`${date} ${hour}`) 15 | return dayjs(dateTimeFormat) 16 | } 17 | 18 | console.log(formatDateHour(endAt).isAfter(formatDateHour(startAt))) 19 | -------------------------------------------------------------------------------- /src/config/upload.config.ts: -------------------------------------------------------------------------------- 1 | import multer from 'multer' 2 | import { resolve } from 'path' 3 | import { randomBytes } from 'crypto' 4 | 5 | const folderTmp = resolve(__dirname, '..', '..', 'tmp') 6 | 7 | export default { 8 | storage: multer.diskStorage({ 9 | destination: folderTmp, 10 | filename: (request, file, callback) => { 11 | const fileHash = randomBytes(16).toString('hex') 12 | const fileName = `${fileHash}-${file.originalname}` 13 | return callback(null, fileName) 14 | }, 15 | }), 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/doctor-info.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { ensureAuthenticate } from '../infra/shared/http/middleware/ensure-authenticate.middleware' 3 | import { createDoctorInfoController } from '../modules/doctor/useCases/create-doctor-info' 4 | 5 | const doctorInfoRouter = Router() 6 | 7 | doctorInfoRouter.post( 8 | '/doctor-info', 9 | ensureAuthenticate, 10 | async (request, response) => { 11 | await createDoctorInfoController.handle(request, response) 12 | } 13 | ) 14 | 15 | export { doctorInfoRouter } 16 | -------------------------------------------------------------------------------- /src/modules/users/useCases/refresh-token/index.ts: -------------------------------------------------------------------------------- 1 | import { JWTToken } from '../../../../infra/shared/token/jwt.token' 2 | import { UserPrismaRepository } from '../../repositories/implementations/user.prisma.repository' 3 | import { RefreshTokenController } from './refresh-token.controller' 4 | 5 | const jwtToken = new JWTToken() 6 | const userPrismaRepository = new UserPrismaRepository() 7 | 8 | const refreshTokenController = new RefreshTokenController( 9 | jwtToken, 10 | userPrismaRepository 11 | ) 12 | 13 | export { refreshTokenController } 14 | -------------------------------------------------------------------------------- /src/routes/doctor-schedule.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { ensureAuthenticate } from '../infra/shared/http/middleware/ensure-authenticate.middleware' 3 | import { createDoctorScheduleController } from '../modules/doctor/useCases/create-doctor-schedule' 4 | 5 | const doctorScheduleRoutes = Router() 6 | 7 | doctorScheduleRoutes.post( 8 | '/doctor-schedule', 9 | ensureAuthenticate, 10 | async (request, response) => { 11 | await createDoctorScheduleController.handle(request, response) 12 | } 13 | ) 14 | 15 | export { doctorScheduleRoutes } 16 | -------------------------------------------------------------------------------- /src/routes/patient.routes.ts: -------------------------------------------------------------------------------- 1 | import { Request, Router, Response } from 'express' 2 | import { createPatientController } from '../modules/patient/useCases/create-patient' 3 | 4 | import uploadConfig from '../config/upload.config' 5 | 6 | import multer from 'multer' 7 | 8 | const upload = multer(uploadConfig) 9 | 10 | const patientRouter = Router() 11 | 12 | patientRouter.post( 13 | '/patients', 14 | upload.single('avatar'), 15 | async (request, response) => { 16 | await createPatientController.handle(request, response) 17 | } 18 | ) 19 | 20 | export { patientRouter } 21 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/create-user/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createUserController = void 0; 4 | const user_prisma_repository_1 = require("../../repositories/implementations/user.prisma.repository"); 5 | const create_user_controller_1 = require("./create-user.controller"); 6 | const userPrismaRepository = new user_prisma_repository_1.UserPrismaRepository(); 7 | const createUserController = new create_user_controller_1.CreateUserController(userPrismaRepository); 8 | exports.createUserController = createUserController; 9 | -------------------------------------------------------------------------------- /src/modules/users/entities/tests/user.entity.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from 'vitest' 2 | import { User } from '../user.entity' 3 | 4 | describe('User entity', () => { 5 | test('Should be able to create a new user', async () => { 6 | const user = await User.create({ 7 | name: 'USER_NAME', 8 | password: 'PASSWORD_TEST', 9 | username: 'USERNAME', 10 | }) 11 | 12 | console.log({ user }) 13 | 14 | expect(user).toBeInstanceOf(User) 15 | expect(user).toHaveProperty('id') 16 | expect(user.password).not.equal('PASSWORD_TEST') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/modules/patient/useCases/create-patient/index.ts: -------------------------------------------------------------------------------- 1 | import { UserPrismaRepository } from '../../../users/repositories/implementations/user.prisma.repository' 2 | import { PatientPrismaRepository } from '../../repositories/prisma/patient.prisma.repository' 3 | import { CreatePatientController } from './create-patient.controller' 4 | 5 | const userRepository = new UserPrismaRepository() 6 | const patientRepository = new PatientPrismaRepository() 7 | 8 | const createPatientController = new CreatePatientController( 9 | userRepository, 10 | patientRepository 11 | ) 12 | 13 | export { createPatientController } 14 | -------------------------------------------------------------------------------- /src/routes/speciality.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { ensureAdmin } from '../infra/shared/http/middleware/ensure-admin.middleware' 3 | import { ensureAuthenticate } from '../infra/shared/http/middleware/ensure-authenticate.middleware' 4 | import { createSpecialityController } from '../modules/speciality/useCases/create-speciality' 5 | 6 | const specialityRouter = Router() 7 | 8 | specialityRouter.post( 9 | '/specialities', 10 | ensureAuthenticate, 11 | ensureAdmin, 12 | async (request, response) => { 13 | await createSpecialityController.handle(request, response) 14 | } 15 | ) 16 | 17 | export { specialityRouter } 18 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/entities/appointment.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Appointment = void 0; 4 | const generateUUID_1 = require("../../../utils/generateUUID"); 5 | class Appointment { 6 | constructor(props) { 7 | this.id = (0, generateUUID_1.generateUUID)(); 8 | this.patientId = props.patientId; 9 | this.doctorId = props.doctorId; 10 | this.date = props.date; 11 | } 12 | static create(data) { 13 | const appointmnet = new Appointment(data); 14 | return appointmnet; 15 | } 16 | } 17 | exports.Appointment = Appointment; 18 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-info/index.ts: -------------------------------------------------------------------------------- 1 | import { DoctorInfoPrismaRepository } from '../../repositories/implementations/prisma/doctor-info.prisma.repository' 2 | import { DoctorPrismaRepository } from '../../repositories/implementations/prisma/doctor.prisma.repository' 3 | import { CreateDoctorInfoController } from './create-doctor-info.controller' 4 | 5 | const doctorRepository = new DoctorPrismaRepository() 6 | const doctorInfoRepository = new DoctorInfoPrismaRepository() 7 | 8 | const createDoctorInfoController = new CreateDoctorInfoController( 9 | doctorRepository, 10 | doctorInfoRepository 11 | ) 12 | 13 | export { createDoctorInfoController } 14 | -------------------------------------------------------------------------------- /src/routes/tests/patient.routes.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest' 2 | import { describe, expect, it } from 'vitest' 3 | import { app } from '../../app' 4 | 5 | describe('Patient', () => { 6 | it('Should be able to create a new patient', async () => { 7 | const result = await request(app).post('/patients').send({ 8 | name: 'user_supertest', 9 | username: 'user_supertest', 10 | password: 'user_password', 11 | email: 'user_email', 12 | document: 'user_document', 13 | }) 14 | 15 | console.log(result.body) 16 | 17 | expect(result.body).toHaveProperty('id') 18 | expect(result.statusCode).eq(200) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /coverage/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /src/infra/cron/notification-appointments-day.cron.ts: -------------------------------------------------------------------------------- 1 | import cron from 'node-cron' 2 | import { AppointmentPrismaRepository } from '../../modules/appointments/repositories/prisma/appointment.prisma.repository' 3 | import { CreateNotificationAppointmentUseCase } from '../../modules/appointments/useCases/create-notification-appointmnet/create-notification-appointment.usecase' 4 | 5 | cron.schedule('0 0 0 * * *', async () => { 6 | const appointmentRepository = new AppointmentPrismaRepository() 7 | const createNotificationAppointmentUseCase = 8 | new CreateNotificationAppointmentUseCase(appointmentRepository) 9 | await createNotificationAppointmentUseCase.execute() 10 | }) 11 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/useCases/create-speciality/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createSpecialityController = void 0; 4 | const speciality_prisma_repository_1 = require("../../repositories/implementations/speciality.prisma.repository"); 5 | const create_speciality_controller_1 = require("./create-speciality.controller"); 6 | const specialityPrismaRepository = new speciality_prisma_repository_1.SpecialityPrismaRepository(); 7 | const createSpecialityController = new create_speciality_controller_1.CreateSpecialityController(specialityPrismaRepository); 8 | exports.createSpecialityController = createSpecialityController; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20221126000614_create_doctor_info/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "doctor_info" ( 3 | "id" TEXT NOT NULL, 4 | "start_at" TEXT NOT NULL, 5 | "end_at" TEXT NOT NULL, 6 | "duration" INTEGER NOT NULL, 7 | "price" DECIMAL(65,30) NOT NULL, 8 | "doctor_id" TEXT NOT NULL, 9 | 10 | CONSTRAINT "doctor_info_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "doctor_info_doctor_id_key" ON "doctor_info"("doctor_id"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "doctor_info" ADD CONSTRAINT "doctor_info_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "doctors"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/free-schedules/index.ts: -------------------------------------------------------------------------------- 1 | import { DoctorSchedulePrismaRepository } from '../../../doctor/repositories/implementations/prisma/doctor-schedule.prisma.repository' 2 | import { AppointmentPrismaRepository } from '../../repositories/prisma/appointment.prisma.repository' 3 | import { FreeSchedulesController } from './free-schedules.controller' 4 | 5 | const doctorSchedulePrismaRepository = new DoctorSchedulePrismaRepository() 6 | const appointmentPrismaRepository = new AppointmentPrismaRepository() 7 | const freeScheduleController = new FreeSchedulesController( 8 | doctorSchedulePrismaRepository, 9 | appointmentPrismaRepository 10 | ) 11 | 12 | export { freeScheduleController } 13 | -------------------------------------------------------------------------------- /src/modules/users/useCases/authenticate-user/index.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticateUserController } from './authenticate-user.controller' 2 | import { PasswordBcrypt } from '../../../../infra/shared/crypto/password.bcrypt' 3 | import { UserPrismaRepository } from '../../repositories/implementations/user.prisma.repository' 4 | import { JWTToken } from '../../../../infra/shared/token/jwt.token' 5 | 6 | const userPrismaRepository = new UserPrismaRepository() 7 | const passwordBcrypt = new PasswordBcrypt() 8 | const token = new JWTToken() 9 | 10 | const authenticateUserController = new AuthenticateUserController( 11 | userPrismaRepository, 12 | passwordBcrypt, 13 | token 14 | ) 15 | 16 | export { authenticateUserController } 17 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/entities/speciality.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Speciality = void 0; 4 | const crypto_1 = require("crypto"); 5 | class Speciality { 6 | constructor({ name, description }) { 7 | this.name = name; 8 | this.description = description; 9 | this.createdAt = new Date(); 10 | this.id = (0, crypto_1.randomUUID)(); 11 | } 12 | static create(props) { 13 | if (!props.name) { 14 | throw new Error('Speciality name is required!'); 15 | } 16 | const speciality = new Speciality(props); 17 | return speciality; 18 | } 19 | } 20 | exports.Speciality = Speciality; 21 | -------------------------------------------------------------------------------- /src/modules/speciality/entities/speciality.entity.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto' 2 | 3 | type ISpeciality = { 4 | name: string 5 | description: string 6 | } 7 | 8 | export class Speciality { 9 | id: string 10 | name: string 11 | description: string 12 | createdAt: Date 13 | 14 | constructor({ name, description }: ISpeciality) { 15 | this.name = name 16 | this.description = description 17 | this.createdAt = new Date() 18 | this.id = randomUUID() 19 | } 20 | 21 | static create(props: ISpeciality) { 22 | if (!props.name) { 23 | throw new Error('Speciality name is required!') 24 | } 25 | 26 | const speciality = new Speciality(props) 27 | return speciality 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-schedule/index.ts: -------------------------------------------------------------------------------- 1 | import { DoctorSchedulePrismaRepository } from '../../repositories/implementations/prisma/doctor-schedule.prisma.repository' 2 | import { DoctorPrismaRepository } from '../../repositories/implementations/prisma/doctor.prisma.repository' 3 | import { CreateDoctorScheduleController } from './create-doctor-schedule.controller' 4 | 5 | const doctorPrismaRepository = new DoctorPrismaRepository() 6 | const doctorSchedulePrismaRepository = new DoctorSchedulePrismaRepository() 7 | 8 | const createDoctorScheduleController = new CreateDoctorScheduleController( 9 | doctorPrismaRepository, 10 | doctorSchedulePrismaRepository 11 | ) 12 | 13 | export { createDoctorScheduleController } 14 | -------------------------------------------------------------------------------- /src/infra/shared/validator/zod.ts: -------------------------------------------------------------------------------- 1 | import { ZodError, ZodSchema } from 'zod' 2 | import { ValidationSchemaError } from '../../../errors/validation-schema.error' 3 | 4 | export type ErrorSchema = { 5 | field: (string | number)[] 6 | message: string 7 | } 8 | 9 | export const validatorSchema = (schema: ZodSchema, payload: any) => { 10 | try { 11 | schema.parse(payload) 12 | } catch (error) { 13 | const typedError = error as ZodError 14 | const errors: ErrorSchema[] = [] 15 | 16 | typedError.errors.forEach((erro) => { 17 | errors.push({ 18 | field: erro.path, 19 | message: erro.message, 20 | }) 21 | }) 22 | 23 | throw new ValidationSchemaError('ERROR_SCHEMA', errors) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { appointmentRoutes } from './appointment.routes' 3 | import { doctorInfoRouter } from './doctor-info.routes' 4 | import { doctorScheduleRoutes } from './doctor-schedule.routes' 5 | import { doctorRouter } from './doctor.routes' 6 | import { patientRouter } from './patient.routes' 7 | import { specialityRouter } from './speciality.routes' 8 | import { userRouter } from './user.routes' 9 | 10 | const router = Router() 11 | 12 | router.use(userRouter) 13 | router.use(specialityRouter) 14 | router.use(doctorRouter) 15 | router.use(doctorInfoRouter) 16 | router.use(patientRouter) 17 | router.use(doctorScheduleRoutes) 18 | router.use(appointmentRoutes) 19 | 20 | export { router } 21 | -------------------------------------------------------------------------------- /src/infra/shared/http/middleware/ensure-admin.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | import { UserPrismaRepository } from '../../../../modules/users/repositories/implementations/user.prisma.repository' 3 | 4 | export const ensureAdmin = async ( 5 | request: Request, 6 | response: Response, 7 | next: NextFunction 8 | ) => { 9 | const userRepository = new UserPrismaRepository() 10 | const user = await userRepository.findById(request.userId) 11 | 12 | if (!user) { 13 | return response.status(400).json({ message: 'User does not exists!' }) 14 | } 15 | 16 | if (!user.isAdmin) { 17 | return response.status(401).json({ message: 'User is not admin!' }) 18 | } 19 | 20 | return next() 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/appointments/entities/appointment.entity.ts: -------------------------------------------------------------------------------- 1 | import { generateUUID } from '../../../utils/generateUUID' 2 | 3 | type AppointmentProps = { 4 | patientId: string 5 | doctorId: string 6 | date: Date 7 | } 8 | 9 | export class Appointment { 10 | patientId: string 11 | doctorId: string 12 | id?: string 13 | date: Date 14 | note?: string 15 | isFinished?: boolean 16 | 17 | private constructor(props: AppointmentProps) { 18 | this.id = generateUUID() 19 | this.patientId = props.patientId 20 | this.doctorId = props.doctorId 21 | this.date = props.date 22 | } 23 | 24 | static create(data: AppointmentProps) { 25 | const appointmnet = new Appointment(data) 26 | return appointmnet 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/speciality/repositories/implementations/speciality.memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { Speciality } from '../../entities/speciality.entity' 2 | import { ISpecialityRepository } from '../speciality.repository' 3 | 4 | export class SpecialityMemoryRepository implements ISpecialityRepository { 5 | items: Speciality[] = [] 6 | 7 | async save(data: Speciality): Promise { 8 | this.items.push(data) 9 | return data 10 | } 11 | async findByName(name: string): Promise { 12 | return this.items.find((speciality) => speciality.name === name) || null 13 | } 14 | async findById(id: string): Promise { 15 | return this.items.find((speciality) => speciality.id === id) || null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /prisma/migrations/20221126211707_create_patient/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "patients" ( 3 | "id" TEXT NOT NULL, 4 | "document" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "user_id" TEXT NOT NULL, 7 | 8 | CONSTRAINT "patients_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "patients_document_key" ON "patients"("document"); 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "patients_email_key" ON "patients"("email"); 16 | 17 | -- CreateIndex 18 | CREATE UNIQUE INDEX "patients_user_id_key" ON "patients"("user_id"); 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "patients" ADD CONSTRAINT "patients_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 22 | -------------------------------------------------------------------------------- /src/infra/providers/redis/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis' 2 | 3 | export type RedisTypeClient = ReturnType 4 | 5 | export class CreateConnectionRedis { 6 | protected client: RedisTypeClient 7 | 8 | constructor() { 9 | this.client = this.createClient() 10 | } 11 | 12 | public async setValue(key: string, value: string) { 13 | return this.client.set(key, value) 14 | } 15 | 16 | public async getValue(key: string) { 17 | return this.client.get(key) 18 | } 19 | 20 | private createClient() { 21 | try { 22 | const client = createClient() 23 | client.connect() 24 | 25 | return client 26 | } catch (err) { 27 | throw new Error('Redis client error ' + err) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /prisma/migrations/20221217180033_create_appointments/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "appointments" ( 3 | "id" TEXT NOT NULL, 4 | "doctor_id" TEXT NOT NULL, 5 | "patient_id" TEXT NOT NULL, 6 | "is_finished" BOOLEAN NOT NULL DEFAULT false, 7 | "date" TIMESTAMP(3) NOT NULL, 8 | "note" TEXT NOT NULL, 9 | 10 | CONSTRAINT "appointments_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "appointments" ADD CONSTRAINT "appointments_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "doctors"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "appointments" ADD CONSTRAINT "appointments_patient_id_fkey" FOREIGN KEY ("patient_id") REFERENCES "patients"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /src/routes/user.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { authenticateUserController } from '../modules/users/useCases/authenticate-user' 3 | import { createUserController } from '../modules/users/useCases/create-user' 4 | import { refreshTokenController } from '../modules/users/useCases/refresh-token' 5 | 6 | const userRouter = Router() 7 | 8 | userRouter.post('/login', async (request, response) => { 9 | await authenticateUserController.handle(request, response) 10 | }) 11 | 12 | userRouter.post('/users', async (request, response) => { 13 | await createUserController.handle(request, response) 14 | }) 15 | 16 | userRouter.post('/refresh-token', async (request, response) => { 17 | await refreshTokenController.handle(request, response) 18 | }) 19 | 20 | export { userRouter } 21 | -------------------------------------------------------------------------------- /src/routes/appointment.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { ensureAuthenticate } from '../infra/shared/http/middleware/ensure-authenticate.middleware' 3 | import { createAppointmentController } from '../modules/appointments/useCases/create-appointment' 4 | import { freeScheduleController } from '../modules/appointments/useCases/free-schedules' 5 | 6 | const appointmentRoutes = Router() 7 | 8 | appointmentRoutes.get('/appointments/free', async (request, response) => { 9 | await freeScheduleController.handle(request, response) 10 | }) 11 | 12 | appointmentRoutes.post( 13 | '/appointments', 14 | ensureAuthenticate, 15 | async (request, response) => { 16 | await createAppointmentController.handle(request, response) 17 | } 18 | ) 19 | 20 | export { appointmentRoutes } 21 | -------------------------------------------------------------------------------- /dist/src/infra/shared/validator/zod.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.validatorSchema = void 0; 4 | const validation_schema_error_1 = require("../../../errors/validation-schema.error"); 5 | const validatorSchema = (schema, payload) => { 6 | try { 7 | schema.parse(payload); 8 | } 9 | catch (error) { 10 | const typedError = error; 11 | const errors = []; 12 | typedError.errors.forEach((erro) => { 13 | errors.push({ 14 | field: erro.path, 15 | message: erro.message, 16 | }); 17 | }); 18 | throw new validation_schema_error_1.ValidationSchemaError('ERROR_SCHEMA', errors); 19 | } 20 | }; 21 | exports.validatorSchema = validatorSchema; 22 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll, afterAll } from 'vitest' 2 | import { randomUUID } from 'crypto' 3 | import { execSync } from 'node:child_process' 4 | import { Client, Connection } from 'pg' 5 | 6 | const schemaDatabaseTest = randomUUID() 7 | process.env.DATABASE_URL = `${process.env.DATABASE_URL}?schema=${schemaDatabaseTest}` 8 | 9 | beforeAll(async () => { 10 | console.log('DATABASE', process.env.DATABASE_URL) 11 | 12 | console.log(process.env.DATABASE_URL) 13 | await execSync('npx prisma migrate deploy') 14 | }) 15 | 16 | afterAll(async () => { 17 | const client = new Client({ 18 | connectionString: process.env.DATABASE_URL, 19 | }) 20 | 21 | await client.connect() 22 | await client.query(`drop schema if exists "${schemaDatabaseTest}" cascade`) 23 | await client.end() 24 | }) 25 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/mapper/doctor.map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DoctorMapper = void 0; 4 | class DoctorMapper { 5 | } 6 | exports.DoctorMapper = DoctorMapper; 7 | DoctorMapper.prismaToEntityDoctor = (data) => { 8 | return { 9 | crm: data.crm, 10 | email: data.email, 11 | specialityId: data.speciality_id, 12 | userId: data.user_id, 13 | id: data.id, 14 | }; 15 | }; 16 | DoctorMapper.prismaToEntityDoctorWithUser = (data) => { 17 | return { 18 | crm: data.crm, 19 | email: data.email, 20 | specialityId: data.speciality_id, 21 | userId: data.user_id, 22 | id: data.id, 23 | user: { 24 | name: data.user.name, 25 | }, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/infra/queue/notification-appointment/notification-appointment.worker.ts: -------------------------------------------------------------------------------- 1 | import { formatDate } from '../../../utils/date' 2 | import { EtherealMailProvider } from '../../providers/mail/implementations/ethereal.mail.provider' 3 | 4 | export type NotificationTask = { 5 | email: string 6 | date: Date 7 | } 8 | const mailProvider = new EtherealMailProvider() 9 | 10 | export async function notificationAppointmentWorker({ 11 | email, 12 | date, 13 | }: NotificationTask): Promise { 14 | await mailProvider.sendMail({ 15 | to: email, 16 | from: 'Agendamento de Consulta ', 17 | html: ` 18 | Olá !
19 | Não se esqueça da sua consulta hoje as ${formatDate(date, 'HH:mm')} 20 | `, 21 | subject: 'Lembrete de agendamento de consulta', 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /dist/src/modules/patient/useCases/create-patient/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createPatientController = void 0; 4 | const user_prisma_repository_1 = require("../../../users/repositories/implementations/user.prisma.repository"); 5 | const patient_prisma_repository_1 = require("../../repositories/prisma/patient.prisma.repository"); 6 | const create_patient_controller_1 = require("./create-patient.controller"); 7 | const userRepository = new user_prisma_repository_1.UserPrismaRepository(); 8 | const patientRepository = new patient_prisma_repository_1.PatientPrismaRepository(); 9 | const createPatientController = new create_patient_controller_1.CreatePatientController(userRepository, patientRepository); 10 | exports.createPatientController = createPatientController; 11 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor/index.ts: -------------------------------------------------------------------------------- 1 | import { SpecialityPrismaRepository } from '../../../speciality/repositories/implementations/speciality.prisma.repository' 2 | import { UserPrismaRepository } from '../../../users/repositories/implementations/user.prisma.repository' 3 | import { DoctorPrismaRepository } from '../../repositories/implementations/prisma/doctor.prisma.repository' 4 | import { CreateDoctorController } from './create-doctor.controller' 5 | 6 | const userRepository = new UserPrismaRepository() 7 | const doctorRepository = new DoctorPrismaRepository() 8 | const specialityRepository = new SpecialityPrismaRepository() 9 | 10 | const createDoctorController = new CreateDoctorController( 11 | userRepository, 12 | doctorRepository, 13 | specialityRepository 14 | ) 15 | 16 | export { createDoctorController } 17 | -------------------------------------------------------------------------------- /src/modules/speciality/useCases/create-speciality/create-speciality.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { ISpecialityRepository } from '../../repositories/speciality.repository' 3 | import { CreateSpecialityUseCase } from './create-speciality.usecase' 4 | 5 | export class CreateSpecialityController { 6 | constructor(private specialityRepository: ISpecialityRepository) {} 7 | 8 | async handle(request: Request, response: Response) { 9 | try { 10 | const useCase = new CreateSpecialityUseCase(this.specialityRepository) 11 | 12 | const result = await useCase.execute(request.body) 13 | 14 | return response.json(result) 15 | } catch (err: any) { 16 | return response.status(err.statusCode || 400).json({ 17 | error: err.message, 18 | }) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/implementations/in-memory/doctor-info-memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { DoctorInfo } from '../../../entities/doctor-info.entity' 2 | import { IDoctorInfoRepository } from '../../doctor-info.repository' 3 | 4 | export class DoctorInfoMemoryRepository implements IDoctorInfoRepository { 5 | items: DoctorInfo[] = [] 6 | async saveOrUpdate(data: DoctorInfo): Promise { 7 | const index = this.items.findIndex( 8 | (doctor) => doctor.doctorId == data.doctorId 9 | ) 10 | if (index >= 0) { 11 | const doctor = this.items[index] 12 | this.items[index] = { 13 | ...doctor, 14 | duration: data.duration, 15 | price: data.price, 16 | } 17 | data = this.items[index] 18 | } else { 19 | this.items.push(data) 20 | } 21 | return data 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dist/src/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | require("dotenv/config"); 7 | const express_1 = __importDefault(require("express")); 8 | const swagger_ui_express_1 = __importDefault(require("swagger-ui-express")); 9 | const swagger_json_1 = __importDefault(require("../swagger.json")); 10 | const routes_1 = require("./routes"); 11 | const app = (0, express_1.default)(); 12 | const port = process.env.port || 3000; 13 | app.use(express_1.default.json()); 14 | app.use(routes_1.router); 15 | app.use('/docs', swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(swagger_json_1.default)); 16 | app.listen(port, () => console.log(`Server is running on PORT ${port}`)); 17 | -------------------------------------------------------------------------------- /dist/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.logger = void 0; 7 | const winston_1 = __importDefault(require("winston")); 8 | const logger = winston_1.default.createLogger({ 9 | format: winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'DD/MM/YYYY HH:mm:ss' }), winston_1.default.format.json()), 10 | transports: [ 11 | new winston_1.default.transports.File({ 12 | filename: 'src/logs/app.log', 13 | }), 14 | new winston_1.default.transports.File({ 15 | filename: 'src/logs/error.log', 16 | level: 'error', 17 | }), 18 | ], 19 | }); 20 | exports.logger = logger; 21 | -------------------------------------------------------------------------------- /src/modules/appointments/repositories/appointment.repository.ts: -------------------------------------------------------------------------------- 1 | import { Appointment } from '../entities/appointment.entity' 2 | 3 | export type AppointmentsDate = { 4 | date: Date 5 | } 6 | 7 | export type AppointmentsWithPatient = { 8 | date: Date 9 | patient: { 10 | email: string 11 | } 12 | } 13 | 14 | export interface IAppointmentRepository { 15 | findAllSchedulesByDoctorAndDate( 16 | doctorId: string, 17 | date: string 18 | ): Promise 19 | 20 | findAppointmentByDoctorAndDatetime( 21 | doctorId: string, 22 | date: string 23 | ): Promise 24 | 25 | findAppointmentByPatientAndDatetime( 26 | patientId: string, 27 | date: string 28 | ): Promise 29 | 30 | save(data: Appointment): Promise 31 | findAllTodayIncludePatients(): Promise 32 | } 33 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-info/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createDoctorInfoController = void 0; 4 | const doctor_info_prisma_repository_1 = require("../../repositories/implementations/prisma/doctor-info.prisma.repository"); 5 | const doctor_prisma_repository_1 = require("../../repositories/implementations/prisma/doctor.prisma.repository"); 6 | const create_doctor_info_controller_1 = require("./create-doctor-info.controller"); 7 | const doctorRepository = new doctor_prisma_repository_1.DoctorPrismaRepository(); 8 | const doctorInfoRepository = new doctor_info_prisma_repository_1.DoctorInfoPrismaRepository(); 9 | const createDoctorInfoController = new create_doctor_info_controller_1.CreateDoctorInfoController(doctorRepository, doctorInfoRepository); 10 | exports.createDoctorInfoController = createDoctorInfoController; 11 | -------------------------------------------------------------------------------- /src/modules/doctor/mapper/doctor.map.ts: -------------------------------------------------------------------------------- 1 | import { Doctor } from '../entities/doctor.entity' 2 | import { Doctor as DoctorPrisma, User as UserPrisma } from '@prisma/client' 3 | import { DoctorWithUserDTO } from '../dto/doctor.dto' 4 | 5 | export class DoctorMapper { 6 | static prismaToEntityDoctor = (data: DoctorPrisma): Doctor => { 7 | return { 8 | crm: data.crm, 9 | email: data.email, 10 | specialityId: data.speciality_id, 11 | userId: data.user_id, 12 | id: data.id, 13 | } 14 | } 15 | 16 | static prismaToEntityDoctorWithUser = ( 17 | data: DoctorPrisma & { user: UserPrisma } 18 | ): DoctorWithUserDTO => { 19 | return { 20 | crm: data.crm, 21 | email: data.email, 22 | specialityId: data.speciality_id, 23 | userId: data.user_id, 24 | id: data.id, 25 | user: { 26 | name: data.user.name, 27 | }, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-info/create-doctor-info.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IDoctorInfoRepository } from '../../repositories/doctor-info.repository' 3 | import { IDoctorRepository } from '../../repositories/doctor.repository' 4 | import { CreateDoctorInfoUseCase } from './create-doctor-info.usecase' 5 | 6 | export class CreateDoctorInfoController { 7 | constructor( 8 | private doctorRepository: IDoctorRepository, 9 | private doctorInfoRepository: IDoctorInfoRepository 10 | ) {} 11 | async handle(request: Request, response: Response) { 12 | const { body, userId } = request 13 | 14 | const createDoctorInfoUseCase = new CreateDoctorInfoUseCase( 15 | this.doctorRepository, 16 | this.doctorInfoRepository 17 | ) 18 | const result = await createDoctorInfoUseCase.execute(body, userId) 19 | return response.json(result) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/useCases/free-schedules/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.freeScheduleController = void 0; 4 | const doctor_schedule_prisma_repository_1 = require("../../../doctor/repositories/implementations/prisma/doctor-schedule.prisma.repository"); 5 | const appointment_prisma_repository_1 = require("../../repositories/prisma/appointment.prisma.repository"); 6 | const free_schedules_controller_1 = require("./free-schedules.controller"); 7 | const doctorSchedulePrismaRepository = new doctor_schedule_prisma_repository_1.DoctorSchedulePrismaRepository(); 8 | const appointmentPrismaRepository = new appointment_prisma_repository_1.AppointmentPrismaRepository(); 9 | const freeScheduleController = new free_schedules_controller_1.FreeSchedulesController(doctorSchedulePrismaRepository, appointmentPrismaRepository); 10 | exports.freeScheduleController = freeScheduleController; 11 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/authenticate-user/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.authenticateUserController = void 0; 4 | const authenticate_user_controller_1 = require("./authenticate-user.controller"); 5 | const password_bcrypt_1 = require("../../../../infra/shared/crypto/password.bcrypt"); 6 | const user_prisma_repository_1 = require("../../repositories/implementations/user.prisma.repository"); 7 | const jwt_token_1 = require("../../../../infra/shared/token/jwt.token"); 8 | const userPrismaRepository = new user_prisma_repository_1.UserPrismaRepository(); 9 | const passwordBcrypt = new password_bcrypt_1.PasswordBcrypt(); 10 | const token = new jwt_token_1.JWTToken(); 11 | const authenticateUserController = new authenticate_user_controller_1.AuthenticateUserController(userPrismaRepository, passwordBcrypt, token); 12 | exports.authenticateUserController = authenticateUserController; 13 | -------------------------------------------------------------------------------- /src/modules/patient/entities/patient.entity.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../errors/custom.error' 2 | import { generateUUID } from '../../../utils/generateUUID' 3 | 4 | export type PatientProps = { 5 | email: string 6 | document: string 7 | userId: string 8 | } 9 | 10 | export class Patient { 11 | email: string 12 | document: string 13 | userId: string 14 | id: string 15 | 16 | private constructor(props: PatientProps) { 17 | if (!props.email) { 18 | throw new CustomError('Email is required!') 19 | } 20 | 21 | if (!props.document || props.document.length <= 5) { 22 | throw new CustomError('Invalid Document') 23 | } 24 | 25 | this.userId = props.userId 26 | this.email = props.email 27 | this.document = props.document 28 | this.id = generateUUID() 29 | } 30 | 31 | static create(data: PatientProps) { 32 | const patient = new Patient(data) 33 | return patient 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dist/src/modules/patient/entities/patient.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Patient = void 0; 4 | const custom_error_1 = require("../../../errors/custom.error"); 5 | const generateUUID_1 = require("../../../utils/generateUUID"); 6 | class Patient { 7 | constructor(props) { 8 | if (!props.email) { 9 | throw new custom_error_1.CustomError('Email is required!'); 10 | } 11 | if (!props.document || props.document.length <= 5) { 12 | throw new custom_error_1.CustomError('Invalid Document'); 13 | } 14 | this.userId = props.userId; 15 | this.email = props.email; 16 | this.document = props.document; 17 | this.id = (0, generateUUID_1.generateUUID)(); 18 | } 19 | static create(data) { 20 | const patient = new Patient(data); 21 | return patient; 22 | } 23 | } 24 | exports.Patient = Patient; 25 | -------------------------------------------------------------------------------- /src/modules/speciality/useCases/create-speciality/create-speciality.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { Speciality } from '../../entities/speciality.entity' 3 | import { ISpecialityRepository } from '../../repositories/speciality.repository' 4 | 5 | type SpecialityRequest = { 6 | name: string 7 | description: string 8 | } 9 | 10 | export class CreateSpecialityUseCase { 11 | constructor(private specialityRepository: ISpecialityRepository) {} 12 | 13 | async execute(data: SpecialityRequest) { 14 | const speciality = new Speciality(data) 15 | 16 | const existSpeciality = await this.specialityRepository.findByName( 17 | data.name 18 | ) 19 | 20 | if (existSpeciality) { 21 | throw new CustomError('Speciality already exists!') 22 | } 23 | 24 | const specialityCreated = await this.specialityRepository.save(speciality) 25 | 26 | return specialityCreated 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /prisma/migrations/20221217164829_create_doctor_schedules_alter_doctor_info/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `end_at` on the `doctor_info` table. All the data in the column will be lost. 5 | - You are about to drop the column `start_at` on the `doctor_info` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "doctor_info" DROP COLUMN "end_at", 10 | DROP COLUMN "start_at"; 11 | 12 | -- CreateTable 13 | CREATE TABLE "doctor_schedules" ( 14 | "id" TEXT NOT NULL, 15 | "start_at" TEXT NOT NULL, 16 | "end_at" TEXT NOT NULL, 17 | "day_of_week" INTEGER NOT NULL, 18 | "doctor_id" TEXT NOT NULL, 19 | 20 | CONSTRAINT "doctor_schedules_pkey" PRIMARY KEY ("id") 21 | ); 22 | 23 | -- AddForeignKey 24 | ALTER TABLE "doctor_schedules" ADD CONSTRAINT "doctor_schedules_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "doctors"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 25 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/entities/doctor-info.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DoctorInfo = void 0; 4 | const custom_error_1 = require("../../../errors/custom.error"); 5 | const generateUUID_1 = require("../../../utils/generateUUID"); 6 | class DoctorInfo { 7 | constructor(props) { 8 | if (!props.doctorId) { 9 | throw new custom_error_1.CustomError('Doctor does not exists!'); 10 | } 11 | if (props.duration <= 0) { 12 | throw new custom_error_1.CustomError('Invalid duration!'); 13 | } 14 | this.id = (0, generateUUID_1.generateUUID)(); 15 | this.duration = props.duration; 16 | this.price = props.price; 17 | this.doctorId = props.doctorId; 18 | } 19 | static create(data) { 20 | const doctorInfo = new DoctorInfo(data); 21 | return doctorInfo; 22 | } 23 | } 24 | exports.DoctorInfo = DoctorInfo; 25 | -------------------------------------------------------------------------------- /src/modules/users/repositories/implementations/user.memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../entities/user.entity' 2 | import { IUserRespository } from '../user.repository' 3 | 4 | export class UserMemoryRepository implements IUserRespository { 5 | users: User[] 6 | 7 | private static instance: UserMemoryRepository 8 | 9 | constructor() { 10 | this.users = [] 11 | } 12 | 13 | static getInstance() { 14 | if (!UserMemoryRepository.instance) { 15 | UserMemoryRepository.instance = new UserMemoryRepository() 16 | } 17 | 18 | return UserMemoryRepository.instance 19 | } 20 | 21 | async findByUsername(username: string) { 22 | return this.users.find((user) => user.username === username) 23 | } 24 | 25 | async save(data: User) { 26 | this.users.push(data) 27 | return data 28 | } 29 | 30 | async findById(id: string): Promise { 31 | return this.users.find((user) => user.id === id) || null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-schedule/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createDoctorScheduleController = void 0; 4 | const doctor_schedule_prisma_repository_1 = require("../../repositories/implementations/prisma/doctor-schedule.prisma.repository"); 5 | const doctor_prisma_repository_1 = require("../../repositories/implementations/prisma/doctor.prisma.repository"); 6 | const create_doctor_schedule_controller_1 = require("./create-doctor-schedule.controller"); 7 | const doctorPrismaRepository = new doctor_prisma_repository_1.DoctorPrismaRepository(); 8 | const doctorSchedulePrismaRepository = new doctor_schedule_prisma_repository_1.DoctorSchedulePrismaRepository(); 9 | const createDoctorScheduleController = new create_doctor_schedule_controller_1.CreateDoctorScheduleController(doctorPrismaRepository, doctorSchedulePrismaRepository); 10 | exports.createDoctorScheduleController = createDoctorScheduleController; 11 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/implementations/prisma/doctor-info.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../../infra/databases/prisma.config' 2 | import { DoctorInfo } from '../../../entities/doctor-info.entity' 3 | import { DoctorInfoMapper } from '../../../mapper/doctor-info.map' 4 | import { IDoctorInfoRepository } from '../../doctor-info.repository' 5 | 6 | export class DoctorInfoPrismaRepository implements IDoctorInfoRepository { 7 | async saveOrUpdate(data: DoctorInfo): Promise { 8 | const doctor = await prismaClient.doctorInfo.upsert({ 9 | where: { doctor_id: data.doctorId }, 10 | create: { 11 | duration: data.duration, 12 | price: data.price, 13 | doctor_id: data.doctorId, 14 | id: data.id, 15 | }, 16 | update: { 17 | duration: data.duration, 18 | price: data.price, 19 | }, 20 | }) 21 | return DoctorInfoMapper.prismaToEntityDoctorInfo(doctor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dist/src/modules/patient/mapper/patient.map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.PatientMapper = void 0; 4 | class PatientMapper { 5 | } 6 | exports.PatientMapper = PatientMapper; 7 | PatientMapper.entityToPrisma = (patient) => { 8 | return { 9 | document: patient.document, 10 | email: patient.email, 11 | id: patient.id, 12 | user_id: patient.userId, 13 | }; 14 | }; 15 | PatientMapper.prismaToEntity = (patient) => { 16 | return { 17 | document: patient.document, 18 | email: patient.email, 19 | id: patient.id, 20 | userId: patient.user_id, 21 | }; 22 | }; 23 | PatientMapper.prismaToEntityIncludesUser = (patient) => { 24 | return { 25 | document: patient.document, 26 | email: patient.email, 27 | id: patient.id, 28 | userId: patient.user_id, 29 | user: { 30 | name: patient.user.name, 31 | }, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/modules/users/useCases/create-user/create-user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IPasswordCrypto } from '../../../../infra/shared/crypto/password.crypto' 3 | import { logger } from '../../../../utils/logger' 4 | import { IUserRespository } from '../../repositories/user.repository' 5 | import { CreateUserUseCase } from './create-user.usecase' 6 | 7 | export class CreateUserController { 8 | constructor(private userRepository: IUserRespository) {} 9 | 10 | async handle(request: Request, response: Response) { 11 | logger.info('Usuário sendo criado!') 12 | try { 13 | const data = request.body 14 | 15 | const useCase = new CreateUserUseCase(this.userRepository) 16 | const result = await useCase.execute(data) 17 | 18 | return response.json(result) 19 | } catch (err: any) { 20 | logger.error(err.stack) 21 | return response.status(err.statusCode).json({ 22 | error: err.message, 23 | }) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/implementations/in-memory/doctor-memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { DoctorWithUserDTO } from './../../../../doctor/dto/doctor.dto' 2 | import { Doctor } from '../../../entities/doctor.entity' 3 | import { IDoctorRepository } from '../../doctor.repository' 4 | 5 | export class DoctorMemoryRepository implements IDoctorRepository { 6 | items: Doctor[] = [] 7 | 8 | async save(data: Doctor): Promise { 9 | this.items.push(data) 10 | return data 11 | } 12 | 13 | async findByCRM(crm: string): Promise { 14 | return this.items.find((doctor) => doctor.crm === crm) || null 15 | } 16 | 17 | async findById(id: string): Promise { 18 | return ( 19 | (this.items.find((doctor) => doctor.id === id) as DoctorWithUserDTO) || 20 | null 21 | ) 22 | } 23 | 24 | async findByUserID(userID: string): Promise { 25 | return this.items.find((doctor) => doctor.userId === userID) || null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/patient/repositories/in-memory/patient.in-memory.repository.ts: -------------------------------------------------------------------------------- 1 | import { PatientWithUserDTO } from '../../dto/patient.dto' 2 | import { Patient } from '../../entities/patient.entity' 3 | import { IPatientRepository } from '../patient.repository' 4 | 5 | export class PatientInMemoryRepository implements IPatientRepository { 6 | items: Patient[] = [] 7 | async save(data: Patient): Promise { 8 | this.items.push(data) 9 | return data 10 | } 11 | async findByDocumentOrEmail( 12 | document: string, 13 | email: string 14 | ): Promise { 15 | throw new Error('Method not implemented.') 16 | } 17 | async findById(id: string): Promise { 18 | return this.items.find((patient) => patient.id === id) || null 19 | } 20 | 21 | async findByUserId(userId: string): Promise { 22 | return ( 23 | (this.items.find( 24 | (patient) => patient.userId === userId 25 | ) as PatientWithUserDTO) || null 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/patient/useCases/create-patient/create-patient.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IUserRespository } from '../../../users/repositories/user.repository' 3 | import { IPatientRepository } from '../../repositories/patient.repository' 4 | import { CreatePatientUseCase } from './create-patient.usecase' 5 | 6 | export class CreatePatientController { 7 | constructor( 8 | private userRepository: IUserRespository, 9 | private patientRepository: IPatientRepository 10 | ) {} 11 | async handle(request: Request, response: Response) { 12 | try { 13 | const createPatientUseCase = new CreatePatientUseCase( 14 | this.userRepository, 15 | this.patientRepository 16 | ) 17 | const avatar = request.file?.filename 18 | const result = await createPatientUseCase.execute(request.body, avatar) 19 | return response.json(result) 20 | } catch (err: any) { 21 | return response.status(err.statusCode || 400).json(err.message) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dist/src/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.router = void 0; 4 | const express_1 = require("express"); 5 | const appointment_routes_1 = require("./appointment.routes"); 6 | const doctor_info_routes_1 = require("./doctor-info.routes"); 7 | const doctor_schedule_routes_1 = require("./doctor-schedule.routes"); 8 | const doctor_routes_1 = require("./doctor.routes"); 9 | const patient_routes_1 = require("./patient.routes"); 10 | const speciality_routes_1 = require("./speciality.routes"); 11 | const user_routes_1 = require("./user.routes"); 12 | const router = (0, express_1.Router)(); 13 | exports.router = router; 14 | router.use(user_routes_1.userRouter); 15 | router.use(speciality_routes_1.specialityRouter); 16 | router.use(doctor_routes_1.doctorRouter); 17 | router.use(doctor_info_routes_1.doctorInfoRouter); 18 | router.use(patient_routes_1.patientRouter); 19 | router.use(doctor_schedule_routes_1.doctorScheduleRoutes); 20 | router.use(appointment_routes_1.appointmentRoutes); 21 | -------------------------------------------------------------------------------- /prisma/migrations/20221106182153_create_doctor/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "doctors" ( 3 | "id" TEXT NOT NULL, 4 | "email" TEXT NOT NULL, 5 | "crm" TEXT NOT NULL, 6 | "user_id" TEXT NOT NULL, 7 | "speciality_id" TEXT NOT NULL, 8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | 10 | CONSTRAINT "doctors_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "doctors_email_key" ON "doctors"("email"); 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "doctors_crm_key" ON "doctors"("crm"); 18 | 19 | -- CreateIndex 20 | CREATE UNIQUE INDEX "doctors_user_id_key" ON "doctors"("user_id"); 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "doctors" ADD CONSTRAINT "doctors_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "doctors" ADD CONSTRAINT "doctors_speciality_id_fkey" FOREIGN KEY ("speciality_id") REFERENCES "specialities"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/free-schedules/free-schedules.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IDoctorScheduleRepository } from '../../../doctor/repositories/doctor-schedule.repository' 3 | import { IAppointmentRepository } from '../../repositories/appointment.repository' 4 | import { FreeSchedulesUseCase } from './free-schedules.usecase' 5 | 6 | export class FreeSchedulesController { 7 | constructor( 8 | private doctorScheduleRepository: IDoctorScheduleRepository, 9 | private appointmentRepository: IAppointmentRepository 10 | ) {} 11 | 12 | async handle(request: Request, response: Response) { 13 | const freeScheduleUseCase = new FreeSchedulesUseCase( 14 | this.doctorScheduleRepository, 15 | this.appointmentRepository 16 | ) 17 | 18 | try { 19 | const result = await freeScheduleUseCase.execute(request.body) 20 | return response.json(result) 21 | } catch (err: any) { 22 | return response.status(err.statusCode ?? 500).json(err.message) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/doctor/entities/doctor-info.entity.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../errors/custom.error' 2 | import { compareEndTimeIsAfter, validateTime } from '../../../utils/date' 3 | import { generateUUID } from '../../../utils/generateUUID' 4 | 5 | export type DoctorInfoProps = { 6 | duration: number 7 | price: number 8 | 9 | doctorId: string 10 | } 11 | 12 | export class DoctorInfo { 13 | id: string 14 | duration: number 15 | price: number 16 | 17 | doctorId: string 18 | 19 | private constructor(props: DoctorInfoProps) { 20 | if (!props.doctorId) { 21 | throw new CustomError('Doctor does not exists!') 22 | } 23 | 24 | if (props.duration <= 0) { 25 | throw new CustomError('Invalid duration!') 26 | } 27 | 28 | this.id = generateUUID() 29 | this.duration = props.duration 30 | this.price = props.price 31 | 32 | this.doctorId = props.doctorId 33 | } 34 | 35 | static create(data: DoctorInfoProps) { 36 | const doctorInfo = new DoctorInfo(data) 37 | return doctorInfo 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/users/useCases/refresh-token/refresh-token.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { TokenError } from '../../../../errors/token.error' 3 | import { IToken } from '../../../../infra/shared/token/token' 4 | import { IUserRespository } from '../../repositories/user.repository' 5 | import { RefreshTokenUseCase } from './refresh-token.usecase' 6 | 7 | export class RefreshTokenController { 8 | constructor( 9 | private token: IToken, 10 | private userRepository: IUserRespository 11 | ) {} 12 | 13 | async handle(request: Request, response: Response) { 14 | try { 15 | const { refreshToken } = request.body 16 | console.log(refreshToken) 17 | const useCase = new RefreshTokenUseCase(this.token, this.userRepository) 18 | const result = await useCase.execute(refreshToken) 19 | return response.json(result) 20 | } catch (err) { 21 | if (err instanceof TokenError) { 22 | return response.status(err.statusCode || 401).json(err.message) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/entities/doctor.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Doctor = void 0; 4 | const crypto_1 = require("crypto"); 5 | const custom_error_1 = require("../../../errors/custom.error"); 6 | class Doctor { 7 | constructor(props) { 8 | if (!props.crm) { 9 | throw new custom_error_1.CustomError('CRM is required!'); 10 | } 11 | if (props.crm.length !== 6) { 12 | throw new custom_error_1.CustomError('CRM length is incorrect!'); 13 | } 14 | if (!props.email) { 15 | throw new custom_error_1.CustomError('Email is required!'); 16 | } 17 | this.id = (0, crypto_1.randomUUID)(); 18 | this.crm = props.crm; 19 | this.email = props.email; 20 | this.userId = props.userId; 21 | this.specialityId = props.specialityId; 22 | } 23 | static create(props) { 24 | const doctor = new Doctor(props); 25 | return doctor; 26 | } 27 | } 28 | exports.Doctor = Doctor; 29 | -------------------------------------------------------------------------------- /src/infra/shared/token/jwt.token.ts: -------------------------------------------------------------------------------- 1 | import { sign, verify } from 'jsonwebtoken' 2 | import { createHmac } from 'crypto' 3 | 4 | import { User } from '../../../modules/users/entities/user.entity' 5 | import { IToken, TokenUser } from './token' 6 | 7 | export class JWTToken implements IToken { 8 | private TOKEN_SECRET = process.env.SECRET_KEY_TOKEN || '' 9 | 10 | private TOKEN_SECRET_CRYPTO = createHmac('sha256', this.TOKEN_SECRET).digest( 11 | 'base64' 12 | ) 13 | 14 | create({ username, isAdmin, id }: User): string { 15 | const token = sign( 16 | { 17 | user: { 18 | username, 19 | isAdmin, 20 | id, 21 | }, 22 | }, 23 | this.TOKEN_SECRET_CRYPTO, 24 | { 25 | subject: id, 26 | expiresIn: '15m', 27 | } 28 | ) 29 | return token 30 | } 31 | 32 | public validate(token: string): TokenUser | null { 33 | try { 34 | return verify(token, this.TOKEN_SECRET_CRYPTO) as TokenUser 35 | } catch (err) { 36 | return null 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/speciality/repositories/implementations/speciality.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../infra/databases/prisma.config' 2 | import { Speciality } from '../../entities/speciality.entity' 3 | import { ISpecialityRepository } from '../speciality.repository' 4 | 5 | export class SpecialityPrismaRepository implements ISpecialityRepository { 6 | async save(data: Speciality): Promise { 7 | const speciality = await prismaClient.speciality.create({ 8 | data: { 9 | name: data.name, 10 | description: data.description, 11 | id: data.id, 12 | }, 13 | }) 14 | 15 | return speciality 16 | } 17 | 18 | async findByName(name: string): Promise { 19 | return await prismaClient.speciality.findUnique({ 20 | where: { 21 | name, 22 | }, 23 | }) 24 | } 25 | 26 | async findById(id: string): Promise { 27 | return await prismaClient.speciality.findUnique({ 28 | where: { 29 | id, 30 | }, 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createDoctorController = void 0; 4 | const speciality_prisma_repository_1 = require("../../../speciality/repositories/implementations/speciality.prisma.repository"); 5 | const user_prisma_repository_1 = require("../../../users/repositories/implementations/user.prisma.repository"); 6 | const doctor_prisma_repository_1 = require("../../repositories/implementations/prisma/doctor.prisma.repository"); 7 | const create_doctor_controller_1 = require("./create-doctor.controller"); 8 | const userRepository = new user_prisma_repository_1.UserPrismaRepository(); 9 | const doctorRepository = new doctor_prisma_repository_1.DoctorPrismaRepository(); 10 | const specialityRepository = new speciality_prisma_repository_1.SpecialityPrismaRepository(); 11 | const createDoctorController = new create_doctor_controller_1.CreateDoctorController(userRepository, doctorRepository, specialityRepository); 12 | exports.createDoctorController = createDoctorController; 13 | -------------------------------------------------------------------------------- /src/modules/doctor/entities/doctor.entity.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto' 2 | import { CustomError } from '../../../errors/custom.error' 3 | 4 | export type DoctorProps = { 5 | crm: string 6 | email: string 7 | userId: string 8 | specialityId: string 9 | } 10 | 11 | export class Doctor { 12 | id: string 13 | crm: string 14 | email: string 15 | userId: string 16 | specialityId: string 17 | 18 | private constructor(props: DoctorProps) { 19 | if (!props.crm) { 20 | throw new CustomError('CRM is required!') 21 | } 22 | 23 | if (props.crm.length !== 6) { 24 | throw new CustomError('CRM length is incorrect!') 25 | } 26 | 27 | if (!props.email) { 28 | throw new CustomError('Email is required!') 29 | } 30 | 31 | this.id = randomUUID() 32 | this.crm = props.crm 33 | this.email = props.email 34 | this.userId = props.userId 35 | this.specialityId = props.specialityId 36 | } 37 | 38 | static create(props: DoctorProps) { 39 | const doctor = new Doctor(props) 40 | return doctor 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-schedule/create-doctor-schedule.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IDoctorScheduleRepository } from '../../repositories/doctor-schedule.repository' 3 | import { IDoctorRepository } from '../../repositories/doctor.repository' 4 | import { CreateDoctorScheduleUseCase } from './create-doctor-schedule.usecase' 5 | 6 | export class CreateDoctorScheduleController { 7 | constructor( 8 | private doctorRepository: IDoctorRepository, 9 | private doctorScheduleRepository: IDoctorScheduleRepository 10 | ) {} 11 | 12 | async handle(request: Request, response: Response) { 13 | const createDoctorScheduleUseCase = new CreateDoctorScheduleUseCase( 14 | this.doctorRepository, 15 | this.doctorScheduleRepository 16 | ) 17 | 18 | try { 19 | await createDoctorScheduleUseCase.execute(request.body, request.userId) 20 | return response.status(204).end() 21 | } catch (err: any) { 22 | return response.status(err.statusCode ?? 500).json(err.message) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /dist/src/infra/shared/token/jwt.token.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.JWTToken = void 0; 4 | const jsonwebtoken_1 = require("jsonwebtoken"); 5 | const crypto_1 = require("crypto"); 6 | class JWTToken { 7 | constructor() { 8 | this.TOKEN_SECRET = process.env.SECRET_KEY_TOKEN || ''; 9 | this.TOKEN_SECRET_CRYPTO = (0, crypto_1.createHmac)('sha256', this.TOKEN_SECRET).digest('base64'); 10 | } 11 | create({ username, isAdmin, id }) { 12 | const token = (0, jsonwebtoken_1.sign)({ 13 | user: { 14 | username, 15 | isAdmin, 16 | id, 17 | }, 18 | }, this.TOKEN_SECRET_CRYPTO, { 19 | subject: id, 20 | expiresIn: '15m', 21 | }); 22 | return token; 23 | } 24 | validate(token) { 25 | try { 26 | return (0, jsonwebtoken_1.verify)(token, this.TOKEN_SECRET_CRYPTO); 27 | } 28 | catch (err) { 29 | return null; 30 | } 31 | } 32 | } 33 | exports.JWTToken = JWTToken; 34 | -------------------------------------------------------------------------------- /src/modules/patient/mapper/patient.map.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '../entities/patient.entity' 2 | import { Patient as PatientPrisma, User as UserPrisma } from '@prisma/client' 3 | import { PatientWithUserDTO } from '../dto/patient.dto' 4 | 5 | export class PatientMapper { 6 | static entityToPrisma = (patient: Patient): PatientPrisma => { 7 | return { 8 | document: patient.document, 9 | email: patient.email, 10 | id: patient.id, 11 | user_id: patient.userId, 12 | } 13 | } 14 | 15 | static prismaToEntity = (patient: PatientPrisma): Patient => { 16 | return { 17 | document: patient.document, 18 | email: patient.email, 19 | id: patient.id, 20 | userId: patient.user_id, 21 | } 22 | } 23 | static prismaToEntityIncludesUser = ( 24 | patient: PatientPrisma & { user: UserPrisma } 25 | ): PatientWithUserDTO => { 26 | return { 27 | document: patient.document, 28 | email: patient.email, 29 | id: patient.id, 30 | userId: patient.user_id, 31 | user: { 32 | name: patient.user.name, 33 | }, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/users/repositories/implementations/user.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../infra/databases/prisma.config' 2 | import { User } from '../../entities/user.entity' 3 | import { IUserRespository } from '../user.repository' 4 | 5 | export class UserPrismaRepository implements IUserRespository { 6 | async findByUsername(username: string): Promise { 7 | try { 8 | const user = await prismaClient.user.findUnique({ 9 | where: { 10 | username, 11 | }, 12 | }) 13 | return user || undefined 14 | } catch (err) { 15 | console.log({ err }) 16 | return undefined 17 | } 18 | } 19 | async save(data: User): Promise { 20 | const user = await prismaClient.user.create({ 21 | data: { 22 | name: data.name, 23 | password: data.password, 24 | username: data.username, 25 | avatar: data.avatar, 26 | }, 27 | }) 28 | 29 | return user 30 | } 31 | 32 | async findById(id: string): Promise { 33 | return await prismaClient.user.findUnique({ 34 | where: { 35 | id, 36 | }, 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/users/useCases/authenticate-user/authenticate-user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IPasswordCrypto } from '../../../../infra/shared/crypto/password.crypto' 3 | import { IToken } from '../../../../infra/shared/token/token' 4 | import { IUserRespository } from '../../repositories/user.repository' 5 | import { AuthenticateUserUseCase } from './authenticate-user.usecase' 6 | 7 | export class AuthenticateUserController { 8 | constructor( 9 | private userRepository: IUserRespository, 10 | private passwordCrypt: IPasswordCrypto, 11 | private token: IToken 12 | ) {} 13 | 14 | async handle(request: Request, response: Response) { 15 | try { 16 | const data = request.body 17 | 18 | const authenticateUserUseCase = new AuthenticateUserUseCase( 19 | this.userRepository, 20 | this.passwordCrypt, 21 | this.token 22 | ) 23 | 24 | const result = await authenticateUserUseCase.execute(data) 25 | 26 | return response.json(result) 27 | } catch (err: any) { 28 | return response.status(err.statusCode).json({ 29 | error: err.message, 30 | }) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/infra/providers/mail/implementations/ethereal.mail.provider.ts: -------------------------------------------------------------------------------- 1 | import nodemailer, { Transporter } from 'nodemailer' 2 | 3 | import { IMailProvider, MailDTO } from '../mail.provider' 4 | 5 | export class EtherealMailProvider implements IMailProvider { 6 | private client!: Transporter 7 | 8 | constructor() { 9 | nodemailer 10 | .createTestAccount() 11 | .then(() => { 12 | const transporter = nodemailer.createTransport({ 13 | host: 'smtp.ethereal.email', 14 | port: 587, 15 | auth: { 16 | user: 'glen20@ethereal.email', 17 | pass: 'mPxHTzmhmwMv3DHsrW', 18 | }, 19 | }) 20 | 21 | this.client = transporter 22 | }) 23 | .catch((err) => console.log(err)) 24 | } 25 | 26 | async sendMail(data: MailDTO): Promise { 27 | const resultMail = await this.client.sendMail({ 28 | to: data.to, 29 | from: data.from, 30 | subject: data.subject, 31 | text: data.text, 32 | html: data.html, 33 | }) 34 | 35 | console.log('Message sent: %s', resultMail.messageId) 36 | console.log('Preview URL: %s', nodemailer.getTestMessageUrl(resultMail)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/modules/users/useCases/create-user/create-user.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { ParameterRequiredError } from '../../../../errors/parameter-required.error' 3 | import { IPasswordCrypto } from '../../../../infra/shared/crypto/password.crypto' 4 | import { User } from '../../entities/user.entity' 5 | import { IUserRespository } from '../../repositories/user.repository' 6 | 7 | export type UserRequest = { 8 | name: string 9 | username: string 10 | password: string 11 | } 12 | 13 | export class CreateUserUseCase { 14 | constructor(private userRepository: IUserRespository) {} 15 | 16 | async execute(data: UserRequest) { 17 | const user = await User.create(data) 18 | 19 | if (!data.username || !data.password) { 20 | throw new ParameterRequiredError('Username/password is required.', 422) 21 | } 22 | 23 | const existUser = await this.userRepository.findByUsername(data.username) 24 | 25 | if (existUser) { 26 | throw new CustomError('Username already exists', 400, 'USER_EXISTS_ERROR') 27 | } 28 | 29 | const userCreated = await this.userRepository.save(user) 30 | 31 | return userCreated 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/create-notification-appointmnet/create-notification-appointment.usecase.ts: -------------------------------------------------------------------------------- 1 | import { IMailProvider } from '../../../../infra/providers/mail/mail.provider' 2 | import { queueAppointmentNotification } from '../../../../infra/queue/notification-appointment/notification-appointment.queue' 3 | import { formatDate } from '../../../../utils/date' 4 | import { IAppointmentRepository } from '../../repositories/appointment.repository' 5 | 6 | export class CreateNotificationAppointmentUseCase { 7 | /* 8 | Listar todos os agendamentos do dia 9 | Enviar e-mail para os pacientes com a informação do horário de atendimento 10 | */ 11 | constructor(private appointmentRepository: IAppointmentRepository) {} 12 | 13 | async execute() { 14 | const appointmnets = 15 | await this.appointmentRepository.findAllTodayIncludePatients() 16 | 17 | appointmnets.forEach(async (appointment) => { 18 | const emailPatient = appointment.patient.email 19 | const date = appointment.date 20 | 21 | await queueAppointmentNotification.push({ 22 | email: emailPatient, 23 | date: date, 24 | }) 25 | }) 26 | 27 | return appointmnets 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-info/create-doctor-info.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { DoctorInfo } from '../../entities/doctor-info.entity' 3 | import { IDoctorInfoRepository } from '../../repositories/doctor-info.repository' 4 | import { IDoctorRepository } from '../../repositories/doctor.repository' 5 | 6 | export type DoctorInfoRequest = { 7 | startAt: string 8 | endAt: string 9 | price: number 10 | duration: number 11 | } 12 | 13 | export class CreateDoctorInfoUseCase { 14 | constructor( 15 | private doctorRepository: IDoctorRepository, 16 | private doctorInfoRepository: IDoctorInfoRepository 17 | ) {} 18 | 19 | async execute(data: DoctorInfoRequest, userId: string) { 20 | const doctorByUserID = await this.doctorRepository.findByUserID(userId) 21 | 22 | if (!doctorByUserID) { 23 | throw new CustomError('Doctor does not exists!') 24 | } 25 | 26 | const doctorInfo = DoctorInfo.create({ 27 | ...data, 28 | doctorId: doctorByUserID.id, 29 | }) 30 | 31 | const doctorCreated = await this.doctorInfoRepository.saveOrUpdate( 32 | doctorInfo 33 | ) 34 | return doctorCreated 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dist/src/routes/doctor.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.doctorRouter = void 0; 13 | const express_1 = require("express"); 14 | const create_doctor_1 = require("../modules/doctor/useCases/create-doctor"); 15 | const doctorRouter = (0, express_1.Router)(); 16 | exports.doctorRouter = doctorRouter; 17 | doctorRouter.post('/doctors', (request, response) => __awaiter(void 0, void 0, void 0, function* () { 18 | yield create_doctor_1.createDoctorController.handle(request, response); 19 | })); 20 | -------------------------------------------------------------------------------- /dist/src/routes/patient.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.patientRouter = void 0; 13 | const express_1 = require("express"); 14 | const create_patient_1 = require("../modules/patient/useCases/create-patient"); 15 | const patientRouter = (0, express_1.Router)(); 16 | exports.patientRouter = patientRouter; 17 | patientRouter.post('/patients', (request, response) => __awaiter(void 0, void 0, void 0, function* () { 18 | yield create_patient_1.createPatientController.handle(request, response); 19 | })); 20 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor-schedule/create-doctor-schedule.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { DoctorSchedule } from '../../entities/doctor-schedule.entity' 3 | import { IDoctorScheduleRepository } from '../../repositories/doctor-schedule.repository' 4 | import { IDoctorRepository } from '../../repositories/doctor.repository' 5 | 6 | export type CreateDoctorScheduleRequest = { 7 | schedules: DoctorSchedulesRequest[] 8 | } 9 | 10 | type DoctorSchedulesRequest = { 11 | startAt: string 12 | endAt: string 13 | dayOfWeek: number 14 | } 15 | 16 | export class CreateDoctorScheduleUseCase { 17 | constructor( 18 | private doctorRepository: IDoctorRepository, 19 | private doctorScheduleRepository: IDoctorScheduleRepository 20 | ) {} 21 | 22 | async execute(data: CreateDoctorScheduleRequest, userId: string) { 23 | const doctor = await this.doctorRepository.findByUserID(userId) 24 | 25 | if (!doctor) { 26 | throw new CustomError('Doctor does not exists!', 400) 27 | } 28 | 29 | const doctorSchedule = DoctorSchedule.create({ 30 | schedules: data.schedules, 31 | doctorId: doctor.id, 32 | }) 33 | 34 | await this.doctorScheduleRepository.save(doctorSchedule) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/create-appointment/index.ts: -------------------------------------------------------------------------------- 1 | import { EtherealMailProvider } from '../../../../infra/providers/mail/implementations/ethereal.mail.provider' 2 | import { DoctorSchedulePrismaRepository } from '../../../doctor/repositories/implementations/prisma/doctor-schedule.prisma.repository' 3 | import { DoctorPrismaRepository } from '../../../doctor/repositories/implementations/prisma/doctor.prisma.repository' 4 | import { PatientPrismaRepository } from '../../../patient/repositories/prisma/patient.prisma.repository' 5 | import { AppointmentPrismaRepository } from '../../repositories/prisma/appointment.prisma.repository' 6 | import { CreateAppointmentController } from './create-appointment.controller' 7 | 8 | const patientRepository = new PatientPrismaRepository() 9 | const doctorRepository = new DoctorPrismaRepository() 10 | const doctorScheduleRepository = new DoctorSchedulePrismaRepository() 11 | const appointmentPrismaRepository = new AppointmentPrismaRepository() 12 | const ethrealMailProvider = new EtherealMailProvider() 13 | 14 | const createAppointmentController = new CreateAppointmentController( 15 | patientRepository, 16 | doctorRepository, 17 | doctorScheduleRepository, 18 | appointmentPrismaRepository, 19 | ethrealMailProvider 20 | ) 21 | 22 | export { createAppointmentController } 23 | -------------------------------------------------------------------------------- /src/modules/users/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto' 2 | import { ParameterRequiredError } from '../../../errors/parameter-required.error' 3 | import { PasswordBcrypt } from '../../../infra/shared/crypto/password.bcrypt' 4 | 5 | type IUser = { 6 | name: string 7 | password: string 8 | username: string 9 | avatar?: string | null 10 | } 11 | 12 | export class User { 13 | name: string 14 | password: string 15 | username: string 16 | id: string 17 | isAdmin: boolean 18 | avatar?: string | null 19 | 20 | private constructor(props: IUser) { 21 | if (!props.username || !props.password) { 22 | throw new ParameterRequiredError('Username/password is required.', 422) 23 | } 24 | 25 | this.name = props.name 26 | this.username = props.username 27 | this.password = props.password 28 | this.id = randomUUID() 29 | this.isAdmin = false 30 | this.avatar = props.avatar 31 | } 32 | 33 | static async create(props: IUser) { 34 | if (!props.password) { 35 | throw new ParameterRequiredError('Username/password is required.', 422) 36 | } 37 | 38 | const bcrypt = new PasswordBcrypt() 39 | const passwordHashed = await bcrypt.hash(props.password) 40 | props.password = passwordHashed 41 | 42 | const user = new User(props) 43 | return user 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/mapper/doctor-schedule.map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DoctorScheduleMapper = void 0; 4 | const generateUUID_1 = require("../../../utils/generateUUID"); 5 | class DoctorScheduleMapper { 6 | } 7 | exports.DoctorScheduleMapper = DoctorScheduleMapper; 8 | DoctorScheduleMapper.entityToPrisma = (data) => { 9 | const doctorSchedulePrisma = []; 10 | data.schedules.forEach((schedule) => { 11 | var _a; 12 | doctorSchedulePrisma.push({ 13 | day_of_week: schedule.dayOfWeek, 14 | doctor_id: data.doctorId, 15 | end_at: schedule.endAt, 16 | start_at: schedule.startAt, 17 | id: (_a = schedule.id) !== null && _a !== void 0 ? _a : (0, generateUUID_1.generateUUID)(), 18 | }); 19 | }); 20 | return doctorSchedulePrisma; 21 | }; 22 | DoctorScheduleMapper.prismaToEntity = (schedule) => { 23 | var _a; 24 | return { 25 | doctorId: schedule.doctor_id, 26 | startAt: schedule.start_at, 27 | endAt: schedule.end_at, 28 | dayOfWeek: schedule.day_of_week, 29 | doctor: { 30 | doctorInfo: { 31 | duration: ((_a = schedule.doctor.doctorInfo) === null || _a === void 0 ? void 0 : _a.duration) || 0, 32 | }, 33 | }, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /dist/src/infra/databases/seed/create-admin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const password_bcrypt_1 = require("../../shared/crypto/password.bcrypt"); 13 | const prisma_config_1 = require("../prisma.config"); 14 | function main() { 15 | return __awaiter(this, void 0, void 0, function* () { 16 | const password = yield new password_bcrypt_1.PasswordBcrypt().hash('admin'); 17 | yield prisma_config_1.prismaClient.user.create({ 18 | data: { 19 | name: 'admin', 20 | password, 21 | username: 'admin', 22 | isAdmin: true, 23 | }, 24 | }); 25 | }); 26 | } 27 | main(); 28 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/useCases/create-appointment/tests/create-appointment.usecase.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const vitest_1 = require("vitest"); 13 | (0, vitest_1.describe)('Create Appointment', () => { 14 | (0, vitest_1.test)('Should not be able to create an appointment without a patient or with an invalid patient', () => __awaiter(void 0, void 0, void 0, function* () { 15 | (0, vitest_1.expect)(true).toBe(true); 16 | })); 17 | (0, vitest_1.test)('Should not be able to create an appointment without a doctor or with an invalid doctor', () => __awaiter(void 0, void 0, void 0, function* () { 18 | (0, vitest_1.expect)(true).toBe(true); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /dist/src/infra/shared/crypto/password.bcrypt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.PasswordBcrypt = void 0; 16 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 17 | class PasswordBcrypt { 18 | hash(password) { 19 | return bcryptjs_1.default.hash(password, 10); 20 | } 21 | compare(password, passwordHash) { 22 | return __awaiter(this, void 0, void 0, function* () { 23 | return bcryptjs_1.default.compare(password, passwordHash); 24 | }); 25 | } 26 | } 27 | exports.PasswordBcrypt = PasswordBcrypt; 28 | -------------------------------------------------------------------------------- /dist/src/routes/doctor-info.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.doctorInfoRouter = void 0; 13 | const express_1 = require("express"); 14 | const ensure_authenticate_middleware_1 = require("../infra/shared/http/middleware/ensure-authenticate.middleware"); 15 | const create_doctor_info_1 = require("../modules/doctor/useCases/create-doctor-info"); 16 | const doctorInfoRouter = (0, express_1.Router)(); 17 | exports.doctorInfoRouter = doctorInfoRouter; 18 | doctorInfoRouter.post('/doctor-info', ensure_authenticate_middleware_1.ensureAuthenticate, (request, response) => __awaiter(void 0, void 0, void 0, function* () { 19 | yield create_doctor_info_1.createDoctorInfoController.handle(request, response); 20 | })); 21 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import utc from 'dayjs/plugin/utc' 3 | 4 | dayjs.extend(utc) 5 | 6 | // Validar o horário de atendimento 7 | 8 | // Validar se o horário de término é maior que horário de início 9 | 10 | export function validateTime(time: string) { 11 | // 99:99 12 | return dayjs(formatDateHour(time)).isValid() 13 | } 14 | 15 | export function formatDateHour(time: string) { 16 | const date = dayjs().format('YYYY-MM-DD ') //2022-12-25 17 | const dateTimeFormat = new Date(`${date} ${time}`) // 2022-12-25 23:55 18 | return dayjs(dateTimeFormat) 19 | } 20 | 21 | export function compareEndTimeIsAfter(startTime: string, endTime: string) { 22 | return formatDateHour(endTime).isAfter(formatDateHour(startTime)) 23 | } 24 | 25 | export function getDayOfWeek(date: string) { 26 | return dayjs(date).day() 27 | } 28 | 29 | export function formatDate(date: Date, format: string) { 30 | return dayjs(date).format(format) 31 | } 32 | 33 | export function formatDateUTC(date: Date, format: string) { 34 | return dayjs(date).utc().format(format) 35 | } 36 | 37 | export function dateToString(date: Date) { 38 | return dayjs(date).format('YYYY-MM-DD').toString() 39 | } 40 | 41 | export function toDate(date: Date) { 42 | return dayjs(date).toDate() 43 | } 44 | 45 | export function startOfDay() { 46 | return dayjs().utc().startOf('D').toDate() 47 | } 48 | 49 | export function endOfDay() { 50 | return dayjs().utc().endOf('D').toDate() 51 | } 52 | -------------------------------------------------------------------------------- /dist/src/routes/doctor-schedule.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.doctorScheduleRoutes = void 0; 13 | const express_1 = require("express"); 14 | const ensure_authenticate_middleware_1 = require("../infra/shared/http/middleware/ensure-authenticate.middleware"); 15 | const create_doctor_schedule_1 = require("../modules/doctor/useCases/create-doctor-schedule"); 16 | const doctorScheduleRoutes = (0, express_1.Router)(); 17 | exports.doctorScheduleRoutes = doctorScheduleRoutes; 18 | doctorScheduleRoutes.post('/doctor-schedule', ensure_authenticate_middleware_1.ensureAuthenticate, (request, response) => __awaiter(void 0, void 0, void 0, function* () { 19 | yield create_doctor_schedule_1.createDoctorScheduleController.handle(request, response); 20 | })); 21 | -------------------------------------------------------------------------------- /dist/src/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.userRouter = void 0; 13 | const express_1 = require("express"); 14 | const authenticate_user_1 = require("../modules/users/useCases/authenticate-user"); 15 | const create_user_1 = require("../modules/users/useCases/create-user"); 16 | const userRouter = (0, express_1.Router)(); 17 | exports.userRouter = userRouter; 18 | userRouter.post('/login', (request, response) => __awaiter(void 0, void 0, void 0, function* () { 19 | yield authenticate_user_1.authenticateUserController.handle(request, response); 20 | })); 21 | userRouter.post('/users', (request, response) => __awaiter(void 0, void 0, void 0, function* () { 22 | yield create_user_1.createUserController.handle(request, response); 23 | })); 24 | -------------------------------------------------------------------------------- /src/modules/doctor/entities/tests/doctor.entity.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, describe } from 'vitest' 2 | import { Doctor } from '../doctor.entity' 3 | 4 | describe('Doctor entity', () => { 5 | test('Should be able to create a new doctor', () => { 6 | const doctor = Doctor.create({ 7 | crm: '123456', 8 | email: 'email@email.com', 9 | specialityId: 'SPEC_ID', 10 | userId: 'USER_ID', 11 | }) 12 | 13 | expect(doctor).toBeInstanceOf(Doctor) 14 | expect(doctor).toHaveProperty('id') 15 | }) 16 | 17 | test('Should not be able to create a new doctor with CRM invalid', () => { 18 | expect(() => { 19 | Doctor.create({ 20 | crm: '', 21 | email: 'email@email.com', 22 | specialityId: 'SPEC_ID', 23 | userId: 'USER_ID', 24 | }) 25 | }).toThrow('CRM is required!') 26 | }) 27 | 28 | test('Should not be able to create a new doctor with CRM length invalid', () => { 29 | expect(() => { 30 | Doctor.create({ 31 | crm: '12345', 32 | email: 'email@email.com', 33 | specialityId: 'SPEC_ID', 34 | userId: 'USER_ID', 35 | }) 36 | }).toThrow('CRM length is incorrect!') 37 | }) 38 | 39 | test('Should not be able to create a new doctor with Email invalid', () => { 40 | expect(() => { 41 | Doctor.create({ 42 | crm: '123456', 43 | email: '', 44 | specialityId: 'SPEC_ID', 45 | userId: 'USER_ID', 46 | }) 47 | }).toThrow('Email is required!') 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /dist/src/infra/shared/http/middleware/ensure-admin.middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ensureAdmin = void 0; 13 | const user_prisma_repository_1 = require("../../../../modules/users/repositories/implementations/user.prisma.repository"); 14 | const ensureAdmin = (request, response, next) => __awaiter(void 0, void 0, void 0, function* () { 15 | const userRepository = new user_prisma_repository_1.UserPrismaRepository(); 16 | const user = yield userRepository.findById(request.userId); 17 | if (!user) { 18 | return response.status(400).json({ message: 'User does not exists!' }); 19 | } 20 | if (!user.isAdmin) { 21 | return response.status(401).json({ message: 'User is not admin!' }); 22 | } 23 | return next(); 24 | }); 25 | exports.ensureAdmin = ensureAdmin; 26 | -------------------------------------------------------------------------------- /dist/src/modules/users/entities/tests/user.entity.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const vitest_1 = require("vitest"); 13 | const user_entity_1 = require("../user.entity"); 14 | (0, vitest_1.describe)('User entity', () => { 15 | (0, vitest_1.test)('Should be able to create a new user', () => __awaiter(void 0, void 0, void 0, function* () { 16 | const user = yield user_entity_1.User.create({ 17 | name: 'USER_NAME', 18 | password: 'PASSWORD_TEST', 19 | username: 'USERNAME', 20 | }); 21 | console.log({ user }); 22 | (0, vitest_1.expect)(user).toBeInstanceOf(user_entity_1.User); 23 | (0, vitest_1.expect)(user).toHaveProperty('id'); 24 | (0, vitest_1.expect)(user.password).not.equal('PASSWORD_TEST'); 25 | })); 26 | }); 27 | -------------------------------------------------------------------------------- /src/infra/shared/http/middleware/ensure-authenticate.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | import { CustomError } from '../../../../errors/custom.error' 3 | import { JWTToken } from '../../token/jwt.token' 4 | 5 | export const ensureAuthenticate = ( 6 | request: Request, 7 | response: Response, 8 | next: NextFunction 9 | ) => { 10 | /** 11 | * 1 - Receber o meu token 12 | * 2 - Validar se token está correto 13 | * 3 - Se correto, passar para o próximo passo 14 | * 4 - Se não, retornar com erro 15 | */ 16 | 17 | const headerAuth = request.headers.authorization 18 | 19 | if (!headerAuth) { 20 | return response.status(401).json({ 21 | error: 'Token is missing', 22 | }) 23 | } 24 | 25 | // Beaerer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoibWFyaWFuYSIsImlzQWRtaW4iOmZhbHNlLCJpZCI6IjNkMDE1MjUyLWQzMjEtNGM2Ni04OGE3LTI0NzRiYjg0ZDkyMSJ9LCJpYXQiOjE2NjcwNTg1MTEsImV4cCI6MTY2NzA1ODU3MSwic3ViIjoiM2QwMTUyNTItZDMyMS00YzY2LTg4YTctMjQ3NGJiODRkOTIxIn0.l_vC5SxJape41xs2etSPtf0TnW9SKOvA6PcdB-TcGK 26 | // [ 0 ] Bearer 27 | // [ 1 ] TOKEN 28 | 29 | const [, token] = headerAuth.split(' ') 30 | 31 | if (!token) { 32 | return response.status(401).json({ 33 | error: 'Token is missing', 34 | }) 35 | } 36 | 37 | const verifyToken = new JWTToken().validate(token) 38 | 39 | if (verifyToken) { 40 | request.userId = verifyToken.sub 41 | return next() 42 | } 43 | 44 | return response.status(401).json({ 45 | error: 'Token invalid!', 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/modules/appointments/useCases/create-appointment/create-appointment.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { IMailProvider } from '../../../../infra/providers/mail/mail.provider' 3 | import { IDoctorScheduleRepository } from '../../../doctor/repositories/doctor-schedule.repository' 4 | import { IDoctorRepository } from '../../../doctor/repositories/doctor.repository' 5 | import { IPatientRepository } from '../../../patient/repositories/patient.repository' 6 | import { IAppointmentRepository } from '../../repositories/appointment.repository' 7 | import { CreateAppointmentUseCase } from './create-appointment.usecase' 8 | 9 | export class CreateAppointmentController { 10 | constructor( 11 | private patientRepository: IPatientRepository, 12 | private doctorRepository: IDoctorRepository, 13 | private doctorScheduleRepository: IDoctorScheduleRepository, 14 | private appointmentRepository: IAppointmentRepository, 15 | private mailProvider: IMailProvider 16 | ) {} 17 | async handle(request: Request, response: Response) { 18 | const createAppointmentUseCase = new CreateAppointmentUseCase( 19 | this.patientRepository, 20 | this.doctorRepository, 21 | this.doctorScheduleRepository, 22 | this.appointmentRepository, 23 | this.mailProvider 24 | ) 25 | try { 26 | await createAppointmentUseCase.execute(request.body, request.userId) 27 | return response.status(204).end() 28 | } catch (err: any) { 29 | return response.status(err.statusCode ?? 500).json(err.message) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/implementations/prisma/doctor-schedule.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../../infra/databases/prisma.config' 2 | import { DoctorSchedule } from '../../../entities/doctor-schedule.entity' 3 | import { 4 | DoctorScheduleMapper, 5 | DoctorScheduleWeek, 6 | } from '../../../mapper/doctor-schedule.map' 7 | import { IDoctorScheduleRepository } from '../../doctor-schedule.repository' 8 | 9 | export class DoctorSchedulePrismaRepository 10 | implements IDoctorScheduleRepository 11 | { 12 | async save(data: DoctorSchedule): Promise { 13 | await prismaClient.$transaction([ 14 | prismaClient.doctorSchedules.deleteMany({ 15 | where: { 16 | doctor_id: data.doctorId, 17 | }, 18 | }), 19 | prismaClient.doctorSchedules.createMany({ 20 | data: DoctorScheduleMapper.entityToPrisma(data), 21 | }), 22 | ]) 23 | } 24 | 25 | async findByDoctorIdAndDayOfWeek( 26 | doctorId: string, 27 | dayOfWeek: number 28 | ): Promise { 29 | const result = await prismaClient.doctorSchedules.findFirst({ 30 | where: { 31 | day_of_week: dayOfWeek, 32 | AND: { 33 | doctor_id: doctorId, 34 | }, 35 | }, 36 | include: { 37 | doctor: { 38 | include: { 39 | doctorInfo: true, 40 | }, 41 | }, 42 | }, 43 | }) 44 | 45 | console.log({ result }) 46 | 47 | if (result) return DoctorScheduleMapper.prismaToEntity(result) 48 | 49 | return null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /dist/src/infra/shared/http/middleware/ensure-authenticate.middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ensureAuthenticate = void 0; 4 | const jwt_token_1 = require("../../token/jwt.token"); 5 | const ensureAuthenticate = (request, response, next) => { 6 | /** 7 | * 1 - Receber o meu token 8 | * 2 - Validar se token está correto 9 | * 3 - Se correto, passar para o próximo passo 10 | * 4 - Se não, retornar com erro 11 | */ 12 | const headerAuth = request.headers.authorization; 13 | if (!headerAuth) { 14 | return response.status(401).json({ 15 | error: 'Token is missing', 16 | }); 17 | } 18 | // Beaerer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoibWFyaWFuYSIsImlzQWRtaW4iOmZhbHNlLCJpZCI6IjNkMDE1MjUyLWQzMjEtNGM2Ni04OGE3LTI0NzRiYjg0ZDkyMSJ9LCJpYXQiOjE2NjcwNTg1MTEsImV4cCI6MTY2NzA1ODU3MSwic3ViIjoiM2QwMTUyNTItZDMyMS00YzY2LTg4YTctMjQ3NGJiODRkOTIxIn0.l_vC5SxJape41xs2etSPtf0TnW9SKOvA6PcdB-TcGK 19 | // [ 0 ] Bearer 20 | // [ 1 ] TOKEN 21 | const [, token] = headerAuth.split(' '); 22 | if (!token) { 23 | return response.status(401).json({ 24 | error: 'Token is missing', 25 | }); 26 | } 27 | const verifyToken = new jwt_token_1.JWTToken().validate(token); 28 | if (verifyToken) { 29 | request.userId = verifyToken.sub; 30 | return next(); 31 | } 32 | return response.status(401).json({ 33 | error: 'Token invalid!', 34 | }); 35 | }; 36 | exports.ensureAuthenticate = ensureAuthenticate; 37 | -------------------------------------------------------------------------------- /src/modules/doctor/mapper/doctor-schedule.map.ts: -------------------------------------------------------------------------------- 1 | import { DoctorSchedule } from '../entities/doctor-schedule.entity' 2 | import { 3 | Doctor, 4 | DoctorInfo, 5 | DoctorSchedules as DoctorSchedulesPrisma, 6 | } from '@prisma/client' 7 | import { generateUUID } from '../../../utils/generateUUID' 8 | 9 | export type DoctorScheduleWeek = { 10 | startAt: string 11 | endAt: string 12 | dayOfWeek: number 13 | doctorId: string 14 | doctor: { 15 | doctorInfo: { 16 | duration: number 17 | } 18 | } 19 | } 20 | 21 | export class DoctorScheduleMapper { 22 | static entityToPrisma = (data: DoctorSchedule): DoctorSchedulesPrisma[] => { 23 | const doctorSchedulePrisma: DoctorSchedulesPrisma[] = [] 24 | data.schedules.forEach((schedule) => { 25 | doctorSchedulePrisma.push({ 26 | day_of_week: schedule.dayOfWeek, 27 | doctor_id: data.doctorId, 28 | end_at: schedule.endAt, 29 | start_at: schedule.startAt, 30 | id: schedule.id ?? generateUUID(), 31 | }) 32 | }) 33 | 34 | return doctorSchedulePrisma 35 | } 36 | 37 | static prismaToEntity = ( 38 | schedule: DoctorSchedulesPrisma & { 39 | doctor: Doctor & { doctorInfo: DoctorInfo | null } 40 | } 41 | ): DoctorScheduleWeek => { 42 | return { 43 | doctorId: schedule.doctor_id, 44 | startAt: schedule.start_at, 45 | endAt: schedule.end_at, 46 | dayOfWeek: schedule.day_of_week, 47 | doctor: { 48 | doctorInfo: { 49 | duration: schedule.doctor.doctorInfo?.duration || 0, 50 | }, 51 | }, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dist/src/routes/speciality.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.specialityRouter = void 0; 13 | const express_1 = require("express"); 14 | const ensure_admin_middleware_1 = require("../infra/shared/http/middleware/ensure-admin.middleware"); 15 | const ensure_authenticate_middleware_1 = require("../infra/shared/http/middleware/ensure-authenticate.middleware"); 16 | const create_speciality_1 = require("../modules/speciality/useCases/create-speciality"); 17 | const specialityRouter = (0, express_1.Router)(); 18 | exports.specialityRouter = specialityRouter; 19 | specialityRouter.post('/specialities', ensure_authenticate_middleware_1.ensureAuthenticate, ensure_admin_middleware_1.ensureAdmin, (request, response) => __awaiter(void 0, void 0, void 0, function* () { 20 | yield create_speciality_1.createSpecialityController.handle(request, response); 21 | })); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medical_appointment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon src/server.ts", 8 | "start": "nodemon src/server.ts", 9 | "build": "tsc", 10 | "test": "vitest", 11 | "test:coverage": "vitest run --coverage", 12 | "test:ui": "vitest --ui", 13 | "test:e2e": "vitest --config ./test/test.e2e.config.ts" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@prisma/client": "^4.4.0", 20 | "bcryptjs": "^2.4.3", 21 | "dayjs": "^1.11.6", 22 | "dotenv": "^16.0.3", 23 | "express": "^4.18.1", 24 | "fastq": "^1.15.0", 25 | "jsonwebtoken": "^8.5.1", 26 | "multer": "^1.4.5-lts.1", 27 | "node-cron": "^3.0.2", 28 | "nodemailer": "^6.8.0", 29 | "redis": "^4.6.5", 30 | "rimraf": "^3.0.2", 31 | "swagger-ui-express": "^4.5.0", 32 | "winston": "^3.8.1", 33 | "zod": "^3.19.1" 34 | }, 35 | "devDependencies": { 36 | "@types/bcryptjs": "^2.4.2", 37 | "@types/jsonwebtoken": "^8.5.9", 38 | "@types/multer": "^1.4.7", 39 | "@types/node-cron": "^3.0.7", 40 | "@types/nodemailer": "^6.4.7", 41 | "@types/supertest": "^2.0.12", 42 | "@types/swagger-ui-express": "^4.1.3", 43 | "@types/winston": "^2.4.4", 44 | "@vitest/coverage-istanbul": "^0.24.5", 45 | "@vitest/ui": "^0.24.5", 46 | "nodemon": "^2.0.19", 47 | "prisma": "^4.4.0", 48 | "supertest": "^6.3.3", 49 | "ts-node": "^10.9.1", 50 | "typescript": "^4.7.4", 51 | "vitest": "^0.24.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/useCases/create-appointment/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createAppointmentController = void 0; 4 | const ethereal_mail_provider_1 = require("../../../../infra/providers/mail/implementations/ethereal.mail.provider"); 5 | const doctor_schedule_prisma_repository_1 = require("../../../doctor/repositories/implementations/prisma/doctor-schedule.prisma.repository"); 6 | const doctor_prisma_repository_1 = require("../../../doctor/repositories/implementations/prisma/doctor.prisma.repository"); 7 | const patient_prisma_repository_1 = require("../../../patient/repositories/prisma/patient.prisma.repository"); 8 | const appointment_prisma_repository_1 = require("../../repositories/prisma/appointment.prisma.repository"); 9 | const create_appointment_controller_1 = require("./create-appointment.controller"); 10 | const patientRepository = new patient_prisma_repository_1.PatientPrismaRepository(); 11 | const doctorRepository = new doctor_prisma_repository_1.DoctorPrismaRepository(); 12 | const doctorScheduleRepository = new doctor_schedule_prisma_repository_1.DoctorSchedulePrismaRepository(); 13 | const appointmentPrismaRepository = new appointment_prisma_repository_1.AppointmentPrismaRepository(); 14 | const ethrealMailProvider = new ethereal_mail_provider_1.EtherealMailProvider(); 15 | const createAppointmentController = new create_appointment_controller_1.CreateAppointmentController(patientRepository, doctorRepository, doctorScheduleRepository, appointmentPrismaRepository, ethrealMailProvider); 16 | exports.createAppointmentController = createAppointmentController; 17 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/repositories/implementations/speciality.memory.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.SpecialityMemoryRepository = void 0; 13 | class SpecialityMemoryRepository { 14 | constructor() { 15 | this.items = []; 16 | } 17 | save(data) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | this.items.push(data); 20 | return data; 21 | }); 22 | } 23 | findByName(name) { 24 | return __awaiter(this, void 0, void 0, function* () { 25 | return this.items.find((speciality) => speciality.name === name) || null; 26 | }); 27 | } 28 | findById(id) { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | return this.items.find((speciality) => speciality.id === id) || null; 31 | }); 32 | } 33 | } 34 | exports.SpecialityMemoryRepository = SpecialityMemoryRepository; 35 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/implementations/in-memory/doctor-info-memory.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.DoctorInfoMemoryRepository = void 0; 13 | class DoctorInfoMemoryRepository { 14 | constructor() { 15 | this.items = []; 16 | } 17 | saveOrUpdate(data) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | const index = this.items.findIndex((doctor) => doctor.doctorId == data.doctorId); 20 | if (index >= 0) { 21 | const doctor = this.items[index]; 22 | this.items[index] = Object.assign(Object.assign({}, doctor), { duration: data.duration, price: data.price }); 23 | data = this.items[index]; 24 | } 25 | else { 26 | this.items.push(data); 27 | } 28 | return data; 29 | }); 30 | } 31 | } 32 | exports.DoctorInfoMemoryRepository = DoctorInfoMemoryRepository; 33 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-info/create-doctor-info.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateDoctorInfoController = void 0; 13 | const create_doctor_info_usecase_1 = require("./create-doctor-info.usecase"); 14 | class CreateDoctorInfoController { 15 | constructor(doctorRepository, doctorInfoRepository) { 16 | this.doctorRepository = doctorRepository; 17 | this.doctorInfoRepository = doctorInfoRepository; 18 | } 19 | handle(request, response) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | const { body, userId } = request; 22 | const createDoctorInfoUseCase = new create_doctor_info_usecase_1.CreateDoctorInfoUseCase(this.doctorRepository, this.doctorInfoRepository); 23 | const result = yield createDoctorInfoUseCase.execute(body, userId); 24 | return response.json(result); 25 | }); 26 | } 27 | } 28 | exports.CreateDoctorInfoController = CreateDoctorInfoController; 29 | -------------------------------------------------------------------------------- /dist/src/routes/appointment.routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.appointmentRoutes = void 0; 13 | const express_1 = require("express"); 14 | const ensure_authenticate_middleware_1 = require("../infra/shared/http/middleware/ensure-authenticate.middleware"); 15 | const create_appointment_1 = require("../modules/appointments/useCases/create-appointment"); 16 | const free_schedules_1 = require("../modules/appointments/useCases/free-schedules"); 17 | const appointmentRoutes = (0, express_1.Router)(); 18 | exports.appointmentRoutes = appointmentRoutes; 19 | appointmentRoutes.get('/appointments/free', (request, response) => __awaiter(void 0, void 0, void 0, function* () { 20 | yield free_schedules_1.freeScheduleController.handle(request, response); 21 | })); 22 | appointmentRoutes.post('/appointments', ensure_authenticate_middleware_1.ensureAuthenticate, (request, response) => __awaiter(void 0, void 0, void 0, function* () { 23 | yield create_appointment_1.createAppointmentController.handle(request, response); 24 | })); 25 | -------------------------------------------------------------------------------- /src/modules/patient/useCases/create-patient/create-patient.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { User } from '../../../users/entities/user.entity' 3 | import { IUserRespository } from '../../../users/repositories/user.repository' 4 | import { IPatientRepository } from '../../repositories/patient.repository' 5 | import { Patient } from '../../entities/patient.entity' 6 | 7 | export type CreatePatientRequest = { 8 | username: string 9 | password: string 10 | email: string 11 | name: string 12 | document: string 13 | } 14 | 15 | export class CreatePatientUseCase { 16 | constructor( 17 | private userRepository: IUserRespository, 18 | private patientRepository: IPatientRepository 19 | ) {} 20 | 21 | async execute(data: CreatePatientRequest, avatar?: string) { 22 | const user = await User.create({ 23 | name: data.name, 24 | password: data.password, 25 | username: data.username, 26 | avatar: avatar, 27 | }) 28 | 29 | const existUser = await this.userRepository.findByUsername(data.username) 30 | 31 | if (existUser) { 32 | throw new CustomError('Username already exists', 400, 'USER_EXISTS_ERROR') 33 | } 34 | 35 | const existPatient = await this.patientRepository.findByDocumentOrEmail( 36 | data.document, 37 | data.email 38 | ) 39 | 40 | if (existPatient) { 41 | throw new CustomError('Patient already exists!') 42 | } 43 | const userCreated = await this.userRepository.save(user) 44 | 45 | const patient = Patient.create({ 46 | document: data.document, 47 | email: data.email, 48 | userId: userCreated.id, 49 | }) 50 | const patientCreated = await this.patientRepository.save(patient) 51 | 52 | return patientCreated 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/useCases/create-speciality/create-speciality.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateSpecialityController = void 0; 13 | const create_speciality_usecase_1 = require("./create-speciality.usecase"); 14 | class CreateSpecialityController { 15 | constructor(specialityRepository) { 16 | this.specialityRepository = specialityRepository; 17 | } 18 | handle(request, response) { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | try { 21 | const useCase = new create_speciality_usecase_1.CreateSpecialityUseCase(this.specialityRepository); 22 | const result = yield useCase.execute(request.body); 23 | return response.json(result); 24 | } 25 | catch (err) { 26 | return response.status(err.statusCode || 400).json({ 27 | error: err.message, 28 | }); 29 | } 30 | }); 31 | } 32 | } 33 | exports.CreateSpecialityController = CreateSpecialityController; 34 | -------------------------------------------------------------------------------- /src/modules/doctor/repositories/implementations/prisma/doctor.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from '@prisma/client' 2 | import { prismaClient } from '../../../../../infra/databases/prisma.config' 3 | import { DoctorWithUserDTO } from '../../../dto/doctor.dto' 4 | import { Doctor } from '../../../entities/doctor.entity' 5 | import { DoctorMapper } from '../../../mapper/doctor.map' 6 | import { IDoctorRepository } from '../../doctor.repository' 7 | 8 | export class DoctorPrismaRepository implements IDoctorRepository { 9 | async save(data: Doctor): Promise { 10 | const doctor = await prismaClient.doctor.create({ 11 | data: { 12 | crm: data.crm, 13 | email: data.email, 14 | speciality_id: data.specialityId, 15 | user_id: data.userId, 16 | }, 17 | }) 18 | return DoctorMapper.prismaToEntityDoctor(doctor) 19 | } 20 | async findByCRM(crm: string): Promise { 21 | const doctor = await prismaClient.doctor.findUnique({ 22 | where: { 23 | crm, 24 | }, 25 | }) 26 | if (doctor) return DoctorMapper.prismaToEntityDoctor(doctor) 27 | return null 28 | } 29 | 30 | async findById(id: string): Promise { 31 | const doctor = await prismaClient.doctor.findUnique({ 32 | where: { 33 | id, 34 | }, 35 | include: { 36 | user: true, 37 | }, 38 | }) 39 | if (doctor) return DoctorMapper.prismaToEntityDoctorWithUser(doctor) 40 | return null 41 | } 42 | 43 | async findByUserID(userID: string): Promise { 44 | const doctor = await prismaClient.doctor.findUnique({ 45 | where: { 46 | user_id: userID, 47 | }, 48 | }) 49 | if (doctor) return DoctorMapper.prismaToEntityDoctor(doctor) 50 | return null 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /dist/src/modules/patient/useCases/create-patient/create-patient.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreatePatientController = void 0; 13 | const create_patient_usecase_1 = require("./create-patient.usecase"); 14 | class CreatePatientController { 15 | constructor(userRepository, patientRepository) { 16 | this.userRepository = userRepository; 17 | this.patientRepository = patientRepository; 18 | } 19 | handle(request, response) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | try { 22 | const createPatientUseCase = new create_patient_usecase_1.CreatePatientUseCase(this.userRepository, this.patientRepository); 23 | const result = yield createPatientUseCase.execute(request.body); 24 | return response.json(result); 25 | } 26 | catch (err) { 27 | return response.status(err.statusCode || 400).json(err.message); 28 | } 29 | }); 30 | } 31 | } 32 | exports.CreatePatientController = CreatePatientController; 33 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/useCases/create-speciality/create-speciality.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateSpecialityUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | const speciality_entity_1 = require("../../entities/speciality.entity"); 15 | class CreateSpecialityUseCase { 16 | constructor(specialityRepository) { 17 | this.specialityRepository = specialityRepository; 18 | } 19 | execute(data) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | const speciality = new speciality_entity_1.Speciality(data); 22 | const existSpeciality = yield this.specialityRepository.findByName(data.name); 23 | if (existSpeciality) { 24 | throw new custom_error_1.CustomError('Speciality already exists!'); 25 | } 26 | const specialityCreated = yield this.specialityRepository.save(speciality); 27 | return specialityCreated; 28 | }); 29 | } 30 | } 31 | exports.CreateSpecialityUseCase = CreateSpecialityUseCase; 32 | -------------------------------------------------------------------------------- /dist/src/modules/users/repositories/implementations/user.memory.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.UserMemoryRepository = void 0; 13 | class UserMemoryRepository { 14 | constructor() { 15 | this.users = []; 16 | } 17 | static getInstance() { 18 | if (!UserMemoryRepository.instance) { 19 | UserMemoryRepository.instance = new UserMemoryRepository(); 20 | } 21 | return UserMemoryRepository.instance; 22 | } 23 | findByUsername(username) { 24 | return __awaiter(this, void 0, void 0, function* () { 25 | return this.users.find((user) => user.username === username); 26 | }); 27 | } 28 | save(data) { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | this.users.push(data); 31 | return data; 32 | }); 33 | } 34 | findById(id) { 35 | return __awaiter(this, void 0, void 0, function* () { 36 | return this.users.find((user) => user.id === id) || null; 37 | }); 38 | } 39 | } 40 | exports.UserMemoryRepository = UserMemoryRepository; 41 | -------------------------------------------------------------------------------- /dist/src/modules/patient/repositories/in-memory/patient.in-memory.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.PatientInMemoryRepository = void 0; 13 | class PatientInMemoryRepository { 14 | constructor() { 15 | this.items = []; 16 | } 17 | save(data) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | this.items.push(data); 20 | return data; 21 | }); 22 | } 23 | findByDocumentOrEmail(document, email) { 24 | return __awaiter(this, void 0, void 0, function* () { 25 | throw new Error('Method not implemented.'); 26 | }); 27 | } 28 | findById(id) { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | return this.items.find((patient) => patient.id === id) || null; 31 | }); 32 | } 33 | findByUserId(userId) { 34 | return __awaiter(this, void 0, void 0, function* () { 35 | return (this.items.find((patient) => patient.userId === userId) || null); 36 | }); 37 | } 38 | } 39 | exports.PatientInMemoryRepository = PatientInMemoryRepository; 40 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/implementations/in-memory/doctor-memory.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.DoctorMemoryRepository = void 0; 13 | class DoctorMemoryRepository { 14 | constructor() { 15 | this.items = []; 16 | } 17 | save(data) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | this.items.push(data); 20 | return data; 21 | }); 22 | } 23 | findByCRM(crm) { 24 | return __awaiter(this, void 0, void 0, function* () { 25 | return this.items.find((doctor) => doctor.crm === crm) || null; 26 | }); 27 | } 28 | findById(id) { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | return (this.items.find((doctor) => doctor.id === id) || 31 | null); 32 | }); 33 | } 34 | findByUserID(userID) { 35 | return __awaiter(this, void 0, void 0, function* () { 36 | return this.items.find((doctor) => doctor.userId === userID) || null; 37 | }); 38 | } 39 | } 40 | exports.DoctorMemoryRepository = DoctorMemoryRepository; 41 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/create-user/create-user.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateUserController = void 0; 13 | const logger_1 = require("../../../../utils/logger"); 14 | const create_user_usecase_1 = require("./create-user.usecase"); 15 | class CreateUserController { 16 | constructor(userRepository) { 17 | this.userRepository = userRepository; 18 | } 19 | handle(request, response) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | logger_1.logger.info('Usuário sendo criado!'); 22 | try { 23 | const data = request.body; 24 | const useCase = new create_user_usecase_1.CreateUserUseCase(this.userRepository); 25 | const result = yield useCase.execute(data); 26 | return response.json(result); 27 | } 28 | catch (err) { 29 | logger_1.logger.error(err.stack); 30 | return response.status(err.statusCode).json({ 31 | error: err.message, 32 | }); 33 | } 34 | }); 35 | } 36 | } 37 | exports.CreateUserController = CreateUserController; 38 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/useCases/free-schedules/free-schedules.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.FreeSchedulesController = void 0; 13 | const free_schedules_usecase_1 = require("./free-schedules.usecase"); 14 | class FreeSchedulesController { 15 | constructor(doctorScheduleRepository, appointmentRepository) { 16 | this.doctorScheduleRepository = doctorScheduleRepository; 17 | this.appointmentRepository = appointmentRepository; 18 | } 19 | handle(request, response) { 20 | var _a; 21 | return __awaiter(this, void 0, void 0, function* () { 22 | const freeScheduleUseCase = new free_schedules_usecase_1.FreeSchedulesUseCase(this.doctorScheduleRepository, this.appointmentRepository); 23 | try { 24 | const result = yield freeScheduleUseCase.execute(request.body); 25 | return response.json(result); 26 | } 27 | catch (err) { 28 | return response.status((_a = err.statusCode) !== null && _a !== void 0 ? _a : 500).json(err.message); 29 | } 30 | }); 31 | } 32 | } 33 | exports.FreeSchedulesController = FreeSchedulesController; 34 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/repositories/implementations/prisma/doctor-info.prisma.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.DoctorInfoPrismaRepository = void 0; 13 | const prisma_config_1 = require("../../../../../infra/databases/prisma.config"); 14 | const doctor_info_map_1 = require("../../../mapper/doctor-info.map"); 15 | class DoctorInfoPrismaRepository { 16 | saveOrUpdate(data) { 17 | return __awaiter(this, void 0, void 0, function* () { 18 | const doctor = yield prisma_config_1.prismaClient.doctorInfo.upsert({ 19 | where: { doctor_id: data.doctorId }, 20 | create: { 21 | duration: data.duration, 22 | price: data.price, 23 | doctor_id: data.doctorId, 24 | id: data.id, 25 | }, 26 | update: { 27 | duration: data.duration, 28 | price: data.price, 29 | }, 30 | }); 31 | return doctor_info_map_1.DoctorInfoMapper.prismaToEntityDoctorInfo(doctor); 32 | }); 33 | } 34 | } 35 | exports.DoctorInfoPrismaRepository = DoctorInfoPrismaRepository; 36 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-info/create-doctor-info.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateDoctorInfoUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | const doctor_info_entity_1 = require("../../entities/doctor-info.entity"); 15 | class CreateDoctorInfoUseCase { 16 | constructor(doctorRepository, doctorInfoRepository) { 17 | this.doctorRepository = doctorRepository; 18 | this.doctorInfoRepository = doctorInfoRepository; 19 | } 20 | execute(data, userId) { 21 | return __awaiter(this, void 0, void 0, function* () { 22 | const doctorByUserID = yield this.doctorRepository.findByUserID(userId); 23 | if (!doctorByUserID) { 24 | throw new custom_error_1.CustomError('Doctor does not exists!'); 25 | } 26 | const doctorInfo = doctor_info_entity_1.DoctorInfo.create(Object.assign(Object.assign({}, data), { doctorId: doctorByUserID.id })); 27 | const doctorCreated = yield this.doctorInfoRepository.saveOrUpdate(doctorInfo); 28 | return doctorCreated; 29 | }); 30 | } 31 | } 32 | exports.CreateDoctorInfoUseCase = CreateDoctorInfoUseCase; 33 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/authenticate-user/authenticate-user.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.AuthenticateUserController = void 0; 13 | const authenticate_user_usecase_1 = require("./authenticate-user.usecase"); 14 | class AuthenticateUserController { 15 | constructor(userRepository, passwordCrypt, token) { 16 | this.userRepository = userRepository; 17 | this.passwordCrypt = passwordCrypt; 18 | this.token = token; 19 | } 20 | handle(request, response) { 21 | return __awaiter(this, void 0, void 0, function* () { 22 | try { 23 | const data = request.body; 24 | const authenticateUserUseCase = new authenticate_user_usecase_1.AuthenticateUserUseCase(this.userRepository, this.passwordCrypt, this.token); 25 | const result = yield authenticateUserUseCase.execute(data); 26 | return response.json(result); 27 | } 28 | catch (err) { 29 | return response.status(err.statusCode).json({ 30 | error: err.message, 31 | }); 32 | } 33 | }); 34 | } 35 | } 36 | exports.AuthenticateUserController = AuthenticateUserController; 37 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-schedule/create-doctor-schedule.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateDoctorScheduleUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | const doctor_schedule_entity_1 = require("../../entities/doctor-schedule.entity"); 15 | class CreateDoctorScheduleUseCase { 16 | constructor(doctorRepository, doctorScheduleRepository) { 17 | this.doctorRepository = doctorRepository; 18 | this.doctorScheduleRepository = doctorScheduleRepository; 19 | } 20 | execute(data, userId) { 21 | return __awaiter(this, void 0, void 0, function* () { 22 | const doctor = yield this.doctorRepository.findByUserID(userId); 23 | if (!doctor) { 24 | throw new custom_error_1.CustomError('Doctor does not exists!', 400); 25 | } 26 | const doctorSchedule = doctor_schedule_entity_1.DoctorSchedule.create({ 27 | schedules: data.schedules, 28 | doctorId: doctor.id, 29 | }); 30 | yield this.doctorScheduleRepository.save(doctorSchedule); 31 | }); 32 | } 33 | } 34 | exports.CreateDoctorScheduleUseCase = CreateDoctorScheduleUseCase; 35 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/useCases/create-doctor-schedule/create-doctor-schedule.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateDoctorScheduleController = void 0; 13 | const create_doctor_schedule_usecase_1 = require("./create-doctor-schedule.usecase"); 14 | class CreateDoctorScheduleController { 15 | constructor(doctorRepository, doctorScheduleRepository) { 16 | this.doctorRepository = doctorRepository; 17 | this.doctorScheduleRepository = doctorScheduleRepository; 18 | } 19 | handle(request, response) { 20 | var _a; 21 | return __awaiter(this, void 0, void 0, function* () { 22 | const createDoctorScheduleUseCase = new create_doctor_schedule_usecase_1.CreateDoctorScheduleUseCase(this.doctorRepository, this.doctorScheduleRepository); 23 | try { 24 | yield createDoctorScheduleUseCase.execute(request.body, request.userId); 25 | return response.status(204).end(); 26 | } 27 | catch (err) { 28 | return response.status((_a = err.statusCode) !== null && _a !== void 0 ? _a : 500).json(err.message); 29 | } 30 | }); 31 | } 32 | } 33 | exports.CreateDoctorScheduleController = CreateDoctorScheduleController; 34 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/entities/tests/doctor.entity.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const vitest_1 = require("vitest"); 4 | const doctor_entity_1 = require("../doctor.entity"); 5 | (0, vitest_1.describe)('Doctor entity', () => { 6 | (0, vitest_1.test)('Should be able to create a new doctor', () => { 7 | const doctor = doctor_entity_1.Doctor.create({ 8 | crm: '123456', 9 | email: 'email@email.com', 10 | specialityId: 'SPEC_ID', 11 | userId: 'USER_ID', 12 | }); 13 | (0, vitest_1.expect)(doctor).toBeInstanceOf(doctor_entity_1.Doctor); 14 | (0, vitest_1.expect)(doctor).toHaveProperty('id'); 15 | }); 16 | (0, vitest_1.test)('Should not be able to create a new doctor with CRM invalid', () => { 17 | (0, vitest_1.expect)(() => { 18 | doctor_entity_1.Doctor.create({ 19 | crm: '', 20 | email: 'email@email.com', 21 | specialityId: 'SPEC_ID', 22 | userId: 'USER_ID', 23 | }); 24 | }).toThrow('CRM is required!'); 25 | }); 26 | (0, vitest_1.test)('Should not be able to create a new doctor with CRM length invalid', () => { 27 | (0, vitest_1.expect)(() => { 28 | doctor_entity_1.Doctor.create({ 29 | crm: '12345', 30 | email: 'email@email.com', 31 | specialityId: 'SPEC_ID', 32 | userId: 'USER_ID', 33 | }); 34 | }).toThrow('CRM length is incorrect!'); 35 | }); 36 | (0, vitest_1.test)('Should not be able to create a new doctor with Email invalid', () => { 37 | (0, vitest_1.expect)(() => { 38 | doctor_entity_1.Doctor.create({ 39 | crm: '123456', 40 | email: '', 41 | specialityId: 'SPEC_ID', 42 | userId: 'USER_ID', 43 | }); 44 | }).toThrow('Email is required!'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/modules/users/useCases/refresh-token/refresh-token.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CreateConnectionRedis } from '../../../../infra/providers/redis' 2 | import { sign, verify } from 'jsonwebtoken' 3 | import { TokenError } from '../../../../errors/token.error' 4 | import { IToken } from '../../../../infra/shared/token/token' 5 | import { IUserRespository } from '../../repositories/user.repository' 6 | 7 | class RefreshTokenUseCase { 8 | constructor( 9 | private token: IToken, 10 | private userRepository: IUserRespository 11 | ) {} 12 | 13 | async execute(refreshToken: string) { 14 | // Recuperar o refreshToken do redis 15 | // Verificar se o refreshToken é valido 16 | // Gerar um novo token 17 | const secretKeyRefreshToken = process.env.SECRET_KEY_REFRESH_TOKEN || '' 18 | try { 19 | const { sub } = verify(refreshToken, secretKeyRefreshToken) 20 | 21 | const redisClient = new CreateConnectionRedis() 22 | const refreshTokenRedis = await redisClient.getValue(String(sub)) 23 | 24 | if (refreshToken !== refreshTokenRedis) { 25 | throw new TokenError('Refresh Token Incorrect', 401) 26 | } 27 | 28 | const user = await this.userRepository.findById(String(sub)) 29 | 30 | if (!user) { 31 | throw new Error('User does not exists!') 32 | } 33 | 34 | const tokenGenerated = this.token.create(user) 35 | 36 | // Gerar um refresh_token 37 | 38 | const refreshTokenSecret = process.env.SECRET_KEY_REFRESH_TOKEN || '' 39 | 40 | const newRefreshToken = sign({}, refreshTokenSecret, { 41 | subject: user.id, 42 | expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, 43 | }) 44 | // Salvar no redis 45 | 46 | await redisClient.setValue(user.id, newRefreshToken) 47 | 48 | return { 49 | token: tokenGenerated, 50 | refreshToken: newRefreshToken, 51 | } 52 | } catch (err) { 53 | throw new TokenError('Token incorrect', 401) 54 | } 55 | } 56 | } 57 | 58 | export { RefreshTokenUseCase } 59 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/create-user/create-user.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateUserUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | const parameter_required_error_1 = require("../../../../errors/parameter-required.error"); 15 | const user_entity_1 = require("../../entities/user.entity"); 16 | class CreateUserUseCase { 17 | constructor(userRepository) { 18 | this.userRepository = userRepository; 19 | } 20 | execute(data) { 21 | return __awaiter(this, void 0, void 0, function* () { 22 | const user = yield user_entity_1.User.create(data); 23 | if (!data.username || !data.password) { 24 | throw new parameter_required_error_1.ParameterRequiredError('Username/password is required.', 422); 25 | } 26 | const existUser = yield this.userRepository.findByUsername(data.username); 27 | if (existUser) { 28 | throw new custom_error_1.CustomError('Username already exists', 400, 'USER_EXISTS_ERROR'); 29 | } 30 | const userCreated = yield this.userRepository.save(user); 31 | return userCreated; 32 | }); 33 | } 34 | } 35 | exports.CreateUserUseCase = CreateUserUseCase; 36 | -------------------------------------------------------------------------------- /dist/src/modules/doctor/entities/doctor-schedule.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DoctorSchedule = void 0; 4 | const custom_error_1 = require("../../../errors/custom.error"); 5 | const date_1 = require("../../../utils/date"); 6 | const generateUUID_1 = require("../../../utils/generateUUID"); 7 | class DoctorSchedule { 8 | constructor(props) { 9 | if (!props.schedules) { 10 | throw new custom_error_1.CustomError('Invalid schedules', 400); 11 | } 12 | validDuplicateSchedules(props.schedules); 13 | validateTimes(props.schedules); 14 | this.doctorId = props.doctorId; 15 | this.schedules = createSchedules(props.schedules); 16 | } 17 | static create(data) { 18 | const doctorSchedule = new DoctorSchedule(data); 19 | return doctorSchedule; 20 | } 21 | } 22 | exports.DoctorSchedule = DoctorSchedule; 23 | const validDuplicateSchedules = (schedules) => { 24 | const hasUniqueValue = new Set(schedules.map((value) => value.dayOfWeek)); 25 | if (hasUniqueValue.size < schedules.length) { 26 | throw new custom_error_1.CustomError('Duplicate Day of Week', 400); 27 | } 28 | }; 29 | const validateTimes = (schedules) => { 30 | schedules.forEach((schedule) => { 31 | if (!(0, date_1.validateTime)(schedule.startAt)) { 32 | throw new custom_error_1.CustomError('Invalid StartAt'); 33 | } 34 | if (!(0, date_1.validateTime)(schedule.endAt)) { 35 | throw new custom_error_1.CustomError('Invalid EndAt'); 36 | } 37 | if (!(0, date_1.compareEndTimeIsAfter)(schedule.startAt, schedule.endAt)) { 38 | throw new custom_error_1.CustomError('End time cannot be earlier than start time!'); 39 | } 40 | }); 41 | }; 42 | const createSchedules = (schedules) => { 43 | return schedules.map((schedule) => { 44 | return Object.assign(Object.assign({}, schedule), { id: (0, generateUUID_1.generateUUID)() }); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/doctor/entities/doctor-schedule.entity.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../errors/custom.error' 2 | import { compareEndTimeIsAfter, validateTime } from '../../../utils/date' 3 | import { generateUUID } from '../../../utils/generateUUID' 4 | 5 | type Schedules = { 6 | endAt: string 7 | startAt: string 8 | dayOfWeek: number 9 | id?: string 10 | } 11 | 12 | type DoctorScheduleProps = { 13 | doctorId: string 14 | schedules: Schedules[] 15 | } 16 | 17 | export class DoctorSchedule { 18 | doctorId: string 19 | schedules: Schedules[] 20 | constructor(props: DoctorScheduleProps) { 21 | if (!props.schedules) { 22 | throw new CustomError('Invalid schedules', 400) 23 | } 24 | 25 | validDuplicateSchedules(props.schedules) 26 | validateTimes(props.schedules) 27 | 28 | this.doctorId = props.doctorId 29 | this.schedules = createSchedules(props.schedules) 30 | } 31 | 32 | static create(data: DoctorScheduleProps) { 33 | const doctorSchedule = new DoctorSchedule(data) 34 | return doctorSchedule 35 | } 36 | } 37 | 38 | const validDuplicateSchedules = (schedules: Schedules[]) => { 39 | const hasUniqueValue = new Set(schedules.map((value) => value.dayOfWeek)) 40 | 41 | if (hasUniqueValue.size < schedules.length) { 42 | throw new CustomError('Duplicate Day of Week', 400) 43 | } 44 | } 45 | 46 | const validateTimes = (schedules: Schedules[]) => { 47 | schedules.forEach((schedule) => { 48 | if (!validateTime(schedule.startAt)) { 49 | throw new CustomError('Invalid StartAt') 50 | } 51 | 52 | if (!validateTime(schedule.endAt)) { 53 | throw new CustomError('Invalid EndAt') 54 | } 55 | 56 | if (!compareEndTimeIsAfter(schedule.startAt, schedule.endAt)) { 57 | throw new CustomError('End time cannot be earlier than start time!') 58 | } 59 | }) 60 | } 61 | 62 | const createSchedules = (schedules: Schedules[]) => { 63 | return schedules.map((schedule) => { 64 | return { 65 | ...schedule, 66 | id: generateUUID(), 67 | } 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/modules/patient/repositories/prisma/patient.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../infra/databases/prisma.config' 2 | import { PatientWithUserDTO } from '../../dto/patient.dto' 3 | import { Patient } from '../../entities/patient.entity' 4 | import { PatientMapper } from '../../mapper/patient.map' 5 | import { IPatientRepository } from '../patient.repository' 6 | 7 | export class PatientPrismaRepository implements IPatientRepository { 8 | async save(data: Patient): Promise { 9 | const patient = await prismaClient.patient.create({ 10 | data: PatientMapper.entityToPrisma(data), 11 | }) 12 | return PatientMapper.prismaToEntity(patient) 13 | } 14 | 15 | async findByDocumentOrEmail( 16 | document: string, 17 | email: string 18 | ): Promise { 19 | const patient = await prismaClient.patient.findFirst({ 20 | where: { 21 | OR: [ 22 | { 23 | email: { 24 | equals: email, 25 | }, 26 | }, 27 | { 28 | document: { 29 | equals: document, 30 | }, 31 | }, 32 | ], 33 | }, 34 | }) 35 | 36 | if (patient) { 37 | return PatientMapper.prismaToEntity(patient) 38 | } 39 | return null 40 | } 41 | 42 | async findById(id: string): Promise { 43 | const patient = await prismaClient.patient.findFirst({ 44 | where: { 45 | id, 46 | }, 47 | }) 48 | 49 | if (patient) { 50 | return PatientMapper.prismaToEntity(patient) 51 | } 52 | 53 | return null 54 | } 55 | 56 | async findByUserId(userId: string): Promise { 57 | const patient = await prismaClient.patient.findFirst({ 58 | where: { 59 | user_id: userId, 60 | }, 61 | include: { 62 | user: true, 63 | }, 64 | }) 65 | 66 | if (patient) { 67 | return PatientMapper.prismaToEntityIncludesUser(patient) 68 | } 69 | 70 | return null 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dist/src/utils/date.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.toDate = exports.dateToString = exports.formatDateUTC = exports.formatDate = exports.getDayOfWeek = exports.compareEndTimeIsAfter = exports.formatDateHour = exports.validateTime = void 0; 7 | const dayjs_1 = __importDefault(require("dayjs")); 8 | const utc_1 = __importDefault(require("dayjs/plugin/utc")); 9 | dayjs_1.default.extend(utc_1.default); 10 | // Validar o horário de atendimento 11 | // Validar se o horário de término é maior que horário de início 12 | function validateTime(time) { 13 | // 99:99 14 | return (0, dayjs_1.default)(formatDateHour(time)).isValid(); 15 | } 16 | exports.validateTime = validateTime; 17 | function formatDateHour(time) { 18 | const date = (0, dayjs_1.default)().format('YYYY-MM-DD '); //2022-12-25 19 | const dateTimeFormat = new Date(`${date} ${time}`); // 2022-12-25 23:55 20 | return (0, dayjs_1.default)(dateTimeFormat); 21 | } 22 | exports.formatDateHour = formatDateHour; 23 | function compareEndTimeIsAfter(startTime, endTime) { 24 | return formatDateHour(endTime).isAfter(formatDateHour(startTime)); 25 | } 26 | exports.compareEndTimeIsAfter = compareEndTimeIsAfter; 27 | function getDayOfWeek(date) { 28 | return (0, dayjs_1.default)(date).day(); 29 | } 30 | exports.getDayOfWeek = getDayOfWeek; 31 | function formatDate(date, format) { 32 | return (0, dayjs_1.default)(date).format(format); 33 | } 34 | exports.formatDate = formatDate; 35 | function formatDateUTC(date, format) { 36 | return (0, dayjs_1.default)(date).utc().format(format); 37 | } 38 | exports.formatDateUTC = formatDateUTC; 39 | function dateToString(date) { 40 | return (0, dayjs_1.default)(date).format('YYYY-MM-DD').toString(); 41 | } 42 | exports.dateToString = dateToString; 43 | function toDate(date) { 44 | return (0, dayjs_1.default)(date).toDate(); 45 | } 46 | exports.toDate = toDate; 47 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor/create-doctor.controller.ts: -------------------------------------------------------------------------------- 1 | import { string, z } from 'zod' 2 | 3 | import { Request, Response } from 'express' 4 | import { CreateDoctorUseCase } from './create-doctor.usecase' 5 | import { ISpecialityRepository } from '../../../speciality/repositories/speciality.repository' 6 | import { IDoctorRepository } from '../../repositories/doctor.repository' 7 | import { IUserRespository } from '../../../users/repositories/user.repository' 8 | import { validatorSchema } from '../../../../infra/shared/validator/zod' 9 | import { ValidationSchemaError } from '../../../../errors/validation-schema.error' 10 | 11 | export class CreateDoctorController { 12 | constructor( 13 | private userRepository: IUserRespository, 14 | private doctorRepository: IDoctorRepository, 15 | private specialityRepository: ISpecialityRepository 16 | ) {} 17 | 18 | async handle(request: Request, response: Response) { 19 | const { body } = request 20 | 21 | const doctorSchema = z.object({ 22 | username: z.string(), 23 | name: z.string(), 24 | email: z.string().email({ 25 | message: 'You need to insert a valid email', 26 | }), 27 | password: z.string(), 28 | crm: z.string().length(6, { 29 | message: 'CRM must contain 6 characters', 30 | }), 31 | specialityId: string().uuid({ 32 | message: 'You need to insert a valid speciality ID', 33 | }), 34 | }) 35 | 36 | try { 37 | validatorSchema(doctorSchema, body) 38 | const createDoctorUseCase = new CreateDoctorUseCase( 39 | this.userRepository, 40 | this.doctorRepository, 41 | this.specialityRepository 42 | ) 43 | 44 | const doctorCreated = await createDoctorUseCase.execute(body) 45 | return response.json(doctorCreated) 46 | } catch (erro: any) { 47 | if (erro instanceof ValidationSchemaError) { 48 | return response.status(erro.statusCode).json(erro.errors) 49 | } 50 | return response.status(erro.statusCode).json(erro.message) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dist/src/modules/users/entities/user.entity.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.User = void 0; 13 | const crypto_1 = require("crypto"); 14 | const parameter_required_error_1 = require("../../../errors/parameter-required.error"); 15 | const password_bcrypt_1 = require("../../../infra/shared/crypto/password.bcrypt"); 16 | class User { 17 | constructor(props) { 18 | if (!props.username || !props.password) { 19 | throw new parameter_required_error_1.ParameterRequiredError('Username/password is required.', 422); 20 | } 21 | this.name = props.name; 22 | this.username = props.username; 23 | this.password = props.password; 24 | this.id = (0, crypto_1.randomUUID)(); 25 | this.isAdmin = false; 26 | } 27 | static create(props) { 28 | return __awaiter(this, void 0, void 0, function* () { 29 | if (!props.password) { 30 | throw new parameter_required_error_1.ParameterRequiredError('Username/password is required.', 422); 31 | } 32 | const bcrypt = new password_bcrypt_1.PasswordBcrypt(); 33 | const passwordHashed = yield bcrypt.hash(props.password); 34 | props.password = passwordHashed; 35 | const user = new User(props); 36 | return user; 37 | }); 38 | } 39 | } 40 | exports.User = User; 41 | -------------------------------------------------------------------------------- /dist/src/modules/speciality/repositories/implementations/speciality.prisma.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.SpecialityPrismaRepository = void 0; 13 | const prisma_config_1 = require("../../../../infra/databases/prisma.config"); 14 | class SpecialityPrismaRepository { 15 | save(data) { 16 | return __awaiter(this, void 0, void 0, function* () { 17 | const speciality = yield prisma_config_1.prismaClient.speciality.create({ 18 | data: { 19 | name: data.name, 20 | description: data.description, 21 | id: data.id, 22 | }, 23 | }); 24 | return speciality; 25 | }); 26 | } 27 | findByName(name) { 28 | return __awaiter(this, void 0, void 0, function* () { 29 | return yield prisma_config_1.prismaClient.speciality.findUnique({ 30 | where: { 31 | name, 32 | }, 33 | }); 34 | }); 35 | } 36 | findById(id) { 37 | return __awaiter(this, void 0, void 0, function* () { 38 | return yield prisma_config_1.prismaClient.speciality.findUnique({ 39 | where: { 40 | id, 41 | }, 42 | }); 43 | }); 44 | } 45 | } 46 | exports.SpecialityPrismaRepository = SpecialityPrismaRepository; 47 | -------------------------------------------------------------------------------- /dist/src/modules/users/repositories/implementations/user.prisma.repository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.UserPrismaRepository = void 0; 13 | const prisma_config_1 = require("../../../../infra/databases/prisma.config"); 14 | class UserPrismaRepository { 15 | findByUsername(username) { 16 | return __awaiter(this, void 0, void 0, function* () { 17 | const user = yield prisma_config_1.prismaClient.user.findUnique({ 18 | where: { 19 | username, 20 | }, 21 | }); 22 | return user || undefined; 23 | }); 24 | } 25 | save(data) { 26 | return __awaiter(this, void 0, void 0, function* () { 27 | const user = yield prisma_config_1.prismaClient.user.create({ 28 | data: { 29 | name: data.name, 30 | password: data.password, 31 | username: data.username, 32 | }, 33 | }); 34 | return user; 35 | }); 36 | } 37 | findById(id) { 38 | return __awaiter(this, void 0, void 0, function* () { 39 | return yield prisma_config_1.prismaClient.user.findUnique({ 40 | where: { 41 | id, 42 | }, 43 | }); 44 | }); 45 | } 46 | } 47 | exports.UserPrismaRepository = UserPrismaRepository; 48 | -------------------------------------------------------------------------------- /src/modules/doctor/useCases/create-doctor/create-doctor.usecase.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseError } from 'pg' 2 | import { CustomError } from '../../../../errors/custom.error' 3 | import { ISpecialityRepository } from '../../../speciality/repositories/speciality.repository' 4 | import { IDoctorRepository } from '../../repositories/doctor.repository' 5 | import { IUserRespository } from '../../../users/repositories/user.repository' 6 | import { User } from '../../../users/entities/user.entity' 7 | import { Doctor } from '../../entities/doctor.entity' 8 | 9 | export type CreateDoctorRequest = { 10 | username: string 11 | name: string 12 | password: string 13 | email: string 14 | crm: string 15 | specialityId: string 16 | } 17 | 18 | export class CreateDoctorUseCase { 19 | constructor( 20 | private userRepository: IUserRespository, 21 | private doctorRepository: IDoctorRepository, 22 | private specialityRepository: ISpecialityRepository 23 | ) {} 24 | 25 | async execute(data: CreateDoctorRequest) { 26 | const user = await User.create({ 27 | name: data.name, 28 | password: data.password, 29 | username: data.username, 30 | }) 31 | 32 | const speciality = await this.specialityRepository.findById( 33 | data.specialityId 34 | ) 35 | 36 | if (!speciality) { 37 | throw new CustomError('Speciality does not exists!', 400) 38 | } 39 | 40 | const existUser = await this.userRepository.findByUsername(data.username) 41 | 42 | if (existUser) { 43 | throw new CustomError('Username already exists', 400, 'USER_EXISTS_ERROR') 44 | } 45 | 46 | const userCreated = await this.userRepository.save(user) 47 | 48 | // Verificação da especialidade 49 | 50 | const doctor = Doctor.create({ 51 | crm: data.crm, 52 | email: data.email, 53 | specialityId: data.specialityId, 54 | userId: userCreated.id, 55 | }) 56 | 57 | const crmExists = await this.doctorRepository.findByCRM(data.crm) 58 | 59 | if (crmExists) { 60 | throw new CustomError('CRM already exists', 400) 61 | } 62 | 63 | const doctorCreated = await this.doctorRepository.save(doctor) 64 | 65 | return doctorCreated 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/modules/users/useCases/authenticate-user/authenticate-user.usecase.ts: -------------------------------------------------------------------------------- 1 | import { CustomError } from '../../../../errors/custom.error' 2 | import { IPasswordCrypto } from '../../../../infra/shared/crypto/password.crypto' 3 | import { IToken } from '../../../../infra/shared/token/token' 4 | import { IUserRespository } from '../../repositories/user.repository' 5 | import { sign } from 'jsonwebtoken' 6 | import { CreateConnectionRedis } from '../../../../infra/providers/redis' 7 | 8 | type AuthenticateRequest = { 9 | username: string 10 | password: string 11 | } 12 | 13 | export class AuthenticateUserUseCase { 14 | /* 15 | * username 16 | password 17 | validar se o usuário existe no sistema 18 | validar se a senha está correta 19 | 20 | mariana 21 | 12345678 22 | $jsdf92349234jsdfsdf9234j234 23 | 24 | */ 25 | 26 | constructor( 27 | private userRepository: IUserRespository, 28 | private passwordCrypto: IPasswordCrypto, 29 | private token: IToken 30 | ) {} 31 | 32 | async execute({ username, password }: AuthenticateRequest) { 33 | if (!username || !password) { 34 | throw new CustomError('Username/password incorrect', 401) 35 | } 36 | 37 | const user = await this.userRepository.findByUsername(username) 38 | 39 | if (!user) { 40 | throw new CustomError('Username/password incorrect', 401) 41 | } 42 | 43 | const comparePasswordEquals = await this.passwordCrypto.compare( 44 | password, 45 | user.password 46 | ) 47 | 48 | if (!comparePasswordEquals) { 49 | throw new CustomError('Username/password incorrect', 401) 50 | } 51 | 52 | const tokenGenerated = this.token.create(user) 53 | 54 | // Gerar um refresh_token 55 | 56 | const refreshTokenSecret = process.env.SECRET_KEY_REFRESH_TOKEN || '' 57 | 58 | const refreshToken = sign({}, refreshTokenSecret, { 59 | subject: user.id, 60 | expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, 61 | }) 62 | // Salvar no redis 63 | 64 | const redisClient = new CreateConnectionRedis() 65 | await redisClient.setValue(user.id, refreshToken) 66 | 67 | return { 68 | token: tokenGenerated, 69 | refreshToken, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dist/src/modules/appointments/useCases/create-appointment/create-appointment.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreateAppointmentController = void 0; 13 | const create_appointment_usecase_1 = require("./create-appointment.usecase"); 14 | class CreateAppointmentController { 15 | constructor(patientRepository, doctorRepository, doctorScheduleRepository, appointmentRepository, mailProvider) { 16 | this.patientRepository = patientRepository; 17 | this.doctorRepository = doctorRepository; 18 | this.doctorScheduleRepository = doctorScheduleRepository; 19 | this.appointmentRepository = appointmentRepository; 20 | this.mailProvider = mailProvider; 21 | } 22 | handle(request, response) { 23 | var _a; 24 | return __awaiter(this, void 0, void 0, function* () { 25 | const createAppointmentUseCase = new create_appointment_usecase_1.CreateAppointmentUseCase(this.patientRepository, this.doctorRepository, this.doctorScheduleRepository, this.appointmentRepository, this.mailProvider); 26 | try { 27 | yield createAppointmentUseCase.execute(request.body, request.userId); 28 | return response.status(204).end(); 29 | } 30 | catch (err) { 31 | return response.status((_a = err.statusCode) !== null && _a !== void 0 ? _a : 500).json(err.message); 32 | } 33 | }); 34 | } 35 | } 36 | exports.CreateAppointmentController = CreateAppointmentController; 37 | -------------------------------------------------------------------------------- /src/modules/appointments/repositories/prisma/appointment.prisma.repository.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from '../../../../infra/databases/prisma.config' 2 | import { endOfDay, startOfDay } from '../../../../utils/date' 3 | import { Appointment } from '../../entities/appointment.entity' 4 | import { 5 | AppointmentsDate, 6 | AppointmentsWithPatient, 7 | IAppointmentRepository, 8 | } from '../appointment.repository' 9 | 10 | export class AppointmentPrismaRepository implements IAppointmentRepository { 11 | async findAllSchedulesByDoctorAndDate( 12 | doctorId: string, 13 | date: string 14 | ): Promise { 15 | return await prismaClient.$queryRaw` 16 | SELECT ap.date from appointments ap where to_char(ap.date, 'YYYY-MM-DD') = ${date} 17 | and doctor_id = ${doctorId} 18 | ` 19 | } 20 | 21 | async findAppointmentByDoctorAndDatetime( 22 | doctorId: string, 23 | date: string 24 | ): Promise { 25 | const result: AppointmentsDate[] = await prismaClient.$queryRaw` 26 | SELECT ap.date from appointments ap where to_char(ap.date, 'YYYY-MM-DD HH24:MI') = ${date} 27 | and doctor_id = ${doctorId} limit 1 28 | ` 29 | return result[0] 30 | } 31 | async findAppointmentByPatientAndDatetime( 32 | patientId: string, 33 | date: string 34 | ): Promise { 35 | const result: AppointmentsDate[] = await prismaClient.$queryRaw` 36 | SELECT ap.date from appointments ap where to_char(ap.date, 'YYYY-MM-DD HH24:MI') = ${date} 37 | and patient_id = ${patientId} limit 1 38 | ` 39 | return result[0] 40 | } 41 | 42 | async save(data: Appointment) { 43 | await prismaClient.appointment.create({ 44 | data: { 45 | date: data.date, 46 | doctor_id: data.doctorId, 47 | patient_id: data.patientId, 48 | id: data.id, 49 | }, 50 | }) 51 | } 52 | 53 | async findAllTodayIncludePatients(): Promise { 54 | const result = await prismaClient.appointment.findMany({ 55 | where: { 56 | date: { 57 | gte: startOfDay(), 58 | lte: endOfDay(), 59 | }, 60 | }, 61 | include: { 62 | patient: true, 63 | }, 64 | }) 65 | 66 | return result 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dist/src/infra/providers/mail/implementations/ethereal.mail.provider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.EtherealMailProvider = void 0; 16 | const nodemailer_1 = __importDefault(require("nodemailer")); 17 | class EtherealMailProvider { 18 | constructor() { 19 | nodemailer_1.default 20 | .createTestAccount() 21 | .then(() => { 22 | const transporter = nodemailer_1.default.createTransport({ 23 | host: 'smtp.ethereal.email', 24 | port: 587, 25 | auth: { 26 | user: 'brittany.sauer83@ethereal.email', 27 | pass: 'r8WaVpgHGW1tfFkqQC', 28 | }, 29 | }); 30 | this.client = transporter; 31 | }) 32 | .catch((err) => console.log(err)); 33 | } 34 | sendMail(data) { 35 | return __awaiter(this, void 0, void 0, function* () { 36 | const resultMail = yield this.client.sendMail({ 37 | to: data.to, 38 | from: data.from, 39 | subject: data.subject, 40 | text: data.text, 41 | html: data.html, 42 | }); 43 | console.log('Message sent: %s', resultMail.messageId); 44 | console.log('Preview URL: %s', nodemailer_1.default.getTestMessageUrl(resultMail)); 45 | }); 46 | } 47 | } 48 | exports.EtherealMailProvider = EtherealMailProvider; 49 | -------------------------------------------------------------------------------- /dist/src/modules/users/useCases/authenticate-user/authenticate-user.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.AuthenticateUserUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | class AuthenticateUserUseCase { 15 | /* 16 | * username 17 | password 18 | validar se o usuário existe no sistema 19 | validar se a senha está correta 20 | 21 | mariana 22 | 12345678 23 | $jsdf92349234jsdfsdf9234j234 24 | 25 | */ 26 | constructor(userRepository, passwordCrypto, token) { 27 | this.userRepository = userRepository; 28 | this.passwordCrypto = passwordCrypto; 29 | this.token = token; 30 | } 31 | execute({ username, password }) { 32 | return __awaiter(this, void 0, void 0, function* () { 33 | if (!username || !password) { 34 | throw new custom_error_1.CustomError('Username/password incorrect', 401); 35 | } 36 | const user = yield this.userRepository.findByUsername(username); 37 | if (!user) { 38 | throw new custom_error_1.CustomError('Username/password incorrect', 401); 39 | } 40 | const comparePasswordEquals = yield this.passwordCrypto.compare(password, user.password); 41 | if (!comparePasswordEquals) { 42 | throw new custom_error_1.CustomError('Username/password incorrect', 401); 43 | } 44 | const tokenGenerated = this.token.create(user); 45 | return tokenGenerated; 46 | }); 47 | } 48 | } 49 | exports.AuthenticateUserUseCase = AuthenticateUserUseCase; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agendamento de consulta médica 2 | 3 | ### **Funcionalidades** 4 | 5 | --- 6 | 7 | ### **Cadastro de Usuário** 8 | 9 | - [x] Deve ser possível o usuário realizar um cadastro 10 | - [x] O usuário não precisa estar autenticado na aplicação para se cadastrar 11 | - [x] Não deve ser possível realizar o cadastro de um usuário sem username e senha. 12 | - [x] Não deve ser possível realizar um cadastro de username já existente. 13 | - [x] Não deve ser possível o usuário cadastrar a permissão de administrador. 14 | 15 | --- 16 | 17 | ### **Cadastro de Especialidade** 18 | 19 | - [] Deve ser possível um usuário cadastrar uma especialidade 20 | - [x] O usuário precisa estar autenticado na aplicação. 21 | - [x] Não deve ser possível realizar o cadastro de uma especialidade já existente, ou seja, com o mesmo nome. 22 | - [x] O usuário precisa ter permissão de administrador. 23 | - [x] Não deve ser possível cadastrar uma especialidade com nome vazio. 24 | 25 | ### **Cadastro de Médico** 26 | 27 | - [x] Deve ser possível cadastrar um médico 28 | - [x] O médico deve possuir um CRM com 6 dígitos 29 | - [x] O médico deve estar atrelado a um usuário 30 | - [x] O médico deve ter uma e somente uma especialidade 31 | - [x] Não deve ser possível cadastrar um médico sem CRM. 32 | - [x] Não deve ser possível cadastrar o mesmo CRM mais de uma vez. 33 | 34 | ### **Cadastro de Informações do médico** 35 | 36 | - [x] Deve ser possível cadastrar a informação de um médico 37 | - [x] O médico ele deve estar cadastrado 38 | - [x] O médico deve estar autenticado na aplicação (ROUTES) 39 | - [x] Não deve ser possível ter mais de um registro de informação por médico 40 | - [x] O horário de término não deve ser menor que o horário de início de atendimento 41 | - [x] A duração da consulta não pode ser menor ou igual a zero 42 | 43 | ### **Cadastro de agendamento** 44 | 45 | - [ ] Deve ser possível cadastrar um agendamento 46 | - [x] O paciente deve estar cadastrado no sistema 47 | - [x] O paciente deve estar autenticado na aplicação 48 | - [x] O médico selecionado deve estar cadastrado no sistema 49 | - [x] O médico escolhido deve ter disponibilidade para o horário selecionado 50 | - [x] O médico deve ter disponibilidade para o dia da semana escolhido 51 | - [x] O horário escolhido deve estar entre o horário de atendimento do médico 52 | - [x] Não deve ser possível possível cadastrar um agendamento se já existir outro agendamento para o mesmo médico com a mesma data e horário selecionado. 53 | - [x] O paciente não deve ter algum agendamento cadastrado para o mesmo dia e horário escolhido. 54 | -------------------------------------------------------------------------------- /dist/src/modules/patient/useCases/create-patient/create-patient.usecase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.CreatePatientUseCase = void 0; 13 | const custom_error_1 = require("../../../../errors/custom.error"); 14 | const user_entity_1 = require("../../../users/entities/user.entity"); 15 | const patient_entity_1 = require("../../entities/patient.entity"); 16 | class CreatePatientUseCase { 17 | constructor(userRepository, patientRepository) { 18 | this.userRepository = userRepository; 19 | this.patientRepository = patientRepository; 20 | } 21 | execute(data) { 22 | return __awaiter(this, void 0, void 0, function* () { 23 | const user = yield user_entity_1.User.create({ 24 | name: data.name, 25 | password: data.password, 26 | username: data.username, 27 | }); 28 | const existUser = yield this.userRepository.findByUsername(data.username); 29 | if (existUser) { 30 | throw new custom_error_1.CustomError('Username already exists', 400, 'USER_EXISTS_ERROR'); 31 | } 32 | const existPatient = yield this.patientRepository.findByDocumentOrEmail(data.document, data.email); 33 | if (existPatient) { 34 | throw new custom_error_1.CustomError('Patient already exists!'); 35 | } 36 | const userCreated = yield this.userRepository.save(user); 37 | const patient = patient_entity_1.Patient.create({ 38 | document: data.document, 39 | email: data.email, 40 | userId: userCreated.id, 41 | }); 42 | const patientCreated = yield this.patientRepository.save(patient); 43 | return patientCreated; 44 | }); 45 | } 46 | } 47 | exports.CreatePatientUseCase = CreatePatientUseCase; 48 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model User { 11 | id String @id @default(uuid()) 12 | name String 13 | username String @unique 14 | password String 15 | isAdmin Boolean @default(false) 16 | createdAt DateTime @default(now()) 17 | doctor Doctor? 18 | patient Patient? 19 | avatar String? 20 | 21 | @@map("users") 22 | } 23 | 24 | model Speciality { 25 | id String @id @default(uuid()) 26 | name String @unique 27 | description String 28 | 29 | createdAt DateTime @default(now()) 30 | doctor Doctor[] 31 | 32 | @@map("specialities") 33 | } 34 | 35 | model Doctor { 36 | id String @id @default(uuid()) 37 | email String @unique 38 | crm String @unique 39 | 40 | user User @relation(fields: [user_id], references: [id]) 41 | user_id String @unique 42 | 43 | speciality Speciality @relation(fields: [speciality_id], references: [id]) 44 | speciality_id String 45 | 46 | createdAt DateTime @default(now()) 47 | doctorInfo DoctorInfo? 48 | doctorSchedules DoctorSchedules[] 49 | appointment Appointment[] 50 | 51 | @@map("doctors") 52 | } 53 | 54 | model DoctorInfo { 55 | id String @id @default(uuid()) 56 | duration Int 57 | price Decimal 58 | doctor_id String @unique 59 | doctor Doctor @relation(fields: [doctor_id], references: [id]) 60 | 61 | @@map("doctor_info") 62 | } 63 | 64 | model Patient { 65 | id String @id @default(uuid()) 66 | document String @unique 67 | email String @unique 68 | user User @relation(fields: [user_id], references: [id]) 69 | user_id String @unique 70 | appointment Appointment[] 71 | 72 | @@map("patients") 73 | } 74 | 75 | model DoctorSchedules { 76 | id String @id @default(uuid()) 77 | start_at String 78 | end_at String 79 | day_of_week Int 80 | doctor_id String 81 | doctor Doctor @relation(fields: [doctor_id], references: [id]) 82 | 83 | @@map("doctor_schedules") 84 | } 85 | 86 | model Appointment { 87 | id String @id @default(uuid()) 88 | doctor_id String 89 | doctor Doctor @relation(fields: [doctor_id], references: [id]) 90 | patient_id String 91 | patient Patient @relation(fields: [patient_id], references: [id]) 92 | is_finished Boolean @default(false) 93 | date DateTime 94 | note String? 95 | 96 | @@map("appointments") 97 | } 98 | --------------------------------------------------------------------------------