/
opt
/
mawid
/
apps
/
api
/
src
/
patients
/
/opt/mawid/apps/api/src/patients
mkdir
upload
Name
Size
Mode
Actions
patients.controller.ts
1462
0644
edit
dl
rm
patients.gdpr.spec.ts
4139
0644
edit
dl
rm
patients.module.ts
269
0644
edit
dl
rm
patients.service.ts
4266
0644
edit
dl
rm
Edit:
/opt/mawid/apps/api/src/patients/patients.service.ts
(4266B)
import { Injectable, NotFoundException } from '@nestjs/common'; import type { UpdatePatientInput } from '@mawid/shared'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class PatientsService { constructor(private readonly prisma: PrismaService) {} list(clinicId: string, search?: string) { return this.prisma.patient.findMany({ where: { clinicId, ...(search ? { OR: [ { name: { contains: search, mode: 'insensitive' } }, { waPhone: { contains: search } }, ], } : {}), }, orderBy: [{ lastVisitAt: { sort: 'desc', nulls: 'last' } }, { createdAt: 'desc' }], take: 100, }); } async get(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId }, include: { appointments: { include: { service: { select: { id: true, name: true } }, staff: { select: { id: true, name: true } }, }, orderBy: { startsAt: 'desc' }, take: 50, }, waitlistEntries: { where: { status: { in: ['active', 'notified'] } } }, }, }); if (!patient) throw new NotFoundException('Patient not found'); return patient; } async update(clinicId: string, id: string, input: UpdatePatientInput) { await this.assertExists(clinicId, id); return this.prisma.patient.update({ where: { id }, data: input }); } /** KVKK/GDPR data export: every record we hold about the patient (Phase 7 task 4). */ async exportData(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId }, include: { appointments: { include: { service: true, staff: true } }, waitlistEntries: true, conversations: { include: { messages: { orderBy: { createdAt: 'asc' } } } }, }, }); if (!patient) throw new NotFoundException('Patient not found'); return { exportedAt: new Date().toISOString(), patient: { id: patient.id, waPhone: patient.waPhone, name: patient.name, language: patient.language, notes: patient.notes, tags: patient.tags, createdAt: patient.createdAt, }, appointments: patient.appointments, waitlistEntries: patient.waitlistEntries, conversations: patient.conversations, }; } /** * KVKK/GDPR delete-on-request: hard-deletes the patient and everything keyed * to them (messages, conversations, waitlist entries + holds, reminder jobs, * appointments). Audited without PII. */ async deleteData(clinicId: string, id: string, actor: string) { await this.assertExists(clinicId, id); await this.prisma.$transaction(async (tx) => { const conversations = await tx.conversation.findMany({ where: { clinicId, patientId: id }, select: { id: true }, }); await tx.message.deleteMany({ where: { conversationId: { in: conversations.map((c) => c.id) } }, }); await tx.conversation.deleteMany({ where: { clinicId, patientId: id } }); await tx.slotHold.deleteMany({ where: { clinicId, waitlistEntry: { patientId: id } } }); await tx.waitlistEntry.deleteMany({ where: { clinicId, patientId: id } }); const appointments = await tx.appointment.findMany({ where: { clinicId, patientId: id }, select: { id: true }, }); await tx.reminderJob.deleteMany({ where: { appointmentId: { in: appointments.map((a) => a.id) } }, }); await tx.appointment.deleteMany({ where: { clinicId, patientId: id } }); await tx.patient.delete({ where: { id } }); await tx.auditLog.create({ data: { clinicId, actor, action: 'patient:data_deleted', meta: { patientId: id }, // id only — no PII in audit logs }, }); }); return { deleted: true }; } private async assertExists(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId } }); if (!patient) throw new NotFoundException('Patient not found'); } }
Save
cmd:
run