Feature/preview flow - #23
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 39 minutes and 55 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a public start page and anonymous-ticket flow: new StartPageController and global model attributes; ticket creation now permits anonymous users via reporter tokens and preview/ticket-created flows; security rules and JWT filter updated to allow new public endpoints; templates, layout, and CSS updated for UI and success toasts. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Controller as TicketController
participant Service as TicketService
participant DB as Database
Browser->>Controller: POST /tickets/previewTicket (dto)
Controller-->>Browser: Render previewTicket
Browser->>Controller: POST /tickets/create (dto)
Controller->>Service: createNewTicket(dto)
activate Service
Service->>Service: securityUtils.getCurrentUserOrNull() -> null or User
alt user == null
Service->>Service: generate UUID token
Service->>DB: save ticket with reporterToken
else user present
Service->>DB: save ticket with reporter
end
DB-->>Service: saved ticket
Service-->>Controller: TicketViewDTO (includes token if anonymous)
deactivate Service
Controller-->>Browser: Redirect to /tickets/ticket-created?token={token} or /tickets/{id}
Browser->>Controller: GET /tickets/ticket-created?token=...
Controller-->>Browser: Render ticket-created view
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
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/alfs/services/TicketService.java (1)
41-54:⚠️ Potential issue | 🔴 Critical
createNewTicketdoesn't setreporterToken, violating NOT NULL constraint.Per the entity definition (
Ticket.javaline 39-40),reporterTokenis@Column(nullable = false). ThecreateAnonymousTicketmethod correctly sets this field with a UUID, butcreateNewTicketdoes not. This will cause a database constraint violation when authenticated users submit tickets.🐛 Proposed fix
public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) { Ticket ticket = new Ticket(); ticket.setTitle(ticketCreateDTO.getTitle()); ticket.setDescription(ticketCreateDTO.getDescription()); User user = requireCurrentUser(); ticket.setReporter(user); + ticket.setReporterToken(java.util.UUID.randomUUID().toString()); Ticket savedTicket = ticketRepository.save(ticket); return ticketMapper.entityToViewDTO(savedTicket); }🤖 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/TicketService.java` around lines 41 - 54, The createNewTicket method in TicketService fails to set the required reporterToken (nullable = false) before saving; update createNewTicket to generate and set a reporterToken (e.g., UUID.randomUUID().toString()) on the Ticket instance—similar to createAnonymousTicket—and ensure ticket.setReporterToken(...) is called prior to ticketRepository.save(ticket), using the provided TicketCreateDTO and requireCurrentUser() flow.
🧹 Nitpick comments (4)
src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java (1)
29-33: Minor inconsistency:/not bypassed in filter but permitted in SecurityConfig.The filter bypasses
/startPagebut not/(root path). Both paths arepermitAll()inSecurityConfig. This works because the filter gracefully handles missing JWTs (lines 63-66), but for consistency and minor performance optimization, consider adding/to the bypass list:- return path.startsWith("/auth") || path.startsWith("/h2-console") || path.startsWith("/startPage"); + return path.startsWith("/auth") || path.startsWith("/h2-console") || path.startsWith("/startPage") || path.equals("/");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java` around lines 29 - 33, The shouldNotFilter method in JwtAuthenticationFilter currently skips "/auth", "/h2-console", and "/startPage" but not the root path; update the shouldNotFilter(HttpServletRequest request) implementation to also bypass "/" by including path.startsWith("/") (or an explicit equality check for "/") in the return condition so the filter consistently aligns with SecurityConfig permitAll() and avoids unnecessary processing for the root path.src/main/jte/create.jte (1)
6-22: Button text doesn't match the new preview action.The form now submits to
/tickets/previewTicket, but the button still says "Create". Consider updating the button text to reflect the preview step:<button type="submit" class="bg-[`#1a2b49`] text-white px-4 py-2 rounded"> - Create + Preview </button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/jte/create.jte` around lines 6 - 22, The submit button text is inconsistent with the form action (/tickets/previewTicket); update the button label in the template (the <button> element inside the form that currently displays "Create") to something that indicates a preview step (e.g., "Preview" or "Preview Ticket") so the UI matches the new preview action.src/main/jte/login.jte (1)
9-15: Inconsistent HTML structure for form fields.The username field's
<label>and<input>are outside any wrapper<div>, while the password field is wrapped. Consider consistent structure:<div> <label for="username">Username</label> <input id="username" name="username" /> </div> <div> <label for="password">Password</label> <input id="password" type="password" name="password" /> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/jte/login.jte` around lines 9 - 15, Wrap the username label and input in the same wrapper structure used for the password field so markup is consistent: move the <label for="username"> and <input id="username" name="username" /> into a containing <div> matching the password block (keeping <label for="password">, <input id="password" type="password" name="password" /> as-is), ensuring both fields use the same wrapper and preserve the id/name attributes for accessibility.src/main/java/org/example/alfs/config/SecurityConfig.java (1)
46-51:/submit-authenticatedis accessible without authentication due to/tickets/**wildcard.The
/tickets/**permitAll (line 51) allows unauthenticated access to/tickets/submit-authenticated. While the service layer will return 401 when it can't find the authenticated user, this is semantically incorrect—the endpoint name implies authentication is required.When tightening security for production, consider:
- Removing the broad
/tickets/**permitAll- Explicitly listing only the endpoints that should be public
.requestMatchers("/tickets/create").permitAll() .requestMatchers("/tickets/previewTicket").permitAll() .requestMatchers("/tickets/submit-anonymous").permitAll() .requestMatchers("/tickets/token/**").permitAll() // /tickets/submit-authenticated should NOT be permitAll🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/alfs/config/SecurityConfig.java` around lines 46 - 51, In SecurityConfig, remove the broad .requestMatchers("/tickets/**").permitAll() that currently makes /tickets/submit-authenticated publicly accessible; instead explicitly permit only the intended public ticket endpoints (e.g., "/tickets/create", "/tickets/previewTicket", "/tickets/submit-anonymous", "/tickets/token/**") via individual .requestMatchers(...).permitAll() entries so that /tickets/submit-authenticated remains protected and requires authentication.
🤖 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/TicketController.java`:
- Line 64: The redirect in TicketController exposes reporter tokens by building
"redirect:/tickets/token/" + ticket.getToken(); remove the token from the URL
and instead convey it via a non-URL channel: store ticket.getToken() in the
user's HttpSession or a flash attribute (e.g.
session.setAttribute("reporterToken", token) or
RedirectAttributes.addFlashAttribute("reporterToken", token)) and change the
redirect target to a non-token path such as "redirect:/tickets/" +
ticket.getId() (or another safe view) and adjust the ticket view handlers to
read the token from session/flash for any reporter-specific logic, ensuring
tokens are never placed in logs/paths.
- Around line 68-76: The submitAuthenticated method in TicketController lacks
the `@PreAuthorize` check, allowing anonymous requests to reach
ticketService.createNewTicket and trigger a late 401; add
`@PreAuthorize`("isAuthenticated()") above the submitAuthenticated method (and
ensure the PreAuthorize import is present and method-level security is enabled
in the app) so authentication is enforced at the controller boundary consistent
with other methods.
- Around line 51-56: The previewTicket method (and the other submit endpoints
that accept TicketCreateDTO) currently take the DTO without validation; update
each such controller method to annotate the TicketCreateDTO parameter with
`@Valid` and add a BindingResult parameter immediately after it (e.g.,
previewTicket(`@Valid` `@ModelAttribute`("ticket") TicketCreateDTO dto,
BindingResult bindingResult, Model model)), then check bindingResult.hasErrors()
and, if true, return the original form view (the same view used by your /create
flow) with the model so validation errors are shown instead of proceeding to the
service layer.
In `@src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java`:
- Line 20: getTicketByToken in TicketService currently maps the Ticket to a
TicketViewDTO via ticketMapper.entityToViewDTO(), which doesn't set the new
token field (defined in TicketViewDTO and populated in createAnonymousTicket),
so update getTicketByToken to explicitly set the token on the returned DTO
(e.g., call view.setToken(ticket.getReporterToken()) after mapping) so anonymous
callers retrieving by token receive a DTO with token populated.
In `@src/main/jte/layout.jte`:
- Line 34: The "Home" anchor in layout.jte currently points to a non-existent
route "/home"; update the href on the anchor whose text is "Home" (the <a ...>
element with class "hover:text-[`#b39359`]") to point to "/" instead so it routes
to StartPageController's root endpoint; ensure only the href value changes and
keep the existing class and link text intact.
In `@src/main/jte/login.jte`:
- Around line 26-34: The form currently submits the sensitive token via GET
(form method="get" action="/tickets/token"), exposing it in URLs; change the
form to use method="post" and update the server-side handler mapping from
`@GetMapping`("/tickets/token") to `@PostMapping`("/tickets/token") (or add a
separate `@PostMapping` handler) so the token is sent in the request body; also
ensure the controller reads the token from the request body/parameter
appropriately and that any CSRF protection/middleware is handled for POST
endpoints.
In `@src/main/jte/previewTicket.jte`:
- Around line 25-32: The "Submit as logged-in user" form (action
"/tickets/submit-authenticated") is rendered for all visitors; change the
template in previewTicket.jte to only render that <form> block when the
request/session indicates an authenticated user (e.g., check the template's
currentUser or session.isAuthenticated() variable), and for anonymous users
replace it with a sign-in CTA (a link/button that directs to your sign-in route)
so anonymous sessions don't see the authenticated-submit path.
In `@src/main/jte/startPage.jte`:
- Around line 19-20: Replace the absolute claim "This system is super secure" in
the startPage.jte copy with a tempered, accurate statement; edit the <h3> or the
adjacent paragraph text that currently reads "This system is super secure" to
something like "Designed with multiple security measures" or "Built with strong
security practices" (or other approved legal-safe phrasing) so the UI no longer
makes categorical security guarantees.
---
Outside diff comments:
In `@src/main/java/org/example/alfs/services/TicketService.java`:
- Around line 41-54: The createNewTicket method in TicketService fails to set
the required reporterToken (nullable = false) before saving; update
createNewTicket to generate and set a reporterToken (e.g.,
UUID.randomUUID().toString()) on the Ticket instance—similar to
createAnonymousTicket—and ensure ticket.setReporterToken(...) is called prior to
ticketRepository.save(ticket), using the provided TicketCreateDTO and
requireCurrentUser() flow.
---
Nitpick comments:
In `@src/main/java/org/example/alfs/config/SecurityConfig.java`:
- Around line 46-51: In SecurityConfig, remove the broad
.requestMatchers("/tickets/**").permitAll() that currently makes
/tickets/submit-authenticated publicly accessible; instead explicitly permit
only the intended public ticket endpoints (e.g., "/tickets/create",
"/tickets/previewTicket", "/tickets/submit-anonymous", "/tickets/token/**") via
individual .requestMatchers(...).permitAll() entries so that
/tickets/submit-authenticated remains protected and requires authentication.
In `@src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java`:
- Around line 29-33: The shouldNotFilter method in JwtAuthenticationFilter
currently skips "/auth", "/h2-console", and "/startPage" but not the root path;
update the shouldNotFilter(HttpServletRequest request) implementation to also
bypass "/" by including path.startsWith("/") (or an explicit equality check for
"/") in the return condition so the filter consistently aligns with
SecurityConfig permitAll() and avoids unnecessary processing for the root path.
In `@src/main/jte/create.jte`:
- Around line 6-22: The submit button text is inconsistent with the form action
(/tickets/previewTicket); update the button label in the template (the <button>
element inside the form that currently displays "Create") to something that
indicates a preview step (e.g., "Preview" or "Preview Ticket") so the UI matches
the new preview action.
In `@src/main/jte/login.jte`:
- Around line 9-15: Wrap the username label and input in the same wrapper
structure used for the password field so markup is consistent: move the <label
for="username"> and <input id="username" name="username" /> into a containing
<div> matching the password block (keeping <label for="password">, <input
id="password" type="password" name="password" /> as-is), ensuring both fields
use the same wrapper and preserve the id/name attributes for accessibility.
🪄 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: aeb12666-6897-4cca-ac67-4d798c054584
📒 Files selected for processing (12)
src/main/java/org/example/alfs/config/SecurityConfig.javasrc/main/java/org/example/alfs/controllers/StartPageController.javasrc/main/java/org/example/alfs/controllers/TicketController.javasrc/main/java/org/example/alfs/dto/ticket/TicketViewDTO.javasrc/main/java/org/example/alfs/entities/Ticket.javasrc/main/java/org/example/alfs/security/JwtAuthenticationFilter.javasrc/main/java/org/example/alfs/services/TicketService.javasrc/main/jte/create.jtesrc/main/jte/layout.jtesrc/main/jte/login.jtesrc/main/jte/previewTicket.jtesrc/main/jte/startPage.jte
💤 Files with no reviewable changes (1)
- src/main/java/org/example/alfs/entities/Ticket.java
| @PostMapping("/previewTicket") | ||
| public String previewTicket(@ModelAttribute("ticket") TicketCreateDTO dto, Model model) { | ||
|
|
||
| model.addAttribute("ticket", dto); | ||
| return "previewTicket"; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the TicketController.java file
fd -t f "TicketController.java" --exec cat -n {} \;Repository: ithsjava25/project-backend-alfs
Length of output: 5356
🏁 Script executed:
# Also look for TicketCreateDTO to understand validation annotations
fd -t f "TicketCreateDTO.java" --exec cat -n {} \;Repository: ithsjava25/project-backend-alfs
Length of output: 704
Add server-side validation to the new submit endpoints.
Lines 52, 60, and 69 accept TicketCreateDTO without @Valid annotation or BindingResult handling, allowing invalid payloads (missing title/description) to bypass the validation checks enforced in the existing /create endpoint (line 39) and reach the service layer.
Suggested fix
`@PostMapping`("/previewTicket")
-public String previewTicket(`@ModelAttribute`("ticket") TicketCreateDTO dto, Model model) {
+public String previewTicket(`@ModelAttribute`("ticket") `@Valid` TicketCreateDTO dto,
+ BindingResult bindingResult,
+ Model model) {
+ if (bindingResult.hasErrors()) {
+ return "create";
+ }
model.addAttribute("ticket", dto);
return "previewTicket";
}
`@PostMapping`("/submit-anonymous")
-public String submitAnonymous(`@ModelAttribute` TicketCreateDTO dto) {
+public String submitAnonymous(`@ModelAttribute` `@Valid` TicketCreateDTO dto,
+ BindingResult bindingResult,
+ Model model) {
+ if (bindingResult.hasErrors()) {
+ model.addAttribute("ticket", dto);
+ return "previewTicket";
+ }
TicketViewDTO ticket = ticketService.createAnonymousTicket(dto);
return "redirect:/tickets/token/" + ticket.getToken();
}
`@PostMapping`("/submit-authenticated")
-public String submitAuthenticated(`@ModelAttribute` TicketCreateDTO dto) {
+public String submitAuthenticated(`@ModelAttribute` `@Valid` TicketCreateDTO dto,
+ BindingResult bindingResult,
+ Model model) {
+ if (bindingResult.hasErrors()) {
+ model.addAttribute("ticket", dto);
+ return "previewTicket";
+ }
TicketViewDTO ticket = ticketService.createNewTicket(dto);
return "redirect:/tickets/" + ticket.getId();
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/alfs/controllers/TicketController.java` around
lines 51 - 56, The previewTicket method (and the other submit endpoints that
accept TicketCreateDTO) currently take the DTO without validation; update each
such controller method to annotate the TicketCreateDTO parameter with `@Valid` and
add a BindingResult parameter immediately after it (e.g., previewTicket(`@Valid`
`@ModelAttribute`("ticket") TicketCreateDTO dto, BindingResult bindingResult,
Model model)), then check bindingResult.hasErrors() and, if true, return the
original form view (the same view used by your /create flow) with the model so
validation errors are shown instead of proceeding to the service layer.
|
|
||
| TicketViewDTO ticket = ticketService.createAnonymousTicket(dto); | ||
|
|
||
| return "redirect:/tickets/token/" + ticket.getToken(); |
There was a problem hiding this comment.
Do not expose reporter tokens in URL paths.
Line 64 places the token in the redirect URL, which leaks sensitive access material through logs/history/referrers. This is especially risky while tokens are stored in plain text.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/alfs/controllers/TicketController.java` at line 64,
The redirect in TicketController exposes reporter tokens by building
"redirect:/tickets/token/" + ticket.getToken(); remove the token from the URL
and instead convey it via a non-URL channel: store ticket.getToken() in the
user's HttpSession or a flash attribute (e.g.
session.setAttribute("reporterToken", token) or
RedirectAttributes.addFlashAttribute("reporterToken", token)) and change the
redirect target to a non-token path such as "redirect:/tickets/" +
ticket.getId() (or another safe view) and adjust the ticket view handlers to
read the token from session/flash for any reporter-specific logic, ensuring
tokens are never placed in logs/paths.
| @PostMapping("/submit-authenticated") | ||
| public String submitAuthenticated( | ||
| @ModelAttribute TicketCreateDTO dto | ||
| ) { | ||
|
|
||
| TicketViewDTO ticket = ticketService.createNewTicket(dto); | ||
|
|
||
| return "redirect:/tickets/" + ticket.getId(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "TicketController.java" -type f | head -5Repository: ithsjava25/project-backend-alfs
Length of output: 141
🏁 Script executed:
cat -n src/main/java/org/example/alfs/controllers/TicketController.javaRepository: ithsjava25/project-backend-alfs
Length of output: 5356
🏁 Script executed:
find . -name "*TicketService*.java" -type f | head -5Repository: ithsjava25/project-backend-alfs
Length of output: 135
🏁 Script executed:
cat -n src/main/java/org/example/alfs/services/TicketService.java | head -100Repository: ithsjava25/project-backend-alfs
Length of output: 4049
🏁 Script executed:
rg -n "requireCurrentUser" --type javaRepository: ithsjava25/project-backend-alfs
Length of output: 1120
🏁 Script executed:
rg -n "submit-authenticated" --type javaRepository: ithsjava25/project-backend-alfs
Length of output: 184
🏁 Script executed:
find . -name "*.html" -o -name "*.jsp" | xargs grep -l "submit-authenticated" 2>/dev/nullRepository: ithsjava25/project-backend-alfs
Length of output: 59
🏁 Script executed:
find . -type f \( -name "*.html" -o -name "*.jsp" -o -name "*.ftl" \) | head -20Repository: ithsjava25/project-backend-alfs
Length of output: 57
🏁 Script executed:
rg -n "preview" --type java -i | head -30Repository: ithsjava25/project-backend-alfs
Length of output: 668
🏁 Script executed:
find . -name "*SecurityConfig*.java" -o -name "*Security*.java" | head -10Repository: ithsjava25/project-backend-alfs
Length of output: 195
🏁 Script executed:
cat -n src/main/java/org/example/alfs/config/SecurityConfig.javaRepository: ithsjava25/project-backend-alfs
Length of output: 3517
🏁 Script executed:
rg -A 20 "public.*createNewTicket" src/main/java/org/example/alfs/services/TicketService.javaRepository: ithsjava25/project-backend-alfs
Length of output: 741
🏁 Script executed:
rg -B 5 -A 10 "requireCurrentUser" src/main/java/org/example/alfs/services/TicketService.javaRepository: ithsjava25/project-backend-alfs
Length of output: 4815
🏁 Script executed:
wc -l src/main/java/org/example/alfs/services/TicketService.javaRepository: ithsjava25/project-backend-alfs
Length of output: 137
🏁 Script executed:
cat -n src/main/java/org/example/alfs/services/TicketService.javaRepository: ithsjava25/project-backend-alfs
Length of output: 13799
🏁 Script executed:
find . -name "*AuthService*.java" -o -name "*SecurityUtil*.java" | head -10Repository: ithsjava25/project-backend-alfs
Length of output: 194
🏁 Script executed:
rg -l "requireCurrentUser" --type javaRepository: ithsjava25/project-backend-alfs
Length of output: 133
🏁 Script executed:
rg -B 2 "@PostMapping\|@GetMapping\|@PreAuthorize" src/main/java/org/example/alfs/controllers/TicketController.java -A 3Repository: ithsjava25/project-backend-alfs
Length of output: 57
Add explicit authentication guard at controller boundary.
Line 68 should declare @PreAuthorize("isAuthenticated()") to enforce authentication upfront and align with the pattern used elsewhere in the controller (e.g., lines 90, 102, 122). Currently, anonymous users reach the service layer and fail with a late-stage 401 error instead of being rejected early.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/alfs/controllers/TicketController.java` around
lines 68 - 76, The submitAuthenticated method in TicketController lacks the
`@PreAuthorize` check, allowing anonymous requests to reach
ticketService.createNewTicket and trigger a late 401; add
`@PreAuthorize`("isAuthenticated()") above the submitAuthenticated method (and
ensure the PreAuthorize import is present and method-level security is enabled
in the app) so authentication is enforced at the controller boundary consistent
with other methods.
| <form method="get" action="/tickets/token" class="space-y-4"> | ||
| <h2 class="text-xl font-bold leading-tight">Login with token</h2> | ||
| <input type="text" name="token" | ||
| placeholder="Enter your token" /> | ||
| <button type="submit" | ||
| class="w-full bg-white text-[#1a2b49] py-2 rounded font-semibold hover:bg-gray-100 transition"> | ||
| View Report | ||
| </button> | ||
| </form> |
There was a problem hiding this comment.
Token exposed in URL via GET method.
Using method="get" puts the token in the URL query string (/tickets/token?token=xxx). This exposes the sensitive token in:
- Browser history
- Server access logs
- Referrer headers when navigating away
Consider using POST instead:
- <form method="get" action="/tickets/token" class="space-y-4">
+ <form method="post" action="/tickets/token" class="space-y-4">This would require updating the corresponding controller endpoint from @GetMapping to @PostMapping.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/jte/login.jte` around lines 26 - 34, The form currently submits the
sensitive token via GET (form method="get" action="/tickets/token"), exposing it
in URLs; change the form to use method="post" and update the server-side handler
mapping from `@GetMapping`("/tickets/token") to `@PostMapping`("/tickets/token") (or
add a separate `@PostMapping` handler) so the token is sent in the request body;
also ensure the controller reads the token from the request body/parameter
appropriately and that any CSRF protection/middleware is handled for POST
endpoints.
| <form method="post" action="/tickets/submit-authenticated"> | ||
| <input type="hidden" name="title" value="${ticket.getTitle()}" /> | ||
| <input type="hidden" name="description" value="${ticket.getDescription()}" /> | ||
|
|
||
| <button class="bg-[#1a2b49] text-white px-4 py-2 rounded"> | ||
| Submit as logged-in user | ||
| </button> | ||
| </form> |
There was a problem hiding this comment.
Hide authenticated-submit action for anonymous sessions.
Lines 25–32 render “Submit as logged-in user” for everyone. For anonymous users this leads to a failed path; render this button only when authenticated and show a sign-in CTA otherwise.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/jte/previewTicket.jte` around lines 25 - 32, The "Submit as
logged-in user" form (action "/tickets/submit-authenticated") is rendered for
all visitors; change the template in previewTicket.jte to only render that
<form> block when the request/session indicates an authenticated user (e.g.,
check the template's currentUser or session.isAuthenticated() variable), and for
anonymous users replace it with a sign-in CTA (a link/button that directs to
your sign-in route) so anonymous sessions don't see the authenticated-submit
path.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/resources/static/css/style.css (1)
1-35:⚠️ Potential issue | 🟡 MinorResolve the current Stylelint failures in this block.
Line 1, Line 8, and Line 28 already match the static-analysis errors: import notation, quoted
Roboto, and non-kebab-case keyframe naming. This will keep the stylesheet red in lint until those are aligned.Suggested fix
-@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'); +@import 'https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'; @@ - font-family: "Roboto", sans-serif; + font-family: Roboto, sans-serif; @@ -@keyframes toastProgress { +@keyframes toast-progress { @@ .animate-toast { - animation: toastProgress 3s linear forwards; + animation: toast-progress 3s linear forwards; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/css/style.css` around lines 1 - 35, Replace the `@import` url(...) notation with a plain `@import` "https://fonts.googleapis.com/..." statement, remove the quotes around the Roboto family name in the body font-family declaration (change font-family: "Roboto", sans-serif; to font-family: Roboto, sans-serif;), and rename the `@keyframes` toastProgress to a kebab-case name (e.g., `@keyframes` toast-progress) and update the .animate-toast rule to use animation: toast-progress 3s linear forwards; so the symbols to change are the `@import` line, the body font-family, the `@keyframes` toastProgress declaration, and the .animate-toast animation reference.
♻️ Duplicate comments (1)
src/main/java/org/example/alfs/controllers/TicketController.java (1)
55-57:⚠️ Potential issue | 🔴 CriticalKeep reporter tokens out of query strings and path segments.
The anonymous flow still transports the reporter token via
redirect:/tickets/ticket-created?token=...,GET /tickets/token/{token}, and@RequestParam String tokenon the confirmation page. That leaks a bearer-style secret into browser history, access logs, and referrers, which is especially risky here because the PR explicitly notes plain-text token storage.Suggested direction
- if (ticket.getToken() != null) { - return "redirect:/tickets/ticket-created?token=" + ticket.getToken(); - } + if (ticket.getToken() != null) { + redirectAttributes.addFlashAttribute("reporterToken", ticket.getToken()); + return "redirect:/tickets/ticket-created"; + } @@ - `@GetMapping`("/token/{token}") - public String viewTicketByToken(`@PathVariable` String token, Model model) { + `@PostMapping`("/token") + public String viewTicketByToken(`@RequestParam` String token, Model model) { @@ - public String ticketCreated(`@RequestParam` String token, Model model) { + public String ticketCreated(`@ModelAttribute`("reporterToken") String token, Model model) { model.addAttribute("token", token); return "ticket-created"; }Also applies to: 66-67, 134-137
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/alfs/controllers/TicketController.java` around lines 55 - 57, The controller currently exposes reporter tokens via query params and path segments (see TicketController usage of ticket.getToken(), the redirect "redirect:/tickets/ticket-created?token=...", the GET /tickets/token/{token} endpoint and handlers with `@RequestParam` String token); instead, stop sending tokens in URLs by storing the token server-side (e.g., put ticket.getToken() into the HttpSession or set a short-lived secure, HttpOnly cookie) before redirecting to "redirect:/tickets/ticket-created" (no token param), then have the confirmation handler (formerly reading `@RequestParam` String token or path variable) retrieve and consume the token from the session/cookie and immediately remove it; update the token-consumption endpoints (including the GET /tickets/token/{token} handler) to accept tokens only from session/cookie or via a POST body to avoid URL exposure.
🧹 Nitpick comments (2)
src/main/java/org/example/alfs/controllers/AuthViewController.java (2)
78-85: Extract JWT cookie writing into one helper.Login and logout duplicate the JWT cookie attributes today. If one side later changes
Secure,Domain, orSameSite, logout can stop clearing the same cookie that login created.♻️ Suggested refactor
+ private void writeJwtCookie(HttpServletResponse response, String value, Duration maxAge) { + ResponseCookie cookie = ResponseCookie.from("JWT", value) + .httpOnly(true) + .path("/") + .maxAge(maxAge) + .sameSite("Lax") + .build(); + + response.addHeader("Set-Cookie", cookie.toString()); + } + `@PostMapping`("/login-form") public String loginForm( `@RequestParam` String username, `@RequestParam` String password, HttpServletResponse response, RedirectAttributes redirectAttributes ) { try { User user = authService.login(username, password); String token = jwtService.generateToken(user); - - ResponseCookie cookie = ResponseCookie.from("JWT", token) - .httpOnly(true) - .path("/") - .maxAge(Duration.ofDays(1)) - .sameSite("Lax") - .build(); - - response.addHeader("Set-Cookie", cookie.toString()); + writeJwtCookie(response, token, Duration.ofDays(1)); redirectAttributes.addFlashAttribute("success", "You are signed in!"); return "redirect:/"; @@ `@PostMapping`("/auth/logout") public String logout(HttpServletResponse response, RedirectAttributes redirectAttributes) { - - ResponseCookie cookie = ResponseCookie.from("JWT", "") - .httpOnly(true) - .path("/") - .maxAge(Duration.ZERO) - .sameSite("Lax") - .build(); - - response.addHeader("Set-Cookie", cookie.toString()); + writeJwtCookie(response, "", Duration.ZERO); redirectAttributes.addFlashAttribute("success", "Successfully signed out"); return "redirect:/"; }Also applies to: 105-112
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/alfs/controllers/AuthViewController.java` around lines 78 - 85, Extract the duplicated JWT cookie construction into a single helper in AuthViewController (e.g., a private buildJwtCookie(String token) and a private clearJwtCookie()) and use those from both the login and logout flows; ensure the helper centralizes attributes httpOnly(true), path("/"), sameSite("Lax"), maxAge(Duration.ofDays(1)) and any Secure/Domain settings, and have clearJwtCookie produce a cookie with the same name/attributes but expired (maxAge 0 or equivalent) so logout clears exactly the same cookie that login sets.
78-83: Consider a profile-backedSecureflag for the auth cookie.
HttpOnlyandSameSiteare set, but the JWT cookie lacks theSecureattribute. For production deployments, enableSecurevia a profile-based configuration and keep logout aligned with the same flag. Spring'sResponseCookieAPI fully supports.secure(boolean)since Spring 5.0+.Given that this project is development-only, this hardening can be deferred for local environments.
Also applies to: 105-110
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/alfs/controllers/AuthViewController.java` around lines 78 - 83, The JWT ResponseCookie in AuthViewController is missing the Secure attribute; update the cookie creation (the ResponseCookie.from(...) call in the login flow) to set .secure(...) based on a profile/config flag and apply the same flag when clearing the cookie in the logout flow (the ResponseCookie built around lines 105-110). Add a configurable boolean (e.g., `@Value`("${auth.cookie.secure:false}") or inject Environment and use Environment.acceptsProfiles("prod")) into AuthViewController, defaulting to false for local/dev, then pass that boolean to ResponseCookie.secure(secureFlag) for both the login token cookie and the logout/clear-cookie ResponseCookie so production profiles enable Secure while dev stays compatible.
🤖 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/config/GlobalModelAttributes.java`:
- Around line 22-27: In GlobalModelAttributes, narrow the broad catch by
replacing the catch(Exception) around securityUtils.getCurrentUser() with
catch(RuntimeException) and add a log statement that records the exception
(e.g., using the class logger) and a brief contextual message; keep the existing
isLoggedIn/username assignments in the try block and ensure the catch only logs
the exception instead of silently swallowing it so issues from getCurrentUser()
are observable.
In `@src/main/jte/layout.jte`:
- Around line 101-124: The toast progress and dismissal are out of sync because
`#toast-bar`'s CSS uses a 3s animation while the script sets bar.style.transition
= "width 5s linear" and removes the toast after 3s; update the script so the JS
is the single source of truth by changing the transition to "width 3s linear"
(or remove the CSS animation and keep JS at 3s), ensure bar initial
width/starting state matches (e.g., start at 100% and set to "0%"), and keep the
setTimeout removal at 3000 so toast, bar, and setTimeout durations (toast, bar,
setTimeout) are all consistent.
In `@src/main/jte/login.jte`:
- Around line 49-57: The onsubmit handler currently concatenates
this.token.value directly into the route, which allows leading/trailing spaces
or reserved characters to corrupt the URL; update the form submit logic
(onsubmit) to trim the token and URL-encode it before building the path (use
something like encodeURIComponent on the trimmed value) so the route becomes
'/tickets/token/' + encodedToken and avoids invalid-token redirects.
---
Outside diff comments:
In `@src/main/resources/static/css/style.css`:
- Around line 1-35: Replace the `@import` url(...) notation with a plain `@import`
"https://fonts.googleapis.com/..." statement, remove the quotes around the
Roboto family name in the body font-family declaration (change font-family:
"Roboto", sans-serif; to font-family: Roboto, sans-serif;), and rename the
`@keyframes` toastProgress to a kebab-case name (e.g., `@keyframes` toast-progress)
and update the .animate-toast rule to use animation: toast-progress 3s linear
forwards; so the symbols to change are the `@import` line, the body font-family,
the `@keyframes` toastProgress declaration, and the .animate-toast animation
reference.
---
Duplicate comments:
In `@src/main/java/org/example/alfs/controllers/TicketController.java`:
- Around line 55-57: The controller currently exposes reporter tokens via query
params and path segments (see TicketController usage of ticket.getToken(), the
redirect "redirect:/tickets/ticket-created?token=...", the GET
/tickets/token/{token} endpoint and handlers with `@RequestParam` String token);
instead, stop sending tokens in URLs by storing the token server-side (e.g., put
ticket.getToken() into the HttpSession or set a short-lived secure, HttpOnly
cookie) before redirecting to "redirect:/tickets/ticket-created" (no token
param), then have the confirmation handler (formerly reading `@RequestParam`
String token or path variable) retrieve and consume the token from the
session/cookie and immediately remove it; update the token-consumption endpoints
(including the GET /tickets/token/{token} handler) to accept tokens only from
session/cookie or via a POST body to avoid URL exposure.
---
Nitpick comments:
In `@src/main/java/org/example/alfs/controllers/AuthViewController.java`:
- Around line 78-85: Extract the duplicated JWT cookie construction into a
single helper in AuthViewController (e.g., a private buildJwtCookie(String
token) and a private clearJwtCookie()) and use those from both the login and
logout flows; ensure the helper centralizes attributes httpOnly(true),
path("/"), sameSite("Lax"), maxAge(Duration.ofDays(1)) and any Secure/Domain
settings, and have clearJwtCookie produce a cookie with the same name/attributes
but expired (maxAge 0 or equivalent) so logout clears exactly the same cookie
that login sets.
- Around line 78-83: The JWT ResponseCookie in AuthViewController is missing the
Secure attribute; update the cookie creation (the ResponseCookie.from(...) call
in the login flow) to set .secure(...) based on a profile/config flag and apply
the same flag when clearing the cookie in the logout flow (the ResponseCookie
built around lines 105-110). Add a configurable boolean (e.g.,
`@Value`("${auth.cookie.secure:false}") or inject Environment and use
Environment.acceptsProfiles("prod")) into AuthViewController, defaulting to
false for local/dev, then pass that boolean to ResponseCookie.secure(secureFlag)
for both the login token cookie and the logout/clear-cookie ResponseCookie so
production profiles enable Secure while dev stays compatible.
🪄 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: 2560a6da-831d-4594-ad79-9726c94f3edb
📒 Files selected for processing (18)
src/main/java/org/example/alfs/config/GlobalModelAttributes.javasrc/main/java/org/example/alfs/config/SecurityConfig.javasrc/main/java/org/example/alfs/controllers/AuthViewController.javasrc/main/java/org/example/alfs/controllers/StartPageController.javasrc/main/java/org/example/alfs/controllers/TicketController.javasrc/main/java/org/example/alfs/dto/ticket/TicketViewDTO.javasrc/main/java/org/example/alfs/entities/Ticket.javasrc/main/java/org/example/alfs/services/TicketService.javasrc/main/jte/create.jtesrc/main/jte/layout.jtesrc/main/jte/login.jtesrc/main/jte/my-tickets.jtesrc/main/jte/previewTicket.jtesrc/main/jte/signup.jtesrc/main/jte/startPage.jtesrc/main/jte/ticket-created.jtesrc/main/jte/view.jtesrc/main/resources/static/css/style.css
🚧 Files skipped from review as they are similar to previous changes (8)
- src/main/java/org/example/alfs/config/SecurityConfig.java
- src/main/java/org/example/alfs/entities/Ticket.java
- src/main/java/org/example/alfs/controllers/StartPageController.java
- src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java
- src/main/jte/previewTicket.jte
- src/main/jte/startPage.jte
- src/main/java/org/example/alfs/services/TicketService.java
- src/main/jte/create.jte
- replace exception-based user lookup with getCurrentUserOrNull - prevent NullPointerException in createNewTicket - fix failing tests for unauthenticated and missing users
…` tests and add test for anonymous reporter scenario
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/security/SecurityUtils.java`:
- Around line 31-36: The current getCurrentUserOrNull method swallows all
RuntimeException instances; change it to only catch authentication-related
exceptions (e.g., Spring Security's AuthenticationException and its common
subclasses like BadCredentialsException, InsufficientAuthenticationException,
AuthenticationCredentialsNotFoundException) when calling getCurrentUser(),
returning null for those cases, and rethrow any other unexpected exceptions so
infrastructure/DB errors are not masked; update the catch clause(s) in
getCurrentUserOrNull to reference those authentication exception types and let
other exceptions propagate.
In `@src/test/java/org/example/alfs/services/TicketServiceTest.java`:
- Around line 78-127: The tests for ticketService.createNewTicket need stronger
token assertions: in the authenticated test
(createNewTicket_shouldSetReporterAsUser) assert the returned
TicketViewDTO.getToken() is null and that the saved Ticket has a null
reporterToken (verify ticketRepository.save(...) asserts
ticket.getReporterToken() == null); in the anonymous test
(createNewTicket_shouldSetReporterToken_whenAnonymous) capture the saved Ticket
(use ArgumentCaptor<Ticket> or verify with a lambda) and assert its
getReporterToken() is non-null and equals the returned TicketViewDTO.getToken()
(i.e., savedTicket.getReporterToken().equals(result.getToken())). Ensure
ticketRepository.save and ticketMapper.entityToViewDTO stubs remain so the saved
instance is available for comparison.
🪄 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: 3589af35-175a-4b41-ab97-4002c050cc20
📒 Files selected for processing (3)
src/main/java/org/example/alfs/security/SecurityUtils.javasrc/main/java/org/example/alfs/services/TicketService.javasrc/test/java/org/example/alfs/services/TicketServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/org/example/alfs/services/TicketService.java
…rtions for reporter token and using `ArgumentCaptor` for better validation of saved entity
There was a problem hiding this comment.
♻️ Duplicate comments (2)
src/main/jte/startPage.jte (1)
13-15:⚠️ Potential issue | 🟠 MajorAvoid absolute identity guarantees in user-facing copy.
Line 14 says “We guarantee that your identity is protected…”. That is still a categorical promise and creates legal/compliance risk, especially while the flow still has known security limitations.
Suggested copy change
- We guarantee that your identity is protected in accordance with applicable whistleblowing legislation. + We work to protect your identity and handle reports in accordance with applicable whistleblowing legislation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/jte/startPage.jte` around lines 13 - 15, In startPage.jte update the user-facing sentence that currently reads “We guarantee that your identity is protected in accordance with applicable whistleblowing legislation.” to avoid an absolute legal promise; replace it with qualified wording such as “We take measures to protect your identity in accordance with applicable whistleblowing legislation” or “We aim to protect your identity, subject to applicable law and technical limitations,” so the copy in the template (startPage.jte) expresses intent and mitigations rather than a categorical guarantee.src/main/jte/login.jte (1)
49-51:⚠️ Potential issue | 🟠 MajorReporter token is still exposed through URL navigation.
At Line 50, redirecting to
/tickets/token/{token}places the token in browser history, logs, and potential referrers. Since this token grants ticket access, it should be exchanged via request body (POST) and then redirected to an opaque/non-secret route.Suggested direction
- <form method="get" - onsubmit="event.preventDefault(); window.location='/tickets/token/' + encodeURIComponent(this.token.value.trim())" + <form method="post" action="/tickets/token/resolve" class="space-y-4">- <input type="text" name="token" + <input type="text" name="token" placeholder="Enter your token" class="w-full px-3 py-2 rounded bg-white text-black" />Then resolve the token server-side and redirect to a non-sensitive URL (for example by ticket id or a short-lived server-side session key), instead of carrying the token in the path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/jte/login.jte` around lines 49 - 51, The current form uses GET and client-side redirect (onsubmit -> window.location='/tickets/token/'+encodeURIComponent(this.token.value.trim())) which exposes the reporter token in the URL; change the form to submit via POST (remove the onsubmit JS) to an endpoint that accepts the token in the request body (e.g., POST /tickets/token), then have server-side code resolve the token and issue a redirect to a non-sensitive URL (ticket id or short-lived session key). Update the form element and any handler tied to this token input (the reference this.token.value) so the token is sent in the POST body and never embedded in a path or query string, and implement server logic to map the token to an opaque identifier before redirecting.
🧹 Nitpick comments (1)
src/main/jte/startPage.jte (1)
39-48: Consider clearer CTA labels for intent.“Leave new report” can be made clearer (e.g., “Submit anonymous report”) to reduce ambiguity on the start page.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/jte/startPage.jte` around lines 39 - 48, Update the ambiguous CTA text "Leave new report" for the link with href "/tickets/create" to a clearer label such as "Submit anonymous report" (or another concise phrasing communicating anonymity and action), and ensure the complementary sign-in link text for href "/login" remains clear; modify the anchor text in the startPage.jte template where the <a href="/tickets/create"> contains "Leave new report" to the new wording and verify hover/aria/title text (if present) reflects the change for accessibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/main/jte/login.jte`:
- Around line 49-51: The current form uses GET and client-side redirect
(onsubmit ->
window.location='/tickets/token/'+encodeURIComponent(this.token.value.trim()))
which exposes the reporter token in the URL; change the form to submit via POST
(remove the onsubmit JS) to an endpoint that accepts the token in the request
body (e.g., POST /tickets/token), then have server-side code resolve the token
and issue a redirect to a non-sensitive URL (ticket id or short-lived session
key). Update the form element and any handler tied to this token input (the
reference this.token.value) so the token is sent in the POST body and never
embedded in a path or query string, and implement server logic to map the token
to an opaque identifier before redirecting.
In `@src/main/jte/startPage.jte`:
- Around line 13-15: In startPage.jte update the user-facing sentence that
currently reads “We guarantee that your identity is protected in accordance with
applicable whistleblowing legislation.” to avoid an absolute legal promise;
replace it with qualified wording such as “We take measures to protect your
identity in accordance with applicable whistleblowing legislation” or “We aim to
protect your identity, subject to applicable law and technical limitations,” so
the copy in the template (startPage.jte) expresses intent and mitigations rather
than a categorical guarantee.
---
Nitpick comments:
In `@src/main/jte/startPage.jte`:
- Around line 39-48: Update the ambiguous CTA text "Leave new report" for the
link with href "/tickets/create" to a clearer label such as "Submit anonymous
report" (or another concise phrasing communicating anonymity and action), and
ensure the complementary sign-in link text for href "/login" remains clear;
modify the anchor text in the startPage.jte template where the <a
href="/tickets/create"> contains "Leave new report" to the new wording and
verify hover/aria/title text (if present) reflects the change for accessibility.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 66e220f3-13e2-4043-86dd-eecebec74237
📒 Files selected for processing (6)
src/main/java/org/example/alfs/config/GlobalModelAttributes.javasrc/main/java/org/example/alfs/security/SecurityUtils.javasrc/main/jte/layout.jtesrc/main/jte/login.jtesrc/main/jte/startPage.jtesrc/test/java/org/example/alfs/services/TicketServiceTest.java
✅ Files skipped from review due to trivial changes (1)
- src/main/jte/layout.jte
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/org/example/alfs/security/SecurityUtils.java
- src/main/java/org/example/alfs/config/GlobalModelAttributes.java
- src/test/java/org/example/alfs/services/TicketServiceTest.java
This PR introduces an improved ticket submission flow with support for anonymous reporting using tokens and a preview step before submission.
Anonymous users can submit tickets without logging in
Tokens are currently stored in plain text
Summary by CodeRabbit
New Features
UI/UX Improvements
Bug Fixes