Skip to content

Commit

Permalink
feat(core/events): legacy handler to read posts and replies from boards
Browse files Browse the repository at this point in the history
The naming `legacy` refers to code that will remain compatible with
`commune-server` once I introduce breaking changes in the future. Note
that this is a draft since I'm not sure what the idiomatic way of
selecting rows from multiple tables without embedding them as JSON
(which I think is an anti-pattern for queries).
  • Loading branch information
avdb13 committed Jan 27, 2024
1 parent 71b16ff commit a6aa659
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 7 deletions.
74 changes: 74 additions & 0 deletions crates/core/src/db/entities/legacy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use matrix::events::space::BoardPostEvent;
use serde::{Deserialize, Serialize};
use tokio_postgres::{types::Json, Error, Row};

#[derive(Deserialize)]
pub struct LegacyEntity {
pub event_id: String,
pub json: BoardPostEvent,
pub room_alias: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub replies: u32,
pub slug: String,
pub reactions: Vec<()>,
pub edited_on: Option<u32>,

// only for replies
pub in_reply_to: Option<String>,
pub downvotes: Option<u32>,
pub upvotes: Option<u32>,
#[serde(skip_deserializing, flatten)]
pub metadata: Option<Metadata>,
}
#[derive(Deserialize)]
pub struct ReactionEntity {
key: String,
url: String,
senders: Vec<String>,
}

#[derive(Default, Serialize, Deserialize)]
#[serde(from = "LegacyEntity")]
pub struct Metadata {
pub edited: bool,
pub downvoted: bool,
pub upvoted: bool,
}

impl From<LegacyEntity> for Metadata {
fn from(inner: LegacyEntity) -> Self {
let edited = inner.edited_on.is_some();
let downvoted = inner.downvotes.unwrap_or(0) > 0;
let upvoted = inner.upvotes.unwrap_or(0) > 0;

Self {
edited,
downvoted,
upvoted,
}
}
}

impl TryFrom<Row> for LegacyEntity {
type Error = Error;

fn try_from(row: Row) -> Result<Self, Self::Error> {
Ok(Self {
event_id: row.try_get(0)?,
json: row.try_get(1).map(|j: Json<BoardPostEvent>| j.0)?,
room_alias: row.try_get(5)?,
display_name: row.try_get(3)?,
avatar_url: row.try_get(4)?,
replies: 0,
slug: row.try_get(6)?,
reactions: vec![],
edited_on: row.try_get(7).map(|n: Option<i64>| n.map(|m| m as u32))?,

in_reply_to: None,
downvotes: None,
upvotes: None,
metadata: None,
})
}
}
4 changes: 4 additions & 0 deletions crates/core/src/db/entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod legacy;
pub mod post;
pub mod reply;

28 changes: 28 additions & 0 deletions crates/core/src/events/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use http::StatusCode;
use thiserror::Error;

use crate::error::HttpStatusCode;

#[derive(Debug, Error)]
pub enum EventsErrorCode {
#[error("Not found")]
NotFound(u64),
#[error("Malformed body")]
MalformedBody,
}

impl HttpStatusCode for EventsErrorCode {
fn status_code(&self) -> StatusCode {
match self {
EventsErrorCode::NotFound(_) => StatusCode::NOT_FOUND,
EventsErrorCode::MalformedBody => StatusCode::BAD_REQUEST,
}
}

fn error_code(&self) -> &'static str {
match self {
EventsErrorCode::NotFound(_) => "NOT_FOUND",
EventsErrorCode::MalformedBody => "BAD_REQUEST",
}
}
}
5 changes: 5 additions & 0 deletions crates/core/src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod service;
pub mod error;

/// This is a re-export of `matrix::events`.
pub use matrix::events as ruma;
87 changes: 80 additions & 7 deletions crates/core/src/events/service.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use std::{str::FromStr};
use std::str::FromStr;

use matrix::events::{
exports::ruma_common::{OwnedEventId, TransactionId},
relation::{InReplyTo},
exports::ruma_common::{OwnedEventId, TransactionId, serde::Raw},
relation::InReplyTo,
room::message::Relation,
space::{BoardPostEventContent, BoardReplyEventContent},
space::{BoardPostEventContent, BoardReplyEventContent}, AnyStateEventContent,
};
use reqwest::{Client as HttpClient, Response as HttpResponse};
use serde::{Deserialize, Serialize};

use tokio_postgres::Client as PostgresClient;

use crate::{Error, Result};
use crate::{Error, Result, db::entities::legacy::LegacyEntity};

#[derive(Serialize, Deserialize, Debug)]
pub struct SendResponse {
Expand All @@ -35,7 +35,7 @@ impl EventsService {
}
}

pub async fn new_post<S: AsRef<str>>(
pub async fn create_post<S: AsRef<str>>(
&self,
content: BoardPostEventContent,
board_id: S,
Expand All @@ -60,7 +60,7 @@ impl EventsService {
Ok(data)
}

pub async fn new_reply<S: AsRef<str>>(
pub async fn create_reply<S: AsRef<str>>(
&self,
mut content: BoardReplyEventContent,
in_reply_to: S,
Expand Down Expand Up @@ -95,6 +95,37 @@ impl EventsService {
Ok(data)
}

pub async fn create_state<S: AsRef<str>>(
&self,
content: Raw<AnyStateEventContent>,
board_id: S,
event_type: S,
state_key: Option<S>,
token: S,
) -> Result<SendResponse> {
let mut endpoint = format!(
"/_matrix/client/v3/rooms/{board_id}/state/{event_type}",
board_id = board_id.as_ref(),
event_type = event_type.as_ref(),
);

if let Some(_state_key) = state_key {
endpoint.push_str("/{state_key}");
}

let resp = self.put(endpoint, token, content).await?;

let body = resp.bytes().await.unwrap();
tracing::debug!(?body);

let data: SendResponse = serde_json::from_slice(&body).map_err(|err| {
tracing::error!(?err, "Failed to deserialize response");
Error::Unknown
})?;

Ok(data)
}

async fn put(
&self,
endpoint: impl AsRef<str>,
Expand All @@ -109,4 +140,46 @@ impl EventsService {
Error::Unknown
})
}

pub async fn all_events(&self) -> Result<Vec<LegacyEntity>> {
let statement = "
SELECT
e.event_id,
js.json,
ms.display_name,
ms.avatar_url,
ra.room_alias,
RIGHT(e.event_id, 11) AS slug,
js.json :: json ->> 'origin_server_ts' AS edited_on,
0 AS replies,
FROM
events e
LEFT JOIN event_json js USING(event_id)
LEFT JOIN room_aliases ra USING(room_id)
LEFT JOIN event_relations er ON er.relates_to_id = e.event_id
LEFT JOIN redactions ON redacts = e.event_id
LEFT JOIN membership_state ms ON ms.user_id = e.sender
AND ms.room_id = e.room_id
WHERE
e.origin_server_ts < $1
AND e.type = 'space.board.post'
AND redacts IS NULL
AND aliases.room_alias IS NOT NULL
";

let event_rows = self.postgres.query(statement, &[]).await.map_err(|err| {
tracing::error!(?err, "Failed to query events");
Error::Unknown
})?;


let result = event_rows.into_iter().map(LegacyEntity::try_from);
let ok = result.flat_map(|r| r.ok()).collect();

Ok(ok)
}

pub async fn all_replies(&self) -> Result<Vec<()>> {
unimplemented!()
}
}

0 comments on commit a6aa659

Please sign in to comment.