mirror of https://gitlab.com/famedly/conduit.git
28 changed files with 765 additions and 339 deletions
@ -1,23 +0,0 @@
|
||||
[build.env] |
||||
# CI uses an S3 endpoint to store sccache artifacts, so their config needs to |
||||
# be available in the cross container as well |
||||
passthrough = [ |
||||
"RUSTC_WRAPPER", |
||||
"AWS_ACCESS_KEY_ID", |
||||
"AWS_SECRET_ACCESS_KEY", |
||||
"SCCACHE_BUCKET", |
||||
"SCCACHE_ENDPOINT", |
||||
"SCCACHE_S3_USE_SSL", |
||||
] |
||||
|
||||
[target.aarch64-unknown-linux-musl] |
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-aarch64-unknown-linux-musl:latest" |
||||
|
||||
[target.arm-unknown-linux-musleabihf] |
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-arm-unknown-linux-musleabihf:latest" |
||||
|
||||
[target.armv7-unknown-linux-musleabihf] |
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-armv7-unknown-linux-musleabihf:latest" |
||||
|
||||
[target.x86_64-unknown-linux-musl] |
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-x86_64-unknown-linux-musl@sha256:b6d689e42f0236c8a38b961bca2a12086018b85ed20e0826310421daf182e2bb" |
||||
@ -0,0 +1,10 @@
|
||||
use crate::{services, Result, Ruma}; |
||||
use std::time::{Duration, SystemTime}; |
||||
|
||||
/// # `GET /_matrix/client/r0/todo`
|
||||
pub async fn get_relating_events_route( |
||||
body: Ruma<get_turn_server_info::v3::Request>, |
||||
) -> Result<get_turn_server_info::v3::Response> { |
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); |
||||
todo!(); |
||||
} |
||||
@ -0,0 +1,49 @@
|
||||
use ruma::api::client::{error::ErrorKind, threads::get_threads}; |
||||
|
||||
use crate::{services, Error, Result, Ruma}; |
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/threads`
|
||||
pub async fn get_threads_route( |
||||
body: Ruma<get_threads::v1::Request>, |
||||
) -> Result<get_threads::v1::Response> { |
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); |
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = body |
||||
.limit |
||||
.and_then(|l| l.try_into().ok()) |
||||
.unwrap_or(10) |
||||
.min(100); |
||||
|
||||
let from = if let Some(from) = &body.from { |
||||
from.parse() |
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, ""))? |
||||
} else { |
||||
u64::MAX |
||||
}; |
||||
|
||||
let threads = services() |
||||
.rooms |
||||
.threads |
||||
.threads_until(sender_user, &body.room_id, from, &body.include)? |
||||
.take(limit) |
||||
.filter_map(|r| r.ok()) |
||||
.filter(|(_, pdu)| { |
||||
services() |
||||
.rooms |
||||
.state_accessor |
||||
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id) |
||||
.unwrap_or(false) |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
let next_batch = threads.last().map(|(count, _)| count.to_string()); |
||||
|
||||
Ok(get_threads::v1::Response { |
||||
chunk: threads |
||||
.into_iter() |
||||
.map(|(_, pdu)| pdu.to_room_event()) |
||||
.collect(), |
||||
next_batch, |
||||
}) |
||||
} |
||||
@ -0,0 +1,78 @@
|
||||
use std::mem; |
||||
|
||||
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId}; |
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result}; |
||||
|
||||
impl service::rooms::threads::Data for KeyValueDatabase { |
||||
fn threads_until<'a>( |
||||
&'a self, |
||||
user_id: &'a UserId, |
||||
room_id: &'a RoomId, |
||||
until: u64, |
||||
include: &'a IncludeThreads, |
||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> { |
||||
let prefix = services() |
||||
.rooms |
||||
.short |
||||
.get_shortroomid(room_id)? |
||||
.expect("room exists") |
||||
.to_be_bytes() |
||||
.to_vec(); |
||||
|
||||
let mut current = prefix.clone(); |
||||
current.extend_from_slice(&(until - 1).to_be_bytes()); |
||||
|
||||
Ok(Box::new( |
||||
self.threadid_userids |
||||
.iter_from(¤t, true) |
||||
.take_while(move |(k, _)| k.starts_with(&prefix)) |
||||
.map(move |(pduid, users)| { |
||||
let count = utils::u64_from_bytes(&pduid[(mem::size_of::<u64>())..]) |
||||
.map_err(|_| Error::bad_database("Invalid pduid in threadid_userids."))?; |
||||
let mut pdu = services() |
||||
.rooms |
||||
.timeline |
||||
.get_pdu_from_id(&pduid)? |
||||
.ok_or_else(|| { |
||||
Error::bad_database("Invalid pduid reference in threadid_userids") |
||||
})?; |
||||
if pdu.sender != user_id { |
||||
pdu.remove_transaction_id()?; |
||||
} |
||||
Ok((count, pdu)) |
||||
}), |
||||
)) |
||||
} |
||||
|
||||
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()> { |
||||
let users = participants |
||||
.iter() |
||||
.map(|user| user.as_bytes()) |
||||
.collect::<Vec<_>>() |
||||
.join(&[0xff][..]); |
||||
|
||||
self.threadid_userids.insert(&root_id, &users)?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>> { |
||||
if let Some(users) = self.threadid_userids.get(&root_id)? { |
||||
Ok(Some( |
||||
users |
||||
.split(|b| *b == 0xff) |
||||
.map(|bytes| { |
||||
UserId::parse(utils::string_from_bytes(bytes).map_err(|_| { |
||||
Error::bad_database("Invalid UserId bytes in threadid_userids.") |
||||
})?) |
||||
.map_err(|_| Error::bad_database("Invalid UserId in threadid_userids.")) |
||||
}) |
||||
.filter_map(|r| r.ok()) |
||||
.collect(), |
||||
)) |
||||
} else { |
||||
Ok(None) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,15 @@
|
||||
use crate::{PduEvent, Result}; |
||||
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId}; |
||||
|
||||
pub trait Data: Send + Sync { |
||||
fn threads_until<'a>( |
||||
&'a self, |
||||
user_id: &'a UserId, |
||||
room_id: &'a RoomId, |
||||
until: u64, |
||||
include: &'a IncludeThreads, |
||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>; |
||||
|
||||
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()>; |
||||
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>>; |
||||
} |
||||
@ -0,0 +1,119 @@
|
||||
mod data; |
||||
use std::sync::Arc; |
||||
|
||||
pub use data::Data; |
||||
use ruma::{ |
||||
api::client::{error::ErrorKind, threads::get_threads::v1::IncludeThreads}, |
||||
events::{relation::BundledThread, StateEventType}, |
||||
uint, CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId, |
||||
}; |
||||
use serde::Deserialize; |
||||
use serde_json::json; |
||||
|
||||
use crate::{services, utils, Error, PduEvent, Result}; |
||||
|
||||
use super::timeline::PduCount; |
||||
|
||||
pub struct Service { |
||||
pub db: &'static dyn Data, |
||||
} |
||||
|
||||
impl Service { |
||||
pub fn threads_until<'a>( |
||||
&'a self, |
||||
user_id: &'a UserId, |
||||
room_id: &'a RoomId, |
||||
until: u64, |
||||
include: &'a IncludeThreads, |
||||
) -> Result<impl Iterator<Item = Result<(u64, PduEvent)>> + 'a> { |
||||
self.db.threads_until(user_id, room_id, until, include) |
||||
} |
||||
|
||||
pub fn add_to_thread<'a>(&'a self, root_event_id: &EventId, pdu: &PduEvent) -> Result<()> { |
||||
let root_id = &services() |
||||
.rooms |
||||
.timeline |
||||
.get_pdu_id(root_event_id)? |
||||
.ok_or_else(|| { |
||||
Error::BadRequest( |
||||
ErrorKind::InvalidParam, |
||||
"Invalid event id in thread message", |
||||
) |
||||
})?; |
||||
|
||||
let root_pdu = services() |
||||
.rooms |
||||
.timeline |
||||
.get_pdu_from_id(root_id)? |
||||
.ok_or_else(|| { |
||||
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found") |
||||
})?; |
||||
|
||||
let mut root_pdu_json = services() |
||||
.rooms |
||||
.timeline |
||||
.get_pdu_json_from_id(root_id)? |
||||
.ok_or_else(|| { |
||||
Error::BadRequest(ErrorKind::InvalidParam, "Thread root pdu not found") |
||||
})?; |
||||
|
||||
if let CanonicalJsonValue::Object(unsigned) = root_pdu_json |
||||
.entry("unsigned".to_owned()) |
||||
.or_insert_with(|| CanonicalJsonValue::Object(Default::default())) |
||||
{ |
||||
if let Some(mut relations) = unsigned |
||||
.get("m.relations") |
||||
.and_then(|r| r.as_object()) |
||||
.and_then(|r| r.get("m.thread")) |
||||
.and_then(|relations| { |
||||
serde_json::from_value::<BundledThread>(relations.clone().into()).ok() |
||||
}) |
||||
{ |
||||
// Thread already existed
|
||||
relations.count += uint!(1); |
||||
relations.latest_event = pdu.to_message_like_event(); |
||||
|
||||
let content = serde_json::to_value(relations).expect("to_value always works"); |
||||
|
||||
unsigned.insert( |
||||
"m.relations".to_owned(), |
||||
json!({ "m.thread": content }) |
||||
.try_into() |
||||
.expect("thread is valid json"), |
||||
); |
||||
} else { |
||||
// New thread
|
||||
let relations = BundledThread { |
||||
latest_event: pdu.to_message_like_event(), |
||||
count: uint!(1), |
||||
current_user_participated: true, |
||||
}; |
||||
|
||||
let content = serde_json::to_value(relations).expect("to_value always works"); |
||||
|
||||
unsigned.insert( |
||||
"m.relations".to_owned(), |
||||
json!({ "m.thread": content }) |
||||
.try_into() |
||||
.expect("thread is valid json"), |
||||
); |
||||
} |
||||
|
||||
services() |
||||
.rooms |
||||
.timeline |
||||
.replace_pdu(root_id, &root_pdu_json, &root_pdu)?; |
||||
} |
||||
|
||||
let mut users = Vec::new(); |
||||
if let Some(userids) = self.db.get_participants(&root_id)? { |
||||
users.extend_from_slice(&userids); |
||||
users.push(pdu.sender.clone()); |
||||
} else { |
||||
users.push(root_pdu.sender); |
||||
users.push(pdu.sender.clone()); |
||||
} |
||||
|
||||
self.db.update_participants(root_id, &users) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue