Initial Matrix implementation
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
fbca4b7c06
commit
a50d157394
10 changed files with 204 additions and 30 deletions
|
@ -1 +1,3 @@
|
|||
- Return API error response on missing query parameter
|
||||
- Initial Matrix implementation
|
||||
- Allows bots to know, when a new meme was uploaded
|
||||
- First step towards decentralization
|
|
@ -7,14 +7,14 @@ use axum::{
|
|||
use reqwest::StatusCode;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ipfs::error::IPFSError;
|
||||
use crate::error::ServiceError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CDNError {
|
||||
#[error("SQL error: {0}")]
|
||||
Sql(#[from] sqlx::Error),
|
||||
#[error("IPFS error: {0}")]
|
||||
Ipfs(#[from] IPFSError),
|
||||
#[error("JMService error: {0}")]
|
||||
Service(#[from] ServiceError),
|
||||
#[error("Decode error: {0}")]
|
||||
Decode(#[from] FromUtf8Error),
|
||||
#[error("Internal server error")]
|
||||
|
|
|
@ -10,6 +10,9 @@ pub struct Config {
|
|||
pub database: String,
|
||||
pub cdn: String,
|
||||
pub ipfs_api: Url,
|
||||
pub matrix_url: Url,
|
||||
pub matrix_token: String,
|
||||
pub matrix_domain: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -19,6 +22,9 @@ impl Config {
|
|||
client,
|
||||
ipfs_url: self.ipfs_api.clone(),
|
||||
cdn_url: self.cdn.clone(),
|
||||
matrix_url: self.matrix_url.clone(),
|
||||
matrix_token: self.matrix_token.clone(),
|
||||
matrix_domain: self.matrix_domain.clone(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
12
src/error.rs
12
src/error.rs
|
@ -1,4 +1,6 @@
|
|||
use hyper::StatusCode;
|
||||
use thiserror::Error;
|
||||
use url::ParseError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum JMError {
|
||||
|
@ -13,3 +15,13 @@ pub enum JMError {
|
|||
#[error("Reqwest error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServiceError {
|
||||
#[error("Reqwest error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
#[error("URL parse error: {0}")]
|
||||
Url(#[from] ParseError),
|
||||
#[error("Invalid response code: {0}")]
|
||||
InvalidResponse(StatusCode),
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
use thiserror::Error;
|
||||
use url::ParseError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum IPFSError {
|
||||
#[error("Reqwest error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
#[error("URL parse error: {0}")]
|
||||
Url(#[from] ParseError),
|
||||
}
|
|
@ -7,11 +7,7 @@ use reqwest::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::JMServiceInner;
|
||||
|
||||
use self::error::IPFSError;
|
||||
|
||||
pub(crate) mod error;
|
||||
use crate::{error::ServiceError, JMServiceInner};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IPFSFile {
|
||||
|
@ -39,7 +35,7 @@ pub struct PinQuery {
|
|||
}
|
||||
|
||||
impl JMServiceInner {
|
||||
pub async fn cat(&self, cid: String) -> Result<Response, IPFSError> {
|
||||
pub async fn cat(&self, cid: String) -> Result<Response, ServiceError> {
|
||||
let request = self
|
||||
.client
|
||||
.post(self.ipfs_url.join("/api/v0/cat")?)
|
||||
|
@ -47,7 +43,7 @@ impl JMServiceInner {
|
|||
Ok(request.send().await?)
|
||||
}
|
||||
|
||||
pub async fn add(&self, file: Bytes, filename: String) -> Result<IPFSFile, IPFSError> {
|
||||
pub async fn add(&self, file: Bytes, filename: String) -> Result<IPFSFile, ServiceError> {
|
||||
let request = self
|
||||
.client
|
||||
.post(self.ipfs_url.join("/api/v0/add")?)
|
||||
|
@ -58,7 +54,7 @@ impl JMServiceInner {
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn pin(&self, cid: String) -> Result<(), IPFSError> {
|
||||
pub async fn pin(&self, cid: String) -> Result<(), ServiceError> {
|
||||
let request = self
|
||||
.client
|
||||
.post(self.ipfs_url.join("/api/v0/pin/add")?)
|
||||
|
|
|
@ -16,6 +16,7 @@ mod config;
|
|||
mod error;
|
||||
mod ipfs;
|
||||
mod lib;
|
||||
mod matrix;
|
||||
mod models;
|
||||
mod sql;
|
||||
mod v1;
|
||||
|
@ -35,6 +36,9 @@ pub struct JMServiceInner {
|
|||
client: Client,
|
||||
ipfs_url: Url,
|
||||
cdn_url: String,
|
||||
matrix_url: Url,
|
||||
matrix_token: String,
|
||||
matrix_domain: String,
|
||||
}
|
||||
|
||||
pub type JMService = Arc<JMServiceInner>;
|
||||
|
|
158
src/matrix/mod.rs
Normal file
158
src/matrix/mod.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::ServiceError, JMServiceInner};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Meme {
|
||||
pub category: String,
|
||||
pub filename: String,
|
||||
pub cid: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UserID {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RoomID {
|
||||
pub room_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct EventID {
|
||||
pub event_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RegisterRequest {
|
||||
#[serde(rename = "type")]
|
||||
pub reg_type: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl JMServiceInner {
|
||||
pub async fn add_meme(
|
||||
&self,
|
||||
category: String,
|
||||
filename: String,
|
||||
cid: String,
|
||||
user: String,
|
||||
id: u64,
|
||||
) -> Result<(), ServiceError> {
|
||||
let meme = Meme {
|
||||
category,
|
||||
filename,
|
||||
cid,
|
||||
};
|
||||
let txid = meme.calc_txid(user.clone());
|
||||
let usr = self.check_user(user).await?;
|
||||
let room_id = self.join_room(&usr).await?;
|
||||
let path = format!(
|
||||
"/_matrix/client/r0/rooms/{}/send/es.jensmem.meme/{}",
|
||||
&room_id, txid
|
||||
);
|
||||
let url = self.matrix_url.join(path.as_str())?;
|
||||
let req = self
|
||||
.client
|
||||
.put(url)
|
||||
.bearer_auth(self.matrix_token.clone())
|
||||
.query(&usr)
|
||||
.json(&meme);
|
||||
let res = req.send().await?;
|
||||
if res.status().is_success() {
|
||||
let event: EventID = res.json().await?;
|
||||
let path = format!(
|
||||
"/_matrix/client/r0/rooms/{}/state/es.jensmem.index/{}",
|
||||
&room_id, id
|
||||
);
|
||||
let req = self
|
||||
.client
|
||||
.put(self.matrix_url.join(path.as_str())?)
|
||||
.bearer_auth(self.matrix_token.clone())
|
||||
.json(&event);
|
||||
let res = req.send().await?;
|
||||
if res.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServiceError::InvalidResponse(res.status()))
|
||||
}
|
||||
} else {
|
||||
Err(ServiceError::InvalidResponse(res.status()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_user(&self, user: String) -> Result<UserID, ServiceError> {
|
||||
let username = format!("jm_{}", user);
|
||||
let user = self.get_mxid(username.clone());
|
||||
let req = self
|
||||
.client
|
||||
.get(self.matrix_url.join("/_matrix/client/r0/account/whoami")?)
|
||||
.bearer_auth(self.matrix_token.clone())
|
||||
.query(&user);
|
||||
let res = req.send().await?;
|
||||
if res.status().is_success() {
|
||||
let mxid: UserID = res.json().await?;
|
||||
Ok(mxid)
|
||||
} else {
|
||||
let mxid = self.register_user(username).await?;
|
||||
Ok(mxid)
|
||||
}
|
||||
}
|
||||
|
||||
async fn register_user(&self, username: String) -> Result<UserID, ServiceError> {
|
||||
let req = self
|
||||
.client
|
||||
.post(self.matrix_url.join("/_matrix/client/r0/register")?)
|
||||
.bearer_auth(self.matrix_token.clone())
|
||||
.json(&RegisterRequest::new(username));
|
||||
let res = req.send().await?;
|
||||
if res.status().is_success() {
|
||||
let user: UserID = res.json().await?;
|
||||
Ok(user)
|
||||
} else {
|
||||
Err(ServiceError::InvalidResponse(res.status()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn join_room(&self, user: &UserID) -> Result<String, ServiceError> {
|
||||
let req = self
|
||||
.client
|
||||
.post(
|
||||
self.matrix_url
|
||||
.join("/_matrix/client/r0/join/%23memes%3Atilera.org")?,
|
||||
)
|
||||
.bearer_auth(self.matrix_token.clone())
|
||||
.query(user);
|
||||
|
||||
let res = req.send().await?;
|
||||
if res.status().is_success() {
|
||||
let room: RoomID = res.json().await?;
|
||||
Ok(room.room_id)
|
||||
} else {
|
||||
Err(ServiceError::InvalidResponse(res.status()))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mxid(&self, username: String) -> UserID {
|
||||
UserID {
|
||||
user_id: format!("@{}:{}", username, self.matrix_domain.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterRequest {
|
||||
pub fn new(username: String) -> Self {
|
||||
Self {
|
||||
reg_type: "m.login.application_service".to_string(),
|
||||
username,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Meme {
|
||||
pub fn calc_txid(&self, user: String) -> String {
|
||||
let txid = format!("{}/{}/{}/{}", user, self.category, self.filename, self.cid);
|
||||
urlencoding::encode(txid.as_str()).into_owned()
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ use reqwest::StatusCode;
|
|||
use thiserror::Error;
|
||||
|
||||
use super::models::ErrorResponse;
|
||||
use crate::ipfs::error::IPFSError;
|
||||
use crate::error::ServiceError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum APIError {
|
||||
|
@ -28,8 +28,8 @@ pub enum APIError {
|
|||
NotFound(String),
|
||||
#[error("{0}")]
|
||||
Internal(String),
|
||||
#[error("IPFS error: {0}")]
|
||||
Ipfs(#[from] IPFSError),
|
||||
#[error("JMService error: {0}")]
|
||||
Service(#[from] ServiceError),
|
||||
#[error("Query rejection: {0}")]
|
||||
Query(#[from] QueryRejection),
|
||||
}
|
||||
|
@ -60,10 +60,8 @@ impl IntoResponse for APIError {
|
|||
APIError::Unauthorized(err) => ErrorResponse::new(StatusCode::UNAUTHORIZED, Some(err)),
|
||||
APIError::Forbidden(err) => ErrorResponse::new(StatusCode::FORBIDDEN, Some(err)),
|
||||
APIError::NotFound(err) => ErrorResponse::new(StatusCode::NOT_FOUND, Some(err)),
|
||||
APIError::Internal(err) => {
|
||||
ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Some(err))
|
||||
}
|
||||
APIError::Ipfs(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
|
||||
APIError::Internal(err) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Some(err)),
|
||||
APIError::Service(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
|
||||
APIError::Query(_) => ErrorResponse::new(StatusCode::BAD_REQUEST, None),
|
||||
};
|
||||
let status = res.status;
|
||||
|
|
|
@ -166,7 +166,15 @@ async fn upload(
|
|||
if res == 0 {
|
||||
return Err(APIError::Internal("Database insertion error".to_string()));
|
||||
}
|
||||
|
||||
service
|
||||
.add_meme(
|
||||
cat.id.clone(),
|
||||
f.name.clone(),
|
||||
f.hash.clone(),
|
||||
user.id.clone(),
|
||||
res,
|
||||
)
|
||||
.await?;
|
||||
service.pin(f.hash).await?;
|
||||
links.push(format!(
|
||||
"{}/{}/{}",
|
||||
|
|
Loading…
Reference in a new issue