feat(mangas): content-warning filter on search + deduped banner on detail
Surfaces the analysis worker's NSFW moderation at the manga level: - repo::manga FILTER_WHERE gains cw_include (AND, page-content-warning join to mangas) and cw_exclude (NONE) clauses; ListQuery + binds renumbered ($6/$7, LIMIT/OFFSET $8/$9). - repo::page_analysis::warnings_for_manga: deduped, alphabetical union across all the manga's pages. - domain::MangaDetail gains content_warnings; get_detail populates it. - api::mangas list accepts cw_include/cw_exclude (reusing the shared parse_warnings_csv validator). Tests: detail union (deduped/sorted) + empty case; list include/exclude filter; unknown-warning 422. Existing manga tests still green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,12 @@ pub struct ListParams {
|
||||
pub genre_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tag_id: Option<String>,
|
||||
/// Comma-separated content warnings the manga must carry (AND).
|
||||
#[serde(default)]
|
||||
pub cw_include: Option<String>,
|
||||
/// Comma-separated content warnings the manga must NOT carry (any).
|
||||
#[serde(default)]
|
||||
pub cw_exclude: Option<String>,
|
||||
#[serde(default = "default_limit")]
|
||||
pub limit: i64,
|
||||
#[serde(default)]
|
||||
@@ -94,6 +100,8 @@ async fn list(
|
||||
author_ids: parse_uuid_csv("author_id", params.author_id.as_deref())?,
|
||||
genre_ids: parse_uuid_csv("genre_id", params.genre_id.as_deref())?,
|
||||
tag_ids: parse_uuid_csv("tag_id", params.tag_id.as_deref())?,
|
||||
cw_include: crate::api::page_tags::parse_warnings_csv(params.cw_include.as_deref())?,
|
||||
cw_exclude: crate::api::page_tags::parse_warnings_csv(params.cw_exclude.as_deref())?,
|
||||
limit,
|
||||
offset,
|
||||
sort: params.sort,
|
||||
|
||||
@@ -292,7 +292,7 @@ fn parse_tags_csv(raw: Option<&str>) -> AppResult<Vec<String>> {
|
||||
/// Split + validate a comma-separated content-warning list against the
|
||||
/// closed vocabulary, returning canonical lowercase names. Unknown values
|
||||
/// are a 422 rather than a silent drop so a typo'd filter is visible.
|
||||
fn parse_warnings_csv(raw: Option<&str>) -> AppResult<Vec<String>> {
|
||||
pub(crate) fn parse_warnings_csv(raw: Option<&str>) -> AppResult<Vec<String>> {
|
||||
let Some(raw) = raw else { return Ok(Vec::new()) };
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut out = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user