fix(db): V51 backfills annotation_id on block comments and notifications

Previously issued block-comment notifications were stored with
annotation_id=NULL because CommentService.postBlockComment did not
populate DocumentComment.annotationId. Now that the code fix is in
place, existing rows need to be filled in so legacy notifications
can also carry the query param that the frontend deep-link flow
expects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-21 13:13:13 +02:00
parent 4658852281
commit 13732ab96b
2 changed files with 134 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
-- Backfill annotation_id on block comments and their notifications.
--
-- Before the upstream fix, CommentService.postBlockComment did not set
-- DocumentComment.annotationId, so block comments were stored with
-- annotation_id = NULL and every notification built from them inherited
-- that NULL (see NotificationService.notifyMentions/notifyReply).
--
-- The frontend deep-link flow needs annotationId in the URL query string
-- to open the correct annotation panel and scroll to the comment.
-- Without this backfill, previously issued notifications would still
-- carry annotation_id = NULL even after the code fix lands.
UPDATE document_comments dc
SET annotation_id = tb.annotation_id
FROM transcription_blocks tb
WHERE dc.block_id = tb.id
AND dc.annotation_id IS NULL;
UPDATE notifications n
SET annotation_id = dc.annotation_id
FROM document_comments dc
WHERE n.reference_id = dc.id
AND n.annotation_id IS NULL
AND dc.annotation_id IS NOT NULL;

View File

@@ -302,6 +302,57 @@ class MigrationIntegrationTest {
).isInstanceOf(DataIntegrityViolationException.class);
}
// ─── V51: backfill annotation_id on block comments and notifications ─────
@Test
void v51_backfillsAnnotationIdOnBlockCommentsFromTheirBlocks() {
UUID docId = createDocument();
UUID annotationId = insertAnnotation(docId);
UUID blockId = insertBlock(docId, annotationId);
UUID commentId = insertBlockCommentWithNullAnnotationId(docId, blockId);
jdbc.update(V51_BACKFILL_COMMENTS_SQL);
UUID stored = jdbc.queryForObject(
"SELECT annotation_id FROM document_comments WHERE id = ?",
UUID.class, commentId);
assertThat(stored).isEqualTo(annotationId);
}
@Test
void v51_backfillsAnnotationIdOnNotificationsFromTheirReferencedComment() {
UUID docId = createDocument();
UUID userId = insertUser("recipient-" + UUID.randomUUID() + "@example.com");
UUID annotationId = insertAnnotation(docId);
UUID blockId = insertBlock(docId, annotationId);
UUID commentId = insertBlockCommentWithAnnotationId(docId, blockId, annotationId);
UUID notificationId = insertNotificationWithNullAnnotationId(docId, commentId, userId);
jdbc.update(V51_BACKFILL_NOTIFICATIONS_SQL);
UUID stored = jdbc.queryForObject(
"SELECT annotation_id FROM notifications WHERE id = ?",
UUID.class, notificationId);
assertThat(stored).isEqualTo(annotationId);
}
private static final String V51_BACKFILL_COMMENTS_SQL = """
UPDATE document_comments dc
SET annotation_id = tb.annotation_id
FROM transcription_blocks tb
WHERE dc.block_id = tb.id
AND dc.annotation_id IS NULL
""";
private static final String V51_BACKFILL_NOTIFICATIONS_SQL = """
UPDATE notifications n
SET annotation_id = dc.annotation_id
FROM document_comments dc
WHERE n.reference_id = dc.id
AND n.annotation_id IS NULL
AND dc.annotation_id IS NOT NULL
""";
// ─── helpers ─────────────────────────────────────────────────────────────
private UUID createPerson(String firstName, String lastName) {
@@ -326,4 +377,63 @@ class MigrationIntegrationTest {
em.flush();
return doc.getId();
}
private UUID insertAnnotation(UUID docId) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO document_annotations
(id, document_id, page_number, x, y, width, height, color)
VALUES (?, ?, 1, 0.1, 0.1, 0.3, 0.1, '#00C7B1')
""", id, docId);
return id;
}
private UUID insertBlock(UUID docId, UUID annotationId) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO transcription_blocks
(id, annotation_id, document_id, text, sort_order)
VALUES (?, ?, ?, '', 0)
""", id, annotationId, docId);
return id;
}
private UUID insertUser(String email) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO users (id, email, password, enabled, notify_on_reply, notify_on_mention)
VALUES (?, ?, 'hash', true, false, false)
""", id, email);
return id;
}
private UUID insertBlockCommentWithNullAnnotationId(UUID docId, UUID blockId) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO document_comments
(id, document_id, block_id, annotation_id, author_name, content)
VALUES (?, ?, ?, NULL, 'Tester', 'Hi')
""", id, docId, blockId);
return id;
}
private UUID insertBlockCommentWithAnnotationId(UUID docId, UUID blockId, UUID annotationId) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO document_comments
(id, document_id, block_id, annotation_id, author_name, content)
VALUES (?, ?, ?, ?, 'Tester', 'Hi')
""", id, docId, blockId, annotationId);
return id;
}
private UUID insertNotificationWithNullAnnotationId(UUID docId, UUID commentId, UUID recipientId) {
UUID id = UUID.randomUUID();
jdbc.update("""
INSERT INTO notifications
(id, recipient_id, type, document_id, reference_id, annotation_id, read, actor_name)
VALUES (?, ?, 'MENTION', ?, ?, NULL, false, 'Tester')
""", id, recipientId, docId, commentId);
return id;
}
}