Import an attendee list into an event

Java · Spring Boot · beginner · greenfield

Organisers have been adding attendees one at a time; this adds a bulk import. `AttendeeImportController` exposes `POST /api/events/{eventId}/attendees/import`, validates the body and checks the caller organises the event. `Attendee` is the request value object, with the bean-validation rules for an entry and value equality on the email. `AttendeeImportService` drops repeated people (keeping the first entry), filters out emails already registered for the event, and saves the rest in one transaction.

Event registration backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL 16, Spring Security. Every `/api/**` request needs a signed-in session; `@AuthenticationPrincipal` resolves an `Organiser(long id, String email)` record. Existing and unchanged: `EventAccess.requireOrganiser(eventId, organiserId)` throws a 404 `ResponseStatusException` unless the event exists and the caller organises it. The `Registration` entity (`id`, `eventId`, `email`, `name`) maps the `registrations` table, which has a unique constraint on `(event_id, email)`; every write path stores `email` lower-cased, so the constraint also rejects case variants. `RegistrationRepository.findEmailsByEventId(eventId)` returns the event's stored (lower-cased) emails as a `Set<String>`. An existing `@RestControllerAdvice` turns `DataIntegrityViolationException` into a 409 with "The attendee list changed while you were importing, try again", for the rare case of two organisers registering the same person at the same moment; a 409 from that race is acceptable. Bean-validation failures on `@Valid @RequestBody` become 400. An event has at most a few thousand registrations.

Requirements

Files touched

--- src/main/java/com/acme/events/attendees/Attendee.java
+package com.acme.events.attendees;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+
+import java.util.Locale;
+
+public final class Attendee {
+
+    @NotBlank
+    @Email
+    @Pattern(regexp = "[\\x21-\\x7E]+")
+    @Size(max = 254)
+    private final String email;
+
+    @NotNull
+    @Size(max = 100)
+    private final String name;
+
+    @JsonCreator
+    public Attendee(@JsonProperty("email") String email, @JsonProperty("name") String name) {
+        this.email = email;
+        this.name = name;

Review this PR

Java practice