mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-07-06 03:06:04 +00:00
Compare commits
23 Commits
20220610-2
...
0.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e5c8ed59b | ||
|
|
a7e0c42ee4 | ||
|
|
386f027a5a | ||
|
|
30c7f553d8 | ||
|
|
e291eef364 | ||
|
|
73443ce883 | ||
|
|
47297e243f | ||
|
|
4188b6d2f2 | ||
|
|
165380c7b4 | ||
|
|
49ca24ac9f | ||
|
|
35f956ce7d | ||
|
|
4baab5d5d5 | ||
|
|
7fc4fb2df7 | ||
|
|
ef757aa2fb | ||
|
|
0585dd0c20 | ||
|
|
f96541efab | ||
|
|
c6414338b6 | ||
|
|
4c4eb60cdb | ||
|
|
11d89b3bf0 | ||
|
|
64bb171cc8 | ||
|
|
6de5ad15c5 | ||
|
|
b5ab16d66f | ||
|
|
b9aad6d38c |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2505,7 +2505,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rauth"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/insertish/rauth?rev=ac61714935e3327c07bb0338a01aeec0318f9a96#ac61714935e3327c07bb0338a01aeec0318f9a96"
|
||||
source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -2730,7 +2730,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-delta"
|
||||
version = "0.5.3-7"
|
||||
version = "0.5.4"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-std",
|
||||
@@ -2986,7 +2986,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rocket_rauth"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/insertish/rauth?rev=ac61714935e3327c07bb0338a01aeec0318f9a96#ac61714935e3327c07bb0338a01aeec0318f9a96"
|
||||
source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394"
|
||||
dependencies = [
|
||||
"okapi",
|
||||
"rauth",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.5.3-7"
|
||||
version = "0.5.4"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
@@ -52,7 +52,7 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s
|
||||
# web
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" }
|
||||
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "ac61714935e3327c07bb0338a01aeec0318f9a96" }
|
||||
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "c2acaf6b31213e3969f8b48f9deed3daa125d394" }
|
||||
|
||||
# spec generation
|
||||
schemars = "0.8.8"
|
||||
|
||||
@@ -36,6 +36,8 @@ pub async fn req(
|
||||
edit.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
Message::validate_sum(&edit.content, &edit.embeds)?;
|
||||
|
||||
let mut message = msg.as_message(db).await?;
|
||||
if message.channel != target {
|
||||
return Err(Error::NotFound);
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct DataMessageSend {
|
||||
/// Unique token to prevent duplicate message sending
|
||||
///
|
||||
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
||||
#[validate(length(min = 1, max = 64))]
|
||||
nonce: Option<String>,
|
||||
|
||||
/// Message content to send
|
||||
@@ -32,6 +33,8 @@ pub struct DataMessageSend {
|
||||
/// Messages to reply to
|
||||
replies: Option<Vec<Reply>>,
|
||||
/// Embeds to include in message
|
||||
///
|
||||
/// Text embed content contributes to the content length cap
|
||||
#[validate(length(min = 1, max = 10))]
|
||||
embeds: Option<Vec<SendableEmbed>>,
|
||||
/// Masquerade to apply to this message
|
||||
@@ -60,6 +63,8 @@ pub async fn message_send(
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
Message::validate_sum(&data.content, &data.embeds)?;
|
||||
|
||||
idempotency.consume_nonce(data.nonce).await?;
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
|
||||
75
crates/delta/src/routes/customisation/emoji_create.rs
Normal file
75
crates/delta/src/routes/customisation/emoji_create.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use revolt_quark::models::emoji::EmojiParent;
|
||||
use revolt_quark::models::{Emoji, File, User};
|
||||
use revolt_quark::{perms, Db, Error, Permission, Result};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::regex::RE_EMOJI;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Emoji Data
|
||||
#[derive(Validate, Deserialize, JsonSchema)]
|
||||
pub struct DataCreateEmoji {
|
||||
/// Server name
|
||||
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
|
||||
name: String,
|
||||
parent: EmojiParent,
|
||||
}
|
||||
|
||||
/// # Create New Emoji
|
||||
///
|
||||
/// Create an emoji by its Autumn upload id.
|
||||
#[openapi(tag = "Emojis")]
|
||||
#[put("/emoji/<id>", data = "<data>")]
|
||||
pub async fn create_emoji(
|
||||
db: &Db,
|
||||
user: User,
|
||||
id: String,
|
||||
data: Json<DataCreateEmoji>,
|
||||
) -> Result<Json<Emoji>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
// Bots cannot manage emojis
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
// Validate we have permission to write into parent
|
||||
match &data.parent {
|
||||
EmojiParent::Server { id } => {
|
||||
let server = db.fetch_server(id).await?;
|
||||
|
||||
// Check for permission
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::ManageCustomisation)
|
||||
.await?;
|
||||
|
||||
// Check that there are no more than 100 emoji
|
||||
// ! FIXME: hardcoded upper limit
|
||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||
if emojis.len() > 100 {
|
||||
return Err(Error::TooManyEmoji);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Find the relevant attachment
|
||||
let attachment = File::use_emoji(db, &id, &id).await?;
|
||||
|
||||
// Create the emoji object
|
||||
let emoji = Emoji {
|
||||
id,
|
||||
parent: data.parent,
|
||||
creator_id: user.id,
|
||||
name: data.name,
|
||||
animated: "image/gif" == &attachment.content_type,
|
||||
};
|
||||
|
||||
// Save emoji
|
||||
emoji.create(db).await?;
|
||||
Ok(Json(emoji))
|
||||
}
|
||||
36
crates/delta/src/routes/customisation/emoji_delete.rs
Normal file
36
crates/delta/src/routes/customisation/emoji_delete.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use revolt_quark::models::{emoji::EmojiParent, User};
|
||||
use revolt_quark::{perms, Db, EmptyResponse, Error, Permission, Ref, Result};
|
||||
|
||||
/// # Delete Emoji
|
||||
///
|
||||
/// Delete an emoji by its id.
|
||||
#[openapi(tag = "Emojis")]
|
||||
#[delete("/emoji/<id>")]
|
||||
pub async fn delete_emoji(db: &Db, user: User, id: Ref) -> Result<EmptyResponse> {
|
||||
// Bots cannot manage emoji
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
// Fetch the emoji
|
||||
let emoji = id.as_emoji(db).await?;
|
||||
|
||||
// If we uploaded the emoji, then we have permission to delete it
|
||||
if emoji.creator_id != user.id {
|
||||
// Otherwise, validate we have permission to delete from parent
|
||||
match &emoji.parent {
|
||||
EmojiParent::Server { id } => {
|
||||
let server = db.fetch_server(id).await?;
|
||||
|
||||
// Check for permission
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::ManageCustomisation)
|
||||
.await?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the emoji
|
||||
emoji.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
13
crates/delta/src/routes/customisation/emoji_fetch.rs
Normal file
13
crates/delta/src/routes/customisation/emoji_fetch.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use revolt_quark::models::{Emoji, User};
|
||||
use revolt_quark::{Db, Ref, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Emoji
|
||||
///
|
||||
/// Fetch an emoji by its id.
|
||||
#[openapi(tag = "Emojis")]
|
||||
#[get("/emoji/<id>")]
|
||||
pub async fn fetch_emoji(db: &Db, _user: User, id: Ref) -> Result<Json<Emoji>> {
|
||||
id.as_emoji(db).await.map(Json)
|
||||
}
|
||||
14
crates/delta/src/routes/customisation/mod.rs
Normal file
14
crates/delta/src/routes/customisation/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use rocket::Route;
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
|
||||
mod emoji_create;
|
||||
mod emoji_delete;
|
||||
mod emoji_fetch;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
emoji_create::create_emoji,
|
||||
emoji_delete::delete_emoji,
|
||||
emoji_fetch::fetch_emoji
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use rocket_okapi::{okapi::openapi3::OpenApi, settings::OpenApiSettings};
|
||||
|
||||
mod bots;
|
||||
mod channels;
|
||||
mod customisation;
|
||||
mod invites;
|
||||
mod onboard;
|
||||
mod push;
|
||||
@@ -25,6 +26,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
"/channels" => channels::routes(),
|
||||
"/servers" => servers::routes(),
|
||||
"/invites" => invites::routes(),
|
||||
"/custom" => customisation::routes(),
|
||||
"/auth/account" => rocket_rauth::routes::account::routes(),
|
||||
"/auth/session" => rocket_rauth::routes::session::routes(),
|
||||
"/auth/mfa" => rocket_rauth::routes::mfa::routes(),
|
||||
@@ -96,6 +98,12 @@ fn custom_openapi_spec() -> OpenApi {
|
||||
"Invites"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Customisation",
|
||||
"tags": [
|
||||
"Emojis"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Authentication",
|
||||
"tags": [
|
||||
|
||||
@@ -32,13 +32,10 @@ pub async fn req(
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
if db.is_username_taken(&data.username).await? {
|
||||
return Err(Error::UsernameTaken);
|
||||
}
|
||||
|
||||
let username = User::validate_username(db, data.username).await?;
|
||||
let user = User {
|
||||
id: session.user_id,
|
||||
username: data.username,
|
||||
username,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
17
crates/delta/src/routes/servers/emoji_list.rs
Normal file
17
crates/delta/src/routes/servers/emoji_list.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use revolt_quark::models::{Emoji, User};
|
||||
use revolt_quark::{perms, Db, Ref, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Server Emoji
|
||||
///
|
||||
/// Fetch all emoji on a server.
|
||||
#[openapi(tag = "Server Customisation")]
|
||||
#[get("/<target>/emojis")]
|
||||
pub async fn list_emoji(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Emoji>>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
// Fetch all emoji from server if we can view it
|
||||
db.fetch_emoji_by_parent_id(&server.id).await.map(Json)
|
||||
}
|
||||
@@ -1,30 +1,26 @@
|
||||
use rocket::Route;
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
|
||||
mod server_ack;
|
||||
mod server_create;
|
||||
mod server_delete;
|
||||
mod server_edit;
|
||||
mod server_fetch;
|
||||
|
||||
mod channel_create;
|
||||
|
||||
mod member_edit;
|
||||
mod member_fetch;
|
||||
mod member_fetch_all;
|
||||
mod member_remove;
|
||||
|
||||
mod ban_create;
|
||||
mod ban_list;
|
||||
mod ban_remove;
|
||||
|
||||
mod channel_create;
|
||||
mod emoji_list;
|
||||
mod invites_fetch;
|
||||
|
||||
mod member_edit;
|
||||
mod member_fetch;
|
||||
mod member_fetch_all;
|
||||
mod member_remove;
|
||||
mod permissions_set;
|
||||
mod permissions_set_default;
|
||||
mod roles_create;
|
||||
mod roles_delete;
|
||||
mod roles_edit;
|
||||
mod server_ack;
|
||||
mod server_create;
|
||||
mod server_delete;
|
||||
mod server_edit;
|
||||
mod server_fetch;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
@@ -46,6 +42,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
roles_edit::req,
|
||||
roles_delete::req,
|
||||
permissions_set::req,
|
||||
permissions_set_default::req
|
||||
permissions_set_default::req,
|
||||
emoji_list::list_emoji
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,11 +10,7 @@ use rocket::State;
|
||||
#[openapi(tag = "Relationships")]
|
||||
#[put("/<target>/friend")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<User>> {
|
||||
let mut target = if let Ok(user) = db.fetch_user_by_username(&target.id).await {
|
||||
user
|
||||
} else {
|
||||
target.as_user(db).await?
|
||||
};
|
||||
let mut target = target.as_user(db).await?;
|
||||
|
||||
if user.bot.is_some() || target.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
|
||||
@@ -6,4 +6,9 @@ use regex::Regex;
|
||||
/// Block zero width space
|
||||
/// Block lookalike characters
|
||||
pub static RE_USERNAME: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[^\u200BА-Яа-яΑ-Ωα-ω]+$").unwrap());
|
||||
Lazy::new(|| Regex::new(r"^[^\u200BА-Яа-яΑ-Ωα-ω@#:\n\r\[\]]+$").unwrap());
|
||||
|
||||
/// Regex for valid emoji names
|
||||
///
|
||||
/// Alphanumeric and underscores
|
||||
pub static RE_EMOJI: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]+$").unwrap());
|
||||
|
||||
@@ -84,7 +84,7 @@ rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_emp
|
||||
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
|
||||
|
||||
# rAuth
|
||||
rauth = { git = "https://github.com/insertish/rauth", rev = "ac61714935e3327c07bb0338a01aeec0318f9a96", features = [ "async-std-runtime" ] }
|
||||
rauth = { git = "https://github.com/insertish/rauth", rev = "c2acaf6b31213e3969f8b48f9deed3daa125d394", features = [ "async-std-runtime" ] }
|
||||
|
||||
# Sentry
|
||||
sentry = "0.25.0"
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::models::message::{AppendMessage, PartialMessage};
|
||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
||||
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
|
||||
use crate::models::{Channel, Member, Message, Server, User, UserSettings};
|
||||
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings};
|
||||
use crate::Error;
|
||||
|
||||
/// WebSocket Client Errors
|
||||
@@ -52,6 +52,7 @@ pub enum EventV1 {
|
||||
servers: Vec<Server>,
|
||||
channels: Vec<Channel>,
|
||||
members: Vec<Member>,
|
||||
emojis: Option<Vec<Emoji>>,
|
||||
},
|
||||
|
||||
/// Ping response
|
||||
@@ -170,4 +171,10 @@ pub enum EventV1 {
|
||||
|
||||
/// Settings updated remotely
|
||||
UserSettingsUpdate { id: String, update: UserSettings },
|
||||
|
||||
/// New emoji
|
||||
EmojiCreate(Emoji),
|
||||
|
||||
/// Delete emoji
|
||||
EmojiDelete { id: String },
|
||||
}
|
||||
|
||||
@@ -151,6 +151,17 @@ impl State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Fetch customisations.
|
||||
let emojis = Some(
|
||||
db.fetch_emoji_by_parent_ids(
|
||||
&servers
|
||||
.iter()
|
||||
.map(|x| x.id.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
// Copy data into local state cache.
|
||||
self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect();
|
||||
self.cache
|
||||
@@ -202,6 +213,7 @@ impl State {
|
||||
servers,
|
||||
channels,
|
||||
members,
|
||||
emojis,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -39,4 +39,9 @@ impl AbstractAttachment for DummyDb {
|
||||
info!("Marked {id} as deleted");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
|
||||
info!("Marked {ids:?} as deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
41
crates/quark/src/impl/dummy/media/emoji.rs
Normal file
41
crates/quark/src/impl/dummy/media/emoji.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::models::emoji::EmojiParent;
|
||||
use crate::models::Emoji;
|
||||
use crate::{AbstractEmoji, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmoji for DummyDb {
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
Ok(Emoji {
|
||||
id: id.into(),
|
||||
name: id.into(),
|
||||
parent: EmojiParent::Server { id: id.into() },
|
||||
creator_id: id.into(),
|
||||
animated: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch emoji by their ids
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
Ok(vec![self.fetch_emoji(parent_id).await?])
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, _parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
info!("Insert {emoji:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete an emoji by its id
|
||||
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
info!("Delete {emoji:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod media {
|
||||
pub mod attachment;
|
||||
pub mod emoji;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
|
||||
@@ -242,7 +242,12 @@ impl Channel {
|
||||
/// Add user to a group
|
||||
pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> {
|
||||
if let Channel::Group { recipients, .. } = self {
|
||||
recipients.push(user.to_string());
|
||||
let user = user.to_string();
|
||||
if recipients.contains(&user) {
|
||||
return Err(Error::AlreadyInGroup);
|
||||
}
|
||||
|
||||
recipients.push(user);
|
||||
}
|
||||
|
||||
match &self {
|
||||
|
||||
@@ -135,7 +135,17 @@ impl Message {
|
||||
|
||||
/// Delete a message
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
let file_ids: Vec<String> = self
|
||||
.attachments
|
||||
.map(|files| files.iter().map(|file| file.id.to_string()).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
if !file_ids.is_empty() {
|
||||
db.mark_attachments_as_deleted(&file_ids).await?;
|
||||
}
|
||||
|
||||
db.delete_message(&self.id).await?;
|
||||
|
||||
EventV1::MessageDelete {
|
||||
id: self.id,
|
||||
channel: self.channel.clone(),
|
||||
@@ -156,6 +166,31 @@ impl Message {
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate the sum of content of a message is under threshold
|
||||
pub fn validate_sum(
|
||||
content: &Option<String>,
|
||||
embeds: &Option<Vec<SendableEmbed>>,
|
||||
) -> Result<()> {
|
||||
let mut running_total = 0;
|
||||
if let Some(content) = content {
|
||||
running_total += content.len();
|
||||
}
|
||||
|
||||
if let Some(embeds) = embeds {
|
||||
for embed in embeds {
|
||||
if let Some(desc) = &embed.description {
|
||||
running_total += desc.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if running_total <= 2000 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::PayloadTooLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoUsers {
|
||||
|
||||
@@ -30,4 +30,9 @@ impl File {
|
||||
db.find_and_use_attachment(id, "banners", "server", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_emoji(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "emojis", "object", parent)
|
||||
.await
|
||||
}
|
||||
}
|
||||
36
crates/quark/src/impl/generic/media/emoji.rs
Normal file
36
crates/quark/src/impl/generic/media/emoji.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{emoji::EmojiParent, Emoji},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Emoji {
|
||||
/// Get parent id
|
||||
fn parent(&self) -> &str {
|
||||
match &self.parent {
|
||||
EmojiParent::Server { id } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an emoji
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_emoji(self).await?;
|
||||
EventV1::EmojiCreate(self.clone())
|
||||
.p(self.parent().to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete an emoji
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
EventV1::EmojiDelete {
|
||||
id: self.id.to_string(),
|
||||
}
|
||||
.p(self.parent().to_string())
|
||||
.await;
|
||||
|
||||
db.mark_attachment_as_deleted(&self.id).await?;
|
||||
db.delete_emoji(&self).await
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod media {
|
||||
pub mod attachment;
|
||||
pub mod emoji;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
|
||||
@@ -150,8 +150,8 @@ impl User {
|
||||
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
||||
}
|
||||
|
||||
/// Update a user's username
|
||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||
/// Sanitise and validate a username can be used
|
||||
pub async fn validate_username(db: &Database, username: String) -> Result<String> {
|
||||
// Trim surrounding spaces
|
||||
let username = username.trim().to_string();
|
||||
|
||||
@@ -173,7 +173,7 @@ impl User {
|
||||
}
|
||||
|
||||
// Ensure none of the following substrings show up in the username
|
||||
const BLOCKED_SUBSTRINGS: &[&str] = &["@", "#", ":", "```", "\n"];
|
||||
const BLOCKED_SUBSTRINGS: &[&str] = &["```"];
|
||||
|
||||
for substr in BLOCKED_SUBSTRINGS {
|
||||
if username_lowercase.contains(substr) {
|
||||
@@ -186,10 +186,15 @@ impl User {
|
||||
return Err(Error::UsernameTaken);
|
||||
}
|
||||
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
/// Update a user's username
|
||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||
self.update(
|
||||
db,
|
||||
PartialUser {
|
||||
username: Some(username),
|
||||
username: Some(User::validate_username(db, username).await?),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
|
||||
@@ -16,7 +16,7 @@ struct MigrationInfo {
|
||||
revision: i32,
|
||||
}
|
||||
|
||||
pub const LATEST_REVISION: i32 = 16;
|
||||
pub const LATEST_REVISION: i32 = 17;
|
||||
|
||||
pub async fn migrate_database(db: &MongoDb) {
|
||||
let migrations = db.col::<Document>("migrations");
|
||||
@@ -611,6 +611,39 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if revision <= 16 {
|
||||
info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and rAuth migration.");
|
||||
|
||||
let rauth_db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
|
||||
rauth_db
|
||||
.run_migration(rauth::Migration::M2022_06_09AddIndexForDeletion)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.db()
|
||||
.create_collection("emojis", None)
|
||||
.await
|
||||
.expect("Failed to create emojis collection.");
|
||||
|
||||
db.db()
|
||||
.run_command(
|
||||
doc! {
|
||||
"createIndexes": "emojis",
|
||||
"indexes": [
|
||||
{
|
||||
"key": {
|
||||
"parent.id": 1_i32,
|
||||
},
|
||||
"name": "parent_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create emoji parent index.");
|
||||
}
|
||||
|
||||
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||
|
||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||
|
||||
@@ -37,30 +37,46 @@ impl AbstractChannelUnread for MongoDb {
|
||||
}
|
||||
|
||||
async fn acknowledge_channels(&self, user: &str, channels: &[String]) -> Result<()> {
|
||||
let current_time = Ulid::new().to_string();
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
.delete_many(
|
||||
doc! {
|
||||
"_id.channel": {
|
||||
"$in": channels
|
||||
},
|
||||
"_id.user": user,
|
||||
"_id.user": user
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"mentions": 1_i32
|
||||
},
|
||||
"$set": {
|
||||
"last_id": Ulid::new().to_string()
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update",
|
||||
with: "channel_unread",
|
||||
operation: "delete_many",
|
||||
with: "channel_unreads",
|
||||
})?;
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.insert_many(
|
||||
channels
|
||||
.iter()
|
||||
.map(|channel| {
|
||||
doc! {
|
||||
"_id": {
|
||||
"channel": channel,
|
||||
"user": user
|
||||
},
|
||||
"last_id": ¤t_time
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Document>>(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update_many",
|
||||
with: "channel_unreads",
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn add_mention_to_unread<'a>(
|
||||
|
||||
@@ -14,7 +14,7 @@ impl MongoDb {
|
||||
pub async fn delete_bulk_messages(&self, projection: Document) -> Result<()> {
|
||||
let mut for_attachments = projection.clone();
|
||||
for_attachments.insert(
|
||||
"attachment",
|
||||
"attachments",
|
||||
doc! {
|
||||
"$exists": 1_i32
|
||||
},
|
||||
@@ -126,10 +126,7 @@ impl AbstractMessage for MongoDb {
|
||||
}
|
||||
|
||||
async fn delete_message(&self, id: &str) -> Result<()> {
|
||||
self.delete_bulk_messages(doc! {
|
||||
"_id": id
|
||||
})
|
||||
.await
|
||||
self.delete_one_by_id(COL, id).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||
|
||||
@@ -122,4 +122,27 @@ impl AbstractAttachment for MongoDb {
|
||||
with: "attachment",
|
||||
})
|
||||
}
|
||||
|
||||
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_many(
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
}
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"deleted": true
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "update",
|
||||
with: "attachments",
|
||||
})
|
||||
}
|
||||
}
|
||||
48
crates/quark/src/impl/mongo/media/emoji.rs
Normal file
48
crates/quark/src/impl/mongo/media/emoji.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::models::Emoji;
|
||||
use crate::{AbstractEmoji, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "emojis";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmoji for MongoDb {
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
self.find_one_by_id(COL, id).await
|
||||
}
|
||||
|
||||
/// Fetch emoji by their ids
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": parent_id
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": {
|
||||
"$in": parent_ids
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
self.insert_one(COL, emoji).await.map(|_| ())
|
||||
}
|
||||
|
||||
/// Delete an emoji by its id
|
||||
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
self.delete_one_by_id(COL, &emoji.id).await.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,9 @@ pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod media {
|
||||
pub mod attachment;
|
||||
pub mod emoji;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
|
||||
@@ -18,6 +18,20 @@ impl MongoDb {
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Delete all emoji.
|
||||
self.col::<Document>("emojis")
|
||||
.delete_many(
|
||||
doc! {
|
||||
"parent.id": &server.id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "emojis",
|
||||
})?;
|
||||
|
||||
// Delete all channels.
|
||||
self.col::<Document>("channels")
|
||||
.delete_many(
|
||||
|
||||
24
crates/quark/src/models/media/emoji.rs
Normal file
24
crates/quark/src/models/media/emoji.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Information about what owns this emoji
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum EmojiParent {
|
||||
Server { id: String },
|
||||
}
|
||||
|
||||
/// Representation of an Emoji on Revolt
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
pub struct Emoji {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// What owns this emoji
|
||||
pub parent: EmojiParent,
|
||||
/// Uploader user id
|
||||
pub creator_id: String,
|
||||
/// Emoji name
|
||||
pub name: String,
|
||||
/// Whether the emoji is animated
|
||||
pub animated: bool,
|
||||
}
|
||||
@@ -3,8 +3,9 @@ mod admin {
|
||||
pub mod simple;
|
||||
}
|
||||
|
||||
mod autumn {
|
||||
mod media {
|
||||
pub mod attachment;
|
||||
pub mod emoji;
|
||||
}
|
||||
|
||||
mod channels {
|
||||
@@ -27,8 +28,8 @@ mod users {
|
||||
}
|
||||
|
||||
pub use admin::*;
|
||||
pub use autumn::*;
|
||||
pub use channels::*;
|
||||
pub use media::*;
|
||||
pub use servers::*;
|
||||
pub use users::*;
|
||||
|
||||
@@ -37,6 +38,7 @@ pub use bot::Bot;
|
||||
pub use channel::Channel;
|
||||
pub use channel_invite::Invite;
|
||||
pub use channel_unread::ChannelUnread;
|
||||
pub use emoji::Emoji;
|
||||
pub use message::Message;
|
||||
pub use migrations::MigrationInfo;
|
||||
pub use server::Server;
|
||||
|
||||
205
crates/quark/src/models/relations.drawio
Normal file
205
crates/quark/src/models/relations.drawio
Normal file
@@ -0,0 +1,205 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="9BKyaxnbqnTkbfLuLd1z" name="Page-1">
|
||||
<mxGraphModel dx="1033" dy="710" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="56" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="6" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="260" y="370"/>
|
||||
<mxPoint x="275" y="370"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="58" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="260" y="370"/>
|
||||
<mxPoint x="342" y="370"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="59" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="300" y="250" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="260" y="370"/>
|
||||
<mxPoint x="300" y="370"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="2" value="Attachment" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="220" y="490" width="80" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="49" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="3" target="5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="195" y="340"/>
|
||||
<mxPoint x="245" y="340"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="51" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" parent="1" source="3" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="290" y="160" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="195" y="260"/>
|
||||
<mxPoint x="260" y="260"/>
|
||||
<mxPoint x="260" y="220"/>
|
||||
<mxPoint x="290" y="220"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="Channel Invite" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="150" y="400" width="90" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="Channel Unread" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="400" width="100" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="43" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;dashed=1;" parent="1" source="5" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="60" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;strokeColor=#FFFFFF;" parent="1" source="5" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="Channel" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="230" y="280" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="45" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="6" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="46" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="6" target="5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="310" y="355"/>
|
||||
<mxPoint x="260" y="355"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="6" value="Message" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="280" y="400" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="54" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="7" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="505" y="350"/>
|
||||
<mxPoint x="355" y="350"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Server Ban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="470" y="400" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="47" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="8" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="405" y="350"/>
|
||||
<mxPoint x="355" y="350"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="48" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="8" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="405" y="350"/>
|
||||
<mxPoint x="310" y="350"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="Server Member" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="400" width="90" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;startArrow=diamondThin;startFill=1;" parent="1" source="10" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="240" y="145"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Bot" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="130" width="40" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="11" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="User Settings" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="150" y="210" width="90" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;" parent="1" source="12" target="13" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="User" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="280" y="130" width="40" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="Account" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="310" y="40" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;endArrow=diamondThin;endFill=1;" parent="1" source="14" target="13" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="Session" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="130" width="60" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="44" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="310" y="160" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="355" y="220"/>
|
||||
<mxPoint x="310" y="220"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="Server" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="330" y="280" width="50" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="55" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;" parent="1" source="4" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="290" y="160" as="targetPoint"/>
|
||||
<mxPoint x="200" y="400" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="90" y="370"/>
|
||||
<mxPoint x="195" y="370"/>
|
||||
<mxPoint x="195" y="260"/>
|
||||
<mxPoint x="260" y="260"/>
|
||||
<mxPoint x="260" y="220"/>
|
||||
<mxPoint x="290" y="220"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="63" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#FFFFFF;entryX=1;entryY=1;entryDx=0;entryDy=0;" parent="1" source="61" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="584.25" y="335" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="584" y="340"/>
|
||||
<mxPoint x="390" y="340"/>
|
||||
<mxPoint x="390" y="210"/>
|
||||
<mxPoint x="320" y="210"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="64" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=1;strokeColor=#FFFFFF;" parent="1" source="61" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="584" y="340"/>
|
||||
<mxPoint x="368" y="340"/>
|
||||
<mxPoint x="368" y="310"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="61" value="Emoji" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="558.5" y="400" width="51.5" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="65" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;endArrow=diamondThin;endFill=0;strokeColor=#B3B3B3;dashed=1;" parent="1" source="2" target="61" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="270" y="500" as="sourcePoint"/>
|
||||
<mxPoint x="320" y="440" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -21,8 +21,10 @@ pub enum Permission {
|
||||
ManagePermissions = 1 << 2,
|
||||
/// Manage roles on server
|
||||
ManageRole = 1 << 3,
|
||||
/// Manage server customisation (includes emoji)
|
||||
ManageCustomisation = 1 << 4,
|
||||
|
||||
// % 2 bits reserved
|
||||
// % 1 bits reserved
|
||||
|
||||
// * Member permissions
|
||||
/// Kick other members below their ranking
|
||||
@@ -122,6 +124,7 @@ bitfield! {
|
||||
pub can_manage_server, _: 62;
|
||||
pub can_manage_permissions, _: 61;
|
||||
pub can_manage_roles, _: 60;
|
||||
pub can_manage_customisation, _: 59;
|
||||
|
||||
// * Member permissions
|
||||
pub can_kick_members, _: 57;
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Result;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractAttachment: Sync + Send {
|
||||
/// Find an attachment by its details and mark it as used by a given parent.
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -10,7 +11,16 @@ pub trait AbstractAttachment: Sync + Send {
|
||||
parent_type: &str,
|
||||
parent_id: &str,
|
||||
) -> Result<File>;
|
||||
|
||||
/// Insert attachment into database.
|
||||
async fn insert_attachment(&self, attachment: &File) -> Result<()>;
|
||||
|
||||
/// Mark an attachment as having been reported.
|
||||
async fn mark_attachment_as_reported(&self, id: &str) -> Result<()>;
|
||||
|
||||
/// Mark an attachment as having been deleted.
|
||||
async fn mark_attachment_as_deleted(&self, id: &str) -> Result<()>;
|
||||
|
||||
/// Mark multiple attachments as having been deleted.
|
||||
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()>;
|
||||
}
|
||||
20
crates/quark/src/traits/media/emoji.rs
Normal file
20
crates/quark/src/traits/media/emoji.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::models::Emoji;
|
||||
use crate::Result;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractEmoji: Sync + Send {
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji>;
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>>;
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>>;
|
||||
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()>;
|
||||
|
||||
/// Delete an emoji by its id
|
||||
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()>;
|
||||
}
|
||||
@@ -2,8 +2,9 @@ mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
mod autumn {
|
||||
mod media {
|
||||
pub mod attachment;
|
||||
pub mod emoji;
|
||||
}
|
||||
|
||||
mod channels {
|
||||
@@ -27,7 +28,8 @@ mod users {
|
||||
|
||||
pub use admin::migrations::AbstractMigrations;
|
||||
|
||||
pub use autumn::attachment::AbstractAttachment;
|
||||
pub use media::attachment::AbstractAttachment;
|
||||
pub use media::emoji::AbstractEmoji;
|
||||
|
||||
pub use channels::channel::AbstractChannel;
|
||||
pub use channels::channel_invite::AbstractChannelInvite;
|
||||
@@ -42,14 +44,12 @@ pub use users::bot::AbstractBot;
|
||||
pub use users::user::AbstractUser;
|
||||
pub use users::user_settings::AbstractUserSettings;
|
||||
|
||||
// pub trait AbstractEventEmitter {}
|
||||
// + AbstractEventEmitter
|
||||
|
||||
pub trait AbstractDatabase:
|
||||
Sync
|
||||
+ Send
|
||||
+ AbstractMigrations
|
||||
+ AbstractAttachment
|
||||
+ AbstractEmoji
|
||||
+ AbstractChannel
|
||||
+ AbstractChannelInvite
|
||||
+ AbstractChannelUnread
|
||||
|
||||
@@ -202,12 +202,22 @@ impl Embed {
|
||||
let mut finder = LinkFinder::new();
|
||||
finder.kinds(&[LinkKind::Url]);
|
||||
|
||||
let links: HashSet<String> = finder
|
||||
// Process all links, stripping anchors and
|
||||
// only taking up to `max_embeds` of links.
|
||||
let links: Vec<String> = finder
|
||||
.links(&content)
|
||||
.map(|x| {
|
||||
x.as_str()
|
||||
.chars()
|
||||
.take_while(|&ch| ch != '#')
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<HashSet<String>>()
|
||||
.into_iter()
|
||||
.take(max_embeds)
|
||||
.map(|x| x.as_str().to_string())
|
||||
.collect();
|
||||
|
||||
// If no links, fail out.
|
||||
if links.is_empty() {
|
||||
return Err(Error::LabelMe);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{Bot, Channel, Invite, Member, Message, Server, ServerBan, User};
|
||||
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User};
|
||||
use crate::presence::presence_is_online;
|
||||
use crate::{Database, Result};
|
||||
|
||||
@@ -63,6 +63,11 @@ impl Ref {
|
||||
pub async fn as_ban(&self, db: &Database, server: &str) -> Result<ServerBan> {
|
||||
db.fetch_ban(server, &self.id).await
|
||||
}
|
||||
|
||||
/// Fetch emoji from Ref
|
||||
pub async fn as_emoji(&self, db: &Database) -> Result<Emoji> {
|
||||
db.fetch_emoji(&self.id).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> FromParam<'r> for Ref {
|
||||
|
||||
@@ -41,6 +41,7 @@ pub enum Error {
|
||||
TooManyAttachments,
|
||||
TooManyReplies,
|
||||
EmptyMessage,
|
||||
PayloadTooLarge,
|
||||
CannotRemoveYourself,
|
||||
GroupTooLarge {
|
||||
max: usize,
|
||||
@@ -55,6 +56,7 @@ pub enum Error {
|
||||
TooManyServers {
|
||||
max: usize,
|
||||
},
|
||||
TooManyEmoji,
|
||||
|
||||
// ? Bot related errors.
|
||||
ReachedMaximumBots,
|
||||
@@ -145,6 +147,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
Error::TooManyAttachments => Status::BadRequest,
|
||||
Error::TooManyReplies => Status::BadRequest,
|
||||
Error::EmptyMessage => Status::UnprocessableEntity,
|
||||
Error::PayloadTooLarge => Status::UnprocessableEntity,
|
||||
Error::CannotRemoveYourself => Status::BadRequest,
|
||||
Error::GroupTooLarge { .. } => Status::Forbidden,
|
||||
Error::AlreadyInGroup => Status::Conflict,
|
||||
@@ -154,6 +157,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
Error::InvalidRole => Status::NotFound,
|
||||
Error::Banned => Status::Forbidden,
|
||||
Error::TooManyServers { .. } => Status::Forbidden,
|
||||
Error::TooManyEmoji => Status::BadRequest,
|
||||
|
||||
Error::ReachedMaximumBots => Status::BadRequest,
|
||||
Error::IsBot => Status::BadRequest,
|
||||
|
||||
@@ -100,7 +100,13 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
|
||||
if let Some(segment) = request.routed_segment(0) {
|
||||
let resource = request.routed_segment(1);
|
||||
match (segment, resource) {
|
||||
("users", _) => ("users", None),
|
||||
("users", _) => {
|
||||
if let Some("default_avatar") = request.routed_segment(2) {
|
||||
return ("default_avatar", None);
|
||||
}
|
||||
|
||||
("users", None)
|
||||
}
|
||||
("bots", _) => ("bots", None),
|
||||
("channels", Some(id)) => {
|
||||
if request.method() == Method::Post {
|
||||
@@ -137,6 +143,7 @@ fn resolve_bucket_limit(bucket: &str) -> u8 {
|
||||
"servers" => 5,
|
||||
"auth" => 15,
|
||||
"auth_delete" => 255,
|
||||
"default_avatar" => 255,
|
||||
"swagger" => 100,
|
||||
_ => 20,
|
||||
}
|
||||
|
||||
@@ -1,18 +1,73 @@
|
||||
version: "3.3"
|
||||
services:
|
||||
# Redis
|
||||
redis:
|
||||
image: eqalpha/keydb
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
# MongoDB
|
||||
database:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- ./.data/db:/data/db
|
||||
|
||||
# MinIO
|
||||
minio:
|
||||
image: minio/minio
|
||||
command: server /data
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./.data/minio:/data
|
||||
ports:
|
||||
- "10000:9000"
|
||||
restart: always
|
||||
|
||||
# Mongo Express
|
||||
mongo-express:
|
||||
image: mongo-express
|
||||
ports:
|
||||
- "8081:8081"
|
||||
environment:
|
||||
- ME_CONFIG_MONGODB_SERVER=database
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
# Create buckets for minio.
|
||||
createbuckets:
|
||||
image: minio/mc
|
||||
depends_on:
|
||||
- minio
|
||||
env_file: .env
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
while ! curl -s --output /dev/null --connect-timeout 1 http://minio:9000; do echo 'Waiting minio...' && sleep 0.1; done;
|
||||
/usr/bin/mc alias set minio http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD;
|
||||
/usr/bin/mc mb minio/attachments;
|
||||
/usr/bin/mc mb minio/avatars;
|
||||
/usr/bin/mc mb minio/backgrounds;
|
||||
/usr/bin/mc mb minio/icons;
|
||||
/usr/bin/mc mb minio/banners;
|
||||
exit 0;
|
||||
"
|
||||
# File server (autumn)
|
||||
autumn:
|
||||
image: ghcr.io/revoltchat/autumn:1.1.5
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- database
|
||||
- createbuckets
|
||||
environment:
|
||||
- AUTUMN_MONGO_URI=mongodb://database
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: always
|
||||
|
||||
# Metadata and image proxy (january)
|
||||
january:
|
||||
image: ghcr.io/revoltchat/january:0.3.4
|
||||
ports:
|
||||
- "7000:7000"
|
||||
restart: always
|
||||
|
||||
Reference in New Issue
Block a user