Skip to main content

arminius_server_client/handlers/
room.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Implements APIs for room management from the matrix spec
6
7use arminius_lib_component::{
8	Component, ServerState,
9	error::{
10		FeatureNotImplemented, InputJsonError, InternalServerError, MatrixApiError, ResultExt as _,
11	},
12	ruma_requests::ArminiusResponse,
13};
14use arminius_lib_db::DefaultDb;
15use arminius_lib_db_room::RoomQueries as _;
16use arminius_lib_db_user::UserQueries as _;
17use arminius_lib_macros::FromNatsError;
18use arminius_lib_room_service_messages::EventProvenance;
19use arminius_lib_roomservice_client::{
20	requests::{
21		AddNewEvent, AddNewEventError, AddRoomMemberEvent, AddRoomMemberEventError,
22		AddRoomMemberEventRequest, CreateRoom, CreateRoomRequest, ErrorOf, ForgetRoom,
23		GetRoomEvent, GetRoomMembers, GetStateEvents, GetStateEventsForKey, NewEvent,
24		ReadMarkerError, ReadMarkerRequest, ReadReceipt, ReadReceiptError, ReadReceiptRequest,
25		RedactEvent, SetFullyReadMarker, TransactionInformation,
26	},
27	roomserver_connection::RoomServerConnection,
28	ruma_request_wrapper::SerializingRumaRequestWrapper,
29};
30use axum::{
31	Router,
32	extract::State,
33	routing::{get, post, put},
34};
35use ruma::{
36	OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
37	api::client::{
38		knock::knock_room,
39		membership::{
40			ban_user, forget_room, get_member_events,
41			invite_user::{
42				self,
43				v3::{InvitationRecipient, InviteUserId},
44			},
45			join_room_by_id, join_room_by_id_or_alias, joined_rooms, kick_user, leave_room,
46			unban_user,
47		},
48		message::send_message_event,
49		read_marker::set_read_marker,
50		receipt::create_receipt,
51		redact::redact_event,
52		room::{create_room, get_room_event},
53		state::{get_state_event_for_key, get_state_events, send_state_event},
54	},
55	events::{
56		receipt::ReceiptType,
57		room::member::{MembershipState, RoomMemberEventContent},
58	},
59};
60use serde::{Deserialize, Serialize};
61
62use crate::client_request::AuthenticatedClientRequest;
63
64/// Compute the via from the request, and append the server name
65fn append_server_to_via(mut via: Vec<OwnedServerName>, room_id: &RoomId) -> Vec<OwnedServerName> {
66	if let Some(room_server_name) = room_id.server_name()
67		&& !via.iter().any(|server_name| server_name == room_server_name)
68	{
69		via.push(room_server_name.to_owned());
70	}
71	via
72}
73
74/// Create the event content of a membership event
75///
76/// Contains profile information
77async fn create_member_event_content(
78	mut database: DefaultDb,
79	membership: MembershipState,
80	user_id: OwnedUserId,
81	reason: Option<String>,
82) -> Result<RoomMemberEventContent, InternalServerError> {
83	let mut event_content = RoomMemberEventContent::new(membership);
84	// If it's a local user, we can populate their displayname from our
85	// database. If we're inviting someone over federation, their username
86	// won't necessarily be in our database
87	if let Some(user_info) = database
88		.fetch_user(&user_id)
89		.await
90		.unrecoverable_error("Could not fetch user from database")?
91	{
92		event_content.displayname = user_info.display_name;
93	}
94
95	event_content.reason = reason;
96	Ok(event_content)
97}
98
99/// Handler for `POST /_matrix/client/v3/createRoom` (See
100/// <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom>).
101///
102/// This endpoint is used to create a room.
103async fn create_room_handler(
104	State(state): State<ServerState>,
105	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
106		create_room::v3::Request,
107	>,
108) -> Result<ArminiusResponse<create_room::v3::Response>, ErrorOf<CreateRoom>> {
109	let request = CreateRoomRequest {
110		creator: claims.user_id.clone(),
111		request: SerializingRumaRequestWrapper(request),
112		creator_join_event_contents: create_member_event_content(
113			state.database,
114			MembershipState::Join,
115			claims.user_id,
116			None,
117		)
118		.await?,
119	};
120	let room_id = RoomServerConnection::new(&state.nats_connection)
121		.make_roomserver_request::<CreateRoom>(None, request)
122		.await??;
123	Ok(create_room::v3::Response::new(room_id).into())
124}
125
126/// Handler for `GET /_matrix/client/v3/rooms/{roomId}/event/{eventId}` (See
127/// <https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3roomsroomideventeventid>).
128///
129/// This endpoint is used to get a specific event from a room.
130async fn get_room_event_handler(
131	room_server: RoomServerConnection,
132	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
133		get_room_event::v3::Request,
134	>,
135) -> Result<ArminiusResponse<get_room_event::v3::Response>, ErrorOf<GetRoomEvent>> {
136	Ok(get_room_event::v3::Response::new(
137		room_server
138			.make_roomserver_request::<GetRoomEvent>(
139				Some(request.room_id.clone()),
140				(claims.user_id, claims.device_id, request.event_id),
141			)
142			.await
143			.map_err(InternalServerError::from)??,
144	)
145	.into())
146}
147
148/// Errors that occur when joining a room
149#[derive(thiserror::Error, MatrixApiError, Debug, Serialize, Deserialize, FromNatsError)]
150enum JoinRoomByIdError {
151	/// Unrecoverable error.
152	/// See <https://bundesmessenger.gitlab.io/backend/neo/arminius/arminius/03-development/errors.html#unrecoverable-errors>
153	#[error(transparent)]
154	InternalServerError(#[from] InternalServerError),
155	/// Error when joining the room
156	#[error(transparent)]
157	AddRoomMemberEventError(#[from] AddRoomMemberEventError),
158	/// When somebody tries to use an alias
159	#[error(transparent)]
160	FeatureNotImplemented(#[from] FeatureNotImplemented),
161}
162
163/// Add a joined event to a room for the user, with the correct username and ID
164async fn join_room(
165	state: ServerState,
166	user_id: OwnedUserId,
167	reason: Option<String>,
168	room_id: OwnedRoomId,
169	via: Vec<OwnedServerName>,
170) -> Result<(), JoinRoomByIdError> {
171	RoomServerConnection::new(&state.nats_connection)
172		.make_roomserver_request::<AddRoomMemberEvent>(
173			Some(room_id),
174			AddRoomMemberEventRequest {
175				via,
176				member_event_content: create_member_event_content(
177					state.database,
178					MembershipState::Join,
179					user_id.clone(),
180					reason,
181				)
182				.await?,
183				member: user_id.clone(),
184				sender: user_id,
185			},
186		)
187		.await??;
188	Ok(())
189}
190
191/// Handler for `POST /_matrix/client/v3/join/{roomIdOrAlias}` (See
192/// <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3joinroomidoralias>).
193///
194/// This endpoint is used to make a user join a room with using its alias or id.
195///
196/// NOTE: For now, room aliases are not supported, so this endpoint will only
197/// work with a room id.
198async fn join_room_by_id_or_alias_handler(
199	State(state): State<ServerState>,
200	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
201		join_room_by_id_or_alias::v3::Request,
202	>,
203) -> Result<ArminiusResponse<join_room_by_id_or_alias::v3::Response>, JoinRoomByIdError> {
204	let room_id = RoomId::parse(&request.room_id_or_alias)
205		// TODO(#268): Get the room by its alias once room aliases are supported.
206		.map_err(|_| FeatureNotImplemented("Room Aliases".to_owned()))?;
207
208	join_room(
209		state,
210		claims.user_id,
211		request.reason,
212		room_id.clone(),
213		append_server_to_via(request.via, &room_id),
214	)
215	.await?;
216	Ok(join_room_by_id_or_alias::v3::Response::new(room_id).into())
217}
218
219/// Handler for	`POST /_matrix/client/v3/rooms/{roomId}/join` (See
220/// <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidjoin>).
221///
222/// This endpoint is used to join a room by its ID.
223async fn join_room_by_id_handler(
224	State(state): State<ServerState>,
225	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
226		join_room_by_id::v3::Request,
227	>,
228) -> Result<ArminiusResponse<join_room_by_id::v3::Response>, JoinRoomByIdError> {
229	join_room(
230		state,
231		claims.user_id,
232		request.reason,
233		request.room_id.clone(),
234		append_server_to_via(vec![], &request.room_id),
235	)
236	.await?;
237	Ok(join_room_by_id::v3::Response::new(request.room_id).into())
238}
239
240/// Handler for `GET /_matrix/client/v3/rooms/{roomId}/members`
241/// (See <https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv3roomsroomidmembers>)
242///
243/// Endpoint used to get the list of members of a room.
244async fn get_member_events_handler(
245	room_server: RoomServerConnection,
246	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
247		get_member_events::v3::Request,
248	>,
249) -> Result<ArminiusResponse<get_member_events::v3::Response>, ErrorOf<GetRoomMembers>> {
250	Ok(room_server
251		.make_roomserver_request::<GetRoomMembers>(
252			Some(request.room_id.clone()),
253			(SerializingRumaRequestWrapper(request), claims.user_id),
254		)
255		.await??
256		.0
257		.into())
258}
259
260/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/leave` (See
261/// <https://spec.matrix.org/v1.14/client-server-api/#post_matrixclientv3roomsroomidleave>).
262///
263/// Endpoint used to leave a room.
264async fn leave_room_handler(
265	State(state): State<ServerState>,
266	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
267		leave_room::v3::Request,
268	>,
269) -> Result<ArminiusResponse<leave_room::v3::Response>, AddRoomMemberEventError> {
270	RoomServerConnection::new(&state.nats_connection)
271		.make_roomserver_request::<AddRoomMemberEvent>(
272			Some(request.room_id.clone()),
273			AddRoomMemberEventRequest {
274				via: append_server_to_via(vec![], &request.room_id),
275				member_event_content: create_member_event_content(
276					state.database,
277					MembershipState::Leave,
278					claims.user_id.clone(),
279					request.reason,
280				)
281				.await?,
282				member: claims.user_id.clone(),
283				sender: claims.user_id,
284			},
285		)
286		.await??;
287
288	Ok(leave_room::v3::Response::new().into())
289}
290
291/// Handler for
292/// `PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}` (See
293/// <https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey>).
294///
295/// Endpoint used to send a state event in a room.
296async fn send_state_event_handler(
297	room_server: RoomServerConnection,
298	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
299		send_state_event::v3::Request,
300	>,
301) -> Result<ArminiusResponse<send_state_event::v3::Response>, AddNewEventError> {
302	let event_id = room_server
303		.make_roomserver_request::<AddNewEvent>(
304			Some(request.room_id.clone()),
305			NewEvent {
306				event_type: request.event_type.into(),
307				sender: claims.user_id,
308				body: request
309					.body
310					.deserialize_as()
311					.map_err(InputJsonError::from)
312					.map_err(AddNewEventError::from)?,
313				state_key: Some(request.state_key),
314				timestamp: None,
315				transaction_information: None,
316			},
317		)
318		.await??;
319
320	Ok(send_state_event::v3::Response::new(event_id).into())
321}
322
323/// Handler for `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
324/// (See <https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid>).
325///
326/// This is the endpoint used to send message events into rooms.
327async fn send_message_event_handler(
328	room_server: RoomServerConnection,
329	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
330		send_message_event::v3::Request,
331	>,
332) -> Result<ArminiusResponse<send_message_event::v3::Response>, AddNewEventError> {
333	let event_id = room_server
334		.make_roomserver_request::<AddNewEvent>(
335			Some(request.room_id.clone()),
336			NewEvent {
337				event_type: request.event_type.into(),
338				sender: claims.user_id,
339				body: request.body.deserialize_as().map_err(InputJsonError::from)?,
340				state_key: None,
341				timestamp: None,
342				transaction_information: Some(TransactionInformation {
343					device_id: claims.device_id,
344					transaction_id: request.txn_id,
345				}),
346			},
347		)
348		.await??;
349
350	Ok(send_message_event::v3::Response::new(event_id).into())
351}
352
353/// Handler for `GET /_matrix/client/v3/rooms/{roomId}/state` (See
354/// <https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3roomsroomidstate>).
355///
356/// This endpoint is used to fetch the state of a room.
357async fn get_state_events_handler(
358	room_server: RoomServerConnection,
359	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
360		get_state_events::v3::Request,
361	>,
362) -> Result<ArminiusResponse<get_state_events::v3::Response>, ErrorOf<GetStateEvents>> {
363	Ok(room_server
364		.make_roomserver_request::<GetStateEvents>(
365			Some(request.room_id.clone()),
366			(SerializingRumaRequestWrapper(request), claims.user_id),
367		)
368		.await??
369		.0
370		.into())
371}
372
373/// Handler for
374/// `GET /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}` (See
375/// <https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3roomsroomidstateeventtypestatekey>).
376///
377/// This is the endpoint used to fetch a single state event of a room using its
378/// type and optionally state key.
379async fn get_state_events_for_key_handler(
380	room_server: RoomServerConnection,
381	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
382		get_state_event_for_key::v3::Request,
383	>,
384) -> Result<ArminiusResponse<get_state_event_for_key::v3::Response>, ErrorOf<GetStateEventsForKey>>
385{
386	Ok(room_server
387		.make_roomserver_request::<GetStateEventsForKey>(
388			Some(request.room_id.clone()),
389			(SerializingRumaRequestWrapper(request), claims.user_id),
390		)
391		.await??
392		.0
393		.into())
394}
395
396/// Error when inviting a user
397#[derive(thiserror::Error, Debug, MatrixApiError, FromNatsError)]
398enum InviteUserError {
399	/// Unrecoverable error.
400	/// See <https://bundesmessenger.gitlab.io/backend/neo/arminius/arminius/03-development/errors.html#unrecoverable-errors>
401	#[error(transparent)]
402	InternalServerError(#[from] InternalServerError),
403	/// Error when adding an event
404	#[error(transparent)]
405	AddRoomMemberEventError(#[from] AddRoomMemberEventError),
406	/// When somebody tries to use an alias
407	#[error(transparent)]
408	FeatureNotImplemented(#[from] FeatureNotImplemented),
409}
410
411/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/invite` (See
412/// <https://spec.matrix.org/v1.15/client-server-api/#thirdparty_post_matrixclientv3roomsroomidinvite>).
413///
414/// This endpoint is used to invite an user to a room.
415async fn invite_user_handler(
416	State(state): State<ServerState>,
417	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
418		invite_user::v3::Request,
419	>,
420) -> Result<ArminiusResponse<invite_user::v3::Response>, InviteUserError> {
421	let (user_id, reason) = match request.recipient {
422		InvitationRecipient::UserId(InviteUserId { user_id, reason, .. }) => Ok((user_id, reason)),
423		_ => Err(FeatureNotImplemented("Non-user-id invite subjects".to_owned())),
424	}?;
425	RoomServerConnection::new(&state.nats_connection)
426		.make_roomserver_request::<AddRoomMemberEvent>(
427			Some(request.room_id.clone()),
428			AddRoomMemberEventRequest {
429				via: vec![],
430				member_event_content: create_member_event_content(
431					state.database,
432					MembershipState::Invite,
433					user_id.clone(),
434					reason,
435				)
436				.await?,
437				member: user_id,
438				sender: claims.user_id,
439			},
440		)
441		.await??;
442
443	Ok(invite_user::v3::Response::new().into())
444}
445
446/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/kick` (See
447/// <https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3roomsroomidkick>).
448///
449/// This endpoint is used to kick a user from a room
450async fn kick_user_handler(
451	State(state): State<ServerState>,
452	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<kick_user::v3::Request>,
453) -> Result<ArminiusResponse<kick_user::v3::Response>, InviteUserError> {
454	RoomServerConnection::new(&state.nats_connection)
455		.make_roomserver_request::<AddRoomMemberEvent>(
456			Some(request.room_id.clone()),
457			AddRoomMemberEventRequest {
458				via: vec![],
459				member_event_content: create_member_event_content(
460					state.database,
461					MembershipState::Leave,
462					request.user_id.clone(),
463					request.reason,
464				)
465				.await?,
466				member: request.user_id,
467				sender: claims.user_id,
468			},
469		)
470		.await??;
471
472	Ok(kick_user::v3::Response::new().into())
473}
474
475/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/ban` (See
476/// <https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3roomsroomidban>).
477///
478/// This endpoint is used to ban a user from a room
479async fn ban_user_handler(
480	State(state): State<ServerState>,
481	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<ban_user::v3::Request>,
482) -> Result<ArminiusResponse<ban_user::v3::Response>, InviteUserError> {
483	RoomServerConnection::new(&state.nats_connection)
484		.make_roomserver_request::<AddRoomMemberEvent>(
485			Some(request.room_id.clone()),
486			AddRoomMemberEventRequest {
487				via: vec![],
488				member_event_content: create_member_event_content(
489					state.database,
490					MembershipState::Ban,
491					request.user_id.clone(),
492					request.reason,
493				)
494				.await?,
495				member: request.user_id,
496				sender: claims.user_id,
497			},
498		)
499		.await??;
500
501	Ok(ban_user::v3::Response::new().into())
502}
503
504/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/unban` (See
505/// <https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3roomsroomidunban>).
506///
507/// This endpoint is used to unban a user from a room
508async fn unban_user_handler(
509	State(state): State<ServerState>,
510	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
511		unban_user::v3::Request,
512	>,
513) -> Result<ArminiusResponse<unban_user::v3::Response>, InviteUserError> {
514	RoomServerConnection::new(&state.nats_connection)
515		.make_roomserver_request::<AddRoomMemberEvent>(
516			Some(request.room_id.clone()),
517			AddRoomMemberEventRequest {
518				via: vec![],
519				member_event_content: create_member_event_content(
520					state.database,
521					MembershipState::Leave,
522					request.user_id.clone(),
523					request.reason,
524				)
525				.await?,
526				member: request.user_id,
527				sender: claims.user_id,
528			},
529		)
530		.await??;
531
532	Ok(unban_user::v3::Response::new().into())
533}
534
535/// Handler for `POST /_matrix/client/v3/knock/{roomIdOrAlias}` (See
536/// <https://spec.matrix.org/v1.15/client-server-api/#post_matrixclientv3knockroomidoralias>).
537///
538/// This endpoint is used to knock on a room.
539///
540/// NOTE: For now, only querying this endpoint with a RoomId will work, since
541/// room aliases aren't implemented yet.
542async fn knock_room_handler(
543	State(state): State<ServerState>,
544	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
545		knock_room::v3::Request,
546	>,
547) -> Result<ArminiusResponse<knock_room::v3::Response>, JoinRoomByIdError> {
548	let room_id = OwnedRoomId::try_from(request.room_id_or_alias.clone())
549		// TODO(#268): Get the room by its alias once room aliases are supported.
550		.map_err(|_| FeatureNotImplemented("Room aliases".to_owned()))?;
551
552	RoomServerConnection::new(&state.nats_connection)
553		.make_roomserver_request::<AddRoomMemberEvent>(
554			Some(room_id.clone()),
555			AddRoomMemberEventRequest {
556				via: append_server_to_via(request.via, &room_id),
557				member_event_content: create_member_event_content(
558					state.database,
559					MembershipState::Knock,
560					claims.user_id.clone(),
561					request.reason,
562				)
563				.await?,
564				member: claims.user_id.clone(),
565				sender: claims.user_id,
566			},
567		)
568		.await??;
569
570	Ok(knock_room::v3::Response::new(room_id).into())
571}
572
573/// Handler for `PUT /_matrix/client/v3/rooms/{roomId}/redact/{eventId}/{txnId}`
574/// (See <https://spec.matrix.org/v1.16/client-server-api/#redactions>).
575///
576/// This endpoint is used to redact an event in a room.
577async fn redact_event_handler(
578	room_server: RoomServerConnection,
579	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
580		redact_event::v3::Request,
581	>,
582) -> Result<ArminiusResponse<redact_event::v3::Response>, ErrorOf<RedactEvent>> {
583	Ok(room_server
584		.make_roomserver_request::<RedactEvent>(
585			Some(request.room_id.clone()),
586			(SerializingRumaRequestWrapper(request), claims.user_id),
587		)
588		.await??
589		.0
590		.into())
591}
592
593/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/forget`
594/// (See <https://spec.matrix.org/v1.14/client-server-api/#post_matrixclientv3roomsroomidforget>).
595///
596/// This endpoint is used to mark room as forgotten, and ready for deletion
597async fn forget_room_handler(
598	room_server: RoomServerConnection,
599	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
600		forget_room::v3::Request,
601	>,
602) -> Result<ArminiusResponse<forget_room::v3::Response>, ErrorOf<ForgetRoom>> {
603	room_server
604		.make_roomserver_request::<ForgetRoom>(Some(request.room_id.clone()), claims.user_id)
605		.await??;
606	Ok(forget_room::v3::Response::new().into())
607}
608
609/// Handler for `GET /_matrix/client/v3/joined_rooms`
610/// (See <https://spec.matrix.org/v1.18/client-server-api/#get_matrixclientv3joined_rooms>).
611///
612/// This endpoint returns the list of rooms that the user is a member of
613async fn joined_rooms_handler(
614	State(mut state): State<ServerState>,
615	AuthenticatedClientRequest(claims, _): AuthenticatedClientRequest<joined_rooms::v3::Request>,
616) -> Result<ArminiusResponse<joined_rooms::v3::Response>, InternalServerError> {
617	Ok(joined_rooms::v3::Response::new(state.database.fetch_joined_rooms(&claims.user_id).await?)
618		.into())
619}
620
621/// Error type for both the read receipts and the read markers endpoints.
622///
623/// This exists since both endpoints can produce either `ReadReceipt` or
624/// `ReadMarker` roomserver requests errors (or internal server errors of
625/// course).
626#[derive(Debug, thiserror::Error, MatrixApiError, Serialize, Deserialize, FromNatsError)]
627enum ReadReceiptOrReadMarkerError {
628	/// Unrecoverable error.
629	/// See <https://bundesmessenger.gitlab.io/backend/neo/arminius/arminius/03-development/errors.html#unrecoverable-errors>
630	#[error(transparent)]
631	InternalServerError(#[from] InternalServerError),
632	/// Setting the request's read receipt failed.
633	#[error(transparent)]
634	ReadReceipt(#[from] ReadReceiptError),
635	/// Setting the request's read marker failed.
636	#[error(transparent)]
637	ReadMarker(#[from] ReadMarkerError),
638	/// If the received read receipt is of a custom type we do not support.
639	#[error(transparent)]
640	FeatureNotImplemented(#[from] FeatureNotImplemented),
641}
642
643/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/receipt/{receiptType}/{eventId}` (See <https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreceiptreceipttypeeventid>).
644///
645/// This endpoint is used to update the read receipt of a specific room up to a
646/// given event.
647async fn create_receipt_handler(
648	state: State<ServerState>,
649	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
650		create_receipt::v3::Request,
651	>,
652) -> Result<ArminiusResponse<create_receipt::v3::Response>, ReadReceiptOrReadMarkerError> {
653	let response = create_receipt::v3::Response::new().into();
654	if request.receipt_type == create_receipt::v3::ReceiptType::FullyRead {
655		RoomServerConnection::new(&state.nats_connection)
656			.make_roomserver_request::<SetFullyReadMarker>(
657				Some(request.room_id.clone()),
658				ReadMarkerRequest { user_id: claims.user_id.clone(), event_id: request.event_id },
659			)
660			.await??;
661
662		return Ok(response);
663	}
664
665	let receipt_type = match request.receipt_type {
666		create_receipt::v3::ReceiptType::Read => ReceiptType::Read,
667		create_receipt::v3::ReceiptType::ReadPrivate => ReceiptType::ReadPrivate,
668		custom => {
669			return Err(FeatureNotImplemented(format!(
670				"Custom receipt type '{custom}' is not handled by this server",
671			))
672			.into());
673		}
674	};
675
676	RoomServerConnection::new(&state.nats_connection)
677		.make_roomserver_request::<ReadReceipt>(
678			Some(request.room_id),
679			ReadReceiptRequest {
680				user_id: claims.user_id,
681				receipt_type,
682				event_id: request.event_id,
683				thread: request.thread,
684				ts: chrono::Utc::now(),
685				provenance: EventProvenance::Local,
686			},
687		)
688		.await??;
689
690	Ok(response)
691}
692
693/// Handler for `POST /_matrix/client/v3/rooms/{roomId}/read_markers` (See
694/// <https://spec.matrix.org/v1.17/client-server-api/#post_matrixclientv3roomsroomidread_markers>).
695///
696/// This endpoints allows setting the position of the read marker for a given
697/// room, and optionally the read receipts location as well.
698async fn set_read_marker_handler(
699	state: State<ServerState>,
700	AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
701		set_read_marker::v3::Request,
702	>,
703) -> Result<ArminiusResponse<set_read_marker::v3::Response>, ReadReceiptOrReadMarkerError> {
704	if let Some(fully_read_marker_event) = request.fully_read {
705		RoomServerConnection::new(&state.nats_connection)
706			.make_roomserver_request::<SetFullyReadMarker>(
707				Some(request.room_id.clone()),
708				ReadMarkerRequest {
709					user_id: claims.user_id.clone(),
710					event_id: fully_read_marker_event,
711				},
712			)
713			.await??;
714	};
715
716	if let Some(read_receipt_event) = request.read_receipt {
717		RoomServerConnection::new(&state.nats_connection)
718			.make_roomserver_request::<ReadReceipt>(
719				Some(request.room_id.clone()),
720				ReadReceiptRequest {
721					user_id: claims.user_id.clone(),
722					receipt_type: ReceiptType::Read,
723					event_id: read_receipt_event,
724					thread: ruma::events::receipt::ReceiptThread::Unthreaded,
725					ts: chrono::Utc::now(),
726					provenance: EventProvenance::Local,
727				},
728			)
729			.await??;
730	};
731
732	if let Some(private_read_receipt_event) = request.private_read_receipt {
733		RoomServerConnection::new(&state.nats_connection)
734			.make_roomserver_request::<ReadReceipt>(
735				Some(request.room_id),
736				ReadReceiptRequest {
737					user_id: claims.user_id.clone(),
738					receipt_type: ReceiptType::ReadPrivate,
739					event_id: private_read_receipt_event,
740					thread: ruma::events::receipt::ReceiptThread::Unthreaded,
741					ts: chrono::Utc::now(),
742					provenance: EventProvenance::Local,
743				},
744			)
745			.await??;
746	};
747
748	Ok(set_read_marker::v3::Response::new().into())
749}
750
751/// Component implementing endpoint for client room management
752#[derive(Debug)]
753pub struct RoomComponent;
754
755impl Component for RoomComponent {
756	fn router() -> Router<ServerState> {
757		Router::new()
758			.route("/_matrix/client/v3/createRoom", post(create_room_handler))
759			.route("/_matrix/client/v3/joined_rooms", get(joined_rooms_handler))
760			.route(
761				"/_matrix/client/v3/join/{roomIdOrAlias}",
762				post(join_room_by_id_or_alias_handler),
763			)
764			.route("/_matrix/client/v3/knock/{roomIdOrAlias}", post(knock_room_handler))
765			.route("/_matrix/client/v3/rooms/{roomId}/ban", post(ban_user_handler))
766			.route("/_matrix/client/v3/rooms/{roomId}/event/{eventId}", get(get_room_event_handler))
767			.route("/_matrix/client/v3/rooms/{roomId}/forget", post(forget_room_handler))
768			.route("/_matrix/client/v3/rooms/{roomId}/invite", post(invite_user_handler))
769			.route("/_matrix/client/v3/rooms/{roomId}/join", post(join_room_by_id_handler))
770			.route("/_matrix/client/v3/rooms/{roomId}/kick", post(kick_user_handler))
771			.route("/_matrix/client/v3/rooms/{roomId}/leave", post(leave_room_handler))
772			.route("/_matrix/client/v3/rooms/{roomId}/members", get(get_member_events_handler))
773			.route("/_matrix/client/v3/rooms/{roomId}/read_markers", post(set_read_marker_handler))
774			.route(
775				"/_matrix/client/v3/rooms/{roomId}/receipt/{receiptType}/{eventId}",
776				post(create_receipt_handler),
777			)
778			.route(
779				"/_matrix/client/v3/rooms/{roomId}/redact/{eventId}/{txnId}",
780				put(redact_event_handler),
781			)
782			.route(
783				"/_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}",
784				put(send_message_event_handler),
785			)
786			.route(
787				"/_matrix/client/v3/rooms/{roomId}/state/{eventType}/",
788				put(send_state_event_handler).get(get_state_events_for_key_handler),
789			)
790			.route(
791				"/_matrix/client/v3/rooms/{roomId}/state/{eventType}",
792				put(send_state_event_handler).get(get_state_events_for_key_handler),
793			)
794			.route(
795				"/_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}",
796				put(send_state_event_handler).get(get_state_events_for_key_handler),
797			)
798			.route("/_matrix/client/v3/rooms/{roomId}/state", get(get_state_events_handler))
799			.route("/_matrix/client/v3/rooms/{roomId}/unban", post(unban_user_handler))
800	}
801}