Skip to content

Add ticket comment functionality with access control - #19

Merged
simonforsberg merged 5 commits into
mainfrom
feature/ticket-comments
Apr 15, 2026
Merged

Add ticket comment functionality with access control#19
simonforsberg merged 5 commits into
mainfrom
feature/ticket-comments

Conversation

@addee1

@addee1 addee1 commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for ticket comments, including creation and retrieval of comments.

Features

  • Added TicketCommentController with endpoints:
    • POST /tickets/{id}/comments
    • GET /tickets/{id}/comments
  • Implemented TicketCommentService
  • Added role-based access control:
    • ADMIN: full access
    • INVESTIGATOR: assigned tickets only
    • REPORTER: own tickets only

Internal Notes

  • Only ADMIN and INVESTIGATOR can create internal notes
  • Internal notes are hidden from REPORTER users

Security

  • Access control enforced in service layer
  • Users cannot access or comment on tickets they do not own or are not assigned to

Testing

  • Tested via Postman
  • Verified access restrictions for different roles

Summary by CodeRabbit

  • New Features

    • Add and view comments directly on individual tickets via the ticket view.
  • Bug Fixes / Security

    • Enforced access controls so anonymous users cannot access comments; only authorized roles can view or create comments.
    • Restricted creation of internal notes to privileged roles (admins/investigators) and improved error responses for unauthorized access.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Added a new Spring MVC controller exposing POST and GET endpoints for ticket comments and integrated explicit role-based access checks and internal-note permission enforcement in TicketCommentService. User lookup is handled with nullable resolution for unauthenticated cases.

Changes

Cohort / File(s) Summary
Ticket Comment Controller
src/main/java/org/example/alfs/controllers/TicketCommentController.java
New controller with POST /tickets/{ticketId}/comments to create comments (binds CommentCreateDTO, resolves current user via SecurityUtils, delegates to service, redirects) and GET /tickets/{ticketId}/comments to return List<CommentViewDTO> JSON. Includes getCurrentUserOrNull() helper.
TicketCommentService (access control & filtering)
src/main/java/org/example/alfs/services/TicketCommentService.java
Centralized access checks: added checkAccess(ticket, actor) and checkInternalNotePermission(internalNote, author) used by addComment and getComments. addComment gates internal-note creation and access before persisting. getComments now resolves ticket earlier and enforces access before loading/filtering comments; reporter filtering applied conditionally via access helper.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant Controller as TicketCommentController
    participant Utils as SecurityUtils
    participant Service as TicketCommentService
    participant Repo as Repository

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Repo: Create Comment
    Client->>Controller: POST /tickets/{ticketId}/comments (CommentCreateDTO)
    Controller->>Utils: resolve current user
    Utils-->>Controller: User or null
    Controller->>Service: addComment(ticketId, dto, user)
    Service->>Repo: find ticket by id
    Service->>Service: checkAccess(ticket, user)
    Service->>Service: checkInternalNotePermission(dto.internalNote, user)
    alt Access allowed
        Service->>Repo: persist comment
        Service-->>Controller: success
    else Access denied
        Service-->>Controller: throws 401/403
    end
    Controller-->>Client: redirect to ticket view
    end
Loading
sequenceDiagram
    participant Client
    participant Controller as TicketCommentController
    participant Utils as SecurityUtils
    participant Service as TicketCommentService
    participant Repo as Repository

    rect rgba(150, 100, 200, 0.5)
    Note over Client,Repo: Retrieve Comments
    Client->>Controller: GET /tickets/{ticketId}/comments
    Controller->>Utils: resolve current user
    Utils-->>Controller: User or null
    Controller->>Service: getComments(ticketId, user)
    Service->>Repo: find ticket by id
    alt Ticket found
        Service->>Service: checkAccess(ticket, user)
        alt Access allowed
            Service->>Repo: load comments
            Service->>Service: filter internal notes based on role
            Service-->>Controller: List<CommentViewDTO>
        else Access denied
            Service-->>Controller: throws 401/403
        end
    else Ticket not found
        Service-->>Controller: throws 404
    end
    Controller-->>Client: JSON response or error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 In burrows bright I hop and cheer,

Comments now guarded, rules are clear,
Admins stamp, investigators pry,
Reporters peek with careful eye,
Hooray — tickets chat without fear! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding ticket comment functionality with access control, which aligns with the primary objectives of introducing TicketCommentController and TicketCommentService with role-based access enforcement.

✏️ 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 feature/ticket-comments

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: 3

🧹 Nitpick comments (1)
src/main/java/org/example/alfs/services/TicketCommentService.java (1)

46-49: Dead code: author == null can never be true here.

After checkAccess(ticket, author) at line 39 passes without throwing, author is guaranteed non-null (checkAccess throws on null). The author == null check is unreachable and can be removed.

Proposed simplification
         boolean internalNote = dto.isInternalNote();
-        if (internalNote && (author == null || (author.getRole() != Role.ADMIN && author.getRole() != Role.INVESTIGATOR))) {
+        if (internalNote && author.getRole() != Role.ADMIN && author.getRole() != Role.INVESTIGATOR) {
             throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Only investigators/admins can create internal notes");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/alfs/services/TicketCommentService.java` around
lines 46 - 49, Remove the unreachable null check from the internal-note
authorization branch: since checkAccess(ticket, author) already throws on a null
author, simplify the condition in the block that uses dto.isInternalNote() to
only validate roles (author.getRole() != Role.ADMIN && author.getRole() !=
Role.INVESTIGATOR) and throw the same ResponseStatusException when the role
check fails; update the if in TicketCommentService (the boolean internalNote
block) to drop the "author == null" predicate so only role checks remain.
🤖 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/alfs/controllers/TicketCommentController.java`:
- Around line 48-54: The getCurrentUserOrNull method currently swallows all
Exceptions; change it to only handle expected authentication failures from
SecurityUtils.getCurrentUser() by catching RuntimeException (or the specific
exception type thrown) and inspecting the message to determine an auth failure,
returning null in that case, but rethrowing or letting other unexpected
exceptions propagate; follow the TicketService pattern used elsewhere (inspect
SecurityUtils.getCurrentUser() messages) and update getCurrentUserOrNull
accordingly so real errors (e.g., DB failures) are not masked.
- Around line 31-32: The inline comment in TicketCommentController next to
getCurrentUserOrNull() is misleading because TicketCommentService.checkAccess()
currently forbids null users; update the comment to reflect that anonymous users
are not allowed (or if anonymous commenting is intended, add a TODO noting that
the feature must be implemented and the check in
TicketCommentService.checkAccess() relaxed/handled later). Reference
getCurrentUserOrNull() and TicketCommentService.checkAccess() in the updated
comment so future readers understand the current restriction and any deferred
work.

In `@src/main/java/org/example/alfs/services/TicketCommentService.java`:
- Around line 82-87: The checkAccess method in TicketCommentService currently
throws ResponseStatusException with HttpStatus.FORBIDDEN when user is null;
change this to throw HttpStatus.UNAUTHORIZED to correctly signal unauthenticated
requests (i.e., replace the FORBIDDEN response with UNAUTHORIZED in
checkAccess), mirroring the behavior in TicketService.requireCurrentUser to keep
authentication vs authorization semantics consistent.

---

Nitpick comments:
In `@src/main/java/org/example/alfs/services/TicketCommentService.java`:
- Around line 46-49: Remove the unreachable null check from the internal-note
authorization branch: since checkAccess(ticket, author) already throws on a null
author, simplify the condition in the block that uses dto.isInternalNote() to
only validate roles (author.getRole() != Role.ADMIN && author.getRole() !=
Role.INVESTIGATOR) and throw the same ResponseStatusException when the role
check fails; update the if in TicketCommentService (the boolean internalNote
block) to drop the "author == null" predicate so only role checks remain.
🪄 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: 729e5a01-ef2a-41a9-9c75-3b037847c968

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd42ef and 9c64050.

📒 Files selected for processing (2)
  • src/main/java/org/example/alfs/controllers/TicketCommentController.java
  • src/main/java/org/example/alfs/services/TicketCommentService.java

Comment thread src/main/java/org/example/alfs/controllers/TicketCommentController.java Outdated
Comment thread src/main/java/org/example/alfs/services/TicketCommentService.java
- Improve exception handling in controller
- Clarify anonymous behavior in comments
- Return 401 instead of 403 for unauthenticated users
- Keep role-based access rules intact
Comment thread src/main/java/org/example/alfs/services/TicketCommentService.java Outdated
…heck into a helper method for improved readability.
@simonforsberg

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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/main/java/org/example/alfs/services/TicketCommentService.java (1)

62-68: Push reporter filtering into the repository query.

At Line 62 you load all comments, then filter internal notes in-memory at Line 64. For long threads, this does unnecessary reads/mapping. Consider querying non-internal comments directly for reporters.

💡 Suggested refactor
-        List<TicketComment> all = ticketCommentRepository.findByTicketIdOrderByCreatedAtAsc(ticketId);
-
-        if (actor.getRole() == Role.REPORTER) {
-            return all.stream()
-                    .filter(comment -> !comment.isInternalNote())
-                    .map(ticketCommentMapper::entityToViewDTO)
-                    .toList();
-        }
+        List<TicketComment> all = (actor.getRole() == Role.REPORTER)
+                ? ticketCommentRepository.findByTicketIdAndInternalNoteFalseOrderByCreatedAtAsc(ticketId)
+                : ticketCommentRepository.findByTicketIdOrderByCreatedAtAsc(ticketId);

Also add a repository method:

List<TicketComment> findByTicketIdAndInternalNoteFalseOrderByCreatedAtAsc(Long ticketId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/alfs/services/TicketCommentService.java` around
lines 62 - 68, Currently the service loads all comments via
ticketCommentRepository.findByTicketIdOrderByCreatedAtAsc and then filters
internal notes in-memory when actor.getRole() == Role.REPORTER; instead add a
repository query List<TicketComment>
findByTicketIdAndInternalNoteFalseOrderByCreatedAtAsc(Long ticketId) and, inside
the branch that checks actor.getRole() == Role.REPORTER in TicketCommentService,
call that new method instead of findByTicketIdOrderByCreatedAtAsc so you only
map non-internal comments with ticketCommentMapper::entityToViewDTO.
🤖 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/main/java/org/example/alfs/services/TicketCommentService.java`:
- Around line 62-68: Currently the service loads all comments via
ticketCommentRepository.findByTicketIdOrderByCreatedAtAsc and then filters
internal notes in-memory when actor.getRole() == Role.REPORTER; instead add a
repository query List<TicketComment>
findByTicketIdAndInternalNoteFalseOrderByCreatedAtAsc(Long ticketId) and, inside
the branch that checks actor.getRole() == Role.REPORTER in TicketCommentService,
call that new method instead of findByTicketIdOrderByCreatedAtAsc so you only
map non-internal comments with ticketCommentMapper::entityToViewDTO.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e8d5e9d-e6da-4297-8abf-b5dc7ea7cdf7

📥 Commits

Reviewing files that changed from the base of the PR and between 9c64050 and 37122ea.

📒 Files selected for processing (2)
  • src/main/java/org/example/alfs/controllers/TicketCommentController.java
  • src/main/java/org/example/alfs/services/TicketCommentService.java

…ltering of internal notes to the repository for improved clarity and performance.
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.

2 participants