//! Thread tracking handlers — track, untrack, tracked threads list. use axum::{ extract::Path, http::StatusCode, response::{IntoResponse, Redirect, Response}, }; use tower_sessions::Session; use crate::auth::MaybeUser; use crate::csrf; use crate::templates::*; use crate::AppState; use super::{parse_uuid, template_user}; /// POST /p/{slug}/{cat}/{thread_id}/track #[tracing::instrument(skip_all)] pub(super) async fn track_thread_handler( axum::extract::State(state): axum::extract::State, Path((slug, category_slug, thread_id_str)): Path<(String, String, String)>, MaybeUser(session_user): MaybeUser, ) -> Result { let user = session_user .ok_or_else(|| Redirect::to("/auth/login").into_response())?; let thread_id = parse_uuid(&thread_id_str)?; mt_db::mutations::track_thread(&state.db, user.user_id, thread_id) .await .map_err(|e| { tracing::error!(error = ?e, "db error tracking thread"); StatusCode::INTERNAL_SERVER_ERROR.into_response() })?; Ok(Redirect::to(&format!( "/p/{slug}/{category_slug}/{thread_id_str}?toast=Thread+tracked" ))) } /// POST /p/{slug}/{cat}/{thread_id}/untrack #[tracing::instrument(skip_all)] pub(super) async fn untrack_thread_handler( axum::extract::State(state): axum::extract::State, Path((slug, category_slug, thread_id_str)): Path<(String, String, String)>, MaybeUser(session_user): MaybeUser, ) -> Result { let user = session_user .ok_or_else(|| Redirect::to("/auth/login").into_response())?; let thread_id = parse_uuid(&thread_id_str)?; mt_db::mutations::untrack_thread(&state.db, user.user_id, thread_id) .await .map_err(|e| { tracing::error!(error = ?e, "db error untracking thread"); StatusCode::INTERNAL_SERVER_ERROR.into_response() })?; Ok(Redirect::to(&format!( "/p/{slug}/{category_slug}/{thread_id_str}?toast=Thread+untracked" ))) } /// POST /tracked/stop-all #[tracing::instrument(skip_all)] pub(super) async fn untrack_all_handler( axum::extract::State(state): axum::extract::State, MaybeUser(session_user): MaybeUser, ) -> Result { let user = session_user .ok_or_else(|| Redirect::to("/auth/login").into_response())?; mt_db::mutations::untrack_all(&state.db, user.user_id) .await .map_err(|e| { tracing::error!(error = ?e, "db error untracking all"); StatusCode::INTERNAL_SERVER_ERROR.into_response() })?; Ok(Redirect::to("/tracked?toast=Stopped+tracking+all")) } /// GET /about/tracking — privacy/tracking info page #[tracing::instrument(skip_all)] pub(super) async fn tracking_info_page( axum::extract::State(state): axum::extract::State, session: Session, MaybeUser(session_user): MaybeUser, ) -> impl IntoResponse { let csrf_token = Some(csrf::get_or_create_token(&session).await); let session_user = session_user.as_ref().map(|u| template_user(u, state.config.platform_admin_id)); TrackingInfoTemplate { csrf_token, session_user, mnw_base_url: state.config.mnw_base_url.clone(), } } /// GET /tracked — tracked threads page #[tracing::instrument(skip_all)] pub(super) async fn tracked_threads_page( axum::extract::State(state): axum::extract::State, session: Session, MaybeUser(session_user): MaybeUser, ) -> Result { let csrf_token = Some(csrf::get_or_create_token(&session).await); let user = session_user .ok_or_else(|| Redirect::to("/auth/login").into_response())?; let db_tracked = mt_db::queries::list_tracked_threads(&state.db, user.user_id) .await .map_err(|e| { tracing::error!(error = ?e, "db error listing tracked threads"); StatusCode::INTERNAL_SERVER_ERROR.into_response() })?; let threads = db_tracked .into_iter() .map(|t| TrackedThreadViewRow { thread_id: t.thread_id.to_string(), thread_title: t.thread_title, community_name: t.community_name, community_slug: t.community_slug, category_slug: t.category_slug, unread_count: t.unread_count.max(0) as u32, has_mention: t.has_mention, }) .collect(); Ok(TrackedThreadsTemplate { csrf_token, session_user: Some(template_user(&user, state.config.platform_admin_id)), mnw_base_url: state.config.mnw_base_url.clone(), threads, }) }