Skip to content

Feature/audit logging - #31

Merged
gitnes94 merged 6 commits into
mainfrom
feature/AuditLogging
Apr 7, 2026
Merged

Feature/audit logging#31
gitnes94 merged 6 commits into
mainfrom
feature/AuditLogging

Conversation

@gitnes94

@gitnes94 gitnes94 commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Closes #10
Closes #16
Closes #15

Summary by CodeRabbit

  • New Features
    • Activity log for tickets: records status changes, file uploads, and comments with timestamps and actor info
    • Commenting on tickets: create and list comments with author and timestamp
    • Ticket deletion endpoint that removes associated comments and activity logs
    • Ticket actions now record the staff member who performed them
    • New endpoints to retrieve ticket activity logs and ticket comments

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an activity log and comment subsystem: new JPA entities, DTOs, repositories, services, and REST controllers; updates TicketController and TicketService APIs to accept actor IDs (performedById/assignedById/uploadedById), record status/file/comment events to activity_logs, and add ticket deletion.

Changes

Cohort / File(s) Summary
Activity Log
src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java, src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java, src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLogResponseDTO.java, src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityType.java, src/main/java/org/example/cyberwatch/features/activitylog/repository/ActivityLogRepository.java, src/main/java/org/example/cyberwatch/features/activitylog/service/ActivityLogService.java
Adds ActivityLog entity, enum, response DTO, repository, service to record status/file/comment events, and a controller endpoint to list logs for a ticket.
Comment subsystem
src/main/java/org/example/cyberwatch/features/comment/controller/CommentController.java, src/main/java/org/example/cyberwatch/features/comment/model/Comment.java, src/main/java/org/example/cyberwatch/features/comment/model/CommentDTO.java, src/main/java/org/example/cyberwatch/features/comment/model/CommentResponseDTO.java, src/main/java/org/example/cyberwatch/features/comment/repository/CommentRepository.java, src/main/java/org/example/cyberwatch/features/comment/service/CommentService.java
Adds Comment JPA entity, request/response DTOs with validation, repository, service that persists comments, calls ActivityLogService, and controller endpoints to create/list comments.
Ticket controller/service changes
src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java, src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java
Controller endpoints and service methods updated to accept actor IDs (performedById, uploadedById, assignedById); TicketService gains ActivityLogService dependency, resolves Staff actors, logs status/file/comment events, and adds deleteTicket.
Ticket model associations
src/main/java/org/example/cyberwatch/features/ticket/model/Ticket.java
Adds bidirectional one-to-many associations activityLogs and comments with cascade ALL and orphanRemoval true, initialized as empty lists.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant CommentController as CommentController
    participant CommentService as CommentService
    participant CommentRepo as CommentRepository
    participant ActivityLogService as ActivityLogService
    participant DB as Database

    Client->>CommentController: POST /api/tickets/{ticketId}/comments
    CommentController->>CommentService: addComment(ticketId, CommentDTO)
    CommentService->>CommentService: load Ticket, load Staff
    CommentService->>CommentRepo: save(Comment)
    CommentRepo->>DB: INSERT comment
    CommentService->>ActivityLogService: logComment(ticket, author, text)
    ActivityLogService->>DB: INSERT activity_log
    CommentService->>CommentController: CommentResponseDTO
    CommentController->>Client: 201 Created
Loading
sequenceDiagram
    participant Client as Client
    participant TicketController as TicketController
    participant TicketService as TicketService
    participant StaffRepo as StaffRepository
    participant TicketRepo as TicketRepository
    participant ActivityLogService as ActivityLogService
    participant DB as Database

    Client->>TicketController: PATCH /api/tickets/{id}/status?performedById=X
    TicketController->>TicketService: setTicketStatus(id, newStatus, performedById)
    TicketService->>TicketRepo: load Ticket
    TicketService->>StaffRepo: load Staff(performedById)
    TicketService->>TicketService: capture oldStatus, update status
    TicketService->>TicketRepo: save(updatedTicket)
    TicketRepo->>DB: UPDATE ticket
    TicketService->>ActivityLogService: logStatusChange(ticket, staff, oldStatus, newStatus)
    ActivityLogService->>DB: INSERT activity_log
    TicketService->>TicketController: TicketResponseDTO
    TicketController->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Ericthilen
  • codebyNorthsteep
  • alicewersen-rgb

Poem

🐰 I hopped through code with tiny paws,

I logged each change and counted clauses,
Comments planted, files took flight,
Actors noted, timestamps bright,
A little hop — audit trails delight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
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 'Feature/audit logging' is vague and uses generic phrasing. While audit logging is present in the changes, the PR encompasses multiple features including comments and ticket CRUD modifications. Consider a more descriptive title that captures the main scope, such as 'Add audit logging, comments, and ticket management enhancements' or focus on the primary feature.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR successfully implements all three linked issue requirements: CRUD operations for tickets [#10], comment system [#16], and audit logging with activity logs [#15].
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issues. New controllers, services, models, and repositories support the three stated objectives without introducing unrelated functionality.

✏️ 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/AuditLogging

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

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/cyberwatch/features/ticket/controller/TicketController.java (1)

82-89: ⚠️ Potential issue | 🟡 Minor

Don't mask an unknown uploader as a generic upload failure.

ticketService.uploadFile(...) can now throw StaffNotFoundException for an invalid uploadedById, but the broad catch (Exception e) converts that into a generic 400 response. Re-throw the staff-not-found case, or narrow this catch to actual storage/upload failures, so the API keeps returning a proper not-found error for bad staff ids.

🩹 Narrow the exception handling
         try {
             return ResponseEntity.ok(ticketService.uploadFile(ticketId, uploadedById, file));
         } catch (TicketNotFoundException e) {
             throw e;
+        } catch (StaffNotFoundException e) {
+            throw e;
         } catch (Exception e) {
             return ResponseEntity.badRequest().body(Map.of("error", "File upload failed"));
         }

Also add:

import org.example.cyberwatch.features.staff.exception.StaffNotFoundException;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java`
around lines 82 - 89, The upload endpoint in TicketController currently catches
Exception and masks StaffNotFoundException thrown by ticketService.uploadFile;
update the handler in the method that takes ticketId, uploadedById, and
MultipartFile to either add a specific catch (catch (StaffNotFoundException e) {
throw e; }) before the generic catch or replace the broad catch with a narrower
catch for storage/upload-related exceptions only, and add the import for
org.example.cyberwatch.features.staff.exception.StaffNotFoundException so
invalid uploadedById results in the proper not-found error instead of a generic
400.
🧹 Nitpick comments (2)
src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java (1)

20-23: Consider validating ticket existence.

The endpoint returns an empty list for non-existent tickets rather than a 404. If distinguishing between "ticket exists with no logs" and "ticket doesn't exist" is important for API consumers, consider adding ticket existence validation.

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

In
`@src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java`
around lines 20 - 23, The getLogs endpoint in ActivityLogController currently
returns an empty list for non-existent tickets; update
ActivityLogController.getLogs to first validate ticket existence (e.g., call a
TicketService.existsById(ticketId) or TicketRepository.existsById(ticketId)) and
if the ticket doesn't exist return ResponseEntity.notFound().build(), otherwise
call activityLogService.getLogsForTicket(ticketId) and return OK with the logs;
ensure you reference and inject the ticket existence check service
(TicketService or TicketRepository) used by other controllers to keep behavior
consistent.
src/main/java/org/example/cyberwatch/features/comment/model/CommentResponseDTO.java (1)

17-24: Lazy loading design is fragile; consider using fetch joins for robustness and defensive null-checking.

  1. Lazy loading within transaction: The ticket and author associations are lazy-loaded, but since getCommentsForTicket() is @Transactional(readOnly = true), lazy loading succeeds. However, this design is fragile—it relies on the transactional context being active. Using explicit fetch joins in the repository query would be more robust and avoid potential issues if the call context changes.

  2. Null safety: While firstName and lastName on Staff have @NotBlank validation (preventing blank values), the database columns lack explicit nullable = false constraints. The concatenation on line 21 could still produce unexpected results. Consider using String.format() or a dedicated method for null-safe name concatenation.

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

In
`@src/main/java/org/example/cyberwatch/features/comment/model/CommentResponseDTO.java`
around lines 17 - 24, The CommentResponseDTO constructor relies on lazy-loaded
associations (Comment.getTicket(), Comment.getAuthor()) and concatenates author
names directly; update the repository method that loads comments
(getCommentsForTicket()) to use explicit fetch joins for ticket and author to
avoid transactional fragility, and make the constructor null-safe by guarding
the author and its name parts (use a null-safe concat/format or Objects.toString
for getAuthor().getFirstName()/getLastName() and handle a missing author by
setting authorId/authorName appropriately); touch CommentResponseDTO(Comment)
constructor and the repository query that returns Comment to implement these
changes.
🤖 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/cyberwatch/features/comment/service/CommentService.java`:
- Around line 38-52: The addComment method currently trusts dto.getAuthorId() to
set the persisted comment author and audit actor, allowing clients to
impersonate staff; change addComment to obtain the author server-side (e.g.,
from the security context or the authenticated principal) instead of using
CommentDTO.getAuthorId(), then use that server-derived Staff instance for
comment.setAuthor(...) and for activityLogService.logComment(...); remove or
ignore authorId from CommentDTO on the server side and keep staffRepository and
Ticket lookup logic intact, updating any callers/tests to stop passing an
authorId in the request body.

In
`@src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java`:
- Around line 47-49: The controller currently accepts caller-controlled
performedById/uploadedById in endpoints such as advanceStatus (calling
ticketService.advanceTicketStatus) and the other similar handlers; instead
resolve the actor server-side from the authenticated principal (e.g.,
SecurityContext/Principal/Authentication) and remove performedById/uploadedById
from the public request signature, then pass the resolved actorId (or principal)
into ticketService.advanceTicketStatus and the corresponding service methods
used by the file-upload and other endpoints; update method signatures and any
DTOs/controllers that referenced performedById/uploadedById accordingly and
ensure authorization checks still run using the server-resolved identity.

In
`@src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java`:
- Around line 204-207: The deleteTicket(Long id) method is failing because
Ticket has related activity_logs rows that are not removed; either remove
dependent logs first or add cascade/on-delete on the Ticket entity. Fix options:
(1) update the Ticket entity (class Ticket) where it maps the activity logs to
include CascadeType.REMOVE (or CascadeType.ALL) and orphanRemoval=true so
ticketRepository.delete(ticket) will cascade deletes to activity logs, or (2)
explicitly delete dependent rows before calling ticketRepository.delete(ticket)
by injecting the ActivityLog repository and calling a method like
activityLogRepository.deleteByTicketId(id) (or equivalent) inside
deleteTicket(Long id). Choose one approach and implement it consistently (entity
mapping change in Ticket or pre-delete cleanup in deleteTicket) so deletes
succeed when audit logs exist.
- Around line 140-145: The activity log is being attributed to the assignee
because logStatusChange(saved, staff, ...) passes the assignee (staff) as the
actor; change the second argument to the actual actor who performed the
assignment (e.g., performedBy or the current user) like reopenTicket does—call
activityLogService.logStatusChange(saved, performedBy, oldStatus,
Status.IN_PROGRESS); if no performedBy variable exists in this method, obtain
the actor from the same source used in reopenTicket (method parameter or
auth/currentUser helper) and pass that instead of staff.

---

Outside diff comments:
In
`@src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java`:
- Around line 82-89: The upload endpoint in TicketController currently catches
Exception and masks StaffNotFoundException thrown by ticketService.uploadFile;
update the handler in the method that takes ticketId, uploadedById, and
MultipartFile to either add a specific catch (catch (StaffNotFoundException e) {
throw e; }) before the generic catch or replace the broad catch with a narrower
catch for storage/upload-related exceptions only, and add the import for
org.example.cyberwatch.features.staff.exception.StaffNotFoundException so
invalid uploadedById results in the proper not-found error instead of a generic
400.

---

Nitpick comments:
In
`@src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java`:
- Around line 20-23: The getLogs endpoint in ActivityLogController currently
returns an empty list for non-existent tickets; update
ActivityLogController.getLogs to first validate ticket existence (e.g., call a
TicketService.existsById(ticketId) or TicketRepository.existsById(ticketId)) and
if the ticket doesn't exist return ResponseEntity.notFound().build(), otherwise
call activityLogService.getLogsForTicket(ticketId) and return OK with the logs;
ensure you reference and inject the ticket existence check service
(TicketService or TicketRepository) used by other controllers to keep behavior
consistent.

In
`@src/main/java/org/example/cyberwatch/features/comment/model/CommentResponseDTO.java`:
- Around line 17-24: The CommentResponseDTO constructor relies on lazy-loaded
associations (Comment.getTicket(), Comment.getAuthor()) and concatenates author
names directly; update the repository method that loads comments
(getCommentsForTicket()) to use explicit fetch joins for ticket and author to
avoid transactional fragility, and make the constructor null-safe by guarding
the author and its name parts (use a null-safe concat/format or Objects.toString
for getAuthor().getFirstName()/getLastName() and handle a missing author by
setting authorId/authorName appropriately); touch CommentResponseDTO(Comment)
constructor and the repository query that returns Comment to implement these
changes.
🪄 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: 8ff74a6d-ca5b-40a0-ae51-8e8096c8030c

📥 Commits

Reviewing files that changed from the base of the PR and between be2c8af and f62ca7b.

📒 Files selected for processing (14)
  • src/main/java/org/example/cyberwatch/features/activitylog/controller/ActivityLogController.java
  • src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java
  • src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLogResponseDTO.java
  • src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityType.java
  • src/main/java/org/example/cyberwatch/features/activitylog/repository/ActivityLogRepository.java
  • src/main/java/org/example/cyberwatch/features/activitylog/service/ActivityLogService.java
  • src/main/java/org/example/cyberwatch/features/comment/controller/CommentController.java
  • src/main/java/org/example/cyberwatch/features/comment/model/Comment.java
  • src/main/java/org/example/cyberwatch/features/comment/model/CommentDTO.java
  • src/main/java/org/example/cyberwatch/features/comment/model/CommentResponseDTO.java
  • src/main/java/org/example/cyberwatch/features/comment/repository/CommentRepository.java
  • src/main/java/org/example/cyberwatch/features/comment/service/CommentService.java
  • src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java
  • src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java (2)

206-210: Consider logging ticket deletion before removing the record.

The deletion will succeed now due to cascade configuration, but unlike other operations, no audit trail is created for delete actions. If audit completeness is important, consider logging the deletion (with the deleter's ID) before calling delete().

Example approach
-    public void deleteTicket(Long id) {
+    public void deleteTicket(Long id, Long deletedById) {
         Ticket ticket = ticketRepository.findById(id)
                 .orElseThrow(() -> new TicketNotFoundException(id));
+        Staff deleter = staffRepository.findById(deletedById)
+                .orElseThrow(() -> new StaffNotFoundException(deletedById));
+        activityLogService.logDeletion(ticket, deleter);
         ticketRepository.delete(ticket);
     }

This would require adding a logDeletion method to ActivityLogService and a new ActivityType.DELETED.

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

In
`@src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java`
around lines 206 - 210, The deleteTicket method in TicketService currently
removes the Ticket without creating an audit entry; update deleteTicket to call
ActivityLogService.logDeletion (or add that method) before
ticketRepository.delete(ticket), passing ticket id, current user/deleter id and
ActivityType.DELETED (add the DELETED enum value if missing), so the deletion is
recorded in the audit trail prior to performing the delete.

126-133: Consider adding audit logging for assignTicket for consistency.

assignTicketToStaff logs activity when assigning, but assignTicket (which also assigns staff via setAssignedTo) does not. If both assignment paths should be audited, consider adding logging here too. If assignTicket is intentionally unaudited (e.g., internal/legacy use), a comment clarifying that distinction would help.

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

In
`@src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java`
around lines 126 - 133, The assignTicket method is missing the audit/logging
that assignTicketToStaff performs; update assignTicket (in TicketService) to
call the same audit/logging mechanism used in assignTicketToStaff after
ticket.setAssignedTo(staff) and before/after ticketRepository.save(ticket)
(e.g., invoke the same auditService or activity logger and include ticketId and
staffId in the log), or if this method is intentionally unaudited, add a concise
comment above assignTicket explaining that distinction for future maintainers.
🤖 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/cyberwatch/features/ticket/controller/TicketController.java`:
- Around line 74-78: The deleteTicket endpoint lacks authorization—update
TicketController.deleteTicket to enforce role/permission checks (either add
`@PreAuthorize`("hasRole('ADMIN')") on the deleteTicket method or implement a
permission check before calling ticketService.deleteTicket). If enforcing
per-ticket permissions, fetch the ticket (via ticketService.findById or
ticketService.getTicket) and verify the current Principal/Authentication
(SecurityContextHolder) has ownership or an ADMIN role, or add a service method
like ticketService.deleteTicketIfAuthorized(Long id, UserPrincipal principal)
that throws AccessDeniedException when unauthorized; then call that method from
deleteTicket instead of ticketService.deleteTicket(id).

---

Nitpick comments:
In
`@src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.java`:
- Around line 206-210: The deleteTicket method in TicketService currently
removes the Ticket without creating an audit entry; update deleteTicket to call
ActivityLogService.logDeletion (or add that method) before
ticketRepository.delete(ticket), passing ticket id, current user/deleter id and
ActivityType.DELETED (add the DELETED enum value if missing), so the deletion is
recorded in the audit trail prior to performing the delete.
- Around line 126-133: The assignTicket method is missing the audit/logging that
assignTicketToStaff performs; update assignTicket (in TicketService) to call the
same audit/logging mechanism used in assignTicketToStaff after
ticket.setAssignedTo(staff) and before/after ticketRepository.save(ticket)
(e.g., invoke the same auditService or activity logger and include ticketId and
staffId in the log), or if this method is intentionally unaudited, add a concise
comment above assignTicket explaining that distinction for future maintainers.
🪄 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: 4286ee07-b4cd-4711-a92c-96b02f50ce9a

📥 Commits

Reviewing files that changed from the base of the PR and between f62ca7b and 0c59f67.

📒 Files selected for processing (3)
  • src/main/java/org/example/cyberwatch/features/ticket/controller/TicketController.java
  • src/main/java/org/example/cyberwatch/features/ticket/model/Ticket.java
  • src/main/java/org/example/cyberwatch/features/ticket/service/TicketService.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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java (1)

14-15: Add table indexes for ticket activity timeline reads.

Activity logs are typically fetched by ticket and sorted by time; add indexes for ticket_id,timestamp (and optionally performed_by_id) to avoid future hot-path scans.

Suggested index diff
-@Table(name = "activity_logs")
+@Table(
+    name = "activity_logs",
+    indexes = {
+        `@Index`(name = "idx_activity_logs_ticket_timestamp", columnList = "ticket_id,timestamp"),
+        `@Index`(name = "idx_activity_logs_performed_by", columnList = "performed_by_id")
+    }
+)

Also applies to: 25-30, 42-43

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

In
`@src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java`
around lines 14 - 15, The entity ActivityLog (annotated with `@Table`) lacks DB
indexes for common ticket-timeline queries; add appropriate indexes via the
`@Table`(indexes = {...}) attribute on the ActivityLog class to create at least a
composite index on ticket_id and timestamp and optionally a second index
including performed_by_id (e.g., columns = {"ticket_id","timestamp"} and
{"ticket_id","performed_by_id","timestamp"}) so reads filtered by ticket and
ordered by time use the index; update the `@Table` on ActivityLog (and the other
referenced entity occurrences) to include these Index definitions referencing
the ticketId, timestamp, and performedById column names.
🤖 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/cyberwatch/features/activitylog/model/ActivityLog.java`:
- Around line 16-43: ActivityLog currently uses a class-level `@Setter` which
allows post-persist mutation of audit fields; change to constructor-based
immutable creation by removing the public setters (remove `@Setter`), provide a
protected no-arg constructor for JPA and a public/all-args constructor for
creation, and mark persisted audit columns as non-updatable (set updatable =
false on `@JoinColumn` for ticket and performedBy and on `@Column` for activityType
and details) while keeping timestamp `@CreationTimestamp` with updatable = false;
ensure only JPA can modify fields (protected no-arg) and that creation uses the
ActivityLog(...) constructor for performedBy, activityType, details, ticket.
- Around line 41-43: Change the timezone-ambiguous ActivityLog.timestamp field
from LocalDateTime to a timezone-safe type (prefer Instant for UTC or
OffsetDateTime) and update its JPA mapping to persist TZ-aware values (e.g., add
columnDefinition = "TIMESTAMP WITH TIME ZONE" on the `@Column`). Update the
ActivityLog entity (field timestamp), the ActivityLogResponseDTO (timestamp type
and any serialization/deserialization), and any repository/query code that
orders or compares by timestamp to use the new type (convert existing
LocalDateTime usages to Instant/OffsetDateTime and ensure ordering uses the
UTC/offset-aware value). Also update any mapping code (entity→DTO, DTO→entity)
to convert between Instant/OffsetDateTime and the JSON representation
consistently.

---

Nitpick comments:
In
`@src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java`:
- Around line 14-15: The entity ActivityLog (annotated with `@Table`) lacks DB
indexes for common ticket-timeline queries; add appropriate indexes via the
`@Table`(indexes = {...}) attribute on the ActivityLog class to create at least a
composite index on ticket_id and timestamp and optionally a second index
including performed_by_id (e.g., columns = {"ticket_id","timestamp"} and
{"ticket_id","performed_by_id","timestamp"}) so reads filtered by ticket and
ordered by time use the index; update the `@Table` on ActivityLog (and the other
referenced entity occurrences) to include these Index definitions referencing
the ticketId, timestamp, and performedById column names.
🪄 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: eee7601d-e319-4bc5-a69b-eba4828c979a

📥 Commits

Reviewing files that changed from the base of the PR and between 451c7a8 and 05393e6.

📒 Files selected for processing (1)
  • src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java

Comment on lines +16 to +43
@Setter
@NoArgsConstructor
public class ActivityLog {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ticket_id", nullable = false)
private Ticket ticket;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "performed_by_id", nullable = false)
private Staff performedBy;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ActivityType activityType;

// For STATUS_CHANGED: describes the transition, e.g. "SUBMITTED → IN_PROGRESS"
// For COMMENT_ADDED: contains the comment text
@Column(columnDefinition = "TEXT")
private String details;

@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime timestamp;

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 | 🟠 Major

Make audit log entries immutable after insert.

Class-level @Setter allows post-create edits to audit history (performedBy, activityType, details), which weakens audit integrity. Prefer constructor-based creation and mark persisted fields non-updatable.

Suggested hardening diff
 import jakarta.persistence.*;
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
-import lombok.Setter;
@@
 `@Entity`
 `@Table`(name = "activity_logs")
 `@Getter`
-@Setter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
 public class ActivityLog {
@@
     `@ManyToOne`(fetch = FetchType.LAZY)
-    `@JoinColumn`(name = "ticket_id", nullable = false)
+    `@JoinColumn`(name = "ticket_id", nullable = false, updatable = false)
     private Ticket ticket;
@@
     `@ManyToOne`(fetch = FetchType.LAZY)
-    `@JoinColumn`(name = "performed_by_id", nullable = false)
+    `@JoinColumn`(name = "performed_by_id", nullable = false, updatable = false)
     private Staff performedBy;
@@
     `@Enumerated`(EnumType.STRING)
-    `@Column`(nullable = false)
+    `@Column`(nullable = false, updatable = false)
     private ActivityType activityType;
@@
-    `@Column`(columnDefinition = "TEXT")
+    `@Column`(columnDefinition = "TEXT", updatable = false)
     private String details;
@@
     `@CreationTimestamp`
     `@Column`(nullable = false, updatable = false)
     private LocalDateTime timestamp;
+
+    public ActivityLog(Ticket ticket, Staff performedBy, ActivityType activityType, String details) {
+        this.ticket = ticket;
+        this.performedBy = performedBy;
+        this.activityType = activityType;
+        this.details = details;
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java`
around lines 16 - 43, ActivityLog currently uses a class-level `@Setter` which
allows post-persist mutation of audit fields; change to constructor-based
immutable creation by removing the public setters (remove `@Setter`), provide a
protected no-arg constructor for JPA and a public/all-args constructor for
creation, and mark persisted audit columns as non-updatable (set updatable =
false on `@JoinColumn` for ticket and performedBy and on `@Column` for activityType
and details) while keeping timestamp `@CreationTimestamp` with updatable = false;
ensure only JPA can modify fields (protected no-arg) and that creation uses the
ActivityLog(...) constructor for performedBy, activityType, details, ticket.

Comment on lines +41 to +43
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime timestamp;

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current timestamp handling across entity, DTOs, and SQL migrations.
rg -n "class ActivityLog|timestamp|LocalDateTime|Instant|OffsetDateTime" --type=java -C2
rg -n "activity_logs|timestamp|TIMESTAMP WITH TIME ZONE|TIMESTAMP|DATETIME" --type=sql -C2
rg -n "ActivityLogResponseDTO|timestamp" --type=java -C2

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 21968


🏁 Script executed:

#!/bin/bash
# Find SQL migration files and check database schema for activity_logs table
find . -type f \( -name "*.sql" -o -name "*.yaml" -o -name "*.yml" \) | head -20

# Search for migration files or schema definitions
fd -e sql -e yaml -e yml -e properties | grep -i -E "(migration|schema|flyway|liquibase)" | head -20

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 100


🏁 Script executed:

#!/bin/bash
# Check application properties/configuration for timezone settings
find . -type f \( -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" \)
fd -type f \( -name "*.properties" -o -name "*.yml" -o -name "*.yaml" \) | xargs rg -l "timezone|TimeZone|UTC" 2>/dev/null | head -10

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 317


🏁 Script executed:

#!/bin/bash
# Check if there are any explicit timezone configurations in the codebase
rg "ZoneId|ZoneOffset|TimeZone|setTimeZone|sql.Timestamp" --type=java -n -A 2 -B 2 | head -50

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Read application.properties
cat src/main/resources/application.properties | head -50

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 692


🏁 Script executed:

#!/bin/bash
# Check compose.yaml for database setup
cat compose.yaml

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 584


🏁 Script executed:

#!/bin/bash
# Search for SQL migration directories
find . -type d -name "db" -o -name "migration" -o -name "migrations" 2>/dev/null
find . -type d -path "*/resources/db*" 2>/dev/null
ls -la src/main/resources/ 2>/dev/null

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 378


🏁 Script executed:

#!/bin/bash
# Look for any SQL files in the resources directory
find src/main/resources -name "*.sql" 2>/dev/null

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Read the full ActivityLog.java file
cat src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 1314


🏁 Script executed:

#!/bin/bash
# Check for Hibernate dialect or custom type mappings
rg "dialect|Dialect|TypeDef|columnDefinition" --type=java -n -B 2 -A 2 | head -60

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 1674


🏁 Script executed:

#!/bin/bash
# Check application.properties for any Hibernate/JPA dialect settings
cat src/main/resources/application.properties | grep -i "dialect\|jpa"

Repository: ithsjava25/project-backend-super-cool-team

Length of output: 147


Use timezone-safe timestamp type for audit data.

LocalDateTime is timezone-ambiguous. With Hibernate's default schema generation, this maps to PostgreSQL TIMESTAMP (without time zone), losing timezone information. For audit trails, use Instant (UTC) or OffsetDateTime with explicit columnDefinition = "TIMESTAMP WITH TIME ZONE" to ensure consistent event ordering and cross-environment interpretation.

Apply this change end-to-end: ActivityLog entity, ActivityLogResponseDTO, and any queries ordering by timestamp.

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

In
`@src/main/java/org/example/cyberwatch/features/activitylog/model/ActivityLog.java`
around lines 41 - 43, Change the timezone-ambiguous ActivityLog.timestamp field
from LocalDateTime to a timezone-safe type (prefer Instant for UTC or
OffsetDateTime) and update its JPA mapping to persist TZ-aware values (e.g., add
columnDefinition = "TIMESTAMP WITH TIME ZONE" on the `@Column`). Update the
ActivityLog entity (field timestamp), the ActivityLogResponseDTO (timestamp type
and any serialization/deserialization), and any repository/query code that
orders or compares by timestamp to use the new type (convert existing
LocalDateTime usages to Instant/OffsetDateTime and ensure ordering uses the
UTC/offset-aware value). Also update any mapping code (entity→DTO, DTO→entity)
to convert between Instant/OffsetDateTime and the JSON representation
consistently.

@gitnes94
gitnes94 merged commit 1ae492e into main Apr 7, 2026
1 check passed
@gitnes94
gitnes94 deleted the feature/AuditLogging branch April 7, 2026 09:48
This was referenced Apr 15, 2026
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.

Implement comment system for tickets Implement Audit Logging CRUD API for tickets

1 participant