Skip to main content

tuwunel_service/rooms/directory/
mod.rs

1//! Public room directory storage.
2//!
3//! The service records which rooms are published and the alias used for each publication.
4//! Callers can query individual visibility or stream every published room.
5
6use std::sync::Arc;
7
8use futures::Stream;
9use ruma::{OwnedRoomAliasId, RoomAliasId, RoomId, api::client::room::Visibility};
10use tuwunel_core::{Result, implement, utils::stream::TryIgnore};
11use tuwunel_database::{Deserialized, Map};
12
13/// Stores and queries public room directory entries.
14///
15/// Each entry is keyed by room ID and optionally retains the alias used to publish it. A room
16/// without an entry is treated as private.
17pub struct Service {
18	db: Data,
19}
20
21struct Data {
22	publicroomids: Arc<Map>,
23}
24
25impl crate::Service for Service {
26	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
27		Ok(Arc::new(Self {
28			db: Data {
29				publicroomids: args.db["publicroomids"].clone(),
30			},
31		}))
32	}
33
34	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
35}
36
37/// Publishes a room in the directory.
38///
39/// The optional alias is stored with the room entry. Publishing without an alias stores an empty
40/// value while retaining public visibility.
41#[implement(Service)]
42pub fn set_public(&self, room_id: &RoomId, alias: Option<&RoomAliasId>) {
43	self.db
44		.publicroomids
45		.insert(room_id, alias.map_or("", RoomAliasId::as_str));
46}
47
48/// Removes a room from the public directory.
49///
50/// Removing an absent entry is harmless. Subsequent visibility queries report the room as
51/// private.
52#[implement(Service)]
53pub fn set_not_public(&self, room_id: &RoomId) { self.db.publicroomids.remove(room_id); }
54
55/// Returns the alias under which a room was published.
56///
57/// Rooms published without an alias store an empty value, which cannot deserialize as a room
58/// alias and therefore returns an error.
59#[implement(Service)]
60pub async fn published_alias(&self, room_id: &RoomId) -> Result<OwnedRoomAliasId> {
61	self.db
62		.publicroomids
63		.get(room_id)
64		.await
65		.deserialized()
66}
67
68/// Streams the room IDs currently published in the directory.
69///
70/// Each borrowed ID is valid only until the stream is polled again and must be copied before
71/// retention. Rows with unparsable room IDs are skipped.
72#[implement(Service)]
73pub fn public_rooms(&self) -> impl Stream<Item = &RoomId> + Send {
74	self.db.publicroomids.keys().ignore_err()
75}
76
77/// Reports whether a room is currently public.
78///
79/// Visibility is determined by the presence of the room's directory entry. Missing entries and
80/// failed lookups are treated as private.
81#[implement(Service)]
82pub async fn is_public_room(&self, room_id: &RoomId) -> bool {
83	self.visibility(room_id).await == Visibility::Public
84}
85
86/// Returns a room's client-facing directory visibility.
87///
88/// A stored directory entry maps to [`Visibility::Public`]. Missing entries and failed lookups map
89/// to [`Visibility::Private`].
90#[implement(Service)]
91pub async fn visibility(&self, room_id: &RoomId) -> Visibility {
92	if self.db.publicroomids.get(room_id).await.is_ok() {
93		Visibility::Public
94	} else {
95		Visibility::Private
96	}
97}