arminius_server_client/handlers/
filter.rs1#![allow(
8 clippy::allow_attributes,
9 reason = "This source file contains enums which allow `clippy::missing_docs_in_private_items`. But an exception to this lint will mistakenly report that it's unfulfilled when used with expect, even though it is not."
10)]
11
12mod tests;
13
14use arminius_lib_authentication::SessionInfo;
15use arminius_lib_component::{
16 Component, ServerState,
17 error::{
18 FeatureNotImplemented, InputJsonError, InternalServerError, MatrixApiError, ResultExt as _,
19 },
20 ruma_requests::ArminiusResponse,
21};
22use arminius_lib_db::DefaultDb;
23use arminius_lib_db_filters::FilterQueries as _;
24use axum::{
25 Router,
26 extract::State,
27 routing::{get, post},
28};
29use reqwest::StatusCode;
30use ruma::api::{
31 client::filter::{create_filter, get_filter},
32 error::ErrorKind,
33};
34
35use crate::client_request::AuthenticatedClientRequest;
36
37#[allow(
39 missing_docs,
40 clippy::missing_docs_in_private_items,
41 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
42)]
43#[derive(Debug, thiserror::Error, MatrixApiError)]
44enum FilterError {
45 #[error(transparent)]
46 InternalServerError(#[from] InternalServerError),
47
48 #[error(transparent)]
49 FeatureNotImplemented(#[from] FeatureNotImplemented),
50
51 #[error(transparent)]
52 InputJsonError(#[from] InputJsonError),
53
54 #[error("The user ID in the param does not match the user ID of the authenticated user")]
57 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
58 UserIdMismatch,
59
60 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam)]
64 #[error("The filter ID provided is not a valid filter ID")]
65 InvalidFilterId,
66
67 #[error("Filter not found")]
69 #[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound)]
70 UnknownFilter,
71}
72
73async fn create_filter_handler(
78 mut database: State<DefaultDb>,
79 AuthenticatedClientRequest(SessionInfo { user_id, device_id }, request): AuthenticatedClientRequest<create_filter::v3::Request>,
80) -> Result<ArminiusResponse<create_filter::v3::Response>, FilterError> {
81 if request.user_id != user_id {
82 return Err(FilterError::UserIdMismatch);
83 }
84
85 let filter_id = database
86 .insert_filter(&user_id, &device_id, request.filter.clone())
87 .await
88 .unrecoverable_error("Fetching filter from database failed")?
89 .to_string();
90
91 Ok(create_filter::v3::Response::new(filter_id).into())
92}
93
94async fn get_filter_handler(
99 mut database: State<DefaultDb>,
100 AuthenticatedClientRequest(SessionInfo { user_id, .. }, request): AuthenticatedClientRequest<
101 get_filter::v3::Request,
102 >,
103) -> Result<ArminiusResponse<get_filter::v3::Response>, FilterError> {
104 if request.user_id != user_id {
105 return Err(FilterError::UserIdMismatch);
106 }
107
108 let filter_id = request.filter_id.parse().map_err(|_| FilterError::InvalidFilterId)?;
109
110 let filter = database
111 .fetch_filter_with_id(filter_id, &user_id)
112 .await
113 .unrecoverable_error("Fetching filter from the database failed")?;
114
115 let Some(filter) = filter else { return Err(FilterError::UnknownFilter) };
116
117 Ok(ArminiusResponse::from(get_filter::v3::Response::new(filter)))
118}
119
120#[derive(Debug)]
122pub struct FilterComponent;
123
124impl Component for FilterComponent {
125 fn router() -> Router<ServerState> {
126 Router::new()
127 .route("/_matrix/client/v3/user/{userId}/filter", post(create_filter_handler))
128 .route("/_matrix/client/v3/user/{userId}/filter/{filterId}", get(get_filter_handler))
129 }
130}