feat(core): permissions query, finish bots impl

This commit is contained in:
Paul Makles
2023-08-05 16:14:47 +01:00
parent 9f3c1036d0
commit a681df04bd
20 changed files with 481 additions and 173 deletions

View File

@@ -1,18 +1,21 @@
use revolt_quark::{models::User, Db, EmptyResponse, Error, Ref, Result};
use revolt_database::{util::reference::Reference, Database, User};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Delete Bot
///
/// Delete a bot by its id.
#[openapi(tag = "Bots")]
#[delete("/<target>")]
pub async fn delete_bot(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
pub async fn delete_bot(
db: &State<Database>,
user: User,
target: Reference,
) -> Result<EmptyResponse> {
let bot = target.as_bot(db).await?;
if bot.owner != user.id {
return Err(Error::NotFound);
return Err(create_error!(NotFound));
}
bot.delete(db).await.map(|_| EmptyResponse)

View File

@@ -1,60 +1,32 @@
use crate::util::regex::RE_USERNAME;
use revolt_quark::{
models::{
bot::{FieldsBot, PartialBot},
Bot, User,
},
Db, Error, Ref, Result,
};
use revolt_database::{util::reference::Reference, Database, PartialBot, User};
use revolt_models::v0::{self, DataEditBot};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
/// # Bot Details
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
pub struct DataEditBot {
/// Bot username
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
/// Whether the bot can be added by anyone
public: Option<bool>,
/// Whether analytics should be gathered for this bot
///
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
analytics: Option<bool>,
/// Interactions URL
#[validate(length(min = 1, max = 2048))]
interactions_url: Option<String>,
/// Fields to remove from bot object
#[validate(length(min = 1))]
remove: Option<Vec<FieldsBot>>,
}
/// # Edit Bot
///
/// Edit bot details by its id.
#[openapi(tag = "Bots")]
#[patch("/<target>", data = "<data>")]
pub async fn edit_bot(
db: &Db,
db: &State<Database>,
user: User,
target: Ref,
target: Reference,
data: Json<DataEditBot>,
) -> Result<Json<Bot>> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
) -> Result<Json<v0::Bot>> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut bot = target.as_bot(db).await?;
if bot.owner != user.id {
return Err(Error::NotFound);
return Err(create_error!(NotFound));
}
if let Some(name) = data.name {
@@ -67,7 +39,7 @@ pub async fn edit_bot(
&& data.interactions_url.is_none()
&& data.remove.is_none()
{
return Ok(Json(bot));
return Ok(Json(bot.into()));
}
let DataEditBot {
@@ -78,26 +50,23 @@ pub async fn edit_bot(
..
} = data;
let mut partial = PartialBot {
let partial = PartialBot {
public,
analytics,
interactions_url,
..Default::default()
};
if let Some(remove) = &remove {
for field in remove {
bot.remove(field);
}
bot.update(
db,
partial,
remove
.unwrap_or_default()
.into_iter()
.map(|v| v.into())
.collect(),
)
.await?;
if remove.iter().any(|x| x == &FieldsBot::Token) {
partial.token = Some(bot.token.clone());
}
}
db.update_bot(&bot.id, &partial, remove.unwrap_or_default())
.await?;
bot.apply_options(partial);
Ok(Json(bot))
Ok(Json(bot.into()))
}

View File

@@ -1,6 +1,6 @@
use revolt_database::{util::reference::Reference, Database};
use revolt_database::{util::reference::Reference, Database, User};
use revolt_models::v0::FetchBotResponse;
use revolt_quark::{models::User, Error, Result};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Fetch Bot
@@ -14,21 +14,16 @@ pub async fn fetch_bot(
bot: Reference,
) -> Result<Json<FetchBotResponse>> {
if user.bot.is_some() {
return Err(Error::IsBot);
return Err(create_error!(IsBot));
}
let bot = bot.as_bot(db).await.map_err(Error::from_core)?;
let bot = bot.as_bot(db).await?;
if bot.owner != user.id {
return Err(Error::NotFound);
return Err(create_error!(NotFound));
}
Ok(Json(FetchBotResponse {
user: db
.fetch_user(&bot.id)
.await
.map_err(Error::from_core)?
.into(None)
.await,
user: db.fetch_user(&bot.id).await?.into(None).await,
bot: bot.into(),
}))
}

View File

@@ -1,31 +1,16 @@
use revolt_quark::{
models::{Bot, User},
Db, Error, Result,
};
use futures::future::join_all;
use revolt_database::{Database, User};
use revolt_models::v0::OwnedBotsResponse;
use revolt_result::Result;
use rocket::serde::json::Json;
use serde::Serialize;
/// # Owned Bots Response
///
/// Both lists are sorted by their IDs.
#[derive(Serialize, JsonSchema)]
pub struct OwnedBotsResponse {
/// Bot objects
bots: Vec<Bot>,
/// User objects
users: Vec<User>,
}
use rocket::State;
/// # Fetch Owned Bots
///
/// Fetch all of the bots that you have control over.
#[openapi(tag = "Bots")]
#[get("/@me")]
pub async fn fetch_owned_bots(db: &Db, user: User) -> Result<Json<OwnedBotsResponse>> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<OwnedBotsResponse>> {
let mut bots = db.fetch_bots_by_user(&user.id).await?;
let user_ids = bots
.iter()
@@ -38,5 +23,8 @@ pub async fn fetch_owned_bots(db: &Db, user: User) -> Result<Json<OwnedBotsRespo
bots.sort_by(|a, b| a.id.cmp(&b.id));
users.sort_by(|a, b| a.id.cmp(&b.id));
Ok(Json(OwnedBotsResponse { users, bots }))
Ok(Json(OwnedBotsResponse {
users: join_all(users.into_iter().map(|user| user.into_self())).await,
bots: bots.into_iter().map(|bot| bot.into()).collect(),
}))
}

View File

@@ -1,6 +1,6 @@
use revolt_database::Database;
use revolt_database::{util::reference::Reference, Database, User};
use revolt_models::v0::PublicBot;
use revolt_quark::{models::User, Error, Ref, Result};
use revolt_result::{create_error, Result};
use rocket::serde::json::Json;
use rocket::State;
@@ -13,13 +13,13 @@ use rocket::State;
pub async fn fetch_public_bot(
db: &State<Database>,
user: Option<User>,
target: Ref,
target: Reference,
) -> Result<Json<PublicBot>> {
let bot = db.fetch_bot(&target.id).await.map_err(Error::from_core)?;
let bot = db.fetch_bot(&target.id).await?;
if !bot.public && user.map_or(true, |x| x.id != bot.owner) {
return Err(Error::NotFound);
return Err(create_error!(NotFound));
}
let user = db.fetch_user(&bot.id).await.map_err(Error::from_core)?;
let user = db.fetch_user(&bot.id).await?;
Ok(Json(bot.into_public_bot(user)))
}

View File

@@ -1,6 +1,12 @@
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result};
use revolt_database::util::permissions::DatabasePermissionQuery;
use revolt_database::Member;
use revolt_database::{util::reference::Reference, Database, User};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket::serde::json::Json;
use rocket_empty::EmptyResponse;
use serde::Deserialize;
/// # Invite Destination
@@ -25,42 +31,41 @@ pub enum InviteBotDestination {
#[openapi(tag = "Bots")]
#[post("/<target>/invite", data = "<dest>")]
pub async fn invite_bot(
db: &Db,
db: &State<Database>,
user: User,
target: Ref,
target: Reference,
dest: Json<InviteBotDestination>,
) -> Result<EmptyResponse> {
if user.bot.is_some() {
return Err(Error::IsBot);
return Err(create_error!(IsBot));
}
let bot = target.as_bot(db).await?;
if !bot.public && bot.owner != user.id {
return Err(Error::BotIsPrivate);
return Err(create_error!(BotIsPrivate));
}
match dest.into_inner() {
InviteBotDestination::Server { server } => {
let server = db.fetch_server(&server).await?;
perms(&user)
.server(&server)
.throw_permission(db, Permission::ManageServer)
.await?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
let user = db.fetch_user(&bot.id).await?;
server
.create_member(db, user, None)
Member::create(db, &server, &user)
.await
.map(|_| EmptyResponse)
}
InviteBotDestination::Group { group } => {
let mut channel = db.fetch_channel(&group).await?;
perms(&user)
.channel(&channel)
.throw_permission_and_view_channel(db, Permission::InviteOthers)
.await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?;
channel
.add_user_to_group(db, &bot.id, &user.id)