feat: add WatchMedium field, general review editing, configurable deploy
Some checks failed
CI / Check / Test (push) Failing after 27m0s
Some checks failed
CI / Check / Test (push) Failing after 27m0s
- WatchMedium enum (cinema/streaming/tv/physical_media/download/media_server/other) - PATCH /api/v1/reviews/:id partial update (rating, comment, watched_at, watch_medium) - edit_review use case w/ ownership + remote review guard, best-effort AP Update broadcast - SPA: icon picker, edit sheet (long-press mobile / pencil desktop), watch medium badge - shared ReviewFormFields, EditableContextMenu, parse_watched_at/format_watched_at - deploy.sh parameterized (--features, --tag), CORS allows PATCH - CONTEXT.md glossary, ADR-0001 general review editing
This commit is contained in:
@@ -1,5 +1,71 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::errors::DomainError;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum WatchMedium {
|
||||
Cinema,
|
||||
Streaming,
|
||||
TV,
|
||||
PhysicalMedia,
|
||||
Download,
|
||||
MediaServer,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl fmt::Display for WatchMedium {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Cinema => write!(f, "cinema"),
|
||||
Self::Streaming => write!(f, "streaming"),
|
||||
Self::TV => write!(f, "tv"),
|
||||
Self::PhysicalMedia => write!(f, "physical_media"),
|
||||
Self::Download => write!(f, "download"),
|
||||
Self::MediaServer => write!(f, "media_server"),
|
||||
Self::Other => write!(f, "other"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for WatchMedium {
|
||||
type Err = DomainError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"cinema" => Ok(Self::Cinema),
|
||||
"streaming" => Ok(Self::Streaming),
|
||||
"tv" => Ok(Self::TV),
|
||||
"physical_media" => Ok(Self::PhysicalMedia),
|
||||
"download" => Ok(Self::Download),
|
||||
"media_server" => Ok(Self::MediaServer),
|
||||
"other" => Ok(Self::Other),
|
||||
_ => Err(DomainError::ValidationError(format!(
|
||||
"unknown watch medium: {s}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_watched_at(s: &str) -> Result<chrono::NaiveDateTime, DomainError> {
|
||||
chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
|
||||
.or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S"))
|
||||
.or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M"))
|
||||
.or_else(|_| {
|
||||
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
|
||||
.map(|d| d.and_hms_opt(0, 0, 0).expect("midnight always valid"))
|
||||
})
|
||||
.map_err(|_| {
|
||||
DomainError::ValidationError(format!(
|
||||
"invalid date '{s}'; expected YYYY-MM-DD or YYYY-MM-DDTHH:MM[:SS]"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn format_watched_at(dt: &chrono::NaiveDateTime) -> String {
|
||||
dt.format("%Y-%m-%dT%H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Rating(u8);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user