Skip to main content

arminius_lib_sync/
messages.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Implements APIs for getting messages.
6
7#![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
12use arminius_lib_authentication::AuthenticatedClaims;
13use arminius_lib_component::{
14	MessageTokenParseError, MessagesToken,
15	error::{InternalServerError, MatrixApiError, RoomNotFound},
16	ruma_requests::{ArminiusResponse, RumaRequest},
17};
18use arminius_lib_db::{Database as _, DefaultDb};
19use arminius_lib_db_room::EventQueries as _;
20use arminius_lib_db_sync::SyncQueries as _;
21use arminius_lib_events::ContextualizedFields as _;
22use axum::extract::State;
23use ruma::{
24	api::{Direction, client::message::get_message_events},
25	serde::Raw,
26};
27
28use crate::sync_token::SyncTokenParseError;
29
30#[allow(
31	missing_docs,
32	clippy::missing_docs_in_private_items,
33	clippy::enum_variant_names,
34	reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
35)]
36#[derive(thiserror::Error, Debug, MatrixApiError, arminius_lib_macros::FromDbError)]
37pub(crate) enum MessagesError {
38	#[error(transparent)]
39	InternalServerError(#[from] InternalServerError),
40	#[error(transparent)]
41	SyncTokenParseError(#[from] SyncTokenParseError),
42	#[error(transparent)]
43	MessagesTokenParseError(#[from] MessageTokenParseError),
44	#[error(transparent)]
45	RoomNotFound(#[from] RoomNotFound),
46}
47
48/// Handler for `GET /_matrix/client/v3/rooms/{roomId}/messages` (See
49/// <https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3roomsroomidmessages>).
50///
51/// Endpoint that returns messages for a specific room.
52pub(crate) async fn get_message_events_handler(
53	mut database: State<DefaultDb>,
54	AuthenticatedClaims(claims): AuthenticatedClaims,
55	RumaRequest(request): RumaRequest<get_message_events::v3::Request>,
56) -> Result<ArminiusResponse<get_message_events::v3::Response>, MessagesError> {
57	tracing::info!(?request, "got /messages request");
58
59	let mut response = get_message_events::v3::Response::new();
60	let event_id = request
61		.from
62		.as_deref()
63		.map(MessagesToken::try_from)
64		.transpose()?
65		.map(|MessagesToken(event_id)| event_id);
66
67	let mut transaction = database.begin_transaction().await?;
68
69	let (events, there_are_more) = match request.dir {
70		Direction::Backward => {
71			transaction
72				.fetch_room_messages_backwards(
73					&claims.user_id,
74					&request.room_id,
75					event_id.as_deref(),
76					request.limit,
77					&request.filter,
78				)
79				.await?
80		}
81		Direction::Forward => {
82			transaction
83				.fetch_room_messages_forwards(
84					&claims.user_id,
85					&request.room_id,
86					event_id.as_deref(),
87					request.limit,
88					&request.filter,
89				)
90				.await?
91		}
92	};
93
94	// FIXME(#680): Remove before release, very GDPR unfriendly
95	tracing::info!("Got events from db {events:?}");
96
97	if there_are_more {
98		response.end =
99			events.last().map(|event| String::from(&MessagesToken(event.event_id().to_owned())));
100	}
101
102	for event in events {
103		let client_context = transaction
104			.fetch_client_context(
105				&request.room_id,
106				event.event_id(),
107				&claims.user_id,
108				&claims.device_id,
109			)
110			.await?;
111		let client_event = event.to_client_event(client_context);
112		response.chunk.push(Raw::from_json(client_event));
113	}
114
115	transaction.commit().await?;
116	Ok(ArminiusResponse(response))
117}