Create projects with the default board tags

Java · Spring Boot · beginner · greenfield

Adds project creation to the API. `ProjectController` exposes `POST /api/projects` for the signed-in user's workspace and validates the body through the new `CreateProjectRequest` record (name and tag limits as Bean Validation constraints). `ProjectService.create` starts from the board's default tags, appends the user's tags normalised to lower case and skips the ones already present, saves the project and returns its view.

Project-tracking backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL, Spring Security. Every `/api/**` request needs a signed-in session and the existing `SecurityConfig` handles CSRF; `@AuthenticationPrincipal` resolves a `WorkspaceUser(Long userId, Long workspaceId)` record whose fields are never null. An existing request filter answers 400 to any request whose body or decoded JSON strings contain NUL or other C0 control characters or an unpaired UTF-16 surrogate (raw or as `\u` escapes) before it reaches a controller. Existing and unchanged: the `Project` entity (`id`, `workspaceId`, `name` as `varchar(100)`, and `tags` as an ordered `@ElementCollection` of `varchar(30)` in `project_tags`), `ProjectRepository extends JpaRepository<Project, Long>`, and the `ProjectView` record with `ProjectView.from(project)`, which copies the tags. `spring-boot-starter-validation` is on the classpath, and Spring answers a failed `@Valid @RequestBody` with 400.

Requirements

Files touched

--- src/main/java/com/acme/projects/projects/CreateProjectRequest.java
+package com.acme.projects.projects;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import java.util.List;
+
+public record CreateProjectRequest(
+        @NotBlank @Size(max = 100) String name,
+        @Size(max = 10) List<@NotNull @Size(max = 40) @Pattern(regexp = "\\s*[A-Za-z0-9-]{1,30}\\s*") String> tags) {
+}
+
--- src/main/java/com/acme/projects/projects/ProjectController.java
+package com.acme.projects.projects;
+
+import com.acme.projects.security.WorkspaceUser;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/projects")
+public class ProjectController {

Review this PR

Java practice