forked from jmug/stoatchat
Compare commits
6
Commits
20220814-1
...
20220912-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86ad72b426 | ||
|
|
044d82d566 | ||
|
|
deffe663cb | ||
|
|
e7f6433d77 | ||
|
|
345e4d5f13 | ||
|
|
81fc3e7760 |
No files matched your search
@@ -1,3 +1,5 @@
|
|||||||
Rocket.toml
|
Rocket.toml
|
||||||
target
|
target
|
||||||
.data
|
.data
|
||||||
|
|
||||||
|
.vercel
|
||||||
@@ -2,13 +2,27 @@ use revolt_quark::{
|
|||||||
models::{channel::PartialChannel, Channel, User},
|
models::{channel::PartialChannel, Channel, User},
|
||||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsChannelDelete {
|
||||||
|
/// Whether to not send a leave message
|
||||||
|
leave_silently: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// # Close Channel
|
/// # Close Channel
|
||||||
///
|
///
|
||||||
/// Deletes a server channel, leaves a group or closes a group.
|
/// Deletes a server channel, leaves a group or closes a group.
|
||||||
#[openapi(tag = "Channel Information")]
|
#[openapi(tag = "Channel Information")]
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>?<options..>")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
pub async fn req(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
options: OptionsChannelDelete,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
let mut channel = target.as_channel(db).await?;
|
let mut channel = target.as_channel(db).await?;
|
||||||
let mut perms = perms(&user).channel(&channel);
|
let mut perms = perms(&user).channel(&channel);
|
||||||
perms.throw_permission(db, Permission::ViewChannel).await?;
|
perms.throw_permission(db, Permission::ViewChannel).await?;
|
||||||
@@ -27,7 +41,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
|||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse),
|
.map(|_| EmptyResponse),
|
||||||
Channel::Group { .. } => channel
|
Channel::Group { .. } => channel
|
||||||
.remove_user_from_group(db, &user.id, None)
|
.remove_user_from_group(
|
||||||
|
db,
|
||||||
|
&user.id,
|
||||||
|
None,
|
||||||
|
options.leave_silently.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse),
|
.map(|_| EmptyResponse),
|
||||||
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
|
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ pub struct DataEditChannel {
|
|||||||
icon: Option<String>,
|
icon: Option<String>,
|
||||||
/// Whether this channel is age-restricted
|
/// Whether this channel is age-restricted
|
||||||
nsfw: Option<bool>,
|
nsfw: Option<bool>,
|
||||||
|
/// Whether this channel is archived
|
||||||
|
archived: Option<bool>,
|
||||||
#[validate(length(min = 1))]
|
#[validate(length(min = 1))]
|
||||||
remove: Option<Vec<FieldsChannel>>,
|
remove: Option<Vec<FieldsChannel>>,
|
||||||
}
|
}
|
||||||
@@ -83,6 +85,7 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transfer ownership
|
// Transfer ownership
|
||||||
|
partial.owner = Some(new_owner.to_string());
|
||||||
let old_owner = std::mem::replace(owner, new_owner.to_string());
|
let old_owner = std::mem::replace(owner, new_owner.to_string());
|
||||||
|
|
||||||
// Notify clients
|
// Notify clients
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.remove_user_from_group(db, &member.id, Some(&user.id))
|
.remove_user_from_group(db, &member.id, Some(&user.id), false)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub async fn create_emoji(
|
|||||||
// Check that there are no more than 100 emoji
|
// Check that there are no more than 100 emoji
|
||||||
// ! FIXME: hardcoded upper limit
|
// ! FIXME: hardcoded upper limit
|
||||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||||
if emojis.len() > 100 {
|
if emojis.len() > 99 {
|
||||||
return Err(Error::TooManyEmoji);
|
return Err(Error::TooManyEmoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
}
|
}
|
||||||
|
|
||||||
server
|
server
|
||||||
.remove_member(db, member, RemovalIntention::Kick)
|
.remove_member(db, member, RemovalIntention::Kick, false)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,27 @@ use revolt_quark::{
|
|||||||
models::{server_member::RemovalIntention, User},
|
models::{server_member::RemovalIntention, User},
|
||||||
Db, EmptyResponse, Ref, Result,
|
Db, EmptyResponse, Ref, Result,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsServerDelete {
|
||||||
|
/// Whether to not send a leave message
|
||||||
|
leave_silently: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// # Delete / Leave Server
|
/// # Delete / Leave Server
|
||||||
///
|
///
|
||||||
/// Deletes a server if owner otherwise leaves.
|
/// Deletes a server if owner otherwise leaves.
|
||||||
#[openapi(tag = "Server Information")]
|
#[openapi(tag = "Server Information")]
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>?<options..>")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
pub async fn req(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
options: OptionsServerDelete,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
let server = target.as_server(db).await?;
|
let server = target.as_server(db).await?;
|
||||||
let member = db.fetch_member(&target.id, &user.id).await?;
|
let member = db.fetch_member(&target.id, &user.id).await?;
|
||||||
|
|
||||||
@@ -16,7 +30,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
|||||||
server.delete(db).await
|
server.delete(db).await
|
||||||
} else {
|
} else {
|
||||||
server
|
server
|
||||||
.remove_member(db, member, RemovalIntention::Leave)
|
.remove_member(
|
||||||
|
db,
|
||||||
|
member,
|
||||||
|
RemovalIntention::Leave,
|
||||||
|
options.leave_silently.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
|
server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
|
||||||
@@ -135,29 +137,44 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Apply new icon
|
// 2. Validate changes
|
||||||
|
if let Some(system_messages) = &partial.system_messages {
|
||||||
|
for id in system_messages.clone().into_channel_ids() {
|
||||||
|
if !server.channels.contains(&id) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(categories) = &mut partial.categories {
|
||||||
|
let mut channel_ids = HashSet::new();
|
||||||
|
for category in categories {
|
||||||
|
for channel in &category.channels {
|
||||||
|
if channel_ids.contains(channel) {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_ids.insert(channel.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
category
|
||||||
|
.channels
|
||||||
|
.retain(|item| server.channels.contains(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Apply new icon
|
||||||
if let Some(icon) = icon {
|
if let Some(icon) = icon {
|
||||||
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
|
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
|
||||||
server.icon = partial.icon.clone();
|
server.icon = partial.icon.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Apply new banner
|
// 4. Apply new banner
|
||||||
if let Some(banner) = banner {
|
if let Some(banner) = banner {
|
||||||
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
|
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
|
||||||
server.banner = partial.banner.clone();
|
server.banner = partial.banner.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Validate changes
|
|
||||||
if let Some(system_messages) = &partial.system_messages {
|
|
||||||
let channels = system_messages.clone().into_channel_ids();
|
|
||||||
if !db
|
|
||||||
.check_channels_exist(&channels.into_iter().collect())
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
return Err(Error::NotFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server
|
server
|
||||||
.update(db, partial, remove.unwrap_or_default())
|
.update(db, partial, remove.unwrap_or_default())
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||||
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
||||||
|
|
||||||
@@ -83,8 +81,4 @@ impl AbstractChannel for DummyDb {
|
|||||||
info!("Updating permissions for role {role} in {channel} with {permissions:?}");
|
info!("Updating permissions for role {role} in {channel} with {permissions:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_channels_exist(&self, _channels: &HashSet<String>) -> Result<bool> {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -286,6 +286,7 @@ impl Channel {
|
|||||||
db: &Database,
|
db: &Database,
|
||||||
user: &str,
|
user: &str,
|
||||||
by: Option<&str>,
|
by: Option<&str>,
|
||||||
|
silent: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match &self {
|
match &self {
|
||||||
Channel::Group {
|
Channel::Group {
|
||||||
@@ -329,20 +330,22 @@ impl Channel {
|
|||||||
.p(id.to_string())
|
.p(id.to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(by) = by {
|
if !silent {
|
||||||
SystemMessage::UserRemove {
|
if let Some(by) = by {
|
||||||
id: user.to_string(),
|
SystemMessage::UserRemove {
|
||||||
by: by.to_string(),
|
id: user.to_string(),
|
||||||
}
|
by: by.to_string(),
|
||||||
} else {
|
}
|
||||||
SystemMessage::UserLeft {
|
} else {
|
||||||
id: user.to_string(),
|
SystemMessage::UserLeft {
|
||||||
|
id: user.to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create(db, self, None)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
.into_message(id.to_string())
|
|
||||||
.create(db, self, None)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
@@ -254,6 +256,7 @@ impl Server {
|
|||||||
db: &Database,
|
db: &Database,
|
||||||
member: Member,
|
member: Member,
|
||||||
intention: RemovalIntention,
|
intention: RemovalIntention,
|
||||||
|
silent: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
db.delete_member(&member.id).await?;
|
db.delete_member(&member.id).await?;
|
||||||
|
|
||||||
@@ -264,20 +267,22 @@ impl Server {
|
|||||||
.p(member.id.server)
|
.p(member.id.server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
if !silent {
|
||||||
RemovalIntention::Leave => x.user_left.as_ref(),
|
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
||||||
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
RemovalIntention::Leave => x.user_left.as_ref(),
|
||||||
RemovalIntention::Ban => x.user_banned.as_ref(),
|
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
||||||
}) {
|
RemovalIntention::Ban => x.user_banned.as_ref(),
|
||||||
match intention {
|
}) {
|
||||||
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
|
match intention {
|
||||||
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
|
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
|
||||||
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
|
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
|
||||||
|
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
|
||||||
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create_no_web_push(db, id, false)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
.into_message(id.to_string())
|
|
||||||
.create_no_web_push(db, id, false)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -302,7 +307,7 @@ impl Server {
|
|||||||
member: Member,
|
member: Member,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
) -> Result<ServerBan> {
|
) -> Result<ServerBan> {
|
||||||
self.remove_member(db, member.clone(), RemovalIntention::Ban)
|
self.remove_member(db, member.clone(), RemovalIntention::Ban, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.ban_user(db, member.id, reason).await
|
self.ban_user(db, member.id, reason).await
|
||||||
@@ -310,23 +315,23 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SystemMessageChannels {
|
impl SystemMessageChannels {
|
||||||
pub fn into_channel_ids(self) -> Vec<String> {
|
pub fn into_channel_ids(self) -> HashSet<String> {
|
||||||
let mut ids = vec![];
|
let mut ids = HashSet::new();
|
||||||
|
|
||||||
if let Some(id) = self.user_joined {
|
if let Some(id) = self.user_joined {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_left {
|
if let Some(id) = self.user_left {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_kicked {
|
if let Some(id) = self.user_kicked {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = self.user_banned {
|
if let Some(id) = self.user_banned {
|
||||||
ids.push(id);
|
ids.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ids
|
ids
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use bson::{Bson, Document};
|
use bson::{Bson, Document};
|
||||||
|
|
||||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||||
@@ -291,25 +289,6 @@ impl AbstractChannel for MongoDb {
|
|||||||
with: "channel",
|
with: "channel",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool> {
|
|
||||||
let count = channels.len() as u64;
|
|
||||||
self.col::<Document>(COL)
|
|
||||||
.count_documents(
|
|
||||||
doc! {
|
|
||||||
"_id": {
|
|
||||||
"$in": channels.iter().cloned().collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|x| x == count)
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "count_documents",
|
|
||||||
with: "channel",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDocumentPath for FieldsChannel {
|
impl IntoDocumentPath for FieldsChannel {
|
||||||
|
|||||||
@@ -86,10 +86,8 @@ pub struct Masquerade {
|
|||||||
/// Replace the display role colour shown on this message
|
/// Replace the display role colour shown on this message
|
||||||
///
|
///
|
||||||
/// Must have `ManageRole` permission to use
|
/// Must have `ManageRole` permission to use
|
||||||
///
|
|
||||||
/// This can be any valid CSS colour
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||||
pub colour: Option<String>,
|
pub colour: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||||
use crate::{OverrideField, Result};
|
use crate::{OverrideField, Result};
|
||||||
|
|
||||||
@@ -55,7 +53,4 @@ pub trait AbstractChannel: Sync + Send {
|
|||||||
role: &str,
|
role: &str,
|
||||||
permissions: OverrideField,
|
permissions: OverrideField,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Validate existence of channels
|
|
||||||
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool>;
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user