chore(monorepo): delta, january, quark
This commit is contained in:
59
crates/delta/src/routes/servers/ban_create.rs
Normal file
59
crates/delta/src/routes/servers/ban_create.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use revolt_quark::{
|
||||
models::{ServerBan, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Ban Information
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataBanCreate {
|
||||
/// Ban reason
|
||||
#[validate(length(min = 1, max = 1024))]
|
||||
reason: Option<String>,
|
||||
}
|
||||
|
||||
/// # Ban User
|
||||
///
|
||||
/// Ban a user by their id.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[put("/<server>/bans/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
server: Ref,
|
||||
target: Ref,
|
||||
data: Json<DataBanCreate>,
|
||||
) -> Result<Json<ServerBan>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let server = server.as_server(db).await?;
|
||||
|
||||
if target.id == user.id {
|
||||
return Err(Error::CannotRemoveYourself);
|
||||
}
|
||||
|
||||
if target.id == server.owner {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::BanMembers)
|
||||
.await?;
|
||||
|
||||
let member = target.as_member(db, &server.id).await?;
|
||||
|
||||
if member.get_ranking(permissions.server.get().unwrap())
|
||||
<= permissions.get_member_rank().unwrap_or(i64::MIN)
|
||||
{
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
server.ban_member(db, member, data.reason).await.map(Json)
|
||||
}
|
||||
67
crates/delta/src/routes/servers/ban_list.rs
Normal file
67
crates/delta/src/routes/servers/ban_list.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use revolt_quark::models::{File, ServerBan, User};
|
||||
use revolt_quark::{perms, Db, Permission, Ref, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// # Banned User
|
||||
///
|
||||
/// Just enoguh user information to list bans.
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
struct BannedUser {
|
||||
/// Id of the banned user
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// Username of the banned user
|
||||
pub username: String,
|
||||
/// Avatar of the banned user
|
||||
pub avatar: Option<File>,
|
||||
}
|
||||
|
||||
/// # Ban List Result
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct BanListResult {
|
||||
/// Users objects
|
||||
users: Vec<BannedUser>,
|
||||
/// Ban objects
|
||||
bans: Vec<ServerBan>,
|
||||
}
|
||||
|
||||
impl From<User> for BannedUser {
|
||||
fn from(user: User) -> Self {
|
||||
BannedUser {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
avatar: user.avatar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Fetch Bans
|
||||
///
|
||||
/// Fetch all bans on a server.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/bans")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<BanListResult>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::BanMembers)
|
||||
.await?;
|
||||
|
||||
let bans = db.fetch_bans(&server.id).await?;
|
||||
let users = db
|
||||
.fetch_users(
|
||||
&bans
|
||||
.iter()
|
||||
.map(|x| &x.id.user)
|
||||
.cloned()
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
Ok(Json(BanListResult { users, bans }))
|
||||
}
|
||||
17
crates/delta/src/routes/servers/ban_remove.rs
Normal file
17
crates/delta/src/routes/servers/ban_remove.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
|
||||
|
||||
/// # Unban user
|
||||
///
|
||||
/// Remove a user's ban.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[delete("/<server>/bans/<target>")]
|
||||
pub async fn req(db: &Db, user: User, server: Ref, target: Ref) -> Result<EmptyResponse> {
|
||||
let server = server.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::BanMembers)
|
||||
.await?;
|
||||
|
||||
let ban = target.as_ban(db, &server.id).await?;
|
||||
db.delete_ban(&ban.id).await.map(|_| EmptyResponse)
|
||||
}
|
||||
120
crates/delta/src/routes/servers/channel_create.rs
Normal file
120
crates/delta/src/routes/servers/channel_create.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{server::PartialServer, Channel, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Channel Type
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
enum ChannelType {
|
||||
/// Text Channel
|
||||
Text,
|
||||
/// Voice Channel
|
||||
Voice,
|
||||
}
|
||||
|
||||
impl Default for ChannelType {
|
||||
fn default() -> Self {
|
||||
ChannelType::Text
|
||||
}
|
||||
}
|
||||
|
||||
/// # 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,
|
||||
user: User,
|
||||
target: Ref,
|
||||
info: Json<DataCreateChannel>,
|
||||
) -> Result<Json<Channel>> {
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
|
||||
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))
|
||||
}
|
||||
25
crates/delta/src/routes/servers/invites_fetch.rs
Normal file
25
crates/delta/src/routes/servers/invites_fetch.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use revolt_quark::{
|
||||
models::{Invite, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Invites
|
||||
///
|
||||
/// Fetch all server invites.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/invites")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Invite>>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::ManageServer)
|
||||
.await?;
|
||||
|
||||
db.fetch_invites_for_server(&server.id).await.map(Json)
|
||||
}
|
||||
84
crates/delta/src/routes/servers/member_edit.rs
Normal file
84
crates/delta/src/routes/servers/member_edit.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
server_member::{FieldsMember, PartialMember},
|
||||
File, Member, User,
|
||||
},
|
||||
perms, Db, Error, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Member Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataMemberEdit {
|
||||
/// Member nickname
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
nickname: Option<String>,
|
||||
/// Attachment Id to set for avatar
|
||||
avatar: Option<String>,
|
||||
/// Array of role ids
|
||||
roles: Option<Vec<String>>,
|
||||
/// Fields to remove from channel object
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsMember>>,
|
||||
}
|
||||
|
||||
/// # Edit Member
|
||||
///
|
||||
/// Edit a member by their id.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[patch("/<server>/members/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
server: Ref,
|
||||
target: Ref,
|
||||
data: Json<DataMemberEdit>,
|
||||
) -> Result<Json<Member>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let server = server.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
let mut member = target.as_member(db, &server.id).await?;
|
||||
|
||||
let DataMemberEdit {
|
||||
nickname,
|
||||
avatar,
|
||||
roles,
|
||||
remove,
|
||||
} = data;
|
||||
|
||||
let mut partial = PartialMember {
|
||||
nickname,
|
||||
roles,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// ! FIXME: calculate permission against member
|
||||
// ! FIXME: also check roles exist lol
|
||||
|
||||
// 1. Remove fields from object
|
||||
if let Some(fields) = &remove {
|
||||
if fields.contains(&FieldsMember::Avatar) {
|
||||
if let Some(avatar) = &member.avatar {
|
||||
db.mark_attachment_as_deleted(&avatar.id).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Apply new avatar
|
||||
if let Some(avatar) = avatar {
|
||||
partial.avatar = Some(File::use_avatar(db, &avatar, &user.id).await?);
|
||||
}
|
||||
|
||||
member
|
||||
.update(db, partial, remove.unwrap_or_default())
|
||||
.await?;
|
||||
|
||||
Ok(Json(member))
|
||||
}
|
||||
17
crates/delta/src/routes/servers/member_fetch.rs
Normal file
17
crates/delta/src/routes/servers/member_fetch.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use revolt_quark::{
|
||||
models::{Member, User},
|
||||
perms, Db, Ref, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Member
|
||||
///
|
||||
/// Retrieve a member.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/members/<member>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<Json<Member>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
member.as_member(db, &server.id).await.map(Json)
|
||||
}
|
||||
73
crates/delta/src/routes/servers/member_fetch_all.rs
Normal file
73
crates/delta/src/routes/servers/member_fetch_all.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use revolt_quark::{
|
||||
models::{Member, User},
|
||||
perms,
|
||||
presence::presence_filter_online,
|
||||
Db, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(Deserialize, JsonSchema, FromForm)]
|
||||
pub struct OptionsFetchAllMembers {
|
||||
/// Whether to exclude offline users
|
||||
exclude_offline: Option<bool>,
|
||||
}
|
||||
|
||||
/// # Member List
|
||||
///
|
||||
/// Both lists are sorted by ID.
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct AllMemberResponse {
|
||||
/// List of members
|
||||
members: Vec<Member>,
|
||||
/// List of users
|
||||
users: Vec<User>,
|
||||
}
|
||||
|
||||
/// # Fetch Members
|
||||
///
|
||||
/// Fetch all server members.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/members?<options..>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
options: OptionsFetchAllMembers,
|
||||
) -> Result<Json<AllMemberResponse>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
let mut members = db.fetch_all_members(&server.id).await?;
|
||||
|
||||
let mut user_ids = vec![];
|
||||
for member in &members {
|
||||
user_ids.push(member.id.user.clone());
|
||||
}
|
||||
|
||||
let online_ids = presence_filter_online(&user_ids).await;
|
||||
let mut users = db
|
||||
.fetch_users(&user_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|mut user| {
|
||||
user.online = Some(online_ids.contains(&user.id));
|
||||
user.foreign()
|
||||
})
|
||||
.collect::<Vec<User>>();
|
||||
|
||||
// Ensure the lists match up exactly.
|
||||
members.sort_by(|a, b| a.id.user.cmp(&b.id.user));
|
||||
users.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
|
||||
// Optionally, remove all offline user entries.
|
||||
if let Some(true) = options.exclude_offline {
|
||||
let mut iter = users.iter();
|
||||
members.retain(|_| iter.next().unwrap().online.unwrap_or(false));
|
||||
users.retain(|user| user.online.unwrap_or(false));
|
||||
}
|
||||
|
||||
Ok(Json(AllMemberResponse { members, users }))
|
||||
}
|
||||
40
crates/delta/src/routes/servers/member_remove.rs
Normal file
40
crates/delta/src/routes/servers/member_remove.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use revolt_quark::{
|
||||
models::{server_member::RemovalIntention, User},
|
||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Kick Member
|
||||
///
|
||||
/// Removes a member from the server.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[delete("/<target>/members/<member>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
||||
let server = target.as_server(db).await?;
|
||||
|
||||
if member.id == user.id {
|
||||
return Err(Error::CannotRemoveYourself);
|
||||
}
|
||||
|
||||
if member.id == server.owner {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::KickMembers)
|
||||
.await?;
|
||||
|
||||
let member = member.as_member(db, &server.id).await?;
|
||||
|
||||
if member.get_ranking(permissions.server.get().unwrap())
|
||||
<= permissions.get_member_rank().unwrap_or(i64::MIN)
|
||||
{
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
server
|
||||
.remove_member(db, member, RemovalIntention::Kick)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
51
crates/delta/src/routes/servers/mod.rs
Normal file
51
crates/delta/src/routes/servers/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
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 invites_fetch;
|
||||
|
||||
mod permissions_set;
|
||||
mod permissions_set_default;
|
||||
mod roles_create;
|
||||
mod roles_delete;
|
||||
mod roles_edit;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
server_create::req,
|
||||
server_delete::req,
|
||||
server_fetch::req,
|
||||
server_edit::req,
|
||||
server_ack::req,
|
||||
channel_create::req,
|
||||
member_fetch_all::req,
|
||||
member_remove::req,
|
||||
member_fetch::req,
|
||||
member_edit::req,
|
||||
ban_create::req,
|
||||
ban_remove::req,
|
||||
ban_list::req,
|
||||
invites_fetch::req,
|
||||
roles_create::req,
|
||||
roles_edit::req,
|
||||
roles_delete::req,
|
||||
permissions_set::req,
|
||||
permissions_set_default::req
|
||||
]
|
||||
}
|
||||
56
crates/delta/src/routes/servers/permissions_set.rs
Normal file
56
crates/delta/src/routes/servers/permissions_set.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{Server, User},
|
||||
perms, Db, Error, Override, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Permission Value
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct DataSetServerRolePermission {
|
||||
/// Allow / deny values for the role in this server.
|
||||
permissions: Override,
|
||||
}
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
/// Sets permissions for the specified role in the server.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
role_id: String,
|
||||
data: Json<DataSetServerRolePermission>,
|
||||
) -> Result<Json<Server>> {
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
if let Some((current_value, rank)) = server.roles.get(&role_id).map(|x| (x.permissions, x.rank))
|
||||
{
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManagePermissions)
|
||||
.await?;
|
||||
|
||||
if rank <= permissions.get_member_rank().unwrap_or(i64::MIN) {
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
let current_value: Override = current_value.into();
|
||||
permissions
|
||||
.throw_permission_override(db, current_value, data.permissions)
|
||||
.await?;
|
||||
|
||||
server
|
||||
.set_role_permission(db, &role_id, data.permissions.into())
|
||||
.await?;
|
||||
|
||||
Ok(Json(server))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
52
crates/delta/src/routes/servers/permissions_set_default.rs
Normal file
52
crates/delta/src/routes/servers/permissions_set_default.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{server::PartialServer, Server, User},
|
||||
perms, Db, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Permission Value
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct DataSetServerDefaultPermission {
|
||||
/// Default member permission value
|
||||
permissions: u64,
|
||||
}
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
/// Sets permissions for the default role in this server.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataSetServerDefaultPermission>,
|
||||
) -> Result<Json<Server>> {
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManagePermissions)
|
||||
.await?;
|
||||
|
||||
permissions
|
||||
.throw_permission_value(db, data.permissions)
|
||||
.await?;
|
||||
|
||||
server
|
||||
.update(
|
||||
db,
|
||||
PartialServer {
|
||||
default_permissions: Some(data.permissions as i64),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(server))
|
||||
}
|
||||
74
crates/delta/src/routes/servers/roles_create.rs
Normal file
74
crates/delta/src/routes/servers/roles_create.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use revolt_quark::{
|
||||
models::{server::Role, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Role Data
|
||||
#[derive(Validate, Deserialize, JsonSchema)]
|
||||
pub struct DataCreateRole {
|
||||
/// Role name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
name: String,
|
||||
/// Ranking position
|
||||
///
|
||||
/// Smaller values take priority.
|
||||
rank: Option<i64>,
|
||||
}
|
||||
|
||||
/// # New Role Response
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct NewRoleResponse {
|
||||
/// Id of the role
|
||||
id: String,
|
||||
/// New role
|
||||
role: Role,
|
||||
}
|
||||
|
||||
/// # Create Role
|
||||
///
|
||||
/// Creates a new server role.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[post("/<target>/roles", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataCreateRole>,
|
||||
) -> Result<Json<NewRoleResponse>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageRole)
|
||||
.await?;
|
||||
|
||||
let member_rank = permissions.get_member_rank();
|
||||
let rank = if let Some(given_rank) = data.rank {
|
||||
if given_rank <= member_rank.unwrap_or(i64::MIN) {
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
given_rank
|
||||
} else {
|
||||
member_rank.unwrap_or(0).saturating_add(1)
|
||||
};
|
||||
|
||||
let role = Role {
|
||||
name: data.name,
|
||||
rank,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Json(NewRoleResponse {
|
||||
id: role.create(db, &server.id).await?,
|
||||
role,
|
||||
}))
|
||||
}
|
||||
29
crates/delta/src/routes/servers/roles_delete.rs
Normal file
29
crates/delta/src/routes/servers/roles_delete.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result};
|
||||
|
||||
/// # Delete Role
|
||||
///
|
||||
/// Delete a server role by its id.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[delete("/<target>/roles/<role_id>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, role_id: String) -> Result<EmptyResponse> {
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageRole)
|
||||
.await?;
|
||||
|
||||
let member_rank = permissions.get_member_rank().unwrap_or(0);
|
||||
|
||||
if let Some(role) = server.roles.remove(&role_id) {
|
||||
if role.rank <= member_rank {
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
role.delete(db, &server.id, &role_id)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
94
crates/delta/src/routes/servers/roles_edit.rs
Normal file
94
crates/delta/src/routes/servers/roles_edit.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
server::{FieldsRole, PartialRole, Role},
|
||||
User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Role Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditRole {
|
||||
/// Role name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
name: Option<String>,
|
||||
/// Role colour
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
colour: Option<String>,
|
||||
/// Whether this role should be displayed separately
|
||||
hoist: Option<bool>,
|
||||
/// Ranking position
|
||||
///
|
||||
/// Smaller values take priority.
|
||||
rank: Option<i64>,
|
||||
/// Fields to remove from role object
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsRole>>,
|
||||
}
|
||||
|
||||
/// # Edit Role
|
||||
///
|
||||
/// Edit a role by its id.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[patch("/<target>/roles/<role_id>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
role_id: String,
|
||||
data: Json<DataEditRole>,
|
||||
) -> Result<Json<Role>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageRole)
|
||||
.await?;
|
||||
|
||||
let member_rank = permissions.get_member_rank().unwrap_or(i64::MIN);
|
||||
|
||||
if let Some(mut role) = server.roles.remove(&role_id) {
|
||||
let DataEditRole {
|
||||
name,
|
||||
colour,
|
||||
hoist,
|
||||
rank,
|
||||
remove,
|
||||
} = data;
|
||||
|
||||
if let Some(rank) = &rank {
|
||||
if rank <= &member_rank {
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
}
|
||||
|
||||
let partial = PartialRole {
|
||||
name,
|
||||
colour,
|
||||
hoist,
|
||||
rank,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
role.update(
|
||||
db,
|
||||
&server.id,
|
||||
&role_id,
|
||||
partial,
|
||||
remove.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(role))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
19
crates/delta/src/routes/servers/server_ack.rs
Normal file
19
crates/delta/src/routes/servers/server_ack.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Ref, Result};
|
||||
|
||||
/// # Mark Server As Read
|
||||
///
|
||||
/// Mark all channels in a server as read.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[put("/<target>/ack")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
db.acknowledge_channels(&user.id, &server.channels)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
98
crates/delta/src/routes/servers/server_create.rs
Normal file
98
crates/delta/src/routes/servers/server_create.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{Channel, Server, User},
|
||||
variables::delta::MAX_SERVER_COUNT,
|
||||
Db, Error, Result, DEFAULT_PERMISSION_SERVER,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
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,
|
||||
user: User,
|
||||
info: Json<DataCreateServer>,
|
||||
) -> Result<Json<CreateServerResponse>> {
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
if !user.can_acquire_server(db).await? {
|
||||
return Err(Error::TooManyServers {
|
||||
max: *MAX_SERVER_COUNT,
|
||||
});
|
||||
}
|
||||
|
||||
let DataCreateServer {
|
||||
name,
|
||||
description,
|
||||
nsfw,
|
||||
} = info;
|
||||
|
||||
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 }))
|
||||
}
|
||||
23
crates/delta/src/routes/servers/server_delete.rs
Normal file
23
crates/delta/src/routes/servers/server_delete.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use revolt_quark::{
|
||||
models::{server_member::RemovalIntention, User},
|
||||
Db, EmptyResponse, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Delete / Leave Server
|
||||
///
|
||||
/// Deletes a server if owner otherwise leaves.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[delete("/<target>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
||||
let server = target.as_server(db).await?;
|
||||
let member = db.fetch_member(&target.id, &user.id).await?;
|
||||
|
||||
if server.owner == user.id {
|
||||
server.delete(db).await
|
||||
} else {
|
||||
server
|
||||
.remove_member(db, member, RemovalIntention::Leave)
|
||||
.await
|
||||
}
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
162
crates/delta/src/routes/servers/server_edit.rs
Normal file
162
crates/delta/src/routes/servers/server_edit.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
|
||||
File, Server, User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Server Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditServer {
|
||||
/// Server name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
name: Option<String>,
|
||||
/// Server description
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
description: Option<String>,
|
||||
|
||||
/// Attachment Id for icon
|
||||
icon: Option<String>,
|
||||
/// Attachment Id for banner
|
||||
banner: Option<String>,
|
||||
|
||||
/// Category structure for server
|
||||
#[validate]
|
||||
categories: Option<Vec<Category>>,
|
||||
/// System message configuration
|
||||
system_messages: Option<SystemMessageChannels>,
|
||||
|
||||
// Whether this server is age-restricted
|
||||
// nsfw: Option<bool>,
|
||||
/// Whether analytics should be collected for this server
|
||||
///
|
||||
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
|
||||
analytics: Option<bool>,
|
||||
|
||||
/// Fields to remove from server object
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsServer>>,
|
||||
}
|
||||
|
||||
/// # Edit Server
|
||||
///
|
||||
/// Edit a server by its id.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[patch("/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataEditServer>,
|
||||
) -> Result<Json<Server>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
let mut permissions = perms(&user).server(&server);
|
||||
permissions.calc(db).await?;
|
||||
|
||||
// Check permissions
|
||||
if data.name.is_none()
|
||||
&& data.description.is_none()
|
||||
&& data.icon.is_none()
|
||||
&& data.banner.is_none()
|
||||
&& data.system_messages.is_none()
|
||||
&& data.categories.is_none()
|
||||
// && data.nsfw.is_none()
|
||||
&& data.analytics.is_none()
|
||||
&& data.remove.is_none()
|
||||
{
|
||||
return Ok(Json(server));
|
||||
} else if data.name.is_some()
|
||||
|| data.description.is_some()
|
||||
|| data.icon.is_some()
|
||||
|| data.banner.is_some()
|
||||
|| data.system_messages.is_some()
|
||||
// || data.nsfw.is_some()
|
||||
|| data.analytics.is_some()
|
||||
|| data.remove.is_some()
|
||||
{
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageServer)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if data.categories.is_some() {
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let DataEditServer {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
banner,
|
||||
categories,
|
||||
system_messages,
|
||||
// nsfw,
|
||||
analytics,
|
||||
remove,
|
||||
} = data;
|
||||
|
||||
let mut partial = PartialServer {
|
||||
name,
|
||||
description,
|
||||
categories,
|
||||
system_messages,
|
||||
// nsfw,
|
||||
analytics,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 1. Remove fields from object
|
||||
if let Some(fields) = &remove {
|
||||
if fields.contains(&FieldsServer::Banner) {
|
||||
if let Some(banner) = &server.banner {
|
||||
db.mark_attachment_as_deleted(&banner.id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if fields.contains(&FieldsServer::Icon) {
|
||||
if let Some(icon) = &server.icon {
|
||||
db.mark_attachment_as_deleted(&icon.id).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Apply new icon
|
||||
if let Some(icon) = icon {
|
||||
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
|
||||
server.icon = partial.icon.clone();
|
||||
}
|
||||
|
||||
// 3. Apply new banner
|
||||
if let Some(banner) = banner {
|
||||
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
|
||||
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
|
||||
.update(db, partial, remove.unwrap_or_default())
|
||||
.await?;
|
||||
|
||||
Ok(Json(server))
|
||||
}
|
||||
17
crates/delta/src/routes/servers/server_fetch.rs
Normal file
17
crates/delta/src/routes/servers/server_fetch.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use revolt_quark::{
|
||||
models::{Server, User},
|
||||
perms, Db, Ref, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Server
|
||||
///
|
||||
/// Fetch a server by its id.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[get("/<target>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Server>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
Ok(Json(server))
|
||||
}
|
||||
Reference in New Issue
Block a user