Skip to content
Snippets Groups Projects
search.rs 3.52 KiB
Newer Older
  • Learn to ignore specific revisions
  • use crate::{database::DatabaseGuard, Error, Result, Ruma};
    
    timokoesters's avatar
    timokoesters committed
    use ruma::api::client::{error::ErrorKind, r0::search::search_events};
    
    
    Timo Kösters's avatar
    Timo Kösters committed
    use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult};
    
    timokoesters's avatar
    timokoesters committed
    use std::collections::BTreeMap;
    
    
    /// # `POST /_matrix/client/r0/search`
    ///
    /// Searches rooms for messages.
    ///
    /// - Only works if the user is currently joined to the room (TODO: Respect history visibility)
    
    #[tracing::instrument(skip(db, body))]
    
    pub async fn search_events_route(
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        db: DatabaseGuard,
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<search_events::Request<'_>>,
    
    ) -> Result<search_events::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    timokoesters's avatar
    timokoesters committed
    
        let search_criteria = body.search_categories.room_events.as_ref().unwrap();
    
    Jonas Platte's avatar
    Jonas Platte committed
        let filter = &search_criteria.filter;
    
        let room_ids = filter.rooms.clone().unwrap_or_else(|| {
            db.rooms
    
                .rooms_joined(sender_user)
    
                .filter_map(|r| r.ok())
                .collect()
        });
    
    timokoesters's avatar
    timokoesters committed
    
        let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
    
    
        let mut searches = Vec::new();
    
        for room_id in room_ids {
            if !db.rooms.is_joined(sender_user, &room_id)? {
                return Err(Error::BadRequest(
                    ErrorKind::Forbidden,
                    "You don't have permission to view this room.",
                ));
            }
    
    
            if let Some(search) = db
    
                .search_pdus(&room_id, &search_criteria.search_term)?
            {
                searches.push(search.0.peekable());
            }
    
    timokoesters's avatar
    timokoesters committed
        }
    
        let skip = match body.next_batch.as_ref().map(|s| s.parse()) {
            Some(Ok(s)) => s,
            Some(Err(_)) => {
                return Err(Error::BadRequest(
                    ErrorKind::InvalidParam,
                    "Invalid next_batch token.",
                ))
            }
            None => 0, // Default to the start
        };
    
    
        let mut results = Vec::new();
        for _ in 0..skip + limit {
            if let Some(s) = searches
                .iter_mut()
                .map(|s| (s.peek().cloned(), s))
                .max_by_key(|(peek, _)| peek.clone())
                .and_then(|(_, i)| i.next())
            {
                results.push(s);
            }
        }
    
    timokoesters's avatar
    timokoesters committed
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        let results: Vec<_> = results
    
    timokoesters's avatar
    timokoesters committed
            .map(|result| {
                Ok::<_, Error>(SearchResult {
    
    Timo Kösters's avatar
    Timo Kösters committed
                    context: EventContextResult {
                        end: None,
                        events_after: Vec::new(),
                        events_before: Vec::new(),
                        profile_info: BTreeMap::new(),
                        start: None,
                    },
    
    timokoesters's avatar
    timokoesters committed
                    rank: None,
    
    timokoesters's avatar
    timokoesters committed
                        .rooms
    
                        .get_pdu_from_id(result)?
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .map(|pdu| pdu.to_room_event()),
    
    timokoesters's avatar
    timokoesters committed
                })
            })
            .filter_map(|r| r.ok())
            .skip(skip)
            .take(limit)
    
    Jonas Platte's avatar
    Jonas Platte committed
            .collect();
    
    timokoesters's avatar
    timokoesters committed
    
        let next_batch = if results.len() < limit as usize {
            None
        } else {
            Some((skip + limit).to_string())
        };
    
    
        Ok(search_events::Response::new(ResultCategories {
    
    Timo Kösters's avatar
    Timo Kösters committed
            room_events: ResultRoomEvents {
    
                count: Some((results.len() as u32).into()), // TODO: set this to none. Element shouldn't depend on it
                groups: BTreeMap::new(),                    // TODO
    
                next_batch,
                results,
                state: BTreeMap::new(), // TODO
    
                highlights: search_criteria
                    .search_term
                    .split_terminator(|c: char| !c.is_alphanumeric())
                    .map(str::to_lowercase)
    
    Jonas Platte's avatar
    Jonas Platte committed
                    .collect(),
    
    Timo Kösters's avatar
    Timo Kösters committed
            },
    
    timokoesters's avatar
    timokoesters committed
    }