Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,631 changes: 6,631 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@swc/cli": "^0.4.1-nightly.20240914",
"@swc/core": "^1.7.35",
"@types/node": "^20.14.10",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.15",
"@typescript-eslint/eslint-plugin": "^8.4.0",
"@typescript-eslint/parser": "^8.4.0",
Expand All @@ -58,16 +59,20 @@
"typescript": "^5.5.3"
},
"dependencies": {
"@fastify/cors": "^9.0.1",
"@fastify/jwt": "^8.0.1",
"@fastify/multipart": "^8.3.0",
"@fastify/static": "^7.0.4",
"@fastify/cors": "^11.0.0",
"@fastify/jwt": "^5.0.0",
"@fastify/multipart": "^6.0.0",
"@fastify/static": "^8.0.0",
"@fastify/swagger": "^9.4.2",
"@fastify/swagger-ui": "^5.2.2",
"@prisma/client": "^5.16.2",
"dayjs": "^1.11.11",
"fastify": "^4.28.1",
"fastify": "^5.2.1",
"fastify-bcrypt": "^1.0.1",
"form-auto-content": "^3.2.1",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.15",
"zod": "^3.23.8"
"zod": "^3.23.8",
"zod-to-json-schema": "^3.24.3"
}
}
67 changes: 65 additions & 2 deletions src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import cors from '@fastify/cors';
import jwt from '@fastify/jwt';
import { fastifyMultipart } from '@fastify/multipart';
import fastifyMultipart from '@fastify/multipart';
import { fastifyStatic } from '@fastify/static';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUi from '@fastify/swagger-ui';
import { fastify } from 'fastify';
import { fastifyBcrypt } from 'fastify-bcrypt';
import cron from 'node-cron';
import { resolve } from 'node:path';

import {
Expand All @@ -14,6 +17,8 @@ import {
authRoutes,
doctorRoutes,
} from '@modules/exports';
import { PatientStatusService } from '@modules/patients/useCases/update-patient-status/update-patient-status-service';
import { Tags } from '@shared/utils/tags';

import { env } from '../env';
import { errorHandler } from './infra/http/middleware/error-handler';
Expand All @@ -25,7 +30,8 @@ export const app = fastify({
app.setErrorHandler(errorHandler);

app.register(cors, {
origin: '*',
origin: ['http://localhost:3003'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
});
app.register(fastifyMultipart);
app.register(fastifyStatic, {
Expand All @@ -38,10 +44,67 @@ app.register(jwt, {
app.register(fastifyBcrypt, {
saltWorkFactor: 12,
});
app.register(fastifySwagger, {
openapi: {
info: {
title: 'Sistema de Gerenciamento Hospitalar',
description: 'API para gerenciamento de pacientes, médicos e consultas.',
version: '1.0.0',
contact: {
name: 'Suporte do Sistema',
email: 'suporte@hospitalsystem.com',
},
},
components: {
securitySchemes: {
Bearer: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
servers: [
{
url: 'http://localhost:6006',
description: 'Ambiente de Desenvolvimento',
},
{
url: env.BASE_URL_WEB || 'https://api.hospitalsystem.com',
description: 'Produção',
},
],
tags: [
{ name: Tags.AUTH, description: 'Autenticação' },
{ name: Tags.USERS, description: 'Gestão de usuários' },
{ name: Tags.PATIENTS, description: 'Gestão de pacientes' },
{ name: Tags.DOCTORS, description: 'Gestão de médicos' },
{ name: Tags.APPOINTMENTS, description: 'Gerenciamento de consultas' },
],
},
});
app.register(fastifySwaggerUi, {
routePrefix: '/docs',
uiConfig: {
docExpansion: 'list',
deepLinking: true,
},
});

app.register(authRoutes);
app.register(userRoutes);
app.register(patientRoutes);
app.register(appointmentRoutes);
app.register(uploadRoutes);
app.register(doctorRoutes);

// Job deve rodar todos os dias as 21hrs
cron.schedule('0 21 * * *', async () => {
try {
app.log.info('Iniciando tarefa de atualização de status de pacientes.');
await PatientStatusService.execute();
app.log.info('Tarefa de atualização concluída com sucesso.');
} catch (error) {
app.log.error('Erro durante a execução da tarefa:', error);
}
});
2 changes: 1 addition & 1 deletion src/app/infra/http/middleware/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@shared/constants/messages';

interface TokenData {
id: number;
id: string;
role: Role;
status: Status;
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/infra/http/middleware/update-last-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const updateLastAccess = (userRepository: UserRepository) => {
}

const token = authorization.split(' ')[1];
const { id } = app.jwt.verify<{ id: number }>(token);
const { id } = app.jwt.verify<{ id: string }>(token);

await userRepository.updateLastAccess(id);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Warnings:

- The primary key for the `appointments` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `doctors` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `users` table will be changed. If it partially fails, the table could be left without primary key constraint.

*/
-- DropForeignKey
ALTER TABLE "appointments" DROP CONSTRAINT "appointments_doctor_id_fkey";

-- AlterTable
ALTER TABLE "appointments" DROP CONSTRAINT "appointments_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ALTER COLUMN "doctor_id" SET DATA TYPE TEXT,
ADD CONSTRAINT "appointments_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "appointments_id_seq";

-- AlterTable
ALTER TABLE "doctors" DROP CONSTRAINT "doctors_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "doctors_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "doctors_id_seq";

-- AlterTable
ALTER TABLE "users" DROP CONSTRAINT "users_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "users_id_seq";

-- AddForeignKey
ALTER TABLE "appointments" ADD CONSTRAINT "appointments_doctor_id_fkey" FOREIGN KEY ("doctor_id") REFERENCES "doctors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
8 changes: 4 additions & 4 deletions src/app/infra/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ enum AppointmentType {
}

model User {
id Int @id @default(autoincrement())
id String @id @default(uuid())
name String
email String @unique
role Role
Expand Down Expand Up @@ -88,7 +88,7 @@ model Patient {
}

model Appointment {
id Int @id @default(autoincrement())
id String @id @default(uuid())
appointment_type AppointmentType @default(QUERY)
examination String?
diagnosis_summary String?
Expand All @@ -97,7 +97,7 @@ model Appointment {
created_at DateTime @default(now())
updated_at DateTime @updatedAt()

doctor_id Int
doctor_id String
doctor Doctor @relation(fields: [doctor_id], references: [id])
patient_id String
patient Patient @relation(fields: [patient_id], references: [id])
Expand All @@ -106,7 +106,7 @@ model Appointment {
}

model Doctor {
id Int @id @default(autoincrement())
id String @id @default(uuid())
name String
sex Sex
crm String @unique
Expand Down
10 changes: 5 additions & 5 deletions src/app/infra/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const doctors = [
email: 'joao.martins@hospital.com',
birth_date: new Date('1975-04-10'),
specialty: 'Cardiologia',
working_days: [1, 3, 5], // Segundas, quartas, sextas
working_days: [1, 3, 5],
},
{
name: 'Dra. Maria Souza',
Expand All @@ -91,7 +91,7 @@ const doctors = [
email: 'maria.souza@hospital.com',
birth_date: new Date('1980-12-20'),
specialty: 'Pediatria',
working_days: [2, 4], // Terças e quintas
working_days: [2, 4],
},
{
name: 'Dr. Ricardo Alves',
Expand All @@ -101,7 +101,7 @@ const doctors = [
email: 'ricardo.alves@hospital.com',
birth_date: new Date('1982-09-15'),
specialty: 'Ortopedia',
working_days: [1, 2, 5], // Segundas, terças e sextas
working_days: [1, 2, 5],
},
{
name: 'Dra. Fernanda Oliveira',
Expand All @@ -111,7 +111,7 @@ const doctors = [
email: 'fernanda.oliveira@hospital.com',
birth_date: new Date('1990-05-18'),
specialty: 'Dermatologia',
working_days: [3, 5], // Quartas e sextas
working_days: [3, 5],
},
{
name: 'Dr. Lucas Lima',
Expand All @@ -121,7 +121,7 @@ const doctors = [
email: 'lucas.lima@hospital.com',
birth_date: new Date('1987-03-30'),
specialty: 'Neurologia',
working_days: [2, 4], // Terças e quintas
working_days: [2, 4],
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class PrismaAppointmentRepository implements AppointmentRepository {
};
}

async findById(appointment_id: number): Promise<IAppointment | null> {
async findById(appointment_id: string): Promise<IAppointment | null> {
return prisma.appointment.findUnique({
where: { id: appointment_id },
include: {
Expand All @@ -113,7 +113,7 @@ export class PrismaAppointmentRepository implements AppointmentRepository {
}

async update(
appointment_id: number,
appointment_id: string,
data: Partial<IAppointment>,
): Promise<IAppointment | null> {
return await prisma.appointment.update({
Expand All @@ -125,7 +125,7 @@ export class PrismaAppointmentRepository implements AppointmentRepository {
}

async updateAppointmentStatus(
appointment_id: number,
appointment_id: string,
status: AppointmentStatus,
): Promise<AppointmentStatus> {
const appointment = await prisma.appointment.update({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export interface AppointmentRepository {
findAllAppointments(
params: FindAllAppointmentsAndCountParams,
): Promise<FindEntitiesAndCountResult<IAppointment>>;
findById(appointment_id: number): Promise<IAppointment | null>;
findById(appointment_id: string): Promise<IAppointment | null>;
create(patient_id: string, dto: IAppointment): Promise<IAppointment>;
update(
appointment_id: number,
appointment_id: string,
dto: Partial<IAppointment>,
): Promise<IAppointment | null>;
updateAppointmentStatus(
appointment_id: number,
appointment_id: string,
status: AppointmentStatus,
): Promise<AppointmentStatus>;
}
2 changes: 1 addition & 1 deletion src/modules/appointments/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const appointmentParamId = z.object({

export const appointmentParamsSchema = z.object({
patientId: z.string().uuid(),
appointmentId: z.coerce.number().int(),
appointmentId: z.string().uuid(),
});

export const appointmentQuerySchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const createAppointmentSchema = z
.object({
appointment_type: z.nativeEnum(AppointmentType),
scheduled_date: z.coerce.date(),
doctor_id: z.number().int(),
doctor_id: z.string().uuid(),
})
.strict();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Status } from '@prisma/client';

import { AppError } from '@app/errors/app-client';
import { prisma } from '@app/infra/prisma/client';
import { AppointmentRepository } from '@modules/appointments/repositories/appointment-repository';
import { DoctorRepository } from '@modules/doctors/repositories/doctor-repository';
import { PatientRepository } from '@modules/patients/repositories/patient-repository';
Expand Down Expand Up @@ -39,6 +40,13 @@ export class CreateAppointmentService {
throw new AppError(DOCTOR_INACTIVE);
}

if (patient.status === Status.INACTIVE) {
await prisma.patient.update({
where: { id: patientId },
data: { status: Status.ACTIVE },
});
}

const appointment = await this.appointmentRepository.create(patientId, dto);

return appointment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class GetAppointmentService {
this.patientRepository = patientRepository;
}

async execute(appointmentId: number, patientId: string) {
async execute(appointmentId: string, patientId: string) {
const patient = await this.patientRepository.findById(patientId);

if (!patient) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class UpdateAppointmentStatusService {
}

async execute(
appointmentId: number,
appointmentId: string,
patientId: string,
dto: UpdateAppointmentStatusDTO,
): Promise<AppointmentStatus> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const updateAppointmentSchema = z
.object({
appointment_type: z.nativeEnum(AppointmentType),
scheduled_date: z.coerce.date(),
doctor_id: z.number().int(),
doctor_id: z.string().uuid(),
examination: z.string().min(3, MIN_LENGTH_TEXT).max(255, MAX_LENGTH_TEXT),
diagnosis_summary: z
.string()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class UpdateAppointmentService {
}

async execute(
appointmentId: number,
appointmentId: string,
patientId: string,
dto: UpdateAppointmentDTO,
) {
Expand Down
Loading