Skip to content

Issue 60 done#63

Merged
LinusWestling merged 5 commits into
mainfrom
issue/60
Apr 21, 2026
Merged

Issue 60 done#63
LinusWestling merged 5 commits into
mainfrom
issue/60

Conversation

@LinusWestling

@LinusWestling LinusWestling commented Apr 20, 2026

Copy link
Copy Markdown
Collaborator

Closes #60

Summary by CodeRabbit

  • New Features

    • Patients can create and view their own cases; the UI auto-assigns the patient for patient users and hides patient selection. Patient profiles will be created if missing during patient self-registration or case creation.
    • Registration now collects first name, last name, and personal identity number with format validation.
  • Bug Fixes / Validation

    • Registration rejects duplicate personal identity numbers.
    • Case form validation enforces patient selection for non-patient users and relaxes it for patient users.

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Patients are integrated into case workflows: patients can create cases for themselves (patientId auto-resolved), view their own non-closed cases, and are recognized in read/create authorization. UI, registration, DTOs, and security-adapter changes support patient persistence and role resolution.

Changes

Cohort / File(s) Summary
Case Service & Tests
src/main/java/org/example/projektarendehantering/application/service/CaseService.java, src/test/java/org/example/projektarendehantering/application/service/CaseServiceTest.java
Add patient-aware creation and listing: createCase resolves/enforces patientId for patient actors, auto-creates minimal PatientEntity when appropriate, getAllCases returns non-CLOSED cases for patient userId, and authorization helpers recognize patient actors. Tests added for patient creation, denial on mismatched patientId, and patient-specific listing.
UI Controller, Form & Template
src/main/java/org/example/projektarendehantering/presentation/web/UiController.java, src/main/resources/templates/cases/new.html, src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java
Controller uses Actor to gate patient list and auto-set caseDTO.patientId; createCase signature gains Model and validates patient selection only for non-patients. Template conditionally hides patient dropdown for patient actors. CreateCaseForm.patientId no longer @NotNull.
Registration & Patient DTO + Template
src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java, src/main/java/org/example/projektarendehantering/presentation/dto/PatientRegistrationDTO.java, src/main/resources/templates/login/register.html
Registration persists a PatientEntity after user account creation, trims inputs, enforces uniqueness on personalIdentityNumber. DTO adds firstName, lastName, personalIdentityNumber with validations; registration template adds corresponding fields.
Security Actor Resolution
src/main/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapter.java, src/test/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapterTest.java
Adapter now accepts UserAccountRepository, looks up user account by normalized email when no Employee is found; tests updated to mock user-account lookups.
Minor Comments
src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java, src/main/java/org/example/projektarendehantering/presentation/web/PatientUiController.java
Non-functional single-line comments added.
sequenceDiagram
    participant Browser
    participant UiController
    participant CaseService
    participant PatientRepo
    participant CaseRepo

    Browser->>UiController: POST /ui/cases (form)
    UiController->>UiController: resolve Actor (current user)
    UiController->>CaseService: createCase(actor, caseDTO)
    CaseService->>PatientRepo: findById(resolvedPatientId)
    alt patient missing and actor is that patient
        PatientRepo-->>CaseService: not found
        CaseService->>PatientRepo: save(minimal PatientEntity)
        PatientRepo-->>CaseService: saved PatientEntity
    else patient found
        PatientRepo-->>CaseService: found PatientEntity
    end
    CaseService->>CaseRepo: save(CaseEntity with patient)
    CaseRepo-->>CaseService: saved CaseEntity
    CaseService-->>UiController: created CaseDTO
    UiController-->>Browser: redirect /ui/cases
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble keys and hop with glee,
Patient cases now belong to me.
Auto-filled IDs, a soft little pat,
I stitched the flow—now that's that!
Hooray—one hop, one saved case, whee!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request includes out-of-scope changes: comments in AuthController and PatientUiController, plus removed @NotNull validation and email autofocus removal that extend beyond issue #60 requirements. Remove unrelated changes: the comment in AuthController, the comment in PatientUiController, and revert the email autofocus removal from the registration template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Issue 60 done' is vague and non-descriptive; it provides no meaningful information about the actual changes made. Use a descriptive title that summarizes the main change, e.g., 'Allow patients to create cases with automatic patient association' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully implements patient case creation without patient selection, automatic patient association, and proper authorization checks across all required components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/60

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/example/projektarendehantering/application/service/CaseService.java (1)

70-83: ⚠️ Potential issue | 🔴 Critical

Critical issue: Patient registration does not create corresponding PatientEntity, causing case creation to fail with 404.

The server-side guard correctly enforces that patients can only create cases for themselves by forcing requestedPatientId = actor.userId() (line 79), but the lookup at line 88-89 will fail for any patient who registered via the standard registration flow.

RegistrationService.registerPatient() creates only a UserAccountEntity with a random UUID. It does NOT create a PatientEntity. When a patient later attempts to create a case, CaseService calls patientRepository.findById(actor.userId()), which will not find any matching PatientEntity since:

  • UserAccountEntity.id is generated via @PrePersist (UUID.randomUUID())
  • PatientEntity.id is generated independently via PatientService.createPatient() (a separate, different UUID)

Result: Patients will receive a 404 "Patient not found" error when trying to create cases, even with valid accounts. Fix by either:

  1. Automatically creating a PatientEntity during user registration (with id matching the new UserAccountEntity.id), or
  2. Modifying the case creation flow to look up the patient via a relationship (e.g., through a user-patient association) rather than assuming id equality.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/application/service/CaseService.java`
around lines 70 - 83, The createCase method enforces patient self-ownership by
setting requestedPatientId = actor.userId() but
patientRepository.findById(actor.userId()) fails because
RegistrationService.registerPatient creates only a UserAccountEntity
(UserAccountEntity.id) while PatientEntity.id is generated separately; fix by
ensuring a PatientEntity is created at registration with the same id as the new
UserAccountEntity (update RegistrationService.registerPatient to create and
persist a PatientEntity with id = userAccount.getId() and any required fields)
or alternatively change createCase to resolve the PatientEntity via the
user->patient relationship (e.g., add a lookup in PatientService that finds
PatientEntity by userAccount id or association and use that instead of
patientRepository.findById(actor.userId())); update PatientService.createPatient
or patientRepository methods as needed to keep ids/associations consistent.
🧹 Nitpick comments (2)
src/main/java/org/example/projektarendehantering/presentation/web/UiController.java (1)

66-96: Consolidate the repeated actor.role() != Role.PATIENT checks and guard against a null role.

The actor.role() != Role.PATIENT predicate is repeated four times across newCase/createCase. Extract a small helper (or use Role.PATIENT.equals(actor.role())) to keep the intent obvious and to avoid subtle drift if more role-specific branches are added.

Also, if actor.role() can ever be null (unauthenticated/misconfigured principal), actor.role() != Role.PATIENT evaluates true and the code will populate the patient dropdown / require patientId — harmless here but inconsistent with the template, which explicitly null-checks currentActor.role(). Worth aligning.

Suggested refactor
+    private static boolean isPatient(Actor actor) {
+        return actor != null && actor.role() == Role.PATIENT;
+    }
+
     `@GetMapping`("/ui/cases/new")
     public String newCase(Model model) {
         Actor actor = securityActorAdapter.currentUser();
         model.addAttribute("createCaseForm", new CreateCaseForm());
-        if (actor.role() != Role.PATIENT) {
+        if (!isPatient(actor)) {
             model.addAttribute("patients", patientService.getAllPatients());
         }
         return "cases/new";
     }

Additionally, there are two trailing lines (170–171) containing only whitespace — please trim.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/presentation/web/UiController.java`
around lines 66 - 96, The repeated checks of actor.role() != Role.PATIENT in
newCase and createCase should be consolidated and null-safe: add a small helper
like isPatient(Actor) (or inline use Role.PATIENT.equals(actor.role())) and
replace all occurrences in newCase and createCase with that helper to avoid
repetition and accidental null-handling differences; ensure createCase uses the
null-safe check when deciding to add patients to the model and when validating
form.getPatientId(), and keep the patient selection logic consistent with
securityActorAdapter.currentUser(); also remove the two trailing whitespace-only
lines near the end of UiController to trim stray whitespace.
src/main/resources/templates/cases/new.html (1)

15-25: Role gating via string comparison is brittle; consider enum comparison.

currentActor.role().toString() != 'PATIENT' relies on the enum's toString() matching the literal string. If the Role enum ever overrides toString() (e.g., for i18n/display), this guard silently breaks and the patient dropdown would be rendered again for patients (a privacy/authorization-visible inconsistency, though the server still enforces it in CaseService). Prefer comparing against the enum directly using Thymeleaf's T(...) operator:

Proposed fix
-        <label class="field" th:if="${currentActor == null || currentActor.role() == null || currentActor.role().toString() != 'PATIENT'}">
+        <label class="field" th:if="${currentActor == null || currentActor.role() == null || currentActor.role() != T(org.example.projektarendehantering.common.Role).PATIENT}">
             ...
-        <p class="muted" th:if="${currentActor != null && currentActor.role() != null && currentActor.role().toString() == 'PATIENT'}">
+        <p class="muted" th:if="${currentActor != null && currentActor.role() != null && currentActor.role() == T(org.example.projektarendehantering.common.Role).PATIENT}">

Also, consider exposing a simple boolean model attribute (e.g., isPatient) from the controller to keep the template free of this duplicated conditional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/cases/new.html` around lines 15 - 25, The
template uses a brittle string comparison currentActor.role().toString() ==
'PATIENT'; change the guard to compare against the Role enum directly using
Thymeleaf's T(...) operator (e.g., currentActor.role() ==
T(com.example.yourpkg.Role).PATIENT) for both the label and the paragraph, or
alternatively add a boolean model attribute (e.g., isPatient) from the
controller and use that (isPatient) in the th:if expressions to keep the
template simpler; update the th:if conditions around currentActor,
currentActor.role() and the patient dropdown/paragraph to use the enum
comparison or the new boolean attribute consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java`:
- Line 20: Remove the stray placeholder comment from AuthController: delete the
line containing "// Just a guy tryna let me commit smth new a wierd" in the
AuthController class so the source contains no unrelated or typo-filled
comments; ensure only meaningful documentation or TODOs remain in class
AuthController and run a quick compile to verify no accidental whitespace or
encoding changes were introduced.

In
`@src/main/java/org/example/projektarendehantering/presentation/web/PatientUiController.java`:
- Around line 14-15: Remove the stray debug comment "// Something wierd" from
the PatientUiController file; locate the PatientUiController class (or its
top-of-file comments/imports) and delete that comment to avoid noise and correct
the typo (weird).

---

Outside diff comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/CaseService.java`:
- Around line 70-83: The createCase method enforces patient self-ownership by
setting requestedPatientId = actor.userId() but
patientRepository.findById(actor.userId()) fails because
RegistrationService.registerPatient creates only a UserAccountEntity
(UserAccountEntity.id) while PatientEntity.id is generated separately; fix by
ensuring a PatientEntity is created at registration with the same id as the new
UserAccountEntity (update RegistrationService.registerPatient to create and
persist a PatientEntity with id = userAccount.getId() and any required fields)
or alternatively change createCase to resolve the PatientEntity via the
user->patient relationship (e.g., add a lookup in PatientService that finds
PatientEntity by userAccount id or association and use that instead of
patientRepository.findById(actor.userId())); update PatientService.createPatient
or patientRepository methods as needed to keep ids/associations consistent.

---

Nitpick comments:
In
`@src/main/java/org/example/projektarendehantering/presentation/web/UiController.java`:
- Around line 66-96: The repeated checks of actor.role() != Role.PATIENT in
newCase and createCase should be consolidated and null-safe: add a small helper
like isPatient(Actor) (or inline use Role.PATIENT.equals(actor.role())) and
replace all occurrences in newCase and createCase with that helper to avoid
repetition and accidental null-handling differences; ensure createCase uses the
null-safe check when deciding to add patients to the model and when validating
form.getPatientId(), and keep the patient selection logic consistent with
securityActorAdapter.currentUser(); also remove the two trailing whitespace-only
lines near the end of UiController to trim stray whitespace.

In `@src/main/resources/templates/cases/new.html`:
- Around line 15-25: The template uses a brittle string comparison
currentActor.role().toString() == 'PATIENT'; change the guard to compare against
the Role enum directly using Thymeleaf's T(...) operator (e.g.,
currentActor.role() == T(com.example.yourpkg.Role).PATIENT) for both the label
and the paragraph, or alternatively add a boolean model attribute (e.g.,
isPatient) from the controller and use that (isPatient) in the th:if expressions
to keep the template simpler; update the th:if conditions around currentActor,
currentActor.role() and the patient dropdown/paragraph to use the enum
comparison or the new boolean attribute consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24925949-3bf6-481d-a630-624224ea613e

📥 Commits

Reviewing files that changed from the base of the PR and between db08130 and fdc5040.

📒 Files selected for processing (7)
  • src/main/java/org/example/projektarendehantering/application/service/CaseService.java
  • src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java
  • src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java
  • src/main/java/org/example/projektarendehantering/presentation/web/PatientUiController.java
  • src/main/java/org/example/projektarendehantering/presentation/web/UiController.java
  • src/main/resources/templates/cases/new.html
  • src/test/java/org/example/projektarendehantering/application/service/CaseServiceTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/example/projektarendehantering/presentation/dto/CreateCaseForm.java

@RequiredArgsConstructor
public class AuthController {

// Just a guy tryna let me commit smth new a wierd

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the stray commit-placeholder comment.

// Just a guy tryna let me commit smth new a wierd looks like a placeholder to force a commit and contains typos. It's unrelated to the PR objectives (patient role support) and should be removed before merge.

Proposed fix
-// Just a guy tryna let me commit smth new a wierd
     private final RegistrationService registrationService;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Just a guy tryna let me commit smth new a wierd
private final RegistrationService registrationService;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java`
at line 20, Remove the stray placeholder comment from AuthController: delete the
line containing "// Just a guy tryna let me commit smth new a wierd" in the
AuthController class so the source contains no unrelated or typo-filled
comments; ensure only meaningful documentation or TODOs remain in class
AuthController and run a quick compile to verify no accidental whitespace or
encoding changes were introduced.

Comment on lines +14 to +15
// Something wierd

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the stray debug comment.

// Something wierd appears to be an unintended artifact (also misspelled — "weird"). Please delete before merging; it adds noise and is unrelated to this PR's scope.

Proposed fix
-// Something wierd
-
 `@Controller`
 public class PatientUiController {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Something wierd
`@Controller`
public class PatientUiController {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/presentation/web/PatientUiController.java`
around lines 14 - 15, Remove the stray debug comment "// Something wierd" from
the PatientUiController file; locate the PatientUiController class (or its
top-of-file comments/imports) and delete that comment to avoid noise and correct
the typo (weird).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/resources/templates/login/register.html (1)

26-30: Optional: mirror the server-side pattern on the client input.

You could add pattern="\d{8}-\d{4}" (and optionally inputmode="numeric" maxlength="13") to the personal identity number input so invalid formats are caught immediately by the browser and the user doesn't need a round-trip to see the error.

♻️ Suggested refinement
-                <input type="text" id="personalIdentityNumber" th:field="*{personalIdentityNumber}" class="input" placeholder="YYYYMMDD-XXXX" required>
+                <input type="text" id="personalIdentityNumber" th:field="*{personalIdentityNumber}" class="input"
+                       placeholder="YYYYMMDD-XXXX" pattern="\d{8}-\d{4}" inputmode="numeric" maxlength="13" required>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/login/register.html` around lines 26 - 30, Add
client-side validation attributes to the personal identity number input to
mirror the server-side pattern: update the input with id
"personalIdentityNumber" (th:field="*{personalIdentityNumber}") to include
pattern="\d{8}-\d{4}" and optionally inputmode="numeric" and maxlength="13" so
the browser validates the YYYYMMDD-XXXX format before submitting.
src/main/java/org/example/projektarendehantering/presentation/dto/PatientRegistrationDTO.java (1)

19-21: Optional: tighten the personal identity number validation.

The regex ^\d{8}-\d{4}$ accepts clearly invalid values such as 99999999-9999 or 00000000-0000. Since the backend relies on this value for patient uniqueness, consider validating the date portion (month 01-12, day 01-31) and/or the standard Luhn checksum used by Swedish personnummer. A more restrictive regex for the date section:

♻️ Suggested tighter pattern
-    `@Pattern`(regexp = "^\\d{8}-\\d{4}$", message = "Use format YYYYMMDD-XXXX")
+    `@Pattern`(
+            regexp = "^(19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])-\\d{4}$",
+            message = "Use format YYYYMMDD-XXXX")
     private String personalIdentityNumber;

Full checksum validation typically requires a custom ConstraintValidator.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/presentation/dto/PatientRegistrationDTO.java`
around lines 19 - 21, The personalIdentityNumber field in PatientRegistrationDTO
currently uses a permissive `@Pattern`; tighten validation by either replacing
that regex with one that enforces a valid date segment (year, month 01-12, day
01-31) or, preferably, implement a custom Bean Validation annotation and
validator to perform full Swedish personnummer checks (including Luhn checksum).
Create an annotation (e.g., `@ValidPersonnummer`) and a ConstraintValidator (e.g.,
PersonnummerValidator implements ConstraintValidator<ValidPersonnummer, String>)
that verifies format, valid date portion and checksum, then annotate
PatientRegistrationDTO.personalIdentityNumber with `@NotBlank` and
`@ValidPersonnummer` and update the validation message to reflect the stricter
rule.
src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java (1)

55-61: Consider modeling the User↔Patient relationship at the JPA level.

Today PatientEntity.id is manually set from savedAccount.getId() and createdAt is copy-propagated from the user account's @PrePersist side effect. There is no @OneToOne, @MapsId, or FK constraint linking the two tables, so:

  • Database-level referential integrity between patients.id and user_accounts.id is not enforced; orphan rows or mismatched ids can exist if future code paths diverge.
  • patientEntity.setCreatedAt(savedAccount.getCreatedAt()) implicitly depends on UserAccountEntity.@PrePersist having already fired on save — a subtle coupling that will silently break if that hook is refactored. Letting PatientEntity own its own @PrePersist (or @CreationTimestamp) avoids this.

A more idiomatic mapping on PatientEntity:

Suggested JPA relationship mapping
`@OneToOne`(fetch = FetchType.LAZY)
`@MapsId`
`@JoinColumn`(name = "id")
private UserAccountEntity userAccount;

Then use patientEntity.setUserAccount(savedAccount) — JPA will derive the id and enforce the FK at the DB layer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java`
around lines 55 - 61, PatientEntity is being manually assigned
savedAccount.getId() and copying createdAt, which risks referential integrity
and hidden coupling; change the JPA model so PatientEntity has a proper
one-to-one mapping to UserAccountEntity (use `@OneToOne`(fetch = FetchType.LAZY)
together with `@MapsId` and `@JoinColumn`(name = "id") on the userAccount field),
stop calling patientEntity.setId(...) and patientEntity.setCreatedAt(...), and
instead call patientEntity.setUserAccount(savedAccount) before
patientRepository.save(patientEntity); also let PatientEntity own its timestamp
(use its own `@PrePersist` or `@CreationTimestamp`) so createdAt is not copied from
the user account.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java`:
- Around line 52-64: In RegistrationService where
userAccountRepository.save(newUserAccount) and
patientRepository.save(patientEntity) are wrapped in a single try/catch for
DataIntegrityViolationException, differentiate which unique constraint failed
and throw the correct BadRequestException (USER_EXISTS vs PATIENT_EXISTS):
either split into two save blocks with their own try/catch around
userAccountRepository.save(...) and patientRepository.save(...), or inspect the
exception root cause (e.g., unwrap to ConstraintViolationException and check the
constraint name or SQL state from ex.getRootCause()) and map that to throw new
BadRequestException("USER_EXISTS", ...) or new
BadRequestException("PATIENT_EXISTS", ...) accordingly.

In
`@src/main/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapter.java`:
- Line 57: The call in SecurityActorAdapter that normalizes the email before
lookup
(userAccountRepository.findByEmail(authentication.getName().trim().toLowerCase()))
is locale-sensitive; change the normalization to use a locale-independent
lowercasing (use Locale.ROOT with the toLowerCase call) so lookups are stable
across JVM locales, and verify the registration/write path also persists emails
lowercased (so the repository lookup and the write path use the same
normalization).

---

Nitpick comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java`:
- Around line 55-61: PatientEntity is being manually assigned
savedAccount.getId() and copying createdAt, which risks referential integrity
and hidden coupling; change the JPA model so PatientEntity has a proper
one-to-one mapping to UserAccountEntity (use `@OneToOne`(fetch = FetchType.LAZY)
together with `@MapsId` and `@JoinColumn`(name = "id") on the userAccount field),
stop calling patientEntity.setId(...) and patientEntity.setCreatedAt(...), and
instead call patientEntity.setUserAccount(savedAccount) before
patientRepository.save(patientEntity); also let PatientEntity own its timestamp
(use its own `@PrePersist` or `@CreationTimestamp`) so createdAt is not copied from
the user account.

In
`@src/main/java/org/example/projektarendehantering/presentation/dto/PatientRegistrationDTO.java`:
- Around line 19-21: The personalIdentityNumber field in PatientRegistrationDTO
currently uses a permissive `@Pattern`; tighten validation by either replacing
that regex with one that enforces a valid date segment (year, month 01-12, day
01-31) or, preferably, implement a custom Bean Validation annotation and
validator to perform full Swedish personnummer checks (including Luhn checksum).
Create an annotation (e.g., `@ValidPersonnummer`) and a ConstraintValidator (e.g.,
PersonnummerValidator implements ConstraintValidator<ValidPersonnummer, String>)
that verifies format, valid date portion and checksum, then annotate
PatientRegistrationDTO.personalIdentityNumber with `@NotBlank` and
`@ValidPersonnummer` and update the validation message to reflect the stricter
rule.

In `@src/main/resources/templates/login/register.html`:
- Around line 26-30: Add client-side validation attributes to the personal
identity number input to mirror the server-side pattern: update the input with
id "personalIdentityNumber" (th:field="*{personalIdentityNumber}") to include
pattern="\d{8}-\d{4}" and optionally inputmode="numeric" and maxlength="13" so
the browser validates the YYYYMMDD-XXXX format before submitting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e3180eba-cbad-417f-a3eb-5c61836fcfd9

📥 Commits

Reviewing files that changed from the base of the PR and between fdc5040 and 84d2eeb.

📒 Files selected for processing (5)
  • src/main/java/org/example/projektarendehantering/application/service/CaseService.java
  • src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java
  • src/main/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapter.java
  • src/main/java/org/example/projektarendehantering/presentation/dto/PatientRegistrationDTO.java
  • src/main/resources/templates/login/register.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/example/projektarendehantering/application/service/CaseService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/test/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapterTest.java (1)

114-114: Stubs pass coincidentally; normalization path is untested.

Production currentUser() calls userAccountRepository.findByEmail(authentication.getName().trim().toLowerCase(Locale.ROOT)) (see SecurityActorAdapter.java:59). These stubs work only because every test name is already lowercase and untrimmed ("some-other-name", "manager-user", etc.). Consider adding at least one test with mixed-case/whitespace input (e.g., " Manager-User ") stubbing findByEmail("manager-user") to lock in the normalization contract — otherwise a regression that removes .trim().toLowerCase(...) would go undetected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapterTest.java`
at line 114, Add a test in SecurityActorAdapterTest that asserts normalization
by calling currentUser() with an Authentication name containing mixed case and
surrounding whitespace (e.g., "  Manager-User  "), and stub
userAccountRepository.findByEmail with the normalized form "manager-user" (the
value currentUser() passes after .trim().toLowerCase(Locale.ROOT)); specifically
update the when(...) stub used in the test (replace or add the existing
when(userAccountRepository.findByEmail("some-other-name")).thenReturn(Optional.empty())
pattern) so it uses the normalized key and verify the adapter still returns the
expected user, thereby locking in the trim()/toLowerCase() contract referenced
in SecurityActorAdapter.currentUser().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/test/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapterTest.java`:
- Line 114: Add a test in SecurityActorAdapterTest that asserts normalization by
calling currentUser() with an Authentication name containing mixed case and
surrounding whitespace (e.g., "  Manager-User  "), and stub
userAccountRepository.findByEmail with the normalized form "manager-user" (the
value currentUser() passes after .trim().toLowerCase(Locale.ROOT)); specifically
update the when(...) stub used in the test (replace or add the existing
when(userAccountRepository.findByEmail("some-other-name")).thenReturn(Optional.empty())
pattern) so it uses the normalized key and verify the adapter still returns the
expected user, thereby locking in the trim()/toLowerCase() contract referenced
in SecurityActorAdapter.currentUser().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5ab8d21-084f-495b-abdb-a2c00c578fd5

📥 Commits

Reviewing files that changed from the base of the PR and between 84d2eeb and 7b45f28.

📒 Files selected for processing (4)
  • src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java
  • src/main/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapter.java
  • src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java
  • src/test/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapterTest.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/example/projektarendehantering/presentation/web/AuthController.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/example/projektarendehantering/application/service/RegistrationService.java
  • src/main/java/org/example/projektarendehantering/infrastructure/security/SecurityActorAdapter.java

@LinusWestling
LinusWestling merged commit d6676c3 into main Apr 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When logged in as patient, Create case should be allowed but without the option to choose Patient, should be autowired to the patient creating the case

1 participant