Some checks failed
CI / Check / Test (push) Failing after 6m5s
Webhook ingestion from media servers — movies land in a pending watch queue, user rates and confirms to create diary entries. - domain: WatchEvent, WebhookToken models, MediaServerParser port - adapters: jellyfin + plex parser crates, SQLite + Postgres repos - application: ingest/confirm/dismiss/cleanup use cases, token mgmt - presentation: webhook endpoints (bearer + query param auth), watch queue + integrations settings HTML pages, OpenAPI docs - worker: WatchEventCleanupJob (daily, 30d retention) Movie resolution deferred to confirm — single canonical path through log_review for enrichment, poster fetch, federation.
269 lines
7.5 KiB
Rust
269 lines
7.5 KiB
Rust
use uuid::Uuid;
|
|
|
|
use domain::models::{
|
|
DiaryEntry, FeedEntry, MonthActivity, Movie, MovieProfile, MovieStats, UserStats, UserSummary,
|
|
UserTrends, collections::Paginated,
|
|
};
|
|
|
|
pub struct RemoteActorView {
|
|
pub handle: String,
|
|
pub display_name: Option<String>,
|
|
pub url: String,
|
|
pub avatar_url: Option<String>,
|
|
}
|
|
|
|
pub struct HtmlPageContext {
|
|
pub user_email: Option<String>,
|
|
pub user_id: Option<Uuid>,
|
|
pub is_admin: bool,
|
|
pub register_enabled: bool,
|
|
pub rss_url: String,
|
|
pub page_title: String,
|
|
pub canonical_url: String,
|
|
pub csrf_token: String,
|
|
pub page_rss_url: Option<String>,
|
|
}
|
|
|
|
impl HtmlPageContext {
|
|
pub fn is_current_user(&self, id: Uuid) -> bool {
|
|
self.user_id == Some(id)
|
|
}
|
|
}
|
|
|
|
pub struct LoginPageData<'a> {
|
|
pub ctx: HtmlPageContext,
|
|
pub error: Option<&'a str>,
|
|
}
|
|
|
|
pub struct RegisterPageData<'a> {
|
|
pub ctx: HtmlPageContext,
|
|
pub error: Option<&'a str>,
|
|
}
|
|
|
|
pub struct NewReviewPageData<'a> {
|
|
pub ctx: HtmlPageContext,
|
|
pub error: Option<&'a str>,
|
|
}
|
|
|
|
pub struct ActivityFeedPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub entries: Paginated<FeedEntry>,
|
|
pub current_offset: u32,
|
|
pub has_more: bool,
|
|
pub limit: u32,
|
|
pub filter: String,
|
|
pub sort_by: String,
|
|
pub search: String,
|
|
}
|
|
|
|
pub struct UsersPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub users: Vec<UserSummary>,
|
|
pub remote_actors: Vec<RemoteActorView>,
|
|
}
|
|
|
|
pub struct ProfilePageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub profile_user_id: Uuid,
|
|
pub profile_user_email: String,
|
|
pub stats: UserStats,
|
|
pub view: String,
|
|
pub entries: Option<Paginated<DiaryEntry>>,
|
|
pub current_offset: u32,
|
|
pub has_more: bool,
|
|
pub limit: u32,
|
|
pub history: Option<Vec<MonthActivity>>,
|
|
pub trends: Option<UserTrends>,
|
|
pub is_own_profile: bool,
|
|
pub error: Option<String>,
|
|
pub following_count: usize,
|
|
pub followers_count: usize,
|
|
pub pending_followers: Vec<RemoteActorView>,
|
|
pub sort_by: String,
|
|
pub search: String,
|
|
}
|
|
|
|
pub struct FollowingPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub user_id: Uuid,
|
|
pub actors: Vec<RemoteActorView>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub struct FollowersPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub user_id: Uuid,
|
|
pub actors: Vec<RemoteActorView>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub struct MovieDetailPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub movie: Movie,
|
|
pub stats: MovieStats,
|
|
pub reviews: Paginated<FeedEntry>,
|
|
pub profile: Option<MovieProfile>,
|
|
pub on_watchlist: bool,
|
|
pub current_offset: u32,
|
|
pub has_more: bool,
|
|
pub limit: u32,
|
|
pub histogram_max: u64,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct WatchlistDisplayEntry {
|
|
/// Always a full URL: /images/{path} for local, https://... for remote
|
|
pub poster_url: Option<String>,
|
|
pub movie_title: String,
|
|
pub release_year: u16,
|
|
/// /movies/{id} for local; None for remote entries without a local movie record
|
|
pub movie_url: Option<String>,
|
|
pub added_at: String,
|
|
/// /watchlist/{movie_id}/remove for owner; None for remote or non-owner
|
|
pub remove_url: Option<String>,
|
|
}
|
|
|
|
pub struct WatchlistPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub owner_id: uuid::Uuid,
|
|
pub display_entries: Vec<WatchlistDisplayEntry>,
|
|
pub current_offset: u32,
|
|
pub has_more: bool,
|
|
pub limit: u32,
|
|
pub is_owner: bool,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub struct ImportUploadPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub profiles: Vec<ImportProfileView>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub struct ImportProfileView {
|
|
pub id: String,
|
|
pub name: String,
|
|
}
|
|
|
|
pub struct ImportMappingPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub session_id: String,
|
|
pub columns: Vec<String>,
|
|
pub sample_rows: Vec<Vec<String>>,
|
|
pub domain_fields: Vec<(&'static str, &'static str)>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub struct ImportPreviewRow {
|
|
pub index: usize,
|
|
pub status: ImportRowStatus,
|
|
pub cells: Vec<String>,
|
|
}
|
|
|
|
pub enum ImportRowStatus {
|
|
Valid,
|
|
Duplicate,
|
|
Invalid(String),
|
|
}
|
|
|
|
pub struct ImportPreviewPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub session_id: String,
|
|
pub columns: Vec<String>,
|
|
pub rows: Vec<ImportPreviewRow>,
|
|
}
|
|
|
|
pub struct ProfileSettingsPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub bio: Option<String>,
|
|
pub avatar_url: Option<String>,
|
|
pub banner_url: Option<String>,
|
|
pub also_known_as: Option<String>,
|
|
pub profile_fields: Vec<(String, String)>,
|
|
pub saved: bool,
|
|
}
|
|
|
|
pub struct BlockedDomainEntry {
|
|
pub domain: String,
|
|
pub reason: Option<String>,
|
|
pub blocked_at: String,
|
|
}
|
|
|
|
pub struct BlockedDomainsPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub domains: Vec<BlockedDomainEntry>,
|
|
}
|
|
|
|
pub struct BlockedActorEntry {
|
|
pub url: String,
|
|
pub handle: String,
|
|
pub display_name: Option<String>,
|
|
pub avatar_url: Option<String>,
|
|
}
|
|
|
|
pub struct BlockedActorsPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub actors: Vec<BlockedActorEntry>,
|
|
}
|
|
|
|
pub struct WebhookTokenView {
|
|
pub id: String,
|
|
pub provider: String,
|
|
pub label: Option<String>,
|
|
pub created_at: String,
|
|
pub last_used_at: Option<String>,
|
|
}
|
|
|
|
pub struct IntegrationsPageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub tokens: Vec<WebhookTokenView>,
|
|
pub webhook_base_url: String,
|
|
pub new_token: Option<String>,
|
|
}
|
|
|
|
pub struct WatchQueueDisplayEntry {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub year: Option<u16>,
|
|
pub source: String,
|
|
pub watched_at: String,
|
|
pub movie_url: Option<String>,
|
|
}
|
|
|
|
pub struct WatchQueuePageData {
|
|
pub ctx: HtmlPageContext,
|
|
pub entries: Vec<WatchQueueDisplayEntry>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub trait HtmlRenderer: Send + Sync {
|
|
fn render_diary_page(
|
|
&self,
|
|
data: &Paginated<DiaryEntry>,
|
|
ctx: HtmlPageContext,
|
|
) -> Result<String, String>;
|
|
fn render_login_page(&self, data: LoginPageData<'_>) -> Result<String, String>;
|
|
fn render_register_page(&self, data: RegisterPageData<'_>) -> Result<String, String>;
|
|
fn render_new_review_page(&self, data: NewReviewPageData<'_>) -> Result<String, String>;
|
|
fn render_activity_feed_page(&self, data: ActivityFeedPageData) -> Result<String, String>;
|
|
fn render_users_page(&self, data: UsersPageData) -> Result<String, String>;
|
|
fn render_profile_page(&self, data: ProfilePageData) -> Result<String, String>;
|
|
fn render_following_page(&self, data: FollowingPageData) -> Result<String, String>;
|
|
fn render_followers_page(&self, data: FollowersPageData) -> Result<String, String>;
|
|
fn render_movie_detail_page(&self, data: MovieDetailPageData) -> Result<String, String>;
|
|
fn render_import_upload_page(&self, data: ImportUploadPageData) -> Result<String, String>;
|
|
fn render_import_mapping_page(&self, data: ImportMappingPageData) -> Result<String, String>;
|
|
fn render_import_preview_page(&self, data: ImportPreviewPageData) -> Result<String, String>;
|
|
fn render_profile_settings_page(&self, data: ProfileSettingsPageData)
|
|
-> Result<String, String>;
|
|
fn render_blocked_domains_page(&self, data: BlockedDomainsPageData) -> Result<String, String>;
|
|
fn render_blocked_actors_page(&self, data: BlockedActorsPageData) -> Result<String, String>;
|
|
fn render_watchlist_page(&self, data: WatchlistPageData) -> Result<String, String>;
|
|
fn render_integrations_page(&self, data: IntegrationsPageData) -> Result<String, String>;
|
|
fn render_watch_queue_page(&self, data: WatchQueuePageData) -> Result<String, String>;
|
|
}
|
|
|
|
pub trait RssFeedRenderer: Send + Sync {
|
|
fn render_feed(&self, entries: &[DiaryEntry], title: &str) -> Result<String, String>;
|
|
}
|