feat(model): add PersonNameAlias entity, type enum, repository, DTO

Introduces the alias domain model: entity with @ManyToOne to Person,
@OneToMany on Person for JPA graph navigation, repository with
sort_order queries, input DTO, and ALIAS_NOT_FOUND error code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-07 13:04:38 +02:00
parent 22fe9600a1
commit 765cbfbaaf
6 changed files with 94 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
package org.raddatz.familienarchiv.dto;
import org.raddatz.familienarchiv.model.PersonNameAliasType;
public record PersonNameAliasDTO(
String lastName,
String firstName,
PersonNameAliasType type
) {}

View File

@@ -11,6 +11,8 @@ public enum ErrorCode {
// --- Persons ---
/** A person with the given ID does not exist. 404 */
PERSON_NOT_FOUND,
/** A person name alias with the given ID does not exist. 404 */
ALIAS_NOT_FOUND,
// --- Documents ---
/** A document with the given ID does not exist. 404 */

View File

@@ -1,9 +1,12 @@
package org.raddatz.familienarchiv.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "persons")
@@ -35,4 +38,12 @@ public class Person {
private Integer birthYear;
private Integer deathYear;
// Entity-graph navigation for JPA JOIN queries (e.g. DocumentSpecifications.hasText).
// Uses entity relationship rather than cross-domain repository access, avoiding a
// separate DB roundtrip while respecting domain boundaries.
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@Builder.Default
private List<PersonNameAlias> nameAliases = new ArrayList<>();
}

View File

@@ -0,0 +1,48 @@
package org.raddatz.familienarchiv.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "person_name_aliases")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PersonNameAlias {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "person_id", nullable = false)
private Person person;
@Column(name = "last_name", nullable = false)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private String lastName;
@Column(name = "first_name")
private String firstName;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private PersonNameAliasType type;
@Column(name = "sort_order", nullable = false)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Integer sortOrder;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
private Instant createdAt;
}

View File

@@ -0,0 +1,8 @@
package org.raddatz.familienarchiv.model;
public enum PersonNameAliasType {
BIRTH,
WIDOWED,
DIVORCED,
OTHER
}

View File

@@ -0,0 +1,16 @@
package org.raddatz.familienarchiv.repository;
import org.raddatz.familienarchiv.model.PersonNameAlias;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.UUID;
public interface PersonNameAliasRepository extends JpaRepository<PersonNameAlias, UUID> {
List<PersonNameAlias> findByPersonIdOrderBySortOrderAscCreatedAtAsc(UUID personId);
@Query("SELECT COALESCE(MAX(a.sortOrder), -1) FROM PersonNameAlias a WHERE a.person.id = :personId")
int findMaxSortOrder(UUID personId);
}