// SPDX-License-Identifier: AGPL-3.0-or-later use crate::{ acl, api::client::{AdminApiClient, ApiResultExt}, middleware::{auth::AuthContext, csrf::CsrfToken}, state::AppState, templates, }; use axum::{ Router, extract::{Query, State}, response::{Html, IntoResponse, Response}, routing::get, }; use serde::Deserialize; #[derive(Deserialize)] struct MessagesQuery { channel_id: Option, message_id: Option, attachment_id: Option, filename: Option, context_limit: Option, before: Option, after: Option, search: Option, } #[derive(Deserialize)] struct ArchivesPageQuery { subject_type: Option, subject_id: Option, } pub fn router() -> Router { Router::new() .route( "/messages", get(messages_page).post(super::message_actions::messages_post), ) .route( "/messages/browse-fragment", get(super::message_actions::messages_browse_fragment), ) .route( "/system-dms", get(system_dms_page).post(super::message_actions::system_dms_post), ) .route("/archives", get(archives_page)) .route( "/archives/download", get(super::message_actions::archives_download), ) .route( "/bulk-actions", get(bulk_actions_page).post(super::message_actions::bulk_actions_post), ) } async fn messages_page( State(state): State, auth: axum::Extension, csrf: axum::Extension, Query(query): Query, ) -> Response { let config = state.config(); let admin_acls = auth .0 .admin_user .as_ref() .map(|user| user.acls.as_slice()) .unwrap_or(&[]); let can_lookup = acl::has_permission(admin_acls, acl::MESSAGE_LOOKUP); let can_delete = acl::has_permission(admin_acls, acl::MESSAGE_DELETE); let client = AdminApiClient::new(state.http_client(), config, &auth.0.session); let channel_id = query.channel_id.as_deref().filter(|s| !s.is_empty()); let message_id = query.message_id.as_deref().filter(|s| !s.is_empty()); let attachment_id = query.attachment_id.as_deref().filter(|s| !s.is_empty()); let filename = query.filename.as_deref().filter(|s| !s.is_empty()); let before = query.before.as_deref().filter(|s| !s.is_empty()); let after = query.after.as_deref().filter(|s| !s.is_empty()); let search = query.search.as_deref().filter(|s| !s.is_empty()); let context_limit = super::message_actions::parse_context_limit(query.context_limit.as_deref()); let mut lookup_result = None; let mut browse_result = None; let mut search_result = None; let mut error = None; if let (Some(cid), Some(aid), Some(name)) = (channel_id, attachment_id, filename) { lookup_result = match client .lookup_message_by_attachment(cid, aid, name, context_limit) .await { Ok(response) => Some(response.data), Err(err) => { tracing::warn!(%err, channel_id = cid, attachment_id = aid, "admin API request failed: lookup message by attachment"); error = Some("Failed to look up message by attachment.".to_owned()); None } }; } else if let (Some(cid), Some(mid)) = (channel_id, message_id) { lookup_result = match client.lookup_message(cid, mid, context_limit).await { Ok(response) => Some(response.data), Err(err) => { tracing::warn!(%err, channel_id = cid, message_id = mid, "admin API request failed: lookup message"); error = Some("Failed to look up message.".to_owned()); None } }; } else if let (Some(cid), Some(q)) = (channel_id, search) { search_result = match client.search_channel_messages(cid, q, None).await { Ok(response) => Some(response.data), Err(err) => { tracing::warn!(%err, channel_id = cid, "admin API request failed: search channel messages"); error = Some("Failed to search channel messages.".to_owned()); None } }; } else if let Some(cid) = channel_id { browse_result = match client .browse_channel(cid, before, after, None) .await .log_error("browse channel messages") { Some(response) => Some(response.data), None => { error = Some("Failed to browse channel messages.".to_owned()); None } }; } let markup = templates::pages::messages_page::messages_page( config, &auth.0, &templates::pages::messages_page::MessagesPageParams { csrf_token: &csrf.0.0, prefill_channel_id: channel_id, can_lookup, can_delete, lookup_result: lookup_result.as_ref(), browse_result: browse_result.as_ref(), search_result: search_result.as_ref(), browse_channel_id: channel_id, search_query_text: search, context_limit, error: error.as_deref(), }, ); Html(markup.into_string()).into_response() } async fn system_dms_page( State(state): State, auth: axum::Extension, csrf: axum::Extension, ) -> Response { let config = state.config(); let markup = templates::pages::system_dm::system_dm_page( config, &auth.0, &templates::pages::system_dm::SystemDmParams { form_error: None, csrf_token: &csrf.0.0, }, ); Html(markup.into_string()).into_response() } async fn archives_page( State(state): State, auth: axum::Extension, Query(query): Query, ) -> Response { let config = state.config(); let subject_type = query.subject_type.as_deref().unwrap_or("all"); let subject_id = query.subject_id.as_deref().filter(|s| !s.is_empty()); let client = AdminApiClient::new(state.http_client(), config, &auth.0.session); let result = client .list_archives(subject_type, subject_id, false, None) .await; let (archives, error) = match result { Ok(resp) => (resp.archives, None), Err(e) => (vec![], Some(format!("{e}"))), }; let markup = templates::pages::archives::archives_page( config, &auth.0, subject_type, subject_id, &archives, error.as_deref(), ); Html(markup.into_string()).into_response() } async fn bulk_actions_page( State(state): State, auth: axum::Extension, csrf: axum::Extension, ) -> Response { let config = state.config(); let markup = templates::pages::bulk_actions::bulk_actions_page(config, &auth.0, &csrf.0.0); Html(markup.into_string()).into_response() }