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

1
Cargo.lock generated
View File

@@ -2974,6 +2974,7 @@ dependencies = [
"bson",
"num_enum 0.6.1",
"once_cell",
"revolt-result",
"schemars",
"serde",
]

View File

@@ -2,9 +2,9 @@ use authifier::AuthifierEvent;
use serde::{Deserialize, Serialize};
use revolt_models::v0::{
Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsWebhook,
MemberCompositeKey, PartialChannel, PartialMember, PartialRole, PartialServer, PartialWebhook,
Server, UserSettings, Webhook,
Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsUser,
FieldsWebhook, MemberCompositeKey, PartialChannel, PartialMember, PartialRole, PartialServer,
PartialUser, PartialWebhook, Server, UserSettings, Webhook,
};
use revolt_result::Error;
@@ -145,7 +145,7 @@ pub enum EventV1 {
/// Server role deleted
ServerRoleDelete { id: String, role_id: String },
/*/// Update existing user
/// Update existing user
UserUpdate {
id: String,
data: PartialUser,
@@ -153,7 +153,7 @@ pub enum EventV1 {
event_id: Option<String>,
},
/// Relationship with another user changed
/*/// Relationship with another user changed
UserRelationship {
id: String,
user: User,

View File

@@ -1,8 +1,9 @@
use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp;
use revolt_models::v0::{Embed, MessageSort, MessageWebhook};
use revolt_result::{create_error, Result};
use crate::File;
use crate::{Database, File};
auto_derived_partial!(
/// Message
@@ -166,7 +167,17 @@ auto_derived!(
);
#[allow(clippy::disallowed_methods)]
impl Message {}
impl Message {
/// Send a message without any notifications
pub async fn send_without_notifications(&mut self, db: &Database) -> Result<()> {
todo!()
}
/// Send a message
pub async fn send(&mut self) -> Result<()> {
todo!()
}
}
impl Interactions {
/// Validate interactions info is correct

View File

@@ -1,5 +1,10 @@
use std::fmt;
use revolt_result::Result;
use ulid::Ulid;
use crate::Database;
auto_derived!(
/// Ratelimit Event
pub struct RatelimitEvent {
@@ -23,3 +28,20 @@ impl fmt::Display for RatelimitEventType {
fmt::Debug::fmt(self, f)
}
}
#[allow(clippy::disallowed_methods)]
impl RatelimitEvent {
/// Create ratelimit event
pub async fn create(
db: &Database,
target_id: String,
event_type: RatelimitEventType,
) -> Result<()> {
db.insert_ratelimit_event(&RatelimitEvent {
id: Ulid::new().to_string(),
target_id,
event_type,
})
.await
}
}

View File

@@ -1,7 +1,11 @@
use iso8601_timestamp::Timestamp;
use revolt_result::Result;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use crate::{Database, File, Server};
use crate::{
events::client::EventV1, util::permissions::DatabasePermissionQuery, Database, File, Server,
User,
};
auto_derived_partial!(
/// Server Member
@@ -57,7 +61,85 @@ auto_derived!(
}
);
#[allow(clippy::disallowed_methods)]
impl Member {
/// Create a new member in a server
pub async fn create(
db: &Database,
server: &Server,
user: &User,
// channels: Option<Vec<Channel>>,
//) -> Result<Vec<Channel>> {
) -> Result<()> {
if db.fetch_ban(&server.id, &user.id).await.is_ok() {
return Err(create_error!(Banned));
}
let member = Member {
id: MemberCompositeKey {
server: server.id.to_string(),
user: user.id.to_string(),
},
..Default::default()
};
db.insert_member(&member).await?;
let mut channels = vec![];
if true {
let query = DatabasePermissionQuery::new(db, user).server(server);
let existing_channels = db.fetch_channels(&server.channels).await?;
for channel in existing_channels {
let mut channel_query = query.clone().channel(&channel);
if calculate_channel_permissions(&mut channel_query)
.await
.has_channel_permission(ChannelPermission::ViewChannel)
{
channels.push(channel);
}
}
}
EventV1::ServerMemberJoin {
id: server.id.clone(),
user: user.id.clone(),
}
.p(server.id.clone())
.await;
EventV1::ServerCreate {
id: server.id.clone(),
server: server.clone().into(),
channels: channels
.clone()
.into_iter()
.map(|channel| channel.into())
.collect(),
}
.private(user.id.clone())
.await;
if let Some(_id) = server
.system_messages
.as_ref()
.and_then(|x| x.user_joined.as_ref())
{
/* TODO: SystemMessage::UserJoined {
id: user.id.clone(),
}
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok(); */
}
// Ok(channels)
Ok(())
}
/// Update member data
pub async fn update<'a>(
&mut self,

View File

@@ -1,6 +1,6 @@
use std::{collections::HashSet, time::Duration};
use crate::{Database, File, RatelimitEvent};
use crate::{events::client::EventV1, Database, File, RatelimitEvent};
use once_cell::sync::Lazy;
use rand::seq::SliceRandom;
@@ -51,6 +51,15 @@ auto_derived_partial!(
);
auto_derived!(
/// Optional fields on user object
pub enum FieldsUser {
Avatar,
StatusText,
StatusPresence,
ProfileContent,
ProfileBackground,
}
/// User's relationship with another user (or themselves)
pub enum RelationshipStatus {
None,
@@ -108,15 +117,6 @@ auto_derived!(
/// Id of the owner of this bot
pub owner: String,
}
/// Optional fields on user object
pub enum FieldsUser {
Avatar,
StatusText,
StatusPresence,
ProfileContent,
ProfileBackground,
}
);
pub static DISCRIMINATOR_SEARCH_SPACE: Lazy<HashSet<String>> = Lazy::new(|| {
@@ -229,11 +229,11 @@ impl User {
return Err(create_error!(DiscriminatorChangeRatelimited));
}
db.insert_ratelimit_event(&RatelimitEvent {
id: Ulid::new().to_string(),
RatelimitEvent::create(
db,
target_id,
event_type: crate::RatelimitEventType::DiscriminatorChange,
})
crate::RatelimitEventType::DiscriminatorChange,
)
.await?;
}
}
@@ -245,6 +245,40 @@ impl User {
.to_string())
}
/// Update a user's username
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
let username = User::validate_username(username)?;
if self.username.to_lowercase() == username.to_lowercase() {
self.update(
db,
PartialUser {
username: Some(username),
..Default::default()
},
vec![],
)
.await
} else {
self.update(
db,
PartialUser {
discriminator: Some(
User::find_discriminator(
db,
&username,
Some((self.discriminator.to_string(), self.id.clone())),
)
.await?,
),
username: Some(username),
..Default::default()
},
vec![],
)
.await
}
}
/// Check whether a username is already in use by another user
#[allow(dead_code)]
async fn is_username_taken(db: &Database, username: &str) -> Result<bool> {
@@ -272,13 +306,14 @@ impl User {
self.apply_options(partial.clone());
db.update_user(&self.id, &partial, remove.clone()).await?;
/* // TODO: EventV1::UserUpdate {
EventV1::UserUpdate {
id: self.id.clone(),
data: partial,
clear: remove,
data: partial.into(),
clear: remove.into_iter().map(|v| v.into()).collect(),
event_id: Some(Ulid::new().to_string()),
}
.p_user(self.id.clone(), db)
.await; */
.await;
Ok(())
}

View File

@@ -34,6 +34,24 @@ impl From<crate::Bot> for Bot {
}
}
impl From<FieldsBot> for crate::FieldsBot {
fn from(value: FieldsBot) -> Self {
match value {
FieldsBot::InteractionsURL => crate::FieldsBot::InteractionsURL,
FieldsBot::Token => crate::FieldsBot::Token,
}
}
}
impl From<crate::FieldsBot> for FieldsBot {
fn from(value: crate::FieldsBot) -> Self {
match value {
crate::FieldsBot::InteractionsURL => FieldsBot::InteractionsURL,
crate::FieldsBot::Token => FieldsBot::Token,
}
}
}
impl From<crate::Invite> for Invite {
fn from(value: crate::Invite) -> Self {
match value {
@@ -622,8 +640,8 @@ impl crate::User {
})
.unwrap_or_default(),
badges: self.badges.unwrap_or_default() as u32,
status: None,
profile: None,
status: self.status.map(|status| status.into()),
profile: self.profile.map(|profile| profile.into()),
flags: self.flags.unwrap_or_default() as u32,
privileged: self.privileged,
bot: self.bot.map(|bot| bot.into()),
@@ -634,6 +652,56 @@ impl crate::User {
}
}
impl From<crate::PartialUser> for PartialUser {
fn from(value: crate::PartialUser) -> Self {
PartialUser {
username: value.username,
discriminator: value.discriminator,
display_name: value.display_name,
avatar: value.avatar.map(|file| file.into()),
relations: value.relations.map(|relationships| {
relationships
.into_iter()
.map(|relationship| relationship.into())
.collect()
}),
badges: value.badges.map(|badges| badges as u32),
status: value.status.map(|status| status.into()),
profile: value.profile.map(|profile| profile.into()),
flags: value.flags.map(|flags| flags as u32),
privileged: value.privileged,
bot: value.bot.map(|bot| bot.into()),
relationship: None,
online: None,
id: value.id,
}
}
}
impl From<FieldsUser> for crate::FieldsUser {
fn from(value: FieldsUser) -> Self {
match value {
FieldsUser::Avatar => crate::FieldsUser::Avatar,
FieldsUser::ProfileBackground => crate::FieldsUser::ProfileBackground,
FieldsUser::ProfileContent => crate::FieldsUser::ProfileContent,
FieldsUser::StatusPresence => crate::FieldsUser::StatusPresence,
FieldsUser::StatusText => crate::FieldsUser::StatusText,
}
}
}
impl From<crate::FieldsUser> for FieldsUser {
fn from(value: crate::FieldsUser) -> Self {
match value {
crate::FieldsUser::Avatar => FieldsUser::Avatar,
crate::FieldsUser::ProfileBackground => FieldsUser::ProfileBackground,
crate::FieldsUser::ProfileContent => FieldsUser::ProfileContent,
crate::FieldsUser::StatusPresence => FieldsUser::StatusPresence,
crate::FieldsUser::StatusText => FieldsUser::StatusText,
}
}
}
impl From<crate::RelationshipStatus> for RelationshipStatus {
fn from(value: crate::RelationshipStatus) -> Self {
match value {

View File

@@ -4,18 +4,19 @@ use revolt_permissions::{
calculate_user_permissions, ChannelType, Override, PermissionQuery, RelationshipStatus,
};
use crate::{Database, User};
use crate::{Channel, Database, Member, Server, User};
/// Permissions calculator
pub struct PermissionCalculator<'a> {
#[derive(Clone)]
pub struct DatabasePermissionQuery<'a> {
#[allow(dead_code)]
database: &'a Database,
perspective: &'a User,
user: Option<Cow<'a, User>>,
// pub channel: Cow<'a, Channel>,
// pub server: Cow<'a, Server>,
// pub member: Cow<'a, Member>,
channel: Option<Cow<'a, Channel>>,
server: Option<Cow<'a, Server>>,
member: Option<Cow<'a, Member>>,
// flag_known_relationship: Option<&'a RelationshipStatus>,
cached_user_permission: Option<u32>,
@@ -23,7 +24,7 @@ pub struct PermissionCalculator<'a> {
}
#[async_trait]
impl PermissionQuery for PermissionCalculator<'_> {
impl PermissionQuery for DatabasePermissionQuery<'_> {
// * For calculating user permission
/// Is our perspective user privileged?
@@ -153,13 +154,16 @@ impl PermissionQuery for PermissionCalculator<'_> {
}
}
impl<'a> PermissionCalculator<'a> {
impl<'a> DatabasePermissionQuery<'a> {
/// Create a new permission calculator
pub fn new(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> {
PermissionCalculator {
pub fn new(database: &'a Database, perspective: &'a User) -> DatabasePermissionQuery<'a> {
DatabasePermissionQuery {
database,
perspective,
user: None,
channel: None,
server: None,
member: None,
cached_user_permission: None,
cached_permission: None,
@@ -167,7 +171,7 @@ impl<'a> PermissionCalculator<'a> {
}
/// Calculate the user permission value
pub async fn calc_user(mut self) -> PermissionCalculator<'a> {
pub async fn calc_user(mut self) -> DatabasePermissionQuery<'a> {
if self.cached_user_permission.is_some() {
return self;
}
@@ -176,14 +180,14 @@ impl<'a> PermissionCalculator<'a> {
panic!("Expected `PermissionCalculator.user to exist.");
}
PermissionCalculator {
DatabasePermissionQuery {
cached_user_permission: Some(calculate_user_permissions(&mut self).await),
..self
}
}
/// Calculate the permission value
pub async fn calc(self) -> PermissionCalculator<'a> {
pub async fn calc(self) -> DatabasePermissionQuery<'a> {
if self.cached_permission.is_some() {
return self;
}
@@ -192,15 +196,39 @@ impl<'a> PermissionCalculator<'a> {
}
/// Use user
pub fn user(self, user: Cow<'a, User>) -> PermissionCalculator {
PermissionCalculator {
user: Some(user),
pub fn user(self, user: &'a User) -> DatabasePermissionQuery {
DatabasePermissionQuery {
user: Some(Cow::Borrowed(user)),
..self
}
}
/// Use channel
pub fn channel(self, channel: &'a Channel) -> DatabasePermissionQuery {
DatabasePermissionQuery {
channel: Some(Cow::Borrowed(channel)),
..self
}
}
/// Use server
pub fn server(self, server: &'a Server) -> DatabasePermissionQuery {
DatabasePermissionQuery {
server: Some(Cow::Borrowed(server)),
..self
}
}
/// Use member
pub fn member(self, member: &'a Member) -> DatabasePermissionQuery {
DatabasePermissionQuery {
member: Some(Cow::Borrowed(member)),
..self
}
}
}
/// Short-hand for creating a permission calculator
pub fn perms<'a>(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> {
PermissionCalculator::new(database, perspective)
pub fn perms<'a>(database: &'a Database, perspective: &'a User) -> DatabasePermissionQuery<'a> {
DatabasePermissionQuery::new(database, perspective)
}

View File

@@ -58,6 +58,12 @@ auto_derived!(
pub flags: u32,
}
/// Optional fields on bot object
pub enum FieldsBot {
Token,
InteractionsURL,
}
/// Flags that may be attributed to a bot
#[repr(u32)]
pub enum BotFlags {
@@ -99,4 +105,40 @@ auto_derived!(
)]
pub name: String,
}
/// New Bot Details
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataEditBot {
/// Bot username
#[cfg_attr(
feature = "validator",
validate(length(min = 2, max = 32), regex = "super::RE_USERNAME")
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Whether the bot can be added by anyone
pub 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).
pub analytics: Option<bool>,
/// Interactions URL
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 2048)))]
pub interactions_url: Option<String>,
/// Fields to remove from bot object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsBot>>,
}
/// Owned Bots Response
///
/// Both lists are sorted by their IDs.
///
/// TODO: user should be in bot object
pub struct OwnedBotsResponse {
/// Bot objects
pub bots: Vec<Bot>,
/// User objects
pub users: Vec<User>,
}
);

View File

@@ -9,7 +9,7 @@ use super::File;
/// Block lookalike characters
pub static RE_USERNAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap());
auto_derived!(
auto_derived_partial!(
/// User
pub struct User {
/// Unique Id
@@ -65,6 +65,18 @@ auto_derived!(
pub relationship: RelationshipStatus,
/// Whether this user is currently online
pub online: bool,
},
"PartialUser"
);
auto_derived!(
/// Optional fields on user object
pub enum FieldsUser {
Avatar,
StatusText,
StatusPresence,
ProfileContent,
ProfileBackground,
}
/// User's relationship with another user (or themselves)

View File

@@ -3,16 +3,16 @@ name = "revolt-permissions"
version = "0.6.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = [ "Paul Makles <me@insrt.uk>" ]
authors = ["Paul Makles <me@insrt.uk>"]
description = "Revolt Backend: Permission Logic"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
bson = ["dep:bson"]
serde = [ "dep:serde" ]
schemas = [ "dep:schemars" ]
try-from-primitive = [ "dep:num_enum" ]
serde = ["dep:serde"]
schemas = ["dep:schemars"]
try-from-primitive = ["dep:num_enum"]
[dev-dependencies]
@@ -20,6 +20,9 @@ try-from-primitive = [ "dep:num_enum" ]
async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies]
# Core
revolt-result = { version = "0.6.5", path = "../result" }
# Utility
auto_ops = "0.3.0"
once_cell = "1.17"
@@ -30,7 +33,7 @@ async-trait = "0.1.51"
# Serialisation
serde = { version = "1", features = ["derive"], optional = true }
bson = { version = "2.1.0", optional = true}
bson = { version = "2.1.0", optional = true }
# Spec Generation
schemars = { version = "0.8.8", optional = true }
schemars = { version = "0.8.8", optional = true }

View File

@@ -1,5 +1,5 @@
use once_cell::sync::Lazy;
use std::ops::Add;
use std::{fmt, ops::Add};
/// Abstract channel type
pub enum ChannelType {
@@ -102,6 +102,12 @@ pub enum ChannelPermission {
GrantAll = u64::MAX,
}
impl fmt::Display for ChannelPermission {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u64 { *a as u64 | *b as u64 });
impl_op_ex_commutative!(+ |a: &u64, b: &ChannelPermission| -> u64 { *a | *b as u64 });
@@ -136,4 +142,9 @@ pub static DEFAULT_PERMISSION_SERVER: Lazy<u64> = Lazy::new(|| {
)
});
pub static DEFAULT_WEBHOOK_PERMISSIONS: Lazy<u64> = Lazy::new(|| ChannelPermission::SendMessage + ChannelPermission::SendEmbeds + ChannelPermission::Masquerade + ChannelPermission::React);
pub static DEFAULT_WEBHOOK_PERMISSIONS: Lazy<u64> = Lazy::new(|| {
ChannelPermission::SendMessage
+ ChannelPermission::SendEmbeds
+ ChannelPermission::Masquerade
+ ChannelPermission::React
});

View File

@@ -3,6 +3,7 @@ mod server;
mod user;
pub use channel::*;
use revolt_result::{create_error, Result};
pub use server::*;
pub use user::*;
@@ -31,6 +32,30 @@ impl PermissionValue {
pub fn restrict(&mut self, v: u64) {
self.0 &= v;
}
/// Check whether certain a permission has been granted
pub fn has(&mut self, v: u64) -> bool {
(self.0 & v) == v
}
/// Check whether certain a channel permission has been granted
pub fn has_channel_permission(&mut self, permission: ChannelPermission) -> bool {
self.has(permission as u64)
}
/// Throw if missing channel permission
pub fn throw_if_lacking_channel_permission(
&mut self,
permission: ChannelPermission,
) -> Result<()> {
if self.has_channel_permission(permission) {
Ok(())
} else {
Err(create_error!(MissingPermission {
permission: permission.to_string()
}))
}
}
}
impl From<i64> for PermissionValue {

View File

@@ -1,3 +1,5 @@
use std::fmt;
/// User's relationship with another user (or themselves)
pub enum RelationshipStatus {
None,
@@ -21,5 +23,11 @@ pub enum UserPermission {
Invite = 1 << 3,
}
impl fmt::Display for UserPermission {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 });
impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 });

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)