Conversation
WalkthroughThis PR converts a Java template into a Spring Boot Event Management System: adds Spring Boot parent and dependencies, security, JPA entities and repositories, services, REST and web controllers, Flyway migrations, Thymeleaf templates, Docker Compose for MySQL, application configs, and extensive tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as RegistrationRestController
participant Service as RegistrationService
participant EventSvc as EventService
participant Repo as RegistrationRepository
participant DB
Client->>API: POST /api/registrations
API->>Service: create(registration)
Service->>Repo: existsByEventIdAndParticipantId(...)
Repo-->>Service: false
Service->>EventSvc: getEventById(eventId)
EventSvc-->>Service: Event
Service->>Repo: countConfirmedByEventId(eventId)
Repo-->>Service: confirmedCount
alt capacity exceeded
Service-->>API: throw RegistrationFullException
API-->>Client: 400 Bad Request
else capacity available
Service->>Repo: save(registration)
Repo->>DB: INSERT registration
DB-->>Repo: success
Repo-->>Service: Registration
Service-->>API: Registration (201)
API-->>Client: 201 Created
end
sequenceDiagram
participant Client
participant API as EventRestController
participant Service as EventService
participant EventRepo as EventRepository
participant ParticipantRepo as ParticipantRepository
Client->>API: GET /api/events/{id}/allergy-report
API->>Service: getAllergyReportForEvent(id)
Service->>EventRepo: findById(id)
EventRepo-->>Service: Event
loop for each registration
Service->>ParticipantRepo: load participant
ParticipantRepo-->>Service: Participant with allergens
Service->>Service: aggregate participant per allergen
end
Service-->>API: Map<String, List<ParticipantAllergyInfo>>
API-->>Client: 200 OK (JSON)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
✨ 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: 6
🧹 Nitpick comments (28)
src/main/java/exercise8/entity/AllergenSeverity.java (1)
3-7: Remove unused enum or translate comments to English.Verification confirms the enum is unused throughout the codebase—it appears only in its definition. Combined with Swedish comments in an English codebase, consider:
- Remove the enum if it's not needed for this exercise, OR
- Translate comments to English for consistency if the enum will be used:
- INFO, // Informativ, mindre allvarlig - WARNING, // Varning, kan ge obehag - CRITICAL // Kritisk, livshotande + INFO, // Informational, less severe + WARNING, // Warning, may cause discomfort + CRITICAL // Critical, life-threateningsrc/main/java/exercise8/exception/GlobalExceptionHandler.java (1)
16-56: Consider extracting common error response logic.All three exception handlers follow the same pattern for building the error response body. Extracting this logic into a helper method would reduce duplication and make the code more maintainable.
Add a helper method to the class:
private Map<String, Object> buildErrorBody(HttpStatus status, String error, String message, WebRequest request) { Map<String, Object> body = new HashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("status", status.value()); body.put("error", error); body.put("message", message); body.put("path", request.getDescription(false).replace("uri=", "")); return body; }Then simplify each handler:
@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<Map<String, Object>> handleResourceNotFoundException( ResourceNotFoundException ex, WebRequest request) { - - Map<String, Object> body = new HashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("status", HttpStatus.NOT_FOUND.value()); - body.put("error", "Not Found"); - body.put("message", ex.getMessage()); - body.put("path", request.getDescription(false).replace("uri=", "")); - + Map<String, Object> body = buildErrorBody(HttpStatus.NOT_FOUND, "Not Found", ex.getMessage(), request); return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); }Apply similar changes to the other two handlers.
README.md (2)
1-4: Minor formatting: Add space after colons.For improved readability, add a space after the colons in the login information.
Apply this diff:
### LoginInformation -- User:admin -- Lösen:admin123 +- User: admin +- Lösen: admin123 - Som oinloggad så omdirigeras du automatiskt till login.
8-47: Consider adding language identifiers to code blocks.The markdown linter flags that fenced code blocks should have a language specified. While the blocks contain plain text endpoint lists, adding
textas the language identifier would satisfy linters and improve consistency.Example for the first block:
-``` +```text - GET /api/events - Lista alla eventsApply similar changes to the other code blocks at lines 16, 24, 31, and 39.
src/test/resources/application.properties (1)
1-29: Consider consolidating test configuration files.Having both
application.propertiesandapplication-test.propertiesinsrc/test/resourceswith different admin credentials (admin/admin123 vs testadmin/testpass123) creates potential confusion. Tests using@ActiveProfiles("test")will use the credentials fromapplication-test.properties, but developers might expect the credentials from this file.Consider either:
- Using only
application-test.propertiesfor test configuration- Documenting clearly which profile uses which credentials
- Consolidating to a single test configuration file
src/main/resources/templates/events/form.html (1)
305-313: Minor: Inconsistent comment language.Line 305 has a Swedish comment while Line 313 has an English comment. For consistency, consider using the same language throughout the codebase.
src/test/java/exercise8/security/BasicSecurityTest.java (1)
1-47: Consider consolidating duplicate security tests.
BasicSecurityTestandComprehensiveSecurityTesthave overlapping test coverage:
- Login page accessibility
- API authentication scenarios (no auth, valid auth, invalid password)
Since
ComprehensiveSecurityTestcovers all scenarios inBasicSecurityTestplus additional cases, you could remove this file or add a comment explaining that it serves as a simpler example for learning purposes.src/main/java/exercise8/entity/Registration.java (1)
34-37: Consider using@PrePersistor Hibernate's@CreationTimestampfor registration timestamp.Setting
registrationDate = LocalDateTime.now()in the constructor captures the time when the object is instantiated in memory, not when it's persisted to the database. This can lead to inaccurate timestamps if there's a delay between object creation and database commit.Apply this pattern instead:
+import org.hibernate.annotations.CreationTimestamp; + @Entity @Table(name = "registration", uniqueConstraints = @UniqueConstraint(columnNames = {"event_id", "participant_id"})) public class Registration { // ... other fields ... + @CreationTimestamp @Column(name = "registration_date") private LocalDateTime registrationDate; // ... public Registration() { - this.registrationDate = LocalDateTime.now(); this.status = RegistrationStatus.PENDING; }Alternatively, use
@PrePersist:@PrePersist protected void onCreate() { if (registrationDate == null) { registrationDate = LocalDateTime.now(); } }src/main/resources/templates/events/list.html (1)
440-452: Consider simplifying backdrop click handling.The manual bounding rectangle calculation for backdrop clicks works but is verbose. Modern browsers support checking if the click target is the dialog itself:
- // Close on click on backdrop deleteDialog.addEventListener('click', (event) => { - const rect = deleteDialog.getBoundingClientRect(); - const isInDialog = ( - rect.top <= event.clientY && - event.clientY <= rect.top + rect.height && - rect.left <= event.clientX && - event.clientX <= rect.left + rect.width - ); - - if (!isInDialog) { + // Close when clicking on the backdrop (the dialog element itself, not its content) + if (event.target === deleteDialog) { closeDeleteDialog(); } });src/main/resources/templates/events/detail.html (1)
472-485: Consider simplifying backdrop click handling (same as in list.html).The backdrop click logic can be simplified by checking if the click target is the dialog element itself:
- // Close on click on backdrop deleteDialog.addEventListener('click', (event) => { - const rect = deleteDialog.getBoundingClientRect(); - const isInDialog = ( - rect.top <= event.clientY && - event.clientY <= rect.top + rect.height && - rect.left <= event.clientX && - event.clientX <= rect.left + rect.width - ); - - if (!isInDialog) { + if (event.target === deleteDialog) { closeDeleteDialog(); } });src/main/java/exercise8/controller/api/PatrolRestController.java (1)
20-62: Patrol REST controller looks correct; only minor style refinements possibleThe CRUD and district endpoints are consistent with the other REST controllers and correctly delegate to
PatrolService, with proper status codes and@Validusage. If you want to tighten things up later, consider switching@Autowiredfield injection to constructor injection and adding aLocationheader onPOST /api/patrolsto point to the created resource URL, but these are purely optional polish items.src/test/java/exercise8/controller/EventRestControllerSimpleTest.java (1)
22-70: WebMvc tests are solid; consider aligning security config with the main REST testThe three tests exercise happy‑path and unauthenticated access correctly and the mocking of
EventServiceis clean. Given you already haveEventRestControllerTestthat imports yourSecurityConfig, you might consider also importing that here (or clearly documenting this as a lighter smoke test) so both suites run against the same security setup, but that’s optional.src/main/java/exercise8/service/AllergenService.java (1)
19-49: Allergen service logic is correct; consider cleaning up the removed description fieldThe CRUD methods are straightforward and the use of
ResourceNotFoundExceptioninfindById/updateis appropriate. Since theallergentable no longer has adescriptioncolumn, you can safely delete the commentedsetDescriptionline to avoid future confusion about non‑existent fields.src/main/java/exercise8/config/SecurityConfig.java (1)
23-81: Security setup matches requirements; only minor, optional DI/style tweaksThe split between public pages, authenticated web, and /api/** (with HTTP Basic + 401 vs redirect) looks coherent, and the in‑memory admin user wired from
app.admin.*is reasonable for this exercise project (hard‑coded creds are expected here). If you want to tighten things up later, you could switch the@Valuefields to constructor injection for consistency with other beans, but that’s purely stylistic here.src/main/java/exercise8/controller/api/EventRestController.java (1)
56-89: CSV export works; consider basic escaping and narrower error handlingThe CSV generation is fine for controlled data, but if
allergyNameor any participant fields can contain quotes, commas, or newlines, the currentString.formatwill produce malformed CSV. You might later add minimal escaping (e.g. replace"with""before wrapping in quotes).Similarly, catching plain
RuntimeExceptionand returning 404 for everything will also turn internal bugs into “not found”. If you introduce a specific exception for “event not found” (or reuseEntityNotFoundException), you can catch that here and let other exceptions bubble up as 500s.src/main/java/exercise8/repository/RegistrationRepository.java (1)
31-33: Prefer a derived count method over hard‑coded'CONFIRMED'in JPQLInstead of the custom query with
r.status = 'CONFIRMED', you can rely on a derived query:-// Count the number of confirmed registrations for an event -@Query("SELECT COUNT(r) FROM Registration r WHERE r.event.id = :eventId AND r.status = 'CONFIRMED'") -long countConfirmedByEventId(@Param("eventId") Long eventId); +// Count the number of registrations for an event with a given status +long countByEventIdAndStatus(Long eventId, RegistrationStatus status);and call it with
RegistrationStatus.CONFIRMED. That keeps things type‑safe and automatically in sync if the enum or mapping changes.src/main/java/exercise8/entity/Participant.java (1)
10-130: Participant JPA mapping and JSON configuration look consistentField mappings, relationships (patrol, registrations, allergens), and
@JsonIgnorePropertiesusage align well withPatrolandRegistration, avoiding circular serialization while keeping useful navigation. Initializing collections tonew ArrayList<>/new HashSet<>is also a good defensive choice. If you later introduce Bean Validation, you could optionally add annotations like@NotBlank/firstName/lastName/src/main/java/exercise8/service/ParticipantService.java (1)
19-70: ParticipantService CRUD and query logic are straightforward and consistentThe service follows the same clear pattern as
RegistrationService/PatrolService: centralizedfindById/findByEmailwithResourceNotFoundException, simplecreate, and anupdatethat copies all relevant participant fields (including patrol and allergens) before saving.findByPatrolIdandsearchByNameare thin, appropriate delegates to the repository. As an optional hardening step, you could null out any incoming ID increateto guard against accidental updates via POST, but for this project’s use it’s not required.src/main/java/exercise8/entity/Event.java (1)
9-101: Event entity design matches the rest of the domain modelThe
Eventmapping (dates, location, maxParticipants) and the@OneToManyregistrations side are consistent with theRegistrationentity and use@JsonIgnorePropertiesappropriately to prevent circular references. This is a clean, minimal aggregate root for the event domain. If you ever target a database whereeventis a reserved word, you might optionally rename the table, but for this exercise setup it’s acceptable.src/main/java/exercise8/controller/api/ParticipantRestController.java (1)
17-18: Prefer constructor injection over field injection for controllersUsing field injection makes testing and immutability harder and is inconsistent with
EventRestController, which uses constructor injection. Consider switching to constructor injection forParticipantServicehere as well.src/main/java/exercise8/service/PatrolService.java (1)
16-17: Optional: align DI style with constructor-based injection
PatrolServiceuses field injection; elsewhere (EventService) you already use constructor injection, which is generally preferred for immutability and easier testing. Not required for this exercise, but you may consider refactoring to a constructor-injectedPatrolRepositoryfor consistency.src/main/java/exercise8/service/EventService.java (1)
31-36: Unify “event not found” handling (RuntimeException vs ResourceNotFoundException)
EventServicethrows plainRuntimeExceptioningetEventById,getAllergyReportForEvent, andgetAllergyStatisticsForEvent, but usesResourceNotFoundExceptionindeleteEvent. This leads to inconsistent error behavior across callers and forcesEventRestControllerto catchRuntimeExceptionexplicitly.Consider standardizing on
ResourceNotFoundException("Event", "id", id)(or similar) for all “event not found” cases, and then letting your global exception handler map that to a 404. That would also simplifyEventRestController, which could drop thetry/catchblocks and rely on centralized exception handling.Also applies to: 47-51, 66-69, 111-114
src/main/java/exercise8/controller/api/RegistrationRestController.java (1)
17-18: Consider constructor injection forRegistrationServiceFor consistency with
EventRestControllerand better testability/immutability, consider switching from field injection to constructor injection forRegistrationService.src/main/java/exercise8/controller/web/EventController.java (3)
23-78: CRUD flow looks solid; consider explicit handling for missing eventsThe create/update paths with
@Valid+BindingResultand redirect patterns look good and idiomatic for a Thymeleaf-backed form flow. One potential gap is what happens ifeventService.getEventById(id)does not find an event (for view/edit/update):
- If it returns
null, later code will NPE.- If it throws (e.g. custom not-found), the user likely gets a generic error page.
Consider standardizing this into a clearer UX, e.g. having
getEventByIdreturn anOptionaland translating “not found” into a 404 view or a redirect to/eventswith a flash error message. This keeps user feedback predictable and avoids hard-to-understand generic errors.
80-91: Delete handler is clear; you may want more specific exception typingThe delete handler correctly uses
RedirectAttributesto surface success/failure messages and redirects to appropriate views. To make failure cases more self-documenting and avoid accidentally catching unrelatedIllegalStateExceptions, consider replacingIllegalStateExceptionwith a domain-specific exception type fromEventService(e.g.EventDeletionNotAllowedException) and catching that here. This keeps the controller logic explicit about what kind of failure it’s prepared to handle.
94-107: Allergy report endpoint is straightforward; watch out for heavy graphs/N+1The allergy-report endpoint nicely aggregates
event,allergyReport, andstatisticsinto the model. Depending on howeventService.getAllergyReportForEventandgetAllergyStatisticsForEventare implemented, this view could trigger:
- Large object graphs (e.g. deeply nested participants/registrations) being loaded and passed to the template.
- Potential N+1 query patterns if related collections are lazily initialized per row during rendering.
If you notice performance issues as data grows, it may be worth introducing dedicated DTOs or optimized queries for this endpoint so only the data needed for the report is loaded.
src/main/java/exercise8/entity/Patrol.java (1)
27-87: JPA mapping is consistent; consider tightening JSON exposure and equality semanticsThe
@OneToMany(mappedBy = "patrol")plus theParticipantside@ManyToOnemapping look consistent, and initializingparticipantswith an emptyArrayListis a good default.A couple of optional improvements:
- The combination of
@JsonIgnoreProperties({"patrol", "registrations"})onparticipantshere and@JsonIgnoreProperties({"participants"})onParticipant.patrolwill prevent the most obvious recursion, butparticipantscan still serialize a fairly large graph (e.g. via other relationships like allergens). If your REST APIs don’t need full participant details when fetching a patrol, consider trimming this further (DTOs, projections, or ignoring the collection entirely) to avoid heavy payloads.- If
Patrolinstances are ever kept in Sets/Maps outside a pure JPA context, consider addingequals/hashCodebased on the identifier (or a natural key) to avoid subtle collection behaviour issues.Both are non-blocking but can improve robustness as the domain grows.
pom.xml (1)
86-127: Test dependency stack is comprehensive; a few entries may be redundantThe test setup (Boot test starter, security test, H2, Testcontainers, MySQL module) is robust and well-aligned with integration testing against a real database.
Minor clean-up you might consider:
spring-boot-starter-testalready pulls in JUnit Jupiter; the explicitorg.testcontainers:junit-jupiteris needed for Testcontainers integration, but an explicit genericjunit-jupiterdependency (if you add one elsewhere) is usually unnecessary.- Spring Boot’s dependency management will typically handle versions for
spring-boot-testcontainersand the coretestcontainersmodules, so you don’t need to specify versions manually (which you are correctly not doing).These are non-blocking, just opportunities to trim the POM a bit if you feel like tidying.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (53)
.gitignore(1 hunks)README.md(1 hunks)docker-compose.yml(1 hunks)pom.xml(1 hunks)src/main/java/exercise8/Exercise8.java(1 hunks)src/main/java/exercise8/config/SecurityConfig.java(1 hunks)src/main/java/exercise8/controller/api/AllergenRestController.java(1 hunks)src/main/java/exercise8/controller/api/EventRestController.java(1 hunks)src/main/java/exercise8/controller/api/ParticipantRestController.java(1 hunks)src/main/java/exercise8/controller/api/PatrolRestController.java(1 hunks)src/main/java/exercise8/controller/api/RegistrationRestController.java(1 hunks)src/main/java/exercise8/controller/web/EventController.java(1 hunks)src/main/java/exercise8/controller/web/HomeController.java(1 hunks)src/main/java/exercise8/entity/Allergen.java(1 hunks)src/main/java/exercise8/entity/AllergenSeverity.java(1 hunks)src/main/java/exercise8/entity/Event.java(1 hunks)src/main/java/exercise8/entity/Participant.java(1 hunks)src/main/java/exercise8/entity/Patrol.java(1 hunks)src/main/java/exercise8/entity/Registration.java(1 hunks)src/main/java/exercise8/entity/RegistrationStatus.java(1 hunks)src/main/java/exercise8/exception/GlobalExceptionHandler.java(1 hunks)src/main/java/exercise8/exception/RegistrationFullException.java(1 hunks)src/main/java/exercise8/exception/ResourceNotFoundException.java(1 hunks)src/main/java/exercise8/repository/AllergenRepository.java(1 hunks)src/main/java/exercise8/repository/EventRepository.java(1 hunks)src/main/java/exercise8/repository/ParticipantRepository.java(1 hunks)src/main/java/exercise8/repository/PatrolRepository.java(1 hunks)src/main/java/exercise8/repository/RegistrationRepository.java(1 hunks)src/main/java/exercise8/service/AllergenService.java(1 hunks)src/main/java/exercise8/service/EventService.java(1 hunks)src/main/java/exercise8/service/ParticipantService.java(1 hunks)src/main/java/exercise8/service/PatrolService.java(1 hunks)src/main/java/exercise8/service/RegistrationService.java(1 hunks)src/main/java/org/example/App.java(0 hunks)src/main/java/org/example/Exercise2025Application.java(1 hunks)src/main/resources/application.properties(1 hunks)src/main/resources/db/migration/V1__create_base_tables.sql(1 hunks)src/main/resources/db/migration/V2__insert_sample_data.sql(1 hunks)src/main/resources/templates/events/allergy-report.html(1 hunks)src/main/resources/templates/events/detail.html(1 hunks)src/main/resources/templates/events/form.html(1 hunks)src/main/resources/templates/events/list.html(1 hunks)src/main/resources/templates/login.html(1 hunks)src/test/java/exercise8/controller/EventRestControllerSimpleTest.java(1 hunks)src/test/java/exercise8/controller/api/AllergenRestControllerTest.java(1 hunks)src/test/java/exercise8/controller/api/EventRestControllerTest.java(1 hunks)src/test/java/exercise8/controller/api/ParticipantRestControllerTest.java(1 hunks)src/test/java/exercise8/security/BasicSecurityTest.java(1 hunks)src/test/java/exercise8/security/ComprehensiveSecurityTest.java(1 hunks)src/test/java/org/example/AppIT.java(0 hunks)src/test/java/org/example/AppTest.java(0 hunks)src/test/resources/application-test.properties(1 hunks)src/test/resources/application.properties(1 hunks)
💤 Files with no reviewable changes (3)
- src/main/java/org/example/App.java
- src/test/java/org/example/AppTest.java
- src/test/java/org/example/AppIT.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jennymakki
Repo: fungover/exercise2025 PR: 204
File: src/main/java/org/example/config/SecurityConfig.java:19-24
Timestamp: 2025-11-16T13:56:11.263Z
Learning: The repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials and other shortcuts for ease of testing are acceptable and intentional.
📚 Learning: 2025-11-16T13:56:11.263Z
Learnt from: jennymakki
Repo: fungover/exercise2025 PR: 204
File: src/main/java/org/example/config/SecurityConfig.java:19-24
Timestamp: 2025-11-16T13:56:11.263Z
Learning: The repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials and other shortcuts for ease of testing are acceptable and intentional.
Applied to files:
README.mdsrc/main/java/org/example/Exercise2025Application.java
📚 Learning: 2025-09-19T11:36:59.329Z
Learnt from: LolloGro
Repo: fungover/exercise2025 PR: 106
File: src/main/java/exercise4/repository/InMemoryProductRepository.java:0-0
Timestamp: 2025-09-19T11:36:59.329Z
Learning: In the InMemoryProductRepository class in src/main/java/exercise4/repository/InMemoryProductRepository.java, the updateProduct method implements a versioning system where multiple products with the same ID can exist, but identical product objects are rejected using contains() check. This allows maintaining product history while preventing duplicate identical entries.
Applied to files:
src/main/java/exercise8/repository/ParticipantRepository.javasrc/main/java/exercise8/repository/PatrolRepository.java
🧬 Code graph analysis (24)
src/main/java/exercise8/entity/Participant.java (3)
src/main/java/exercise8/entity/Allergen.java (1)
Entity(6-42)src/main/java/exercise8/entity/Patrol.java (1)
Entity(8-88)src/main/java/exercise8/entity/Registration.java (1)
Entity(6-93)
src/main/java/exercise8/service/AllergenService.java (2)
src/main/java/exercise8/service/EventService.java (1)
Service(14-161)src/main/java/exercise8/service/ParticipantService.java (1)
Service(12-71)
src/main/java/exercise8/service/ParticipantService.java (2)
src/main/java/exercise8/service/PatrolService.java (1)
Service(12-57)src/main/java/exercise8/service/RegistrationService.java (1)
Service(14-106)
src/main/java/exercise8/exception/ResourceNotFoundException.java (1)
src/main/java/exercise8/exception/RegistrationFullException.java (1)
ResponseStatus(6-16)
src/main/java/exercise8/controller/api/ParticipantRestController.java (3)
src/main/java/exercise8/controller/api/AllergenRestController.java (1)
RestController(14-57)src/main/java/exercise8/controller/api/PatrolRestController.java (1)
RestController(13-63)src/main/java/exercise8/controller/api/RegistrationRestController.java (1)
RestController(13-86)
src/test/java/exercise8/controller/api/ParticipantRestControllerTest.java (2)
src/test/java/exercise8/controller/EventRestControllerSimpleTest.java (1)
WebMvcTest(22-71)src/test/java/exercise8/controller/api/AllergenRestControllerTest.java (1)
WebMvcTest(29-280)
src/main/java/exercise8/entity/Event.java (2)
src/main/java/exercise8/entity/Participant.java (1)
Entity(10-131)src/main/java/exercise8/entity/Registration.java (1)
Entity(6-93)
src/main/java/exercise8/entity/Registration.java (2)
src/main/java/exercise8/entity/Event.java (1)
Entity(9-102)src/main/java/exercise8/entity/Participant.java (1)
Entity(10-131)
src/test/java/exercise8/security/BasicSecurityTest.java (1)
src/test/java/exercise8/security/ComprehensiveSecurityTest.java (1)
SpringBootTest(18-142)
src/main/java/exercise8/entity/Allergen.java (1)
src/main/java/exercise8/entity/Participant.java (1)
Entity(10-131)
src/test/java/exercise8/controller/api/AllergenRestControllerTest.java (2)
src/test/java/exercise8/controller/api/EventRestControllerTest.java (1)
WebMvcTest(30-201)src/test/java/exercise8/controller/api/ParticipantRestControllerTest.java (1)
WebMvcTest(29-328)
src/main/java/exercise8/controller/api/PatrolRestController.java (2)
src/main/java/exercise8/controller/api/AllergenRestController.java (1)
RestController(14-57)src/main/java/exercise8/controller/api/ParticipantRestController.java (1)
RestController(13-66)
src/main/java/exercise8/service/PatrolService.java (2)
src/main/java/exercise8/service/AllergenService.java (1)
Service(12-50)src/main/java/exercise8/service/ParticipantService.java (1)
Service(12-71)
src/main/java/exercise8/service/RegistrationService.java (2)
src/main/java/exercise8/service/EventService.java (1)
Service(14-161)src/main/java/exercise8/service/ParticipantService.java (1)
Service(12-71)
src/main/java/exercise8/controller/web/HomeController.java (1)
src/main/java/exercise8/controller/web/EventController.java (1)
Controller(12-108)
src/main/java/exercise8/controller/web/EventController.java (1)
src/main/java/exercise8/controller/web/HomeController.java (1)
Controller(6-23)
src/main/java/exercise8/entity/Patrol.java (1)
src/main/java/exercise8/entity/Participant.java (1)
Entity(10-131)
src/main/java/exercise8/controller/api/RegistrationRestController.java (2)
src/main/java/exercise8/controller/api/EventRestController.java (1)
RestController(15-106)src/main/java/exercise8/controller/api/ParticipantRestController.java (1)
RestController(13-66)
src/main/java/exercise8/service/EventService.java (2)
src/main/java/exercise8/service/ParticipantService.java (1)
Service(12-71)src/main/java/exercise8/service/RegistrationService.java (1)
Service(14-106)
src/test/java/exercise8/controller/api/EventRestControllerTest.java (2)
src/test/java/exercise8/controller/EventRestControllerSimpleTest.java (1)
WebMvcTest(22-71)src/test/java/exercise8/controller/api/AllergenRestControllerTest.java (1)
WebMvcTest(29-280)
src/test/java/exercise8/controller/EventRestControllerSimpleTest.java (1)
src/test/java/exercise8/controller/api/EventRestControllerTest.java (1)
WebMvcTest(30-201)
src/test/java/exercise8/security/ComprehensiveSecurityTest.java (1)
src/test/java/exercise8/security/BasicSecurityTest.java (1)
SpringBootTest(14-47)
src/main/java/exercise8/exception/RegistrationFullException.java (1)
src/main/java/exercise8/exception/ResourceNotFoundException.java (1)
ResponseStatus(6-16)
src/main/java/exercise8/controller/api/AllergenRestController.java (4)
src/main/java/exercise8/controller/api/EventRestController.java (1)
RestController(15-106)src/main/java/exercise8/controller/api/ParticipantRestController.java (1)
RestController(13-66)src/main/java/exercise8/controller/api/PatrolRestController.java (1)
RestController(13-63)src/main/java/exercise8/controller/api/RegistrationRestController.java (1)
RestController(13-86)
🪛 markdownlint-cli2 (0.18.1)
README.md
8-8: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
16-16: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
24-24: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
31-31: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
39-39: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (22)
.gitignore (1)
1-32: LGTM!The expanded .gitignore appropriately covers Maven build artifacts, IDE files, OS-specific files, and logs. This is a comprehensive and well-structured ignore configuration for a Spring Boot project.
src/main/java/exercise8/exception/RegistrationFullException.java (1)
6-16: LGTM!The exception class follows the same pattern as
ResourceNotFoundExceptionand provides both a generic and a formatted constructor for flexibility. The@ResponseStatus(HttpStatus.BAD_REQUEST)annotation appropriately maps this business logic exception to HTTP 400.src/main/java/exercise8/controller/web/HomeController.java (1)
6-23: LGTM!The controller provides clean navigation with appropriate redirects to the events listing and a dedicated login endpoint. The implementation aligns well with the security configuration and event controller.
src/main/java/exercise8/entity/RegistrationStatus.java (1)
3-8: LGTM!The enum provides a clear set of registration lifecycle states. The status values (PENDING, CONFIRMED, CANCELLED, WAITLIST) appropriately model the domain requirements for camp registration management.
docker-compose.yml (1)
3-3: No issues found—MySQL 9.5.0 is a valid and available release.MySQL 9.5.0 exists as MySQL Community Server 9.5.0 (Innovation) with release notes dated October 2025. The version specified in the docker-compose.yml file is current and valid for use.
Likely an incorrect or invalid review comment.
src/main/java/exercise8/Exercise8.java (1)
1-12: LGTM! Standard Spring Boot entry point.The implementation follows the canonical Spring Boot application structure with proper annotations and main method.
src/main/java/exercise8/exception/ResourceNotFoundException.java (1)
1-17: LGTM! Well-structured custom exception.The exception properly uses
@ResponseStatusfor automatic HTTP 404 responses and provides flexible constructors for different error scenarios.src/test/java/exercise8/controller/api/EventRestControllerTest.java (1)
1-201: LGTM! Comprehensive test coverage.The test suite effectively covers:
- Authentication scenarios (unauthorized, mock user, basic auth, wrong password)
- CRUD operations on events
- Allergy report endpoints (JSON and CSV)
- Error handling (404 cases)
The test structure follows Spring Boot testing best practices with proper mocking and security configuration.
Based on learnings
src/main/resources/templates/login.html (1)
1-162: LGTM! Well-designed login template.The template provides a clean, user-friendly login interface with:
- Proper Thymeleaf integration for Spring Security
- Clear error and success messaging
- Responsive layout with modern styling
- Good form usability (autofocus, required fields)
src/main/resources/templates/events/form.html (1)
266-338: Solid client-side date validation.The JavaScript validation effectively:
- Prevents startDate > endDate submissions
- Dynamically updates min/max constraints as dates change
- Provides clear visual feedback with error messages
- Disables submit button when dates are invalid
src/main/resources/application.properties (1)
1-31: Good configuration structure with externalized credentials.The configuration properly demonstrates environment variable usage for sensitive credentials while providing sensible defaults for local development. The combination of Flyway for schema management and
validatemode forddl-autoensures schema consistency.Based on learnings: This is an exercise/learning project where simplified local development settings (like empty database password) are acceptable.
src/test/java/exercise8/security/ComprehensiveSecurityTest.java (1)
1-142: Excellent comprehensive security test coverage.This test class thoroughly validates the security configuration with well-structured tests covering:
- Public vs. protected endpoint access patterns
- Form-based login for web endpoints
- HTTP Basic authentication for API endpoints
- CSRF protection enforcement
The separation between web (redirect to login) and API (401 Unauthorized) authentication strategies is properly tested.
src/main/java/exercise8/repository/AllergenRepository.java (1)
1-14: LGTM!Clean Spring Data JPA repository following best practices with
Optional<Allergen>return type for null safety.src/main/resources/templates/events/allergy-report.html (1)
225-237: Simple and effective export implementation.The JavaScript export functions appropriately use
window.location.hrefto trigger file downloads from the API endpoints. TheeventIdis safely inlined from the server-side template.src/main/resources/db/migration/V2__insert_sample_data.sql (1)
1-72: Sample data seeding matches the schema and relationshipsThe insert order respects foreign keys and the referenced IDs (patrols 1–4, events 1–3, participants 1–8) line up correctly with the base tables from V1. For an exercise project this looks like a good, realistic dataset to drive the UI and reports.
src/main/java/exercise8/repository/PatrolRepository.java (1)
10-20: PatrolRepository query methods are idiomatic and consistentExtending
JpaRepositoryplus the three derived queries (findByName,findByDistrict,findByNameContainingIgnoreCase) are all correctly named for Spring Data and match the domain needs (exact lookup, district filter, and case‑insensitive search).src/main/resources/db/migration/V1__create_base_tables.sql (1)
1-64: Base table definitions are coherent and match the domain modelThe schema for patrols, allergens, events, participants, registrations, and the
participant_allergenjoin table is internally consistent and lines up with the relationships used in your JPA entities and services (including the unique(event_id, participant_id)registration constraint). This is a solid foundation for the rest of the application.src/main/java/exercise8/repository/ParticipantRepository.java (1)
15-30: ParticipantRepository custom queries are well-formed and align with usage
findByEmail/findByPatrolIduse standard derived queries, and the JPQL forfindByAllergenIdandsearchByNamecorrectly referencep.allergens, use a bound:allergenId, and perform case‑insensitive partial matching on first/last name. This should support the service and filtering features without issues.src/test/java/exercise8/controller/api/ParticipantRestControllerTest.java (1)
62-327: Comprehensive participant REST + security tests look solidThe test suite cleanly covers unauthorized vs authorized access, happy paths, and 404 scenarios for all participant endpoints, including patrol-based lookup, with correct Mockito stubbing and verification. This is nicely aligned with the Allergen/Event controller tests and should give good confidence in the web + security layer.
src/test/java/exercise8/controller/api/AllergenRestControllerTest.java (1)
58-279: Allergen REST controller tests provide thorough coverageThe tests exercise all CRUD endpoints under both unauthenticated and authenticated scenarios, including 404 paths, and verify service interaction where it matters. The structure and naming are consistent with the other controller tests, which keeps the suite easy to follow and maintain in this exercise project.
src/main/java/exercise8/service/EventService.java (1)
63-106: Allergy report/statistics aggregation looks correct and efficientThe allergy report (
getAllergyReportForEvent) and statistics (getAllergyStatisticsForEvent) methods:
- Iterate registrations and participants’ allergens once.
- Group by allergen name into a
LinkedHashMap→TreeMapto get deterministic, sorted keys.- Sort each participant list by last name then first name.
- Use
TreeMap+mergefor the statistics view.This is a clear and efficient implementation for the intended use case.
Also applies to: 109-124
pom.xml (1)
8-23: Java 25 and Spring Boot 3.5.6 compatibility is officially supported—no changes needed.Verification confirms that Spring Boot 3.5.6 (released September 18, 2025) explicitly documents Java 25 support, and the combination is fully compatible. The minimum Java requirement for Spring Boot 3.5.x is Java 17, and Java 25 is officially tested and supported.
Your configuration is correct as written.
| // Check if the event is available | ||
| if (event.getMaxParticipants() != null) { | ||
| long confirmedCount = registrationRepository.countConfirmedByEventId(event.getId()); | ||
| if (confirmedCount >= event.getMaxParticipants()) { | ||
| throw new RegistrationFullException(event.getName(), event.getMaxParticipants()); | ||
| } | ||
| } | ||
|
|
||
| registration.setRegistrationDate(LocalDateTime.now()); |
There was a problem hiding this comment.
Event capacity can be bypassed via confirm/update without checks
Capacity is enforced only in create using countConfirmedByEventId, but:
confirm(Long id)can freely change status toCONFIRMEDwithout re-checkingmaxParticipants.update(Long id, Registration registrationDetails)can also set status toCONFIRMED(or other states) without any capacity logic.
This allows exceeding maxParticipants, e.g.:
- Create registrations while capacity is available (or even just below it).
- Later confirm multiple PENDING registrations, pushing
CONFIRMEDabove the limit.
Consider centralizing the capacity check and calling it from both create and any path that transitions to CONFIRMED:
+ private void assertCapacityAvailable(Event event, RegistrationStatus targetStatus) {
+ if (event.getMaxParticipants() != null && targetStatus == RegistrationStatus.CONFIRMED) {
+ long confirmedCount = registrationRepository.countConfirmedByEventId(event.getId());
+ if (confirmedCount >= event.getMaxParticipants()) {
+ throw new RegistrationFullException(event.getName(), event.getMaxParticipants());
+ }
+ }
+ }
@@
public Registration create(Registration registration) {
Event event = registration.getEvent();
@@
- // Check if the event is available
- if (event.getMaxParticipants() != null) {
- long confirmedCount = registrationRepository.countConfirmedByEventId(event.getId());
- if (confirmedCount >= event.getMaxParticipants()) {
- throw new RegistrationFullException(event.getName(), event.getMaxParticipants());
- }
- }
+ // Check if the event can accept another (potentially confirmed) registration
+ assertCapacityAvailable(event, RegistrationStatus.PENDING);
@@
public Registration update(Long id, Registration registrationDetails) {
Registration registration = findById(id);
- registration.setStatus(registrationDetails.getStatus());
+ var newStatus = registrationDetails.getStatus();
+ assertCapacityAvailable(registration.getEvent(), newStatus);
+ registration.setStatus(newStatus);
@@
public Registration confirm(Long id) {
Registration registration = findById(id);
+ assertCapacityAvailable(registration.getEvent(), RegistrationStatus.CONFIRMED);
registration.setStatus(RegistrationStatus.CONFIRMED);You can adapt this sketch to your exact status model, but some form of capacity check on status transitions to CONFIRMED is important to keep the max-participant rule reliable.
Also applies to: 62-70, 72-77
| void main(String[] args) { | ||
| SpringApplication.run(Exercise2025Application.class, args); | ||
| } |
There was a problem hiding this comment.
Critical: Main method must be public and static.
The main method signature is incorrect. Java application entry points must be public static void main(String[] args). The current method is package-private and non-static, which will prevent the application from starting.
Apply this diff to fix the method signature:
- void main(String[] args) {
+ public static void main(String[] args) {
SpringApplication.run(Exercise2025Application.class, args);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void main(String[] args) { | |
| SpringApplication.run(Exercise2025Application.class, args); | |
| } | |
| public static void main(String[] args) { | |
| SpringApplication.run(Exercise2025Application.class, args); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/org/example/Exercise2025Application.java around lines 9 to 11,
the main method is declared as a package-private, non-static method; change its
signature to the Java entry-point signature by making it public and static
(i.e., public static void main(String[] args)) so the JVM can invoke it, leaving
the body to call SpringApplication.run(Exercise2025Application.class, args).
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/main/java/exercise8/controller/api/AllergenRestController.java (1)
17-18: Consider constructor injection for consistency with EventRestController.While field injection works fine in this exercise project, switching to constructor injection would improve testability and align with the pattern used in
EventRestController. That said, this approach is consistent with other REST controllers in the codebase (PatrolRestController,ParticipantRestController,RegistrationRestController).If you'd like to refactor, apply this diff:
- @Autowired - private AllergenService allergenService; + private final AllergenService allergenService; + + public AllergenRestController(AllergenService allergenService) { + this.allergenService = allergenService; + }src/main/java/exercise8/repository/EventRepository.java (2)
13-14: Clarify the comment to mention "after".The comment states "Find events by a specific date" but the method finds events starting after that date. Consider updating the comment to be more precise: "Find events starting after a specific date".
22-23: Parameter order has been corrected.The parameter order now correctly aligns with the method name (StartDate before EndDate). However, the parameter names
startDateandendDatemight be slightly misleading for the "ongoing events" use case described in the comment.For finding ongoing events at a specific point in time, both parameters typically represent the same moment (e.g., "now"), so names like
beforeDateandafterDateor simplydatefor both might better convey the intent. That said, the current implementation is functional and the comment provides adequate guidance.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/exercise8/controller/api/AllergenRestController.java(1 hunks)src/main/java/exercise8/entity/Allergen.java(1 hunks)src/main/java/exercise8/repository/EventRepository.java(1 hunks)src/main/java/exercise8/service/RegistrationService.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/exercise8/service/RegistrationService.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: jennymakki
Repo: fungover/exercise2025 PR: 204
File: src/main/java/org/example/config/SecurityConfig.java:19-24
Timestamp: 2025-11-16T13:56:11.263Z
Learning: The repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials and other shortcuts for ease of testing are acceptable and intentional.
🧬 Code graph analysis (2)
src/main/java/exercise8/entity/Allergen.java (1)
src/main/java/exercise8/entity/Participant.java (1)
Entity(10-131)
src/main/java/exercise8/controller/api/AllergenRestController.java (3)
src/main/java/exercise8/controller/api/PatrolRestController.java (1)
RestController(13-63)src/main/java/exercise8/controller/api/ParticipantRestController.java (1)
RestController(13-66)src/main/java/exercise8/controller/api/RegistrationRestController.java (1)
RestController(13-86)
🔇 Additional comments (3)
src/main/java/exercise8/entity/Allergen.java (1)
43-54: LGTM! The equals() and hashCode() implementation correctly addresses Set usage.The implementation using the business key (
name) for equality is appropriate and resolves the previous review concern. Sincenamehas a unique constraint, this approach ensures correct behavior whenAllergenentities are used inSet<Allergen>collections within theParticipantentity.src/main/java/exercise8/controller/api/AllergenRestController.java (1)
20-55: LGTM! REST endpoints follow proper conventions.The CRUD endpoints are well-structured with appropriate HTTP methods, status codes (201 CREATED, 204 NO CONTENT), and validation using
@Valid. The implementation aligns with REST best practices.src/main/java/exercise8/repository/EventRepository.java (1)
1-11: LGTM! Clean repository structure.The repository interface is correctly structured with appropriate Spring Data JPA annotations and extends the proper base interface.
| var report = eventService.getAllergyReportForEvent(id); | ||
|
|
||
| StringBuilder csv = new StringBuilder(); | ||
| csv.append("Allergi,Antal,Efternamn,Förnamn,Patrull,Roll\n"); |
Deansie
left a comment
There was a problem hiding this comment.
I really appreciate how you turned an assignment into something actually applicable and sought of after in real life, I would really like to see your finished thesis once done!
Well structured and easy to comprehend, well done! Left a nitpick comment regarding the exported .csv-file you perhaps could consider.

I have made a small backend test version of my intended thesis. A dynamic camp registration with a focus on food logistics. A central function is the management of allergy information with the ability to filter and sort participants by allergy type and organizational affiliation (parish/scout corps), as well as generate statistics on specific food allergies.
In this case, Thymeleaf is responsible for the HTML rendering, but in the thesis, the frontend tool Angular will be used.
User is admin password is admin123 for this project. I have not spent much time trying to hide it.
There are tests for REST controller endpoints and Security access control. All endpoints for the project can be found in the readme file
Summary by CodeRabbit
New Features
Infrastructure
Tests
Documentation / Chores
✏️ Tip: You can customize this high-level summary in your review settings.