From 50d0aac1528597ac798545d659ef4e634b25e18d Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Fri, 27 Mar 2026 14:03:20 +0100 Subject: [PATCH 1/7] feat: implement Attachment entity and repository --- .../example/vet1177/entities/Attachment.java | 93 +++++++++++++++++++ .../repository/AttachmentRepository.java | 19 ++++ 2 files changed, 112 insertions(+) create mode 100644 src/main/java/org/example/vet1177/entities/Attachment.java create mode 100644 src/main/java/org/example/vet1177/repository/AttachmentRepository.java diff --git a/src/main/java/org/example/vet1177/entities/Attachment.java b/src/main/java/org/example/vet1177/entities/Attachment.java new file mode 100644 index 0000000..eeecb2c --- /dev/null +++ b/src/main/java/org/example/vet1177/entities/Attachment.java @@ -0,0 +1,93 @@ +package org.example.vet1177.entities; + +import jakarta.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "attachment") +public class Attachment { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "record_id", nullable = false) + private MedicalRecord medicalRecord; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "uploaded_by", nullable = false) + private User uploadedBy; + + @Column(name = "file_name", nullable = false, length = 500) + private String fileName; + + // Den unika nyckeln/sökvägen i S3-bucket + @Column(name = "s3_key", nullable = false, unique = true, length = 1000) + private String s3Key; + + @Column(name = "s3_bucket", nullable = false, length = 255) + private String s3Bucket; + + @Column(name = "file_type", length = 100) + private String fileType; + + @Column(name = "file_size_bytes") + private Long fileSizeBytes; + + @Column(name = "uploaded_at", nullable = false, updatable = false) + private Instant uploadedAt; + + public Attachment() { + } + + @PrePersist + protected void onCreate() { + this.uploadedAt = Instant.now(); + } + + // Getters och Setters + public UUID getId() { return id; } + + public MedicalRecord getMedicalRecord() { return medicalRecord; } + + public void setMedicalRecord(MedicalRecord medicalRecord) { + this.medicalRecord = medicalRecord; } + + public User getUploadedBy() { + return uploadedBy; } + + public void setUploadedBy(User uploadedBy) { + this.uploadedBy = uploadedBy; } + + public String getFileName() { + return fileName; } + + public void setFileName(String fileName) { + this.fileName = fileName; } + + public String getS3Key() { + return s3Key; } + + public void setS3Key(String s3Key) { + this.s3Key = s3Key; } + + public String getS3Bucket() { + return s3Bucket; } + + public void setS3Bucket(String s3Bucket) { + this.s3Bucket = s3Bucket; } + + public String getFileType() { return fileType; } + public void setFileType(String fileType) { this.fileType = fileType; } + + public Long getFileSizeBytes() { + return fileSizeBytes; } + + public void setFileSizeBytes(Long fileSizeBytes) { + this.fileSizeBytes = fileSizeBytes; } + + public Instant getUploadedAt() { + return uploadedAt; } +} \ No newline at end of file diff --git a/src/main/java/org/example/vet1177/repository/AttachmentRepository.java b/src/main/java/org/example/vet1177/repository/AttachmentRepository.java new file mode 100644 index 0000000..7c5e46f --- /dev/null +++ b/src/main/java/org/example/vet1177/repository/AttachmentRepository.java @@ -0,0 +1,19 @@ +package org.example.vet1177.repository; + +import org.example.vet1177.entities.Attachment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface AttachmentRepository extends JpaRepository { + + List findByMedicalRecordId(UUID recordId); + + List findByUploadedById(UUID userId); + + Optional findByS3Key(String s3Key); +} From 83453345cce1d340e80b1da1429ddb3b8d9e74c1 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 08:42:43 +0200 Subject: [PATCH 2/7] refactor: add database-level cascade delete for medical records Updated Attachment entity with @OnDelete(action = OnDeleteAction.CASCADE) to ensure attachments are removed when their parent MedicalRecord is deleted. --- src/main/java/org/example/vet1177/entities/Attachment.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/example/vet1177/entities/Attachment.java b/src/main/java/org/example/vet1177/entities/Attachment.java index eeecb2c..29b87de 100644 --- a/src/main/java/org/example/vet1177/entities/Attachment.java +++ b/src/main/java/org/example/vet1177/entities/Attachment.java @@ -1,6 +1,9 @@ package org.example.vet1177.entities; import jakarta.persistence.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + import java.time.Instant; import java.util.UUID; @@ -14,6 +17,7 @@ public class Attachment { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "record_id", nullable = false) + @OnDelete(action = OnDeleteAction.CASCADE) private MedicalRecord medicalRecord; @ManyToOne(fetch = FetchType.LAZY) From acbfd1c20ac578f05b09bc0c6cb43fcbf3579f5d Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 09:00:50 +0200 Subject: [PATCH 3/7] refactor: add attachment collection and cascading to MedicalRecord Ensures that attachments are persisted, updated, and deleted automatically when their parent MedicalRecord is modified or removed. --- .../vet1177/entities/MedicalRecord.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/example/vet1177/entities/MedicalRecord.java b/src/main/java/org/example/vet1177/entities/MedicalRecord.java index 7ade838..07d0f59 100644 --- a/src/main/java/org/example/vet1177/entities/MedicalRecord.java +++ b/src/main/java/org/example/vet1177/entities/MedicalRecord.java @@ -2,6 +2,8 @@ import jakarta.persistence.*; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Entity @@ -47,6 +49,9 @@ public class MedicalRecord { @JoinColumn(name = "updated_by") private User updatedBy; + @OneToMany(mappedBy = "medicalRecord", cascade = CascadeType.ALL, orphanRemoval = true) + private java.util.List attachments = new ArrayList<>(); + // Timestamps @Column(name = "created_at", updatable = false) private Instant createdAt; @@ -109,6 +114,21 @@ public MedicalRecord() {} public User getUpdatedBy() { return updatedBy; } public void setUpdatedBy(User updatedBy) { this.updatedBy = updatedBy; } + // Getters/Setters för Attachments + public List getAttachments() { return attachments; } + public void setAttachments(List attachments) { this.attachments = attachments; } + + + public void addAttachment(Attachment attachment) { + attachments.add(attachment); + attachment.setMedicalRecord(this); + } + + public void removeAttachment(Attachment attachment) { + attachments.remove(attachment); + attachment.setMedicalRecord(null); + } + public Instant getCreatedAt() { return createdAt; } public Instant getUpdatedAt() { return updatedAt; } public Instant getClosedAt() { return closedAt; } From 6d30689bb8c83fe8bdb7a2e46d0364409bf1591f Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 09:12:47 +0200 Subject: [PATCH 4/7] refactor: add uploadedAttachments relation to User entity --- .../org/example/vet1177/entities/User.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/org/example/vet1177/entities/User.java b/src/main/java/org/example/vet1177/entities/User.java index 5fc4a23..41e137e 100644 --- a/src/main/java/org/example/vet1177/entities/User.java +++ b/src/main/java/org/example/vet1177/entities/User.java @@ -2,6 +2,8 @@ import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Entity @@ -24,6 +26,11 @@ public class User { @Enumerated(EnumType.STRING) private Role role; //role använder enum OWNER, VET, ADMIN + // Koppling till bilagor som användaren laddat upp + // Vi använder inte CascadeType.REMOVE för att skydda medicinsk data om en användare raderas + @OneToMany(mappedBy = "uploadedBy", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + private List uploadedAttachments = new ArrayList<>(); + public User() { } //tom konsturktor för JPA @@ -69,4 +76,18 @@ public Role getRole() { public void setRole(Role role) { this.role = role; } + + + public List getUploadedAttachments() { + return uploadedAttachments; + } + + public void setUploadedAttachments(List uploadedAttachments) { + this.uploadedAttachments = uploadedAttachments; + } + + public void addAttachment(Attachment attachment) { + uploadedAttachments.add(attachment); + attachment.setUploadedBy(this); + } } From 519c346f63a850601274ca51eea32853178118fe Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 09:24:07 +0200 Subject: [PATCH 5/7] refactor: enforce bidirectional invariant for User attachments(CodeRabbit suggestion) --- .../org/example/vet1177/entities/User.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/example/vet1177/entities/User.java b/src/main/java/org/example/vet1177/entities/User.java index 41e137e..19257ff 100644 --- a/src/main/java/org/example/vet1177/entities/User.java +++ b/src/main/java/org/example/vet1177/entities/User.java @@ -79,15 +79,32 @@ public void setRole(Role role) { public List getUploadedAttachments() { - return uploadedAttachments; + return java.util.Collections.unmodifiableList(uploadedAttachments); } - public void setUploadedAttachments(List uploadedAttachments) { - this.uploadedAttachments = uploadedAttachments; + + public void setUploadedAttachments(List attachments) { + this.uploadedAttachments.clear(); + if (attachments != null) { + attachments.forEach(this::addAttachment); + } } + public void addAttachment(Attachment attachment) { - uploadedAttachments.add(attachment); - attachment.setUploadedBy(this); + if (attachment != null) { + this.uploadedAttachments.add(attachment); + // Säkerställ att bilagan pekar på denna användare + if (attachment.getUploadedBy() != this) { + attachment.setUploadedBy(this); + } + } + } + + public void removeAttachment(Attachment attachment) { + if (attachment != null) { + this.uploadedAttachments.remove(attachment); + attachment.setUploadedBy(null); + } } } From 23907a9809874dbdf25d5ccb70c1105607dd6774 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 10:36:25 +0200 Subject: [PATCH 6/7] Final fixes from CodeRabbit Suggestions. Read Summary to se all changes/fixes. --- .../example/vet1177/entities/Attachment.java | 1 + .../vet1177/entities/MedicalRecord.java | 31 ++++++++++++++----- .../org/example/vet1177/entities/User.java | 2 -- src/main/resources/schema.sql | 4 +-- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/example/vet1177/entities/Attachment.java b/src/main/java/org/example/vet1177/entities/Attachment.java index 29b87de..353b881 100644 --- a/src/main/java/org/example/vet1177/entities/Attachment.java +++ b/src/main/java/org/example/vet1177/entities/Attachment.java @@ -22,6 +22,7 @@ public class Attachment { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "uploaded_by", nullable = false) + @OnDelete(action = OnDeleteAction.SET_NULL) private User uploadedBy; @Column(name = "file_name", nullable = false, length = 500) diff --git a/src/main/java/org/example/vet1177/entities/MedicalRecord.java b/src/main/java/org/example/vet1177/entities/MedicalRecord.java index 07d0f59..1a45032 100644 --- a/src/main/java/org/example/vet1177/entities/MedicalRecord.java +++ b/src/main/java/org/example/vet1177/entities/MedicalRecord.java @@ -114,19 +114,36 @@ public MedicalRecord() {} public User getUpdatedBy() { return updatedBy; } public void setUpdatedBy(User updatedBy) { this.updatedBy = updatedBy; } - // Getters/Setters för Attachments - public List getAttachments() { return attachments; } - public void setAttachments(List attachments) { this.attachments = attachments; } + + public List getAttachments() { + return java.util.Collections.unmodifiableList(attachments); + } + + public void setAttachments(List newAttachments) { + this.attachments.clear(); + if (newAttachments != null) { + for (Attachment attachment : newAttachments) { + this.addAttachment(attachment); + } + } + } public void addAttachment(Attachment attachment) { - attachments.add(attachment); - attachment.setMedicalRecord(this); + if (attachment != null) { + this.attachments.add(attachment); + // Sätt baksidan av relationen om den inte redan är satt + if (attachment.getMedicalRecord() != this) { + attachment.setMedicalRecord(this); + } + } } public void removeAttachment(Attachment attachment) { - attachments.remove(attachment); - attachment.setMedicalRecord(null); + if (attachment != null) { + this.attachments.remove(attachment); + attachment.setMedicalRecord(null); + } } public Instant getCreatedAt() { return createdAt; } diff --git a/src/main/java/org/example/vet1177/entities/User.java b/src/main/java/org/example/vet1177/entities/User.java index 19257ff..4f75091 100644 --- a/src/main/java/org/example/vet1177/entities/User.java +++ b/src/main/java/org/example/vet1177/entities/User.java @@ -82,7 +82,6 @@ public List getUploadedAttachments() { return java.util.Collections.unmodifiableList(uploadedAttachments); } - public void setUploadedAttachments(List attachments) { this.uploadedAttachments.clear(); if (attachments != null) { @@ -90,7 +89,6 @@ public void setUploadedAttachments(List attachments) { } } - public void addAttachment(Attachment attachment) { if (attachment != null) { this.uploadedAttachments.add(attachment); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index b04a579..c7714a8 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -69,8 +69,8 @@ CREATE TABLE IF NOT EXISTS comment ( CREATE TABLE IF NOT EXISTS attachment ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - record_id UUID NOT NULL REFERENCES medical_record(id), - uploaded_by UUID NOT NULL REFERENCES users(id), + record_id UUID NOT NULL REFERENCES medical_record(id) ON DELETE CASCADE, + uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL, file_name VARCHAR(500) NOT NULL, s3_key VARCHAR(1000) NOT NULL UNIQUE, s3_bucket VARCHAR(255) NOT NULL, From c6e0488bb9115668c4417a7e52251664bae0be97 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Sun, 29 Mar 2026 10:52:29 +0200 Subject: [PATCH 7/7] Final fixes from CodeRabbit Suggestions part 2. Read Summary to se all changes/fixes. --- .../java/org/example/vet1177/entities/Attachment.java | 2 +- .../java/org/example/vet1177/entities/MedicalRecord.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/vet1177/entities/Attachment.java b/src/main/java/org/example/vet1177/entities/Attachment.java index 353b881..079267b 100644 --- a/src/main/java/org/example/vet1177/entities/Attachment.java +++ b/src/main/java/org/example/vet1177/entities/Attachment.java @@ -21,7 +21,7 @@ public class Attachment { private MedicalRecord medicalRecord; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "uploaded_by", nullable = false) + @JoinColumn(name = "uploaded_by", nullable = true) @OnDelete(action = OnDeleteAction.SET_NULL) private User uploadedBy; diff --git a/src/main/java/org/example/vet1177/entities/MedicalRecord.java b/src/main/java/org/example/vet1177/entities/MedicalRecord.java index 1a45032..6699567 100644 --- a/src/main/java/org/example/vet1177/entities/MedicalRecord.java +++ b/src/main/java/org/example/vet1177/entities/MedicalRecord.java @@ -120,7 +120,12 @@ public List getAttachments() { } public void setAttachments(List newAttachments) { - this.attachments.clear(); + List currentAttachments = new ArrayList<>(this.attachments); + + for (Attachment attachment : currentAttachments) { + this.removeAttachment(attachment); + } + if (newAttachments != null) { for (Attachment attachment : newAttachments) { this.addAttachment(attachment); @@ -128,7 +133,6 @@ public void setAttachments(List newAttachments) { } } - public void addAttachment(Attachment attachment) { if (attachment != null) { this.attachments.add(attachment);