refactor: group use cases into DDD bounded contexts

Flat use_cases/ (44 files) + monolithic commands.rs/queries.rs
split into diary/, movies/, watchlist/, import/, auth/, users/,
integrations/, search/, person/, federation/ — each with own
commands.rs, queries.rs, and use case modules.

Inline tests extracted to sibling tests/ dirs.
This commit is contained in:
2026-06-02 19:49:09 +02:00
parent aadad3cfb0
commit dcc9244d4e
92 changed files with 1617 additions and 1500 deletions

View File

@@ -4,40 +4,53 @@ use axum::{
Form,
extract::{Extension, Multipart, Path, Query, State},
http::{HeaderValue, StatusCode, header::SET_COOKIE},
response::{Html, IntoResponse, Redirect},
response::{IntoResponse, Redirect},
};
use chrono::Utc;
use uuid::Uuid;
#[cfg(feature = "federation")]
use application::ports::{
BlockedActorEntry, BlockedActorsPageData, BlockedDomainEntry, BlockedDomainsPageData,
FollowersPageData, FollowingPageData,
};
use application::{
commands::{
AddToWatchlistCommand, ConfirmWatchEventsCommand, DeleteReviewCommand,
DismissWatchEventsCommand, GenerateWebhookTokenCommand, MovieInput,
RemoveFromWatchlistCommand, RevokeWebhookTokenCommand, WatchEventConfirmation,
auth::{login as login_uc, queries::LoginQuery},
diary::{
commands::{DeleteReviewCommand, MovieInput},
delete_review, export_diary as export_diary_uc, get_movie_social_page, log_review,
queries::{ExportQuery, GetMovieSocialPageQuery},
},
ports::{
HtmlPageContext, IntegrationsPageData, LoginPageData, MovieDetailPageData,
NewReviewPageData, ProfileSettingsPageData, RegisterPageData, RemoteActorView,
WatchQueueDisplayEntry, WatchQueuePageData, WatchlistPageData, WebhookTokenView,
integrations::{
commands::{
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
RevokeWebhookTokenCommand, WatchEventConfirmation,
},
confirm as confirm_watch_events, dismiss as dismiss_watch_events,
generate_token as generate_webhook_token, get_queue as get_watch_queue,
get_tokens as get_webhook_tokens,
queries::{GetWatchQueueQuery, GetWebhookTokensQuery},
revoke_token as revoke_webhook_token,
},
queries::{
ExportQuery, GetMovieSocialPageQuery, GetWatchQueueQuery, GetWebhookTokensQuery,
IsOnWatchlistQuery, LoginQuery,
},
use_cases::{
add_to_watchlist, confirm_watch_events, delete_review, dismiss_watch_events,
export_diary as export_diary_uc, generate_webhook_token, get_movie_social_page,
get_watch_queue, get_webhook_tokens, is_on_watchlist, log_review, login as login_uc,
remove_from_watchlist, revoke_webhook_token, update_profile, update_profile_fields,
users::{update_profile, update_profile_fields},
watchlist::{
add as add_to_watchlist,
commands::{AddToWatchlistCommand, RemoveFromWatchlistCommand},
is_on as is_on_watchlist,
queries::IsOnWatchlistQuery,
remove as remove_from_watchlist,
},
};
use crate::render::render_page;
use application::ports::HtmlPageContext;
use domain::models::ExportFormat;
use domain::{errors::DomainError, value_objects::UserId};
use template_askama::{
ActivityFeedTemplate, IntegrationsTemplate, LoginTemplate, MonthlyRatingRow,
MovieDetailTemplate, NewReviewTemplate, ProfileSettingsTemplate, ProfileTemplate,
RegisterTemplate, RemoteActorData, RemoteActorDisplay, UserSummaryView, UsersTemplate,
WatchQueueTemplate, WatchlistTemplate, bar_height_px, build_heatmap, build_page_items,
};
#[cfg(feature = "federation")]
use template_askama::{
BlockedActorsTemplate, BlockedDomainsTemplate, FollowersTemplate, FollowingTemplate,
};
#[cfg(feature = "federation")]
use crate::forms::{
@@ -57,13 +70,7 @@ pub(crate) async fn build_page_context(
) -> HtmlPageContext {
let uuid = user_id.as_ref().map(|u| u.value());
let (user_email, is_admin) = if let Some(ref id) = user_id {
let user = state
.app_ctx
.user_repository
.find_by_id(id)
.await
.ok()
.flatten();
let user = state.app_ctx.repos.user.find_by_id(id).await.ok().flatten();
let email = user.as_ref().map(|u| u.email().value().to_string());
let admin = user
.as_ref()
@@ -128,14 +135,10 @@ pub async fn get_login_page(
csrf_token: csrf.0,
page_rss_url: None,
};
let html = state
.html_renderer
.render_login_page(LoginPageData {
ctx,
error: params.error.as_deref(),
})
.expect("login template failed");
Html(html)
render_page(LoginTemplate {
ctx: &ctx,
error: params.error.as_deref(),
})
}
pub async fn post_login(
@@ -195,14 +198,11 @@ pub async fn get_register_page(
csrf_token: csrf.0,
page_rss_url: None,
};
let html = state
.html_renderer
.render_register_page(RegisterPageData {
ctx,
error: params.error.as_deref(),
})
.expect("register template failed");
Html(html).into_response()
render_page(RegisterTemplate {
ctx: &ctx,
error: params.error.as_deref(),
})
.into_response()
}
pub async fn post_register(
@@ -216,9 +216,9 @@ pub async fn post_register(
if crate::csrf::mismatch(&csrf, &form.csrf_token) {
return StatusCode::FORBIDDEN.into_response();
}
match application::use_cases::register_and_login::execute(
match application::auth::register_and_login::execute(
&state.app_ctx,
application::commands::RegisterAndLoginCommand {
application::auth::commands::RegisterAndLoginCommand {
email: form.email,
username: form.username,
password: form.password,
@@ -246,14 +246,10 @@ pub async fn get_new_review_page(
let mut ctx = build_page_context(&state, Some(user_id), csrf.0).await;
ctx.page_title = "Log a Review — Movies Diary".to_string();
ctx.canonical_url = format!("{}/reviews/new", state.app_ctx.config.base_url);
let html = state
.html_renderer
.render_new_review_page(NewReviewPageData {
ctx,
error: params.error.as_deref(),
})
.expect("new_review template failed");
Html(html)
render_page(NewReviewTemplate {
ctx: &ctx,
error: params.error.as_deref(),
})
}
pub async fn post_review(
@@ -374,7 +370,7 @@ pub async fn get_activity_feed(
_ => "date",
};
let query = application::queries::GetActivityFeedQuery {
let query = application::diary::queries::GetActivityFeedQuery {
limit,
offset,
sort_by: sort_by_str.parse().unwrap_or_default(),
@@ -387,26 +383,30 @@ pub async fn get_activity_feed(
filter_following,
};
match application::use_cases::get_activity_feed::execute(&state.app_ctx, query).await {
match application::diary::get_activity_feed::execute(&state.app_ctx, query).await {
Ok(entries) => {
let entry_limit = entries.limit;
let entry_offset = entries.offset;
let has_more =
(entry_offset as u64).saturating_add(entry_limit as u64) < entries.total_count;
let data = application::ports::ActivityFeedPageData {
ctx,
let total_pages = (entries.total_count as u32)
.saturating_add(entry_limit.saturating_sub(1))
.checked_div(entry_limit)
.unwrap_or(1);
let current_page = entry_offset.checked_div(entry_limit).unwrap_or(0);
let page_items = build_page_items(total_pages, current_page);
render_page(ActivityFeedTemplate {
entries: entries.items.as_slice(),
current_offset: entry_offset,
has_more,
limit: entry_limit,
entries,
has_more,
ctx: &ctx,
page_items,
filter: filter_str.to_string(),
sort_by: sort_by_str.to_string(),
search: params.search,
};
match state.html_renderer.render_activity_feed_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
})
.into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
@@ -421,32 +421,54 @@ pub async fn get_users_list(
ctx.page_title = "Members — Movies Diary".to_string();
ctx.canonical_url = format!("{}/users", state.app_ctx.config.base_url);
match application::use_cases::get_users::execute(
match application::users::get_users::execute(
&state.app_ctx,
application::queries::GetUsersQuery,
application::users::queries::GetUsersQuery,
)
.await
{
Ok(result) => {
let actor_views = result
.remote_actors
let users: Vec<UserSummaryView> = result
.users
.into_iter()
.map(|a| application::ports::RemoteActorView {
handle: a.handle,
display_name: a.display_name,
url: a.url,
avatar_url: None,
.map(|u| {
let name = u.email().split('@').next().unwrap_or("?").to_string();
let initial = name.chars().next().unwrap_or('?').to_ascii_uppercase();
let avg_display = u
.avg_rating
.map(|r| format!("{:.1}", r))
.unwrap_or_else(|| "".to_string());
let avatar_url = u.avatar_path.map(|p| format!("/images/{}", p));
UserSummaryView {
user_id: u.user_id.value(),
display_name: name,
initial,
avg_rating_display: avg_display,
total_movies: u.total_movies,
avatar_url,
}
})
.collect();
let data = application::ports::UsersPageData {
ctx,
users: result.users,
remote_actors: actor_views,
};
match state.html_renderer.render_users_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
let remote_actors: Vec<RemoteActorDisplay> = result
.remote_actors
.into_iter()
.map(|a| {
let display = a.display_name.unwrap_or_else(|| a.handle.clone());
let initial = display.chars().next().unwrap_or('?').to_ascii_uppercase();
RemoteActorDisplay {
handle: a.handle,
display_name: display,
initial,
url: a.url,
}
})
.collect();
render_page(UsersTemplate {
users,
ctx: &ctx,
remote_actors,
})
.into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
@@ -460,7 +482,7 @@ pub async fn get_user_by_username(
Ok(u) => u,
Err(_) => return StatusCode::NOT_FOUND.into_response(),
};
match state.app_ctx.user_repository.find_by_username(&uname).await {
match state.app_ctx.repos.user.find_by_username(&uname).await {
Ok(Some(user)) => {
axum::response::Redirect::permanent(&format!("/users/{}", user.id().value()))
.into_response()
@@ -505,7 +527,7 @@ pub async fn get_user_profile(
let mut ctx = build_page_context(&state, user_id.clone(), csrf.0).await;
let view_str = params.view.as_deref().unwrap_or("recent");
let profile_view = match application::queries::ProfileView::from_str(view_str) {
let profile_view = match application::users::queries::ProfileView::from_str(view_str) {
Ok(v) => v,
Err(_) => {
return (
@@ -518,7 +540,8 @@ pub async fn get_user_profile(
let profile_user = match state
.app_ctx
.user_repository
.repos
.user
.find_by_id(&domain::value_objects::UserId::from_uuid(profile_user_uuid))
.await
{
@@ -546,7 +569,7 @@ pub async fn get_user_profile(
.map(|u| u.value() == profile_user_uuid)
.unwrap_or(false);
let query = application::queries::GetUserProfileQuery {
let query = application::users::queries::GetUserProfileQuery {
user_id: profile_user_uuid,
view: profile_view,
limit: params.limit,
@@ -560,7 +583,7 @@ pub async fn get_user_profile(
is_own_profile,
};
match application::use_cases::get_user_profile::execute(&state.app_ctx, query).await {
match application::users::get_profile::execute(&state.app_ctx, query).await {
Ok(profile) => {
let (offset, has_more, limit) = profile
.entries
@@ -573,28 +596,80 @@ pub async fn get_user_profile(
if !is_own_profile {
ctx.page_rss_url = Some(format!("/users/{}/feed.rss", profile_user_uuid));
}
let pending_followers: Vec<application::ports::RemoteActorView> = profile
let email = profile_user.email().value().to_string();
let display_name = email.split('@').next().unwrap_or("?").to_string();
let avg_rating_display = profile
.stats
.avg_rating
.map(|r| format!("{:.1}", r))
.unwrap_or_else(|| "".to_string());
let favorite_director_display = profile
.stats
.favorite_director
.clone()
.unwrap_or_else(|| "".to_string());
let most_active_month_display = profile
.stats
.most_active_month
.clone()
.unwrap_or_else(|| "".to_string());
let heatmap = profile
.history
.as_deref()
.map(build_heatmap)
.unwrap_or_default();
let monthly_rating_rows: Vec<MonthlyRatingRow<'_>> = profile
.trends
.as_ref()
.map(|t| {
t.monthly_ratings
.iter()
.map(|r| MonthlyRatingRow {
rating: r,
bar_height_px: bar_height_px(r.avg_rating),
})
.collect()
})
.unwrap_or_default();
let total = profile
.entries
.as_ref()
.map(|e| e.total_count as u32)
.unwrap_or(0);
let total_pages = total
.saturating_add(limit.saturating_sub(1))
.checked_div(limit)
.unwrap_or(1);
let current_page = offset.checked_div(limit).unwrap_or(0);
let page_items = build_page_items(total_pages, current_page);
let pending_followers: Vec<RemoteActorData> = profile
.pending_followers
.into_iter()
.map(|p| application::ports::RemoteActorView {
.map(|p| RemoteActorData {
handle: p.handle,
url: p.url,
display_name: p.display_name,
avatar_url: p.avatar_url,
})
.collect();
let data = application::ports::ProfilePageData {
ctx,
render_page(ProfileTemplate {
ctx: &ctx,
profile_display_name: display_name,
profile_user_id: profile_user_uuid,
profile_user_email: profile_user.email().value().to_string(),
stats: profile.stats,
view: profile_view.as_str().to_string(),
entries: profile.entries,
stats: &profile.stats,
avg_rating_display,
favorite_director_display,
most_active_month_display,
view: profile_view.as_str(),
entries: profile.entries.as_ref(),
current_offset: offset,
has_more,
limit,
history: profile.history,
trends: profile.trends,
history: profile.history.as_ref(),
trends: profile.trends.as_ref(),
monthly_rating_rows,
heatmap,
page_items,
is_own_profile,
error: params.error,
following_count: profile.following_count,
@@ -602,11 +677,8 @@ pub async fn get_user_profile(
pending_followers,
sort_by: sort_by_str.to_string(),
search: params.search.clone(),
};
match state.html_renderer.render_profile_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
})
.into_response()
}
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
@@ -818,25 +890,22 @@ pub async fn get_following_page(
);
match state.ap_service.get_following(user_id.value()).await {
Ok(following) => {
let actors = following
let actors: Vec<RemoteActorData> = following
.into_iter()
.map(|a| RemoteActorView {
.map(|a| RemoteActorData {
handle: a.handle,
display_name: a.display_name,
url: a.url,
avatar_url: a.avatar_url.clone(),
})
.collect();
let data = FollowingPageData {
render_page(FollowingTemplate {
ctx,
user_id: profile_user_uuid,
actors,
error: params.error,
};
match state.html_renderer.render_following_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
})
.into_response()
}
Err(e) => {
tracing::error!("get_following error: {:?}", e);
@@ -872,25 +941,22 @@ pub async fn get_followers_page(
.await
{
Ok(followers) => {
let actors = followers
let actors: Vec<RemoteActorData> = followers
.into_iter()
.map(|a| RemoteActorView {
.map(|a| RemoteActorData {
handle: a.handle,
display_name: a.display_name,
url: a.url,
avatar_url: a.avatar_url.clone(),
})
.collect();
let data = FollowersPageData {
render_page(FollowersTemplate {
ctx,
user_id: profile_user_uuid,
actors,
error: params.error,
};
match state.html_renderer.render_followers_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
})
.into_response()
}
Err(e) => {
tracing::error!("get_followers error: {:?}", e);
@@ -985,25 +1051,21 @@ pub async fn get_movie_detail(
.unwrap_or(false),
None => false,
};
let data = MovieDetailPageData {
ctx,
movie: result.movie,
stats: result.stats,
profile: result.profile,
let current_offset = result.reviews.offset;
let reviews_limit = result.reviews.limit;
render_page(MovieDetailTemplate {
ctx: &ctx,
movie: &result.movie,
stats: &result.stats,
profile: result.profile.as_ref(),
reviews: result.reviews.items.as_slice(),
on_watchlist,
current_offset: result.reviews.offset,
current_offset,
has_more,
limit: result.reviews.limit,
reviews: result.reviews,
limit: reviews_limit,
histogram_max,
};
match state.html_renderer.render_movie_detail_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => {
tracing::error!("template error: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
.into_response()
}
}
}
@@ -1018,9 +1080,9 @@ pub async fn get_watchlist_page(
let ctx = build_page_context(&state, viewer_id.clone(), csrf.0).await;
let is_owner = viewer_id.map(|u| u.value() == owner_id).unwrap_or(false);
let result = match application::use_cases::get_watchlist_page::execute(
let result = match application::watchlist::get_page::execute(
&state.app_ctx,
application::queries::GetWatchlistQuery {
application::watchlist::queries::GetWatchlistQuery {
user_id: owner_id,
limit: params.limit.or(Some(20)),
offset: params.offset.or(Some(0)),
@@ -1036,23 +1098,17 @@ pub async fn get_watchlist_page(
}
};
let data = WatchlistPageData {
ctx,
render_page(WatchlistTemplate {
ctx: &ctx,
owner_id,
display_entries: result.display_entries,
display_entries: &result.display_entries,
current_offset: result.current_offset,
has_more: result.has_more,
limit: result.limit,
is_owner,
error: params.error,
};
match state.html_renderer.render_watchlist_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => {
tracing::error!("watchlist template error: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
})
.into_response()
}
pub async fn post_watchlist_add(
@@ -1180,7 +1236,7 @@ pub async fn get_profile_settings(
ctx.page_title = "Profile Settings — Movies Diary".to_string();
ctx.canonical_url = format!("{}/settings/profile", state.app_ctx.config.base_url);
let user = match state.app_ctx.user_repository.find_by_id(&user_id).await {
let user = match state.app_ctx.repos.user.find_by_id(&user_id).await {
Ok(Some(u)) => u,
Ok(None) => return StatusCode::NOT_FOUND.into_response(),
Err(e) => {
@@ -1197,9 +1253,10 @@ pub async fn get_profile_settings(
.banner_path()
.map(|path| format!("{}/images/{}", base_url, path));
let profile_fields = state
let profile_fields: Vec<(String, String)> = state
.app_ctx
.profile_fields_repository
.repos
.profile_fields
.get_fields(&user_id)
.await
.unwrap_or_default()
@@ -1209,23 +1266,19 @@ pub async fn get_profile_settings(
let saved = params.saved.as_deref() == Some("1");
let data = ProfileSettingsPageData {
ctx,
bio: user.bio().map(|s| s.to_string()),
avatar_url,
banner_url,
also_known_as: user.also_known_as().map(|s| s.to_string()),
profile_fields,
saved,
};
let bio = user.bio().map(|s| s.to_string());
let also_known_as = user.also_known_as().map(|s| s.to_string());
match state.html_renderer.render_profile_settings_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => {
tracing::error!("profile_settings template error: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
render_page(ProfileSettingsTemplate {
ctx: &ctx,
bio: bio.as_deref(),
avatar_url: avatar_url.as_deref(),
banner_url: banner_url.as_deref(),
also_known_as: also_known_as.as_deref(),
profile_fields: &profile_fields,
saved,
})
.into_response()
}
pub async fn get_tag(Path(tag): Path<String>) -> impl IntoResponse {
@@ -1247,21 +1300,19 @@ pub async fn get_blocked_domains_page(
ctx.canonical_url = format!("{}/admin/blocked-domains", state.app_ctx.config.base_url);
match state.ap_service.get_blocked_domains().await {
Ok(domains) => {
let data = BlockedDomainsPageData {
ctx,
domains: domains
.into_iter()
.map(|d| BlockedDomainEntry {
domain: d.domain,
reason: d.reason,
blocked_at: d.blocked_at,
})
.collect(),
};
match state.html_renderer.render_blocked_domains_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
let entries: Vec<template_askama::BlockedDomainEntry> = domains
.into_iter()
.map(|d| template_askama::BlockedDomainEntry {
domain: d.domain,
reason: d.reason,
blocked_at: d.blocked_at,
})
.collect();
render_page(BlockedDomainsTemplate {
ctx: &ctx,
domains: &entries,
})
.into_response()
}
Err(e) => {
tracing::error!("get_blocked_domains error: {:?}", e);
@@ -1328,22 +1379,20 @@ pub async fn get_blocked_actors_page(
ctx.canonical_url = format!("{}/social/blocked", state.app_ctx.config.base_url);
match state.ap_service.get_blocked_actors(user_id.value()).await {
Ok(actors) => {
let data = BlockedActorsPageData {
ctx,
actors: actors
.into_iter()
.map(|a| BlockedActorEntry {
url: a.url,
handle: a.handle,
display_name: a.display_name,
avatar_url: a.avatar_url,
})
.collect(),
};
match state.html_renderer.render_blocked_actors_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
}
let entries: Vec<template_askama::BlockedActorEntry> = actors
.into_iter()
.map(|a| template_askama::BlockedActorEntry {
url: a.url,
handle: a.handle,
display_name: a.display_name,
avatar_url: a.avatar_url,
})
.collect();
render_page(BlockedActorsTemplate {
ctx: &ctx,
actors: &entries,
})
.into_response()
}
Err(e) => {
tracing::error!("get_blocked_actors error: {:?}", e);
@@ -1475,7 +1524,7 @@ pub async fn post_profile_settings(
}
}
let cmd = application::commands::UpdateProfileCommand {
let cmd = application::users::commands::UpdateProfileCommand {
user_id: user_id.value(),
display_name,
bio,
@@ -1498,7 +1547,7 @@ pub async fn post_profile_settings(
})
.collect();
let fields_cmd = application::commands::UpdateProfileFieldsCommand {
let fields_cmd = application::users::commands::UpdateProfileFieldsCommand {
user_id: user_id.value(),
fields,
};
@@ -1526,9 +1575,9 @@ pub async fn get_integrations_page(
.await
.unwrap_or_default();
let token_views: Vec<WebhookTokenView> = tokens
let token_views: Vec<template_askama::WebhookTokenView> = tokens
.into_iter()
.map(|t| WebhookTokenView {
.map(|t| template_askama::WebhookTokenView {
id: t.id().value().to_string(),
provider: t.provider().to_string(),
label: t.label().map(String::from),
@@ -1539,20 +1588,14 @@ pub async fn get_integrations_page(
})
.collect();
let data = IntegrationsPageData {
ctx,
tokens: token_views,
webhook_base_url: state.app_ctx.config.base_url.clone(),
new_token: params.token,
};
match state.html_renderer.render_integrations_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => {
tracing::error!("integrations template error: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
let webhook_base_url = state.app_ctx.config.base_url.clone();
render_page(IntegrationsTemplate {
ctx: &ctx,
tokens: &token_views,
webhook_base_url: &webhook_base_url,
new_token: params.token.as_deref(),
})
.into_response()
}
pub async fn post_generate_token(
@@ -1632,9 +1675,9 @@ pub async fn get_watch_queue_page(
.await
.unwrap_or_default();
let entries: Vec<WatchQueueDisplayEntry> = events
let entries: Vec<template_askama::WatchQueueDisplayEntry> = events
.into_iter()
.map(|e| WatchQueueDisplayEntry {
.map(|e| template_askama::WatchQueueDisplayEntry {
id: e.id().value().to_string(),
title: e.title().to_string(),
year: e.year(),
@@ -1644,19 +1687,12 @@ pub async fn get_watch_queue_page(
})
.collect();
let data = WatchQueuePageData {
ctx,
entries,
error: params.error,
};
match state.html_renderer.render_watch_queue_page(data) {
Ok(html) => Html(html).into_response(),
Err(e) => {
tracing::error!("watch_queue template error: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
render_page(WatchQueueTemplate {
ctx: &ctx,
entries: &entries,
error: params.error.as_deref(),
})
.into_response()
}
pub async fn post_confirm_single(