@@ -10,13 +10,16 @@ import {
1010 UseInterceptors ,
1111 UploadedFiles ,
1212 BadRequestException ,
13+ UseGuards ,
1314} from '@nestjs/common' ;
1415import { StreamableFile } from '@nestjs/common/file-stream' ;
1516import { FilesInterceptor } from '@nestjs/platform-express' ;
1617import { MedicalRecordsService } from './medical-records.service' ;
1718import { MedicalRecordsExportService } from './medical-records-export.service' ;
1819import { CreateMedicalRecordDto } from './dto/create-medical-record.dto' ;
1920import { UpdateMedicalRecordDto } from './dto/update-medical-record.dto' ;
21+ import { AppendRecordDto } from './dto/append-record.dto' ;
22+ import { SearchMedicalRecordsDto } from './dto/search-medical-records.dto' ;
2023import { VerifyRecordDto , RevokeVerificationDto } from './dto/verify-record.dto' ;
2124import {
2225 ExportMedicalRecordsDto ,
@@ -25,72 +28,75 @@ import {
2528} from './dto/export-medical-records.dto' ;
2629import { RecordType } from './entities/medical-record.entity' ;
2730import { PetSpecies } from '../pets/entities/pet.entity' ;
28-
31+ import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard' ;
32+ import { RolesGuard } from '../../auth/guards/roles.guard' ;
33+ import { Roles } from '../../auth/decorators/roles.decorator' ;
34+ import { Permissions } from '../../auth/decorators/permissions.decorator' ;
35+ import { CurrentUser } from '../../auth/decorators/current-user.decorator' ;
36+ import { RoleName } from '../../auth/constants/roles.enum' ;
37+ import { Permission } from '../../auth/constants/permissions.enum' ;
38+
39+ @UseGuards ( JwtAuthGuard , RolesGuard )
2940@Controller ( 'medical-records' )
3041export class MedicalRecordsController {
3142 constructor (
3243 private readonly medicalRecordsService : MedicalRecordsService ,
3344 private readonly exportService : MedicalRecordsExportService ,
34- ) { }
45+ ) { }
3546
3647 @Post ( )
48+ @Permissions ( Permission . CREATE_MEDICAL_RECORDS )
3749 @UseInterceptors ( FilesInterceptor ( 'files' , 10 ) )
3850 async create (
3951 @Body ( ) createMedicalRecordDto : CreateMedicalRecordDto ,
52+ @CurrentUser ( 'id' ) userId : string ,
4053 @UploadedFiles ( ) files ?: Express . Multer . File [ ] ,
4154 ) {
42- // Handle file uploads
43- if ( files && files . length > 0 ) {
44- const attachments = await Promise . all (
45- files . map ( ( file ) => this . medicalRecordsService . saveAttachment ( file ) ) ,
55+ if ( files ?. length ) {
56+ createMedicalRecordDto . attachments = await Promise . all (
57+ files . map ( ( f ) => this . medicalRecordsService . saveAttachment ( f ) ) ,
4658 ) ;
47- createMedicalRecordDto . attachments = attachments ;
4859 }
60+ return this . medicalRecordsService . create ( createMedicalRecordDto , userId ) ;
61+ }
4962
50- return this . medicalRecordsService . create ( createMedicalRecordDto ) ;
63+ /** Rich search: ?q=text&petId=&recordType=&accessLevel=&vetId=&verified=&startDate=&endDate= */
64+ @Get ( 'search' )
65+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
66+ search ( @Query ( ) dto : SearchMedicalRecordsDto ) {
67+ return this . medicalRecordsService . search ( dto ) ;
5168 }
5269
5370 @Get ( )
71+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
5472 findAll (
5573 @Query ( 'petId' ) petId ?: string ,
5674 @Query ( 'recordType' ) recordType ?: RecordType ,
5775 @Query ( 'startDate' ) startDate ?: string ,
5876 @Query ( 'endDate' ) endDate ?: string ,
5977 ) {
60- return this . medicalRecordsService . findAll (
61- petId ,
62- recordType ,
63- startDate ,
64- endDate ,
65- ) ;
78+ return this . medicalRecordsService . findAll ( petId , recordType , startDate , endDate ) ;
6679 }
6780
6881 @Get ( 'templates/:petType' )
82+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
6983 getTemplates ( @Param ( 'petType' ) petType : PetSpecies ) {
7084 return this . medicalRecordsService . getTemplatesByPetType ( petType ) ;
7185 }
7286
7387 @Post ( 'templates' )
88+ @Roles ( RoleName . Veterinarian , RoleName . Admin )
7489 createTemplate (
7590 @Body ( 'petType' ) petType : PetSpecies ,
7691 @Body ( 'recordType' ) recordType : RecordType ,
7792 @Body ( 'templateFields' ) templateFields : Record < string , any > ,
7893 @Body ( 'description' ) description ?: string ,
7994 ) {
80- return this . medicalRecordsService . createTemplate (
81- petType ,
82- recordType ,
83- templateFields ,
84- description ,
85- ) ;
86- }
87-
88- /**
89- * Export medical records as PDF, CSV, or FHIR.
90- * GET: use query params (format, petId, recordType, startDate, endDate).
91- * POST: use body for full options including recordIds for batch export.
92- */
95+ return this . medicalRecordsService . createTemplate ( petType , recordType , templateFields , description ) ;
96+ }
97+
9398 @Get ( 'export' )
99+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
94100 async exportGet (
95101 @Query ( 'format' ) format : ExportFormat ,
96102 @Query ( 'recordIds' ) recordIdsStr ?: string ,
@@ -100,21 +106,10 @@ export class MedicalRecordsController {
100106 @Query ( 'endDate' ) endDate ?: string ,
101107 @Query ( 'includeAttachments' ) includeAttachments ?: string ,
102108 ) {
103- const recordIds = recordIdsStr
104- ? recordIdsStr
105- . split ( ',' )
106- . map ( ( s ) => s . trim ( ) )
107- . filter ( Boolean )
108- : undefined ;
109+ const recordIds = recordIdsStr ?. split ( ',' ) . map ( ( s ) => s . trim ( ) ) . filter ( Boolean ) ;
109110 const dto : ExportMedicalRecordsDto = {
110- format,
111- recordIds,
112- petId,
113- recordType,
114- startDate,
115- endDate,
116- includeAttachments :
117- includeAttachments === 'true' || includeAttachments === undefined ,
111+ format, recordIds, petId, recordType, startDate, endDate,
112+ includeAttachments : includeAttachments !== 'false' ,
118113 } ;
119114 const result = await this . exportService . export ( dto ) ;
120115 return new StreamableFile ( result . buffer , {
@@ -124,6 +119,7 @@ export class MedicalRecordsController {
124119 }
125120
126121 @Post ( 'export' )
122+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
127123 async exportPost ( @Body ( ) dto : ExportMedicalRecordsDto ) {
128124 const result = await this . exportService . export ( dto ) ;
129125 return new StreamableFile ( result . buffer , {
@@ -132,79 +128,98 @@ export class MedicalRecordsController {
132128 } ) ;
133129 }
134130
135- /**
136- * Generate export and send it by email.
137- * Requires MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS to be set.
138- */
139131 @Post ( 'export/email' )
132+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
140133 async exportEmail (
141134 @Body ( ) dto : EmailExportMedicalRecordsDto ,
142135 @Query ( 'userEmail' ) userEmail ?: string ,
143136 ) {
144137 const recipient = dto . to || userEmail ;
145- if ( ! recipient ) {
146- throw new BadRequestException (
147- 'Provide "to" in body or userEmail query param.' ,
148- ) ;
149- }
138+ if ( ! recipient ) throw new BadRequestException ( 'Provide "to" in body or userEmail query param.' ) ;
150139 return this . exportService . sendExportByEmail ( dto , recipient ) ;
151140 }
152141
153- // --- Vet Verification / Signature ---
142+ // --- Vet Verification ---
154143
155144 @Post ( ':id/verify' )
145+ @Roles ( RoleName . Veterinarian , RoleName . Admin )
156146 verifyRecord (
157147 @Param ( 'id' ) id : string ,
158- @Body ( ) verifyRecordDto : VerifyRecordDto ,
148+ @Body ( ) dto : VerifyRecordDto ,
149+ @CurrentUser ( 'id' ) userId : string ,
159150 ) {
160- return this . medicalRecordsService . verifyRecord ( id , verifyRecordDto ) ;
151+ return this . medicalRecordsService . verifyRecord ( id , dto , userId ) ;
161152 }
162153
163154 @Post ( ':id/revoke-verification' )
155+ @Roles ( RoleName . Veterinarian , RoleName . Admin )
164156 revokeVerification (
165157 @Param ( 'id' ) id : string ,
166- @Body ( ) revokeDto : RevokeVerificationDto ,
158+ @Body ( ) dto : RevokeVerificationDto ,
159+ @CurrentUser ( 'id' ) userId : string ,
160+ ) {
161+ return this . medicalRecordsService . revokeVerification ( id , dto , userId ) ;
162+ }
163+
164+ // --- Append-only observation ---
165+
166+ @Post ( ':id/append' )
167+ @Permissions ( Permission . CREATE_MEDICAL_RECORDS )
168+ appendObservation (
169+ @Param ( 'id' ) id : string ,
170+ @Body ( ) dto : AppendRecordDto ,
171+ @CurrentUser ( 'id' ) userId : string ,
167172 ) {
168- return this . medicalRecordsService . revokeVerification ( id , revokeDto ) ;
173+ return this . medicalRecordsService . append ( id , dto , userId ) ;
169174 }
170175
171- // --- Record Versioning ---
176+ // --- Versioning / history ---
177+
178+ @Get ( ':id/history' )
179+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
180+ getHistory ( @Param ( 'id' ) id : string ) {
181+ return this . medicalRecordsService . getRecordVersions ( id ) ;
182+ }
172183
173184 @Get ( ':id/versions' )
185+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
174186 getVersions ( @Param ( 'id' ) id : string ) {
175187 return this . medicalRecordsService . getRecordVersions ( id ) ;
176188 }
177189
178190 @Get ( ':id/versions/:versionId' )
179- getVersion (
180- @Param ( 'id' ) id : string ,
181- @Param ( 'versionId' ) versionId : string ,
182- ) {
191+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
192+ getVersion ( @Param ( 'id' ) id : string , @Param ( 'versionId' ) versionId : string ) {
183193 return this . medicalRecordsService . getRecordVersion ( id , versionId ) ;
184194 }
185195
186- // --- Core record endpoints ---
196+ // --- Core CRUD ---
187197
188198 @Get ( ':id' )
199+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
189200 findOne ( @Param ( 'id' ) id : string ) {
190201 return this . medicalRecordsService . findOne ( id ) ;
191202 }
192203
193204 @Get ( ':id/qr' )
205+ @Permissions ( Permission . READ_MEDICAL_RECORDS )
194206 getQRCode ( @Param ( 'id' ) id : string ) {
195207 return this . medicalRecordsService . getQRCode ( id ) ;
196208 }
197209
198210 @Patch ( ':id' )
211+ @Permissions ( Permission . UPDATE_MEDICAL_RECORDS )
199212 update (
200213 @Param ( 'id' ) id : string ,
201- @Body ( ) updateMedicalRecordDto : UpdateMedicalRecordDto ,
214+ @Body ( ) dto : UpdateMedicalRecordDto ,
215+ @CurrentUser ( 'id' ) userId : string ,
202216 ) {
203- return this . medicalRecordsService . update ( id , updateMedicalRecordDto ) ;
217+ return this . medicalRecordsService . update ( id , dto , userId ) ;
204218 }
205219
206220 @Delete ( ':id' )
207- remove ( @Param ( 'id' ) id : string ) {
208- return this . medicalRecordsService . remove ( id ) ;
221+ @Permissions ( Permission . DELETE_MEDICAL_RECORDS )
222+ remove ( @Param ( 'id' ) id : string , @CurrentUser ( 'id' ) userId : string ) {
223+ return this . medicalRecordsService . remove ( id , userId ) ;
209224 }
210225}
0 commit comments