Add Spoiler Flag with veil, AP content-warning encoding, and full-stack plumbing

Authors can flag a review's comment as containing spoilers; the comment is
blurred behind a reveal control for every viewer except the author, omitted
from RSS, and federates as containsSpoilers plus an AP summary content
warning (ADR-0011) so Mastodon-class clients also collapse it. Flag follows
the general-edit path (ADR-0001) across domain, both DB backends, REST,
HTML templates, SPA (shadcn), TUI fixtures, and exports.
This commit is contained in:
2026-10-17 21:12:56 +02:00
parent 9dd9df1f73
commit bb2001b9cc
66 changed files with 513 additions and 167 deletions

View File

@@ -27,7 +27,7 @@ fn stream_csv(
use futures::StreamExt;
let header = futures::stream::once(async {
Ok(Bytes::from_static(
b"title,year,director,rating,comment,watched_at,external_metadata_id\n",
b"title,year,director,rating,comment,watched_at,external_metadata_id,spoilers\n",
))
});
let rows = entries.map(|r| r.map(|e| Bytes::from(csv_row(&e))));
@@ -80,9 +80,10 @@ fn csv_row(e: &DiaryEntry) -> String {
.external_metadata_id()
.map(|id| id.value().to_string())
.unwrap_or_default();
let spoilers = e.review().spoilers();
format!(
"{},{},{},{},{},{},{}\n",
title, year, director, rating, comment, watched_at, ext_id
"{},{},{},{},{},{},{},{}\n",
title, year, director, rating, comment, watched_at, ext_id, spoilers
)
}
@@ -103,6 +104,7 @@ fn entry_to_json(e: &DiaryEntry) -> serde_json::Value {
"comment": e.review().comment().map(|c| c.value().to_string()),
"watched_at": e.review().watched_at().format("%Y-%m-%d").to_string(),
"external_metadata_id": e.movie().external_metadata_id().map(|id| id.value().to_string()),
"spoilers": e.review().spoilers(),
})
}

View File

@@ -62,6 +62,7 @@ fn make_entry_full(
.and_hms_opt(0, 0, 0)
.unwrap(),
None,
false,
)
.unwrap();
DiaryEntry::new(movie, review)
@@ -81,7 +82,7 @@ async fn csv_has_header_and_one_row() {
collect_stream(adapter.stream_entries(entry_stream(vec![entry]), ExportFormat::Csv)).await;
let text = String::from_utf8(bytes).unwrap();
assert!(
text.starts_with("title,year,director,rating,comment,watched_at,external_metadata_id\n")
text.starts_with("title,year,director,rating,comment,watched_at,external_metadata_id,spoilers\n")
);
assert!(text.contains("Inception"));
assert!(text.contains("2010"));
@@ -149,7 +150,7 @@ async fn empty_entries_returns_csv_header_only() {
let text = String::from_utf8(bytes).unwrap();
assert_eq!(
text,
"title,year,director,rating,comment,watched_at,external_metadata_id\n"
"title,year,director,rating,comment,watched_at,external_metadata_id,spoilers\n"
);
}