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:
@@ -26,6 +26,7 @@ pub struct PersistedReview {
|
||||
pub created_at: NaiveDateTime,
|
||||
pub source: ReviewSource,
|
||||
pub watch_medium: Option<WatchMedium>,
|
||||
pub spoilers: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -34,6 +35,7 @@ pub struct ReviewEdit {
|
||||
pub comment: Option<Option<Comment>>,
|
||||
pub watched_at: Option<NaiveDateTime>,
|
||||
pub watch_medium: Option<Option<WatchMedium>>,
|
||||
pub spoilers: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -47,6 +49,7 @@ pub struct Review {
|
||||
created_at: NaiveDateTime,
|
||||
source: ReviewSource,
|
||||
watch_medium: Option<WatchMedium>,
|
||||
spoilers: bool,
|
||||
}
|
||||
|
||||
impl Review {
|
||||
@@ -57,6 +60,7 @@ impl Review {
|
||||
comment: Option<Comment>,
|
||||
watched_at: NaiveDateTime,
|
||||
watch_medium: Option<WatchMedium>,
|
||||
spoilers: bool,
|
||||
) -> Result<Self, DomainError> {
|
||||
Ok(Self {
|
||||
id: ReviewId::generate(),
|
||||
@@ -68,6 +72,7 @@ impl Review {
|
||||
created_at: Utc::now().naive_utc(),
|
||||
source: ReviewSource::Local,
|
||||
watch_medium,
|
||||
spoilers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -82,6 +87,7 @@ impl Review {
|
||||
created_at: row.created_at,
|
||||
source: row.source,
|
||||
watch_medium: row.watch_medium,
|
||||
spoilers: row.spoilers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +108,7 @@ impl Review {
|
||||
Some(wm) => wm,
|
||||
None => self.watch_medium,
|
||||
},
|
||||
spoilers: edit.spoilers.unwrap_or(self.spoilers),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +139,9 @@ impl Review {
|
||||
pub fn watch_medium(&self) -> Option<&WatchMedium> {
|
||||
self.watch_medium.as_ref()
|
||||
}
|
||||
pub fn spoilers(&self) -> bool {
|
||||
self.spoilers
|
||||
}
|
||||
pub fn stars(&self) -> [bool; 5] {
|
||||
let r = self.rating.value();
|
||||
[r >= 1, r >= 2, r >= 3, r >= 4, r >= 5]
|
||||
|
||||
@@ -79,6 +79,7 @@ fn make_review() -> Review {
|
||||
Some(Comment::new("great".into()).unwrap()),
|
||||
chrono::Utc::now().naive_utc(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -114,10 +115,12 @@ fn review_from_persistence() {
|
||||
actor_url: "https://example.com/actor".into(),
|
||||
},
|
||||
watch_medium: None,
|
||||
spoilers: true,
|
||||
});
|
||||
assert_eq!(*r.id(), id);
|
||||
assert!(r.is_remote());
|
||||
assert_eq!(r.comment(), None);
|
||||
assert!(r.spoilers());
|
||||
}
|
||||
|
||||
// ── User ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -12,6 +12,7 @@ fn make_review(watch_medium: Option<WatchMedium>) -> Review {
|
||||
Some(Comment::new("great film".into()).unwrap()),
|
||||
NaiveDateTime::parse_from_str("2024-06-15 20:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
|
||||
watch_medium,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -71,6 +72,23 @@ fn apply_edit_clears_watch_medium() {
|
||||
assert_eq!(edited.watch_medium(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_toggles_spoilers() {
|
||||
let original = make_review(None);
|
||||
let flag_on = original.apply_edit(ReviewEdit {
|
||||
spoilers: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(flag_on.spoilers());
|
||||
let flag_off = flag_on.apply_edit(ReviewEdit {
|
||||
spoilers: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(!flag_off.spoilers());
|
||||
let untouched = original.apply_edit(ReviewEdit::default());
|
||||
assert!(!untouched.spoilers());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_no_changes_preserves_all() {
|
||||
let original = make_review(Some(WatchMedium::TV));
|
||||
|
||||
@@ -29,6 +29,7 @@ fn review_with_rating(movie_id: &MovieId, rating: u8, watched_at: NaiveDateTime)
|
||||
None,
|
||||
watched_at,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user