feat(stammbaum): RelationshipController for the Stammbaum API
Seven endpoints in one controller, two roots:
- GET /api/network → NetworkDTO
- GET /api/persons/{id}/relationships → List<RelationshipDTO>
- GET /api/persons/{id}/inferred-relationships
- GET /api/persons/{aId}/relationship-to/{bId} → 200 or 404
- POST /api/persons/{id}/relationships WRITE_ALL
- DEL /api/persons/{id}/relationships/{relId} WRITE_ALL, 204
- PATCH /api/persons/{id}/family-member WRITE_ALL
PersonController is intentionally untouched. Controller-boundary
validation via RelationType.valueOf catches unknown types as 400 before
the service is invoked. FamilyMemberPatchDTO is a one-field record for
the family-member toggle.
Refs #358.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
package org.raddatz.familienarchiv.relationship;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.raddatz.familienarchiv.model.Person;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.CreateRelationshipRequest;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.FamilyMemberPatchDTO;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.InferredRelationshipDTO;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.InferredRelationshipWithPersonDTO;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.NetworkDTO;
|
||||||
|
import org.raddatz.familienarchiv.relationship.dto.RelationshipDTO;
|
||||||
|
import org.raddatz.familienarchiv.security.Permission;
|
||||||
|
import org.raddatz.familienarchiv.security.RequirePermission;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stammbaum API. Endpoints split across two roots:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code /api/network} — the family graph</li>
|
||||||
|
* <li>{@code /api/persons/{id}/...} — per-person relationship operations
|
||||||
|
* (PersonController is intentionally left untouched)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RelationshipController {
|
||||||
|
|
||||||
|
private final RelationshipService relationshipService;
|
||||||
|
|
||||||
|
@GetMapping("/api/network")
|
||||||
|
public NetworkDTO getNetwork() {
|
||||||
|
return relationshipService.getFamilyNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/persons/{id}/relationships")
|
||||||
|
public List<RelationshipDTO> getRelationships(@PathVariable UUID id) {
|
||||||
|
return relationshipService.getRelationships(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/persons/{id}/inferred-relationships")
|
||||||
|
public List<InferredRelationshipWithPersonDTO> getInferredRelationships(@PathVariable UUID id) {
|
||||||
|
return relationshipService.getInferredRelationships(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/persons/{aId}/relationship-to/{bId}")
|
||||||
|
public InferredRelationshipDTO getRelationshipBetween(@PathVariable UUID aId, @PathVariable UUID bId) {
|
||||||
|
return relationshipService.getRelationshipBetween(aId, bId)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(
|
||||||
|
HttpStatus.NOT_FOUND, "No relationship path between " + aId + " and " + bId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/persons/{id}/relationships")
|
||||||
|
@RequirePermission(Permission.WRITE_ALL)
|
||||||
|
public ResponseEntity<RelationshipDTO> addRelationship(
|
||||||
|
@PathVariable UUID id,
|
||||||
|
@Valid @RequestBody CreateRelationshipRequest dto) {
|
||||||
|
validateRelationType(dto.relationType());
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED)
|
||||||
|
.body(relationshipService.addRelationship(id, dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/api/persons/{id}/relationships/{relId}")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@RequirePermission(Permission.WRITE_ALL)
|
||||||
|
public void deleteRelationship(@PathVariable UUID id, @PathVariable UUID relId) {
|
||||||
|
relationshipService.deleteRelationship(id, relId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/api/persons/{id}/family-member")
|
||||||
|
@RequirePermission(Permission.WRITE_ALL)
|
||||||
|
public Person patchFamilyMember(@PathVariable UUID id, @RequestBody FamilyMemberPatchDTO dto) {
|
||||||
|
return relationshipService.setFamilyMember(id, dto.familyMember());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateRelationType(String typeName) {
|
||||||
|
if (typeName == null || typeName.isBlank()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "relationType is required");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RelationType.valueOf(typeName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new ResponseStatusException(
|
||||||
|
HttpStatus.BAD_REQUEST, "Unknown relationType: " + typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package org.raddatz.familienarchiv.relationship.dto;
|
||||||
|
|
||||||
|
/** Body for {@code PATCH /api/persons/{id}/family-member}. */
|
||||||
|
public record FamilyMemberPatchDTO(boolean familyMember) {}
|
||||||
Reference in New Issue
Block a user