Skip to content

Commit 9399818

Browse files
Mounika-2311claude
andcommitted
feat: patient education updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent aea37b4 commit 9399818

5 files changed

Lines changed: 114 additions & 83 deletions

File tree

src/main/java/com/qiaben/ciyex/controller/PatientEducationAssignmentController.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@ public class PatientEducationAssignmentController {
2020
public ResponseEntity<ApiResponse<PatientEducationAssignmentDto>> assign(
2121
@PathVariable Long educationId,
2222
@RequestBody PatientEducationAssignmentDto dto) {
23-
return ResponseEntity.ok(ApiResponse.<PatientEducationAssignmentDto>builder()
24-
.success(true)
25-
.message("Assigned successfully")
26-
.data(service.assign(educationId, dto))
27-
.build());
23+
try {
24+
return ResponseEntity.ok(ApiResponse.<PatientEducationAssignmentDto>builder()
25+
.success(true)
26+
.message("Assigned successfully")
27+
.data(service.assign(educationId, dto))
28+
.build());
29+
} catch (Exception e) {
30+
return ResponseEntity.ok(ApiResponse.<PatientEducationAssignmentDto>builder()
31+
.success(false)
32+
.message("Failed to assign education: " + e.getMessage())
33+
.build());
34+
}
2835
}
2936

3037
@GetMapping("/patient/{patientId}")

src/main/java/com/qiaben/ciyex/controller/PatientEducationController.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,18 @@ public ResponseEntity<ApiResponse<PatientEducationDto>> get(@PathVariable Long i
4747

4848
@PutMapping("/{id}")
4949
public ResponseEntity<ApiResponse<PatientEducationDto>> update(@PathVariable Long id, @RequestBody PatientEducationDto dto) {
50-
return ResponseEntity.ok(ApiResponse.<PatientEducationDto>builder()
51-
.success(true)
52-
.message("Patient education updated successfully")
53-
.data(service.update(id, dto))
54-
.build());
50+
try {
51+
return ResponseEntity.ok(ApiResponse.<PatientEducationDto>builder()
52+
.success(true)
53+
.message("Patient education updated successfully")
54+
.data(service.update(id, dto))
55+
.build());
56+
} catch (Exception e) {
57+
return ResponseEntity.ok(ApiResponse.<PatientEducationDto>builder()
58+
.success(false)
59+
.message("Failed to update patient education: " + e.getMessage())
60+
.build());
61+
}
5562
}
5663

5764
@DeleteMapping("/{id}")

src/main/java/com/qiaben/ciyex/entity/Document.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ public class Document extends AuditableEntity {
2222
private String tenantName; // Tenant name from X-Tenant-Name header, nullable
2323
// audit fields are provided by AuditableEntity
2424

25-
// S3 details
25+
// S3 details (null when stored locally)
2626
private String s3Bucket;
2727
private String s3Key;
2828

29+
// Local storage fallback — used when S3 is not configured
30+
@Lob
31+
@Column(name = "content", columnDefinition = "TEXT")
32+
private String contentBase64;
33+
2934
// Encryption metadata (Base64 encoded)
3035
@Column(length = 512)
3136
private String encryptionKey;

src/main/java/com/qiaben/ciyex/service/DocumentService.java

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -110,40 +110,42 @@ public DocumentDto create(String tenantName, Long patientId, DocumentDto dto, Mu
110110
}
111111
}
112112

113-
// 6. Upload to S3
113+
// 6. Upload to S3 or fall back to local DB storage
114114
StorageConfig.S3 s3Config = configProvider.getS3DocumentStorage();
115-
if (s3Config == null) {
116-
throw new RuntimeException("S3 document storage is not configured");
117-
}
118-
S3Client s3 = buildS3Client(s3Config);
119-
120-
String key = tenantName + "/documents/" + patientId + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
121-
try {
122-
s3.putObject(
123-
PutObjectRequest.builder()
124-
.bucket(s3Config.getBucket())
125-
.key(key)
126-
.contentType(file.getContentType())
127-
.build(),
128-
RequestBody.fromBytes(fileBytes)
129-
);
130-
log.info("Uploaded to S3 bucket={} key={}", s3Config.getBucket(), key);
131-
} catch (Exception e) {
132-
throw new RuntimeException("Failed to upload file to S3", e);
133-
}
134-
135-
// 7. Save metadata in DB
136115
dto.setPatientId(patientId);
137116
dto.setFileName(file.getOriginalFilename());
138117
dto.setContentType(file.getContentType());
139-
dto.setS3Bucket(s3Config.getBucket());
140-
dto.setS3Key(key);
141118

142119
Document entity = mapToEntity(dto);
143-
entity.setTenantName(tenantName); // Save tenant name from header
120+
entity.setTenantName(tenantName);
144121
entity.setEncryptionKey(base64Key);
145122
entity.setInitializationVector(base64Iv);
146123

124+
if (s3Config != null) {
125+
S3Client s3 = buildS3Client(s3Config);
126+
String key = tenantName + "/documents/" + patientId + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
127+
try {
128+
s3.putObject(
129+
PutObjectRequest.builder()
130+
.bucket(s3Config.getBucket())
131+
.key(key)
132+
.contentType(file.getContentType())
133+
.build(),
134+
RequestBody.fromBytes(fileBytes)
135+
);
136+
log.info("Uploaded to S3 bucket={} key={}", s3Config.getBucket(), key);
137+
} catch (Exception e) {
138+
throw new RuntimeException("Failed to upload file to S3", e);
139+
}
140+
entity.setS3Bucket(s3Config.getBucket());
141+
entity.setS3Key(key);
142+
} else {
143+
// Local DB fallback when S3 is not configured
144+
entity.setContentBase64(Base64.getEncoder().encodeToString(fileBytes));
145+
log.info("S3 not configured — stored file content locally for patientId={}", patientId);
146+
}
147+
148+
// 7. Save metadata (and optionally content) in DB
147149
repository.save(entity);
148150
dto.setId(entity.getId());
149151
dto.setContent(null);
@@ -156,20 +158,20 @@ public void delete(Long documentId) {
156158
Document document = repository.findById(documentId)
157159
.orElseThrow(() -> new RuntimeException("Document not found"));
158160

159-
StorageConfig.S3 s3Config = configProvider.getS3DocumentStorage();
160-
if (s3Config == null) {
161-
throw new RuntimeException("S3 document storage is not configured");
162-
}
163-
S3Client s3 = buildS3Client(s3Config);
164-
165-
try {
166-
s3.deleteObject(DeleteObjectRequest.builder()
167-
.bucket(document.getS3Bucket())
168-
.key(document.getS3Key())
169-
.build());
170-
log.info("Deleted from S3: bucket={}, key={}", document.getS3Bucket(), document.getS3Key());
171-
} catch (Exception e) {
172-
throw new RuntimeException("Failed to delete file from S3", e);
161+
if (document.getS3Key() != null && !document.getS3Key().isBlank()) {
162+
StorageConfig.S3 s3Config = configProvider.getS3DocumentStorage();
163+
if (s3Config != null) {
164+
try {
165+
S3Client s3 = buildS3Client(s3Config);
166+
s3.deleteObject(DeleteObjectRequest.builder()
167+
.bucket(document.getS3Bucket())
168+
.key(document.getS3Key())
169+
.build());
170+
log.info("Deleted from S3: bucket={}, key={}", document.getS3Bucket(), document.getS3Key());
171+
} catch (Exception e) {
172+
throw new RuntimeException("Failed to delete file from S3", e);
173+
}
174+
}
173175
}
174176

175177
repository.delete(document);
@@ -180,42 +182,48 @@ public DownloadResult download(Long documentId) {
180182
Document document = repository.findById(documentId)
181183
.orElseThrow(() -> new RuntimeException("Document not found"));
182184

183-
StorageConfig.S3 s3Config = configProvider.getS3DocumentStorage();
184-
if (s3Config == null) {
185-
throw new RuntimeException("S3 document storage is not configured");
186-
}
187-
S3Client s3 = buildS3Client(s3Config);
188-
189-
try (InputStream is = s3.getObject(GetObjectRequest.builder()
190-
.bucket(document.getS3Bucket())
191-
.key(document.getS3Key())
192-
.build())) {
193-
194-
byte[] fileBytes = is.readAllBytes();
195-
196-
// 🔑 decrypt if metadata exists
197-
if (document.getEncryptionKey() != null && document.getInitializationVector() != null) {
198-
try {
199-
byte[] decodedKey = Base64.getDecoder().decode(document.getEncryptionKey());
200-
SecretKey key = EncryptionUtil.fromBytes(decodedKey);
201-
byte[] iv = Base64.getDecoder().decode(document.getInitializationVector());
185+
byte[] fileBytes;
202186

203-
fileBytes = EncryptionUtil.decrypt(fileBytes, key, iv);
204-
log.info("Decrypted file before sending: {}", document.getFileName());
205-
} catch (Exception e) {
206-
throw new RuntimeException("Failed to decrypt file", e);
207-
}
187+
if (document.getS3Key() != null && !document.getS3Key().isBlank()) {
188+
// Retrieve from S3
189+
StorageConfig.S3 s3Config = configProvider.getS3DocumentStorage();
190+
if (s3Config == null) {
191+
throw new RuntimeException("S3 document storage is not configured");
208192
}
193+
S3Client s3 = buildS3Client(s3Config);
194+
try (InputStream is = s3.getObject(GetObjectRequest.builder()
195+
.bucket(document.getS3Bucket())
196+
.key(document.getS3Key())
197+
.build())) {
198+
fileBytes = is.readAllBytes();
199+
} catch (Exception e) {
200+
throw new RuntimeException("Failed to download file from S3", e);
201+
}
202+
} else if (document.getContentBase64() != null && !document.getContentBase64().isBlank()) {
203+
// Retrieve from local DB storage
204+
fileBytes = Base64.getDecoder().decode(document.getContentBase64());
205+
} else {
206+
throw new RuntimeException("No file content available for document " + documentId);
207+
}
209208

210-
return new DownloadResult(
211-
new java.io.ByteArrayInputStream(fileBytes),
212-
document.getContentType(),
213-
document.getFileName()
214-
);
215-
216-
} catch (Exception e) {
217-
throw new RuntimeException("Failed to download file from S3", e);
209+
// Decrypt if metadata exists
210+
if (document.getEncryptionKey() != null && document.getInitializationVector() != null) {
211+
try {
212+
byte[] decodedKey = Base64.getDecoder().decode(document.getEncryptionKey());
213+
SecretKey key = EncryptionUtil.fromBytes(decodedKey);
214+
byte[] iv = Base64.getDecoder().decode(document.getInitializationVector());
215+
fileBytes = EncryptionUtil.decrypt(fileBytes, key, iv);
216+
log.info("Decrypted file before sending: {}", document.getFileName());
217+
} catch (Exception e) {
218+
throw new RuntimeException("Failed to decrypt file", e);
219+
}
218220
}
221+
222+
return new DownloadResult(
223+
new java.io.ByteArrayInputStream(fileBytes),
224+
document.getContentType(),
225+
document.getFileName()
226+
);
219227
}
220228

221229
@Transactional(readOnly = true)

src/main/java/com/qiaben/ciyex/service/PatientEducationAssignmentService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ private PatientEducationAssignmentDto toDto(PatientEducationAssignment a) {
6666
}
6767

6868
public PatientEducationAssignmentDto assign(Long educationId, PatientEducationAssignmentDto dto) {
69+
if (dto.getPatientId() == null) {
70+
throw new IllegalArgumentException("Patient ID must not be null. Please select a patient before assigning.");
71+
}
72+
6973
PatientEducation education = educationRepository.findById(educationId)
7074
.orElseThrow(() -> new RuntimeException("Education not found"));
7175

@@ -77,7 +81,7 @@ public PatientEducationAssignmentDto assign(Long educationId, PatientEducationAs
7781

7882
// Fetch patient to get their name
7983
Patient patient = patientRepository.findById(dto.getPatientId())
80-
.orElseThrow(() -> new RuntimeException("Patient not found"));
84+
.orElseThrow(() -> new RuntimeException("Patient not found with id: " + dto.getPatientId()));
8185

8286
PatientEducationAssignment assignment = PatientEducationAssignment.builder()
8387
.education(education)

0 commit comments

Comments
 (0)