feat(document): reject RANGE with end before start (#678)

Add ErrorCode.INVALID_DATE_RANGE and a validateDateRange guard on
DocumentService.updateDocument, run right after applyDatePrecision so it
fires before any save (updateDocumentTags persists earlier in the method).
Mirrors the V69 chk_meta_date_end_after_start CHECK: end >= start with a
null start allowed, using isBefore so equal dates stay valid. Turns a user
date typo into a clean 400 instead of a 500 + Sentry alert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-01 09:12:54 +02:00
parent 246568301a
commit a574d96351
3 changed files with 53 additions and 0 deletions

View File

@@ -381,6 +381,7 @@ public class DocumentService {
doc.setTitle(dto.getTitle());
doc.setDocumentDate(dto.getDocumentDate());
applyDatePrecision(doc, dto);
validateDateRange(doc); // guard before any save (updateDocumentTags below persists)
doc.setLocation(dto.getLocation());
doc.setTranscription(dto.getTranscription());
doc.setSummary(dto.getSummary());
@@ -468,6 +469,25 @@ public class DocumentService {
}
}
/**
* Friendly guard for the two V69 date-range CHECK constraints, run before save so a
* user date typo returns a clean 400 INVALID_DATE_RANGE instead of falling through to
* the generic handler (HTTP 500 + Sentry + ERROR log). Validates the post-apply {@code doc}
* state, not the DTO, because precision/end may have been carried over from the stored row
* when the DTO field was null. The DB CHECK remains the backstop; this never weakens it.
*/
private void validateDateRange(Document doc) {
// Mirrors chk_meta_date_end_after_start: end >= start, with null start allowed.
// Use isBefore (equal dates are valid) — never !isAfter, which would contradict the DB's >=.
if (doc.getMetaDatePrecision() == DatePrecision.RANGE
&& doc.getDocumentDate() != null
&& doc.getMetaDateEnd() != null
&& doc.getMetaDateEnd().isBefore(doc.getDocumentDate())) {
throw DomainException.badRequest(ErrorCode.INVALID_DATE_RANGE,
"meta_date_end must not be before meta_date");
}
}
@Transactional
public Document updateDocumentTags(UUID docId, List<String> tagNames) {
Document doc = documentRepository.findById(docId)

View File

@@ -26,6 +26,8 @@ public enum ErrorCode {
FILE_UPLOAD_FAILED,
/** The uploaded file's content type is not supported (PDF/JPEG/PNG/TIFF only). 400 */
UNSUPPORTED_FILE_TYPE,
/** A RANGE date is invalid: meta_date_end is before meta_date, or an end date is set without RANGE precision. 400 */
INVALID_DATE_RANGE,
// --- Users ---
/** A user with the given ID or username does not exist. 404 */