Compare commits

...
Author SHA1 Message Date
Paul Makles cb813e91f0 chore: cut new 0.6.9 release 2023-10-28 14:39:10 +01:00
Paul Makles a6bc9fcbb3 refactor: implement Server::create 2023-10-28 14:25:29 +01:00
Paul Makles cd0737911b refactor: use fixtures to populate db
refactor: method for creating server channels
2023-10-27 16:31:37 +01:00
24 changed files with 499 additions and 553 deletions
Generated
+9 -9
View File
@@ -3494,7 +3494,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-std",
"async-tungstenite",
@@ -3512,7 +3512,7 @@ dependencies = [
[[package]]
name = "revolt-config"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-std",
"cached",
@@ -3524,7 +3524,7 @@ dependencies = [
[[package]]
name = "revolt-database"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-lock",
"async-recursion",
@@ -3569,7 +3569,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-channel",
"async-std",
@@ -3615,7 +3615,7 @@ dependencies = [
[[package]]
name = "revolt-models"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"indexmap",
"iso8601-timestamp 0.2.11",
@@ -3631,7 +3631,7 @@ dependencies = [
[[package]]
name = "revolt-permissions"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-std",
"async-trait",
@@ -3646,7 +3646,7 @@ dependencies = [
[[package]]
name = "revolt-presence"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-std",
"log",
@@ -3657,7 +3657,7 @@ dependencies = [
[[package]]
name = "revolt-quark"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-lock",
"async-recursion",
@@ -3710,7 +3710,7 @@ dependencies = [
[[package]]
name = "revolt-result"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"revolt_okapi",
"revolt_rocket_okapi",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.6.8"
version = "0.6.9"
license = "AGPL-3.0-or-later"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-config"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
+6 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-database"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -23,11 +23,11 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies]
# Core
revolt-config = { version = "0.6.8", path = "../config" }
revolt-result = { version = "0.6.8", path = "../result" }
revolt-models = { version = "0.6.8", path = "../models" }
revolt-presence = { version = "0.6.8", path = "../presence" }
revolt-permissions = { version = "0.6.8", path = "../permissions", features = [
revolt-config = { version = "0.6.9", path = "../config" }
revolt-result = { version = "0.6.9", path = "../result" }
revolt-models = { version = "0.6.9", path = "../models" }
revolt-presence = { version = "0.6.9", path = "../presence" }
revolt-permissions = { version = "0.6.9", path = "../permissions", features = [
"serde",
"bson",
] }
@@ -0,0 +1,89 @@
[
{
"_object_type": "User",
"_id": "__ID:0__",
"username": "Owner",
"discriminator": "0001"
},
{
"_object_type": "User",
"_id": "__ID:1__",
"username": "Moderator",
"discriminator": "0001"
},
{
"_object_type": "User",
"_id": "__ID:2__",
"username": "User",
"discriminator": "0001"
},
{
"_object_type": "Channel",
"_id": "__ID:3__",
"channel_type": "TextChannel",
"name": "General",
"server": "__ID:4__",
"default_permissions": {
"a": 0,
"d": 1048576
},
"role_permissions": {
"__ID:5__": {
"a": 1048576,
"d": 0
}
}
},
{
"_object_type": "Server",
"_id": "__ID:4__",
"owner": "__ID:0__",
"name": "Server",
"channels": ["__ID:3__"],
"roles": {
"__ID:5__": {
"name": "Moderator",
"permissions": {
"a": 545270208,
"d": 0
},
"rank": 3
},
"__ID:6__": {
"name": "Owner",
"permissions": {
"a": 0,
"d": 0
},
"rank": 0
}
},
"default_permissions": 4000322560
},
{
"_object_type": "ServerMember",
"_id": {
"user": "__ID:0__",
"server": "__ID:4__"
},
"roles": ["__ID:6__"],
"joined_at": 1698318340195
},
{
"_object_type": "ServerMember",
"_id": {
"user": "__ID:1__",
"server": "__ID:4__"
},
"roles": ["__ID:5__"],
"joined_at": 1698318340195
},
{
"_object_type": "ServerMember",
"_id": {
"user": "__ID:2__",
"server": "__ID:4__"
},
"joined_at": 1698318340195
}
]
+72 -120
View File
@@ -6,7 +6,10 @@ use revolt_permissions::OverrideField;
use revolt_result::Result;
use serde::{Deserialize, Serialize};
use crate::{events::client::EventV1, Database, File, IntoDocumentPath, SystemMessage, User};
use crate::{
events::client::EventV1, Database, File, IntoDocumentPath, PartialServer, Server,
SystemMessage, User,
};
auto_derived!(
#[serde(tag = "channel_type")]
@@ -189,6 +192,67 @@ impl Channel {
Ok(())
}*/
/// Create a new server channel
pub async fn create_server_channel(
db: &Database,
server: &mut Server,
data: v0::DataCreateServerChannel,
update_server: bool,
) -> Result<Channel> {
let config = config().await;
if server.channels.len() > config.features.limits.default.server_channels {
return Err(create_error!(TooManyChannels {
max: config.features.limits.default.server_channels,
}));
};
let id = ulid::Ulid::new().to_string();
let channel = match data.channel_type {
v0::LegacyServerChannelType::Text => Channel::TextChannel {
id: id.clone(),
server: server.id.to_owned(),
name: data.name,
description: data.description,
icon: None,
last_message_id: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
},
v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
id: id.clone(),
server: server.id.to_owned(),
name: data.name,
description: data.description,
icon: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
},
};
db.insert_channel(&channel).await?;
if update_server {
server
.update(
db,
PartialServer {
channels: Some([server.channels.clone(), [id].into()].concat()),
..Default::default()
},
vec![],
)
.await?;
EventV1::ChannelCreate(channel.clone().into())
.p(server.id.clone())
.await;
}
Ok(channel)
}
/// Create a group
pub async fn create_group(
db: &Database,
@@ -627,130 +691,18 @@ impl IntoDocumentPath for FieldsChannel {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, OverrideField};
use crate::{
util::permissions::DatabasePermissionQuery, Channel, Member, MemberCompositeKey, Role,
Server, User,
};
use crate::{fixture, util::permissions::DatabasePermissionQuery};
#[async_std::test]
async fn permissions_text_channel() {
database_test!(|db| async move {
let owner = User::create(&db, "Owner".to_string(), None, None)
.await
.unwrap();
let moderator = User::create(&db, "Moderator".to_string(), None, None)
.await
.unwrap();
let user = User::create(&db, "User".to_string(), None, None)
.await
.unwrap();
let server_id = ulid::Ulid::new().to_string();
let channel = Channel::TextChannel {
id: ulid::Ulid::new().to_string(),
server: server_id.clone(),
name: "Channel".to_string(),
description: None,
icon: None,
last_message_id: None,
default_permissions: Some(OverrideField {
d: 1048576, // TODO: bitfield
..Default::default()
}),
role_permissions: HashMap::from([(
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
OverrideField {
a: 1048576, // TODO: bitfield
..Default::default()
},
)]),
nsfw: false,
};
let server = Server {
id: server_id,
owner: owner.id.clone(),
name: "My Server".to_string(),
description: None,
channels: vec![channel.id()],
categories: None,
system_messages: None,
roles: HashMap::from([
(
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
Role {
name: "Moderator".to_string(),
permissions: OverrideField {
a: 545270208, // TODO: explicit
..Default::default()
},
colour: None,
hoist: true,
rank: 3,
},
),
(
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
Role {
name: "Owner".to_string(),
permissions: Default::default(),
colour: None,
hoist: true,
rank: 0,
},
),
]),
default_permissions: 4000322560, // TODO: use bitfield
icon: None,
banner: None,
flags: None,
nsfw: false,
analytics: false,
discoverable: false,
};
// TODO: proper creation
db.insert_channel(&channel).await.unwrap();
server.create(&db).await.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: owner.id.clone(),
server: server.id.clone(),
},
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
..Default::default()
})
.await
.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: moderator.id.clone(),
server: server.id.clone(),
},
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
..Default::default()
})
.await
.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: user.id.clone(),
server: server.id.clone(),
},
..Default::default()
})
.await
.unwrap();
fixture!(db, "server_with_roles",
owner user 0
moderator user 1
user user 2
channel channel 3);
let mut query = DatabasePermissionQuery::new(&db, &owner).channel(&channel);
assert!(calculate_channel_permissions(&mut query)
@@ -3,8 +3,8 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use crate::{
events::client::EventV1, util::permissions::DatabasePermissionQuery, Database, File, Server,
SystemMessage, User,
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel, Database, File,
Server, SystemMessage, User,
};
auto_derived_partial!(
@@ -80,9 +80,8 @@ impl Member {
db: &Database,
server: &Server,
user: &User,
// channels: Option<Vec<Channel>>,
//) -> Result<Vec<Channel>> {
) -> Result<()> {
channels: Option<Vec<Channel>>,
) -> Result<Vec<Channel>> {
if db.fetch_ban(&server.id, &user.id).await.is_ok() {
return Err(create_error!(Banned));
}
@@ -101,9 +100,10 @@ impl Member {
db.insert_member(&member).await?;
let mut channels = vec![];
let should_fetch = channels.is_none();
let mut channels = channels.unwrap_or_default();
if true {
if should_fetch {
let query = DatabasePermissionQuery::new(db, user).server(server);
let existing_channels = db.fetch_channels(&server.channels).await?;
@@ -152,8 +152,7 @@ impl Member {
.ok();
}
// Ok(channels)
Ok(())
Ok(channels)
}
/// Update member data
+56 -176
View File
@@ -1,10 +1,11 @@
use std::collections::{HashMap, HashSet};
use revolt_permissions::OverrideField;
use revolt_models::v0::{self, DataCreateServerChannel};
use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
use revolt_result::Result;
use ulid::Ulid;
use crate::{events::client::EventV1, Database, File};
use crate::{events::client::EventV1, Channel, Database, File, User};
auto_derived_partial!(
/// Server
@@ -131,8 +132,51 @@ auto_derived!(
#[allow(clippy::disallowed_methods)]
impl Server {
/// Create a server
pub async fn create(&self, db: &Database) -> Result<()> {
db.insert_server(self).await
pub async fn create(
db: &Database,
data: v0::DataCreateServer,
owner: &User,
create_default_channels: bool,
) -> Result<(Server, Vec<Channel>)> {
let mut server = Server {
id: ulid::Ulid::new().to_string(),
owner: owner.id.to_string(),
name: data.name,
description: data.description,
channels: vec![],
nsfw: data.nsfw.unwrap_or(false),
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
analytics: false,
banner: None,
categories: None,
discoverable: false,
flags: None,
icon: None,
roles: HashMap::new(),
system_messages: None,
};
let channels: Vec<Channel> = if create_default_channels {
vec![
Channel::create_server_channel(
db,
&mut server,
DataCreateServerChannel {
channel_type: v0::LegacyServerChannelType::Text,
name: "General".to_string(),
..Default::default()
},
false,
)
.await?,
]
} else {
vec![]
};
db.insert_server(&server).await?;
Ok((server, channels))
}
/// Update server data
@@ -209,80 +253,7 @@ impl Server {
}
}
/* /// Create a new member in a server
pub async fn create_member(
&self,
db: &Database,
user: User,
channels: Option<Vec<Channel>>,
) -> Result<Vec<Channel>> {
if db.fetch_ban(&self.id, &user.id).await.is_ok() {
return Err(Error::Banned);
}
let member = Member {
id: MemberCompositeKey {
server: self.id.clone(),
user: user.id.clone(),
},
joined_at: Timestamp::now_utc(),
nickname: None,
avatar: None,
roles: vec![],
timeout: None,
};
db.insert_member(&member).await?;
let should_fetch = channels.is_none();
let mut channels = channels.unwrap_or_default();
if should_fetch {
let perm = perms(&user).server(self).member(&member);
let existing_channels = db.fetch_channels(&self.channels).await?;
for channel in existing_channels {
if perm
.clone()
.channel(&channel)
.has_permission(db, Permission::ViewChannel)
.await?
{
channels.push(channel);
}
}
}
/* // TODO: EventV1::ServerMemberJoin {
id: self.id.clone(),
user: user.id.clone(),
}
.p(self.id.clone())
.await;
EventV1::ServerCreate {
id: self.id.clone(),
server: self.clone(),
channels: channels.clone(),
}
.private(user.id.clone())
.await; */
if let Some(id) = self
.system_messages
.as_ref()
.and_then(|x| x.user_joined.as_ref())
{
SystemMessage::UserJoined {
id: user.id.clone(),
}
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok();
}
Ok(channels)
}
/*
/// Remove a member from a server
pub async fn remove_member(
@@ -453,109 +424,18 @@ impl SystemMessageChannels {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_permissions::{calculate_server_permissions, ChannelPermission, OverrideField};
use crate::{
util::permissions::DatabasePermissionQuery, Member, MemberCompositeKey, Role, Server, User,
};
use crate::{fixture, util::permissions::DatabasePermissionQuery};
#[async_std::test]
async fn permissions() {
database_test!(|db| async move {
let owner = User::create(&db, "Owner".to_string(), None, None)
.await
.unwrap();
let moderator = User::create(&db, "Moderator".to_string(), None, None)
.await
.unwrap();
let user = User::create(&db, "User".to_string(), None, None)
.await
.unwrap();
let server_id = ulid::Ulid::new().to_string();
// TODO: seeder functions
// e.g. seed!("channel", "file.json")
let server = Server {
id: server_id,
owner: owner.id.clone(),
name: "My Server".to_string(),
description: None,
channels: vec![],
categories: None,
system_messages: None,
roles: HashMap::from([
(
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
Role {
name: "Moderator".to_string(),
permissions: OverrideField {
a: 545270208, // TODO: explicit
..Default::default()
},
colour: None,
hoist: true,
rank: 3,
},
),
(
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
Role {
name: "Owner".to_string(),
permissions: Default::default(),
colour: None,
hoist: true,
rank: 0,
},
),
]),
default_permissions: 4000322560, // TODO: use bitfield
icon: None,
banner: None,
flags: None,
nsfw: false,
analytics: false,
discoverable: false,
};
// TODO: proper creation
server.create(&db).await.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: owner.id.clone(),
server: server.id.clone(),
},
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
..Default::default()
})
.await
.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: moderator.id.clone(),
server: server.id.clone(),
},
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
..Default::default()
})
.await
.unwrap();
db.insert_member(&Member {
id: MemberCompositeKey {
user: user.id.clone(),
server: server.id.clone(),
},
..Default::default()
})
.await
.unwrap();
fixture!(db, "server_with_roles",
owner user 0
moderator user 1
user user 2
server server 4);
let mut query = DatabasePermissionQuery::new(&db, &owner).server(&server);
assert!(calculate_server_permissions(&mut query)
@@ -4,6 +4,7 @@ use crate::{events::client::EventV1, Database, File, RatelimitEvent};
use once_cell::sync::Lazy;
use rand::seq::SliceRandom;
use revolt_config::config;
use revolt_result::{create_error, Error, ErrorType, Result};
use ulid::Ulid;
@@ -218,6 +219,18 @@ impl User {
.is_empty())
}
/// Check if this user can acquire another server
pub async fn can_acquire_server(&self, db: &Database) -> Result<()> {
let config = config().await;
if db.fetch_server_count(&self.id).await? <= config.features.limits.default.servers {
Ok(())
} else {
Err(create_error!(TooManyServers {
max: config.features.limits.default.servers
}))
}
}
/// Sanitise and validate a username can be used
pub fn validate_username(username: String) -> Result<String> {
// Copy the username for validation
+3
View File
@@ -2,3 +2,6 @@ pub mod bridge;
pub mod idempotency;
pub mod permissions;
pub mod reference;
#[cfg(test)]
pub mod test_fixtures;
+6 -1
View File
@@ -7,7 +7,7 @@ use schemars::{
JsonSchema,
};
use crate::{Bot, Channel, Database, Emoji, Message, User, Webhook};
use crate::{Bot, Channel, Database, Emoji, Message, Server, User, Webhook};
/// Reference to some object in the database
#[derive(Serialize, Deserialize)]
@@ -42,6 +42,11 @@ impl Reference {
db.fetch_message(&self.id).await
}
/// Fetch server from Ref
pub async fn as_server(&self, db: &Database) -> Result<Server> {
db.fetch_server(&self.id).await
}
/// Fetch user from Ref
pub async fn as_user(&self, db: &Database) -> Result<User> {
db.fetch_user(&self.id).await
@@ -0,0 +1,103 @@
use std::collections::HashMap;
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use serde_json::from_str;
use crate::{Channel, Database, Member, Server, User};
static RE_ID: Lazy<Regex> = Lazy::new(|| Regex::new("__ID:(\\d+)__").unwrap());
#[derive(Debug, Deserialize)]
#[serde(tag = "_object_type")]
enum LoadedFixture {
User(User),
Channel(Channel),
Server(Server),
ServerMember(Member),
}
pub async fn load_fixture(db: &Database, input: &str) -> HashMap<String, String> {
let mut ids = HashMap::<String, String>::new();
let input = RE_ID.replace_all(input, |cap: &Captures| {
let d = cap.get(1).unwrap().as_str();
if !ids.contains_key(d) {
ids.insert(d.to_string(), ulid::Ulid::new().to_string());
}
ids.get(d).unwrap().clone()
});
// Deserialise the fixtures
let items: Vec<LoadedFixture> = from_str(&input).expect("Failed to deserialise fixture");
// Load all of the items within
for item in items {
#[allow(clippy::disallowed_methods)]
match item {
LoadedFixture::User(user) => db.insert_user(&user).await.unwrap(),
LoadedFixture::Channel(channel) => db.insert_channel(&channel).await.unwrap(),
LoadedFixture::Server(server) => db.insert_server(&server).await.unwrap(),
LoadedFixture::ServerMember(member) => db.insert_member(&member).await.unwrap(),
}
}
// Return IDs for ease of use
ids
}
#[async_trait]
pub trait FetchFixture {
async fn user(&self, db: &Database, d: usize) -> User;
async fn channel(&self, db: &Database, d: usize) -> Channel;
async fn server(&self, db: &Database, d: usize) -> Server;
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member;
}
#[async_trait]
impl FetchFixture for HashMap<String, String> {
async fn user(&self, db: &Database, d: usize) -> User {
db.fetch_user(self.get(&d.to_string()).unwrap())
.await
.unwrap()
}
async fn channel(&self, db: &Database, d: usize) -> Channel {
db.fetch_channel(self.get(&d.to_string()).unwrap())
.await
.unwrap()
}
async fn server(&self, db: &Database, d: usize) -> Server {
db.fetch_server(self.get(&d.to_string()).unwrap())
.await
.unwrap()
}
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member {
db.fetch_member(
self.get(&d_server.to_string()).unwrap(),
self.get(&d_user.to_string()).unwrap(),
)
.await
.unwrap()
}
}
#[macro_export]
macro_rules! fixture {
( $database:expr, $name:expr, $( $variable:ident $type:ident $id: expr )+ ) => {
use $crate::util::test_fixtures::FetchFixture;
let fixtures = $crate::util::test_fixtures::load_fixture(
&$database,
include_str!(concat!("../../../fixtures/", $name, ".json")),
)
.await;
$(
let $variable = fixtures.$type(&$database, $id).await;
)+
};
}
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-models"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -18,8 +18,8 @@ default = ["serde", "partials"]
[dependencies]
# Core
revolt-config = { version = "0.6.8", path = "../config" }
revolt-permissions = { version = "0.6.8", path = "../permissions" }
revolt-config = { version = "0.6.9", path = "../config" }
revolt-permissions = { version = "0.6.9", path = "../permissions" }
# Utility
regex = "1"
+32 -4
View File
@@ -212,24 +212,52 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(validator::Validate))]
pub struct DataCreateGroup {
/// Group name
#[validate(length(min = 1, max = 32))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub name: String,
/// Group description
#[validate(length(min = 0, max = 1024))]
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
pub description: Option<String>,
/// Group icon
#[validate(length(min = 1, max = 128))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
pub icon: Option<String>,
/// Array of user IDs to add to the group
///
/// Must be friends with these users.
#[validate(length(min = 0, max = 49))]
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 49)))]
#[serde(default)]
pub users: HashSet<String>,
/// Whether this group is age-restricted
#[serde(skip_serializing_if = "Option::is_none")]
pub nsfw: Option<bool>,
}
/// Server Channel Type
#[derive(Default)]
pub enum LegacyServerChannelType {
/// Text Channel
#[default]
Text,
/// Voice Channel
Voice,
}
/// Create new server channel
#[derive(Default)]
#[cfg_attr(feature = "validator", derive(validator::Validate))]
pub struct DataCreateServerChannel {
/// Channel type
#[serde(rename = "type", default = "LegacyServerChannelType::default")]
pub channel_type: LegacyServerChannelType,
/// Channel name
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub name: String,
/// Channel description
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
pub description: Option<String>,
/// Whether this channel is age restricted
#[serde(skip_serializing_if = "Option::is_none")]
pub nsfw: Option<bool>,
}
);
impl Channel {
+27 -1
View File
@@ -1,8 +1,11 @@
use super::File;
use super::{Channel, File};
use revolt_permissions::OverrideField;
use std::collections::HashMap;
#[cfg(feature = "validator")]
use validator::Validate;
auto_derived_partial!(
/// Server
pub struct Server {
@@ -141,4 +144,27 @@ auto_derived!(
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub user_banned: Option<String>,
}
/// Information about new server to create
#[derive(Default)]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataCreateServer {
/// Server name
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub name: String,
/// Server description
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
pub description: Option<String>,
/// Whether this server is age-restricted
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub nsfw: Option<bool>,
}
/// Information returned when creating server
pub struct CreateServerLegacyResponse {
/// Server object
pub server: Server,
/// Default channels
pub channels: Vec<Channel>,
}
);
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-permissions"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-presence"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-result"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.6.8"
version = "0.6.9"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"
+13 -22
View File
@@ -42,7 +42,7 @@ pub async fn invite_bot(
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
Member::create(db, &server, &bot_user)
Member::create(db, &server, &bot_user, None)
.await
.map(|_| EmptyResponse)
}
@@ -66,7 +66,7 @@ pub async fn invite_bot(
mod test {
use crate::{rocket, util::test::TestHarness};
use revolt_database::{events::client::EventV1, Bot, Channel, Server};
use revolt_models::v0;
use revolt_models::v0::{self, DataCreateServer};
use rocket::http::{ContentType, Header, Status};
#[rocket::async_test]
@@ -125,26 +125,17 @@ mod test {
.await
.expect("`Bot`");
// FIXME: Server::create_server
let server = Server {
id: ulid::Ulid::new().to_string(),
name: TestHarness::rand_string(),
owner: user.id.to_string(),
analytics: false,
discoverable: false,
nsfw: false,
banner: None,
icon: None,
categories: None,
channels: vec![],
default_permissions: 0,
description: None,
flags: None,
roles: Default::default(),
system_messages: None,
};
server.create(&harness.db).await.unwrap();
let (server, _) = Server::create(
&harness.db,
DataCreateServer {
name: TestHarness::rand_string(),
..Default::default()
},
&user,
false,
)
.await
.unwrap();
let response = harness
.client
+27 -107
View File
@@ -1,119 +1,39 @@
use std::collections::HashMap;
use revolt_quark::{
models::{server::PartialServer, Channel, User},
perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_CHANNEL_COUNT,
};
use revolt_database::util::permissions::DatabasePermissionQuery;
use revolt_database::{util::reference::Reference, Channel, Database, User};
use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use rocket::State;
use validator::Validate;
/// # Channel Type
#[derive(Serialize, Deserialize, JsonSchema, Default)]
enum ChannelType {
/// Text Channel
#[default]
Text,
/// Voice Channel
Voice,
}
/// # Channel Data
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataCreateChannel {
/// Channel type
#[serde(rename = "type", default = "ChannelType::default")]
channel_type: ChannelType,
/// Channel name
#[validate(length(min = 1, max = 32))]
name: String,
/// Channel description
#[validate(length(min = 0, max = 1024))]
description: Option<String>,
/// Whether this channel is age restricted
#[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>,
}
/// # Create Channel
///
/// Create a new Text or Voice channel.
#[openapi(tag = "Server Information")]
#[post("/<target>/channels", data = "<info>")]
pub async fn req(
db: &Db,
#[post("/<server>/channels", data = "<data>")]
pub async fn create_server_channel(
db: &State<Database>,
user: User,
target: Ref,
info: Json<DataCreateChannel>,
) -> Result<Json<Channel>> {
let info = info.into_inner();
info.validate()
.map_err(|error| Error::FailedValidation { error })?;
server: Reference,
data: Json<v0::DataCreateServerChannel>,
) -> Result<Json<v0::Channel>> {
let data = data.into_inner();
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut server = target.as_server(db).await?;
perms(&user)
.server(&server)
.throw_permission(db, Permission::ManageChannel)
.await?;
let mut server = server.as_server(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
if server.channels.len() > *MAX_CHANNEL_COUNT {
return Err(Error::TooManyChannels { max: *MAX_CHANNEL_COUNT })
};
let id = Ulid::new().to_string();
let mut channels = server.channels.clone();
channels.push(id.clone());
let DataCreateChannel {
name,
description,
nsfw,
channel_type,
} = info;
let channel = match channel_type {
ChannelType::Text => Channel::TextChannel {
id,
server: server.id.clone(),
name,
description,
icon: None,
last_message_id: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: nsfw.unwrap_or(false),
},
ChannelType::Voice => Channel::VoiceChannel {
id,
server: server.id.clone(),
name,
description,
icon: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: nsfw.unwrap_or(false),
},
};
channel.create(db).await?;
server
.update(
db,
PartialServer {
channels: Some(channels),
..Default::default()
},
vec![],
)
.await?;
Ok(Json(channel))
Channel::create_server_channel(db, &mut server, data, true)
.await
.map(|channel| channel.into())
.map(Json)
}
+2 -2
View File
@@ -25,12 +25,12 @@ mod server_fetch;
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
server_create::req,
server_create::create_server,
server_delete::req,
server_fetch::req,
server_edit::req,
server_ack::req,
channel_create::req,
channel_create::create_server_channel,
member_fetch_all::req,
member_remove::req,
member_fetch::req,
@@ -1,102 +1,39 @@
use std::collections::HashMap;
use revolt_quark::{
models::{Channel, Server, User},
variables::delta::MAX_SERVER_COUNT,
Db, Error, Result, DEFAULT_PERMISSION_SERVER,
};
use revolt_database::{Database, Member, Server, User};
use revolt_models::v0;
use revolt_result::{create_error, Result};
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use rocket::State;
use validator::Validate;
/// # Server Data
#[derive(Validate, Deserialize, JsonSchema)]
pub struct DataCreateServer {
/// Server name
#[validate(length(min = 1, max = 32))]
name: String,
/// Server description
#[validate(length(min = 0, max = 1024))]
description: Option<String>,
/// Whether this server is age-restricted
#[serde(skip_serializing_if = "Option::is_none")]
nsfw: Option<bool>,
}
/// # Create Server Response
#[derive(Validate, Serialize, JsonSchema)]
pub struct CreateServerResponse {
/// Server object
server: Server,
/// Default channels
channels: Vec<Channel>,
}
/// # Create Server
///
/// Create a new server.
#[openapi(tag = "Server Information")]
#[post("/create", data = "<info>")]
pub async fn req(
db: &Db,
#[post("/create", data = "<data>")]
pub async fn create_server(
db: &State<Database>,
user: User,
info: Json<DataCreateServer>,
) -> Result<Json<CreateServerResponse>> {
data: Json<v0::DataCreateServer>,
) -> Result<Json<v0::CreateServerLegacyResponse>> {
if user.bot.is_some() {
return Err(Error::IsBot);
return Err(create_error!(IsBot));
}
let info = info.into_inner();
info.validate()
.map_err(|error| Error::FailedValidation { error })?;
let data = data.into_inner();
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
if !user.can_acquire_server(db).await? {
return Err(Error::TooManyServers {
max: *MAX_SERVER_COUNT,
});
}
user.can_acquire_server(db).await?;
let DataCreateServer {
name,
description,
nsfw,
} = info;
let (server, channels) = Server::create(db, data, &user, true).await?;
let channels = Member::create(db, &server, &user, Some(channels)).await?;
let channel_id = Ulid::new().to_string();
let server_id = Ulid::new().to_string();
let channel = Channel::TextChannel {
id: channel_id.clone(),
server: server_id.clone(),
name: "General".into(),
description: None,
icon: None,
last_message_id: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: nsfw.unwrap_or(false),
};
db.insert_channel(&channel).await?;
let server = Server {
id: server_id.clone(),
owner: user.id.clone(),
name,
description,
channels: vec![channel_id],
nsfw: nsfw.unwrap_or(false),
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
..Default::default()
};
server.create(db).await?;
let channels = server.create_member(db, user, Some(vec![channel])).await?;
Ok(Json(CreateServerResponse { server, channels }))
Ok(Json(v0::CreateServerLegacyResponse {
server: server.into(),
channels: channels.into_iter().map(|channel| channel.into()).collect(),
}))
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-quark"
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "AGPL-3.0-or-later"