forked from jmug/stoatchat
refactor(quark): port edit_user, fetch_dms, fetch_profile, fetch_self, fetch_user_flags, fetch_user, find_mutual, open_dm
#283
This commit is contained in:
@@ -5,6 +5,7 @@ use revolt_models::v0::{self, MessageAuthor};
|
||||
use revolt_permissions::OverrideField;
|
||||
use revolt_result::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1, tasks::ack::AckEvent, Database, File, IntoDocumentPath, PartialServer,
|
||||
@@ -295,6 +296,43 @@ impl Channel {
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
/// Create a DM (or return the existing one / saved messages)
|
||||
pub async fn create_dm(db: &Database, user_a: &User, user_b: &User) -> Result<Channel> {
|
||||
// Try to find existing channel
|
||||
if let Ok(channel) = db.find_direct_message_channel(&user_a.id, &user_b.id).await {
|
||||
Ok(channel)
|
||||
} else {
|
||||
let channel = if user_a.id == user_b.id {
|
||||
// Create a new saved messages channel
|
||||
Channel::SavedMessages {
|
||||
id: Ulid::new().to_string(),
|
||||
user: user_a.id.to_string(),
|
||||
}
|
||||
} else {
|
||||
// Create a new DM channel
|
||||
Channel::DirectMessage {
|
||||
id: Ulid::new().to_string(),
|
||||
active: true, // show by default
|
||||
recipients: vec![user_a.id.clone(), user_b.id.clone()],
|
||||
last_message_id: None,
|
||||
}
|
||||
};
|
||||
|
||||
db.insert_channel(&channel).await?;
|
||||
|
||||
match &channel {
|
||||
Channel::DirectMessage { .. } => {
|
||||
let event = EventV1::ChannelCreate(channel.clone().into());
|
||||
event.clone().private(user_a.id.clone()).await;
|
||||
event.private(user_b.id.clone()).await;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add user to a group
|
||||
pub async fn add_user_to_group(
|
||||
&mut self,
|
||||
|
||||
@@ -94,20 +94,22 @@ auto_derived!(
|
||||
}
|
||||
|
||||
/// User's active status
|
||||
#[derive(Default)]
|
||||
pub struct UserStatus {
|
||||
/// Custom status text
|
||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||
pub text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<String>,
|
||||
/// Current presence option
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub presence: Option<Presence>,
|
||||
}
|
||||
|
||||
/// User's profile
|
||||
#[derive(Default)]
|
||||
pub struct UserProfile {
|
||||
/// Text content on user's profile
|
||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||
pub content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
/// Background visible on user's profile
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub background: Option<File>,
|
||||
@@ -541,7 +543,7 @@ impl User {
|
||||
FieldsUser::Avatar => self.avatar = None,
|
||||
FieldsUser::StatusText => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.text = String::new();
|
||||
x.text = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::StatusPresence => {
|
||||
@@ -551,7 +553,7 @@ impl User {
|
||||
}
|
||||
FieldsUser::ProfileContent => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.content = String::new();
|
||||
x.content = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileBackground => {
|
||||
|
||||
@@ -15,6 +15,7 @@ impl crate::Bot {
|
||||
description: user
|
||||
.profile
|
||||
.map(|profile| profile.content)
|
||||
.flatten()
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
@@ -892,6 +893,18 @@ impl From<crate::Presence> for Presence {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Presence> for crate::Presence {
|
||||
fn from(value: Presence) -> crate::Presence {
|
||||
match value {
|
||||
Presence::Online => crate::Presence::Online,
|
||||
Presence::Idle => crate::Presence::Idle,
|
||||
Presence::Focus => crate::Presence::Focus,
|
||||
Presence::Busy => crate::Presence::Busy,
|
||||
Presence::Invisible => crate::Presence::Invisible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::UserStatus> for UserStatus {
|
||||
fn from(value: crate::UserStatus) -> Self {
|
||||
UserStatus {
|
||||
|
||||
@@ -3,12 +3,21 @@ use regex::Regex;
|
||||
|
||||
use super::File;
|
||||
|
||||
#[cfg(feature = "validator")]
|
||||
use validator::Validate;
|
||||
|
||||
/// Regex for valid usernames
|
||||
///
|
||||
/// Block zero width space
|
||||
/// Block lookalike characters
|
||||
pub static RE_USERNAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap());
|
||||
|
||||
/// Regex for valid display names
|
||||
///
|
||||
/// Block zero width space
|
||||
/// Block newline and carriage return
|
||||
pub static RE_DISPLAY_NAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[^\u200B\n\r]+$").unwrap());
|
||||
|
||||
auto_derived_partial!(
|
||||
/// User
|
||||
pub struct User {
|
||||
@@ -123,20 +132,26 @@ auto_derived!(
|
||||
}
|
||||
|
||||
/// User's active status
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||
pub struct UserStatus {
|
||||
/// Custom status text
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub text: String,
|
||||
#[validate(length(min = 0, max = 128))]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub text: Option<String>,
|
||||
/// Current presence option
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub presence: Option<Presence>,
|
||||
}
|
||||
|
||||
/// User's profile
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||
pub struct UserProfile {
|
||||
/// Text content on user's profile
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub content: String,
|
||||
#[validate(length(min = 0, max = 2000))]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub content: Option<String>,
|
||||
/// Background visible on user's profile
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub background: Option<File>,
|
||||
@@ -182,6 +197,67 @@ auto_derived!(
|
||||
Spam = 8,
|
||||
}
|
||||
|
||||
/// New user profile data
|
||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||
pub struct DataUserProfile {
|
||||
/// Text to set as user profile description
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub content: Option<String>,
|
||||
/// Attachment Id for background
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||
pub background: Option<String>,
|
||||
}
|
||||
|
||||
/// New user information
|
||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||
pub struct DataEditUser {
|
||||
/// New display name
|
||||
#[cfg_attr(
|
||||
feature = "validator",
|
||||
validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME")
|
||||
)]
|
||||
pub display_name: Option<String>,
|
||||
/// Attachment Id for avatar
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||
pub avatar: Option<String>,
|
||||
|
||||
/// New user status
|
||||
#[cfg_attr(feature = "validator", validate)]
|
||||
pub status: Option<UserStatus>,
|
||||
/// New user profile data
|
||||
///
|
||||
/// This is applied as a partial.
|
||||
#[cfg_attr(feature = "validator", validate)]
|
||||
pub profile: Option<DataUserProfile>,
|
||||
|
||||
/// Bitfield of user badges
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub badges: Option<i32>,
|
||||
/// Enum of user flags
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub flags: Option<i32>,
|
||||
|
||||
/// Fields to remove from user object
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
|
||||
pub remove: Option<Vec<FieldsUser>>,
|
||||
}
|
||||
|
||||
/// User flag reponse
|
||||
pub struct FlagResponse {
|
||||
/// Flags
|
||||
pub flags: i32,
|
||||
}
|
||||
|
||||
/// Mutual friends and servers response
|
||||
pub struct MutualResponse {
|
||||
/// Array of mutual user IDs that both users are friends with
|
||||
pub users: Vec<String>,
|
||||
/// Array of mutual server IDs that both users are in
|
||||
pub servers: Vec<String>,
|
||||
}
|
||||
|
||||
/// Bot information for if the user is a bot
|
||||
pub struct BotInformation {
|
||||
/// Id of the owner of this bot
|
||||
|
||||
@@ -53,6 +53,17 @@ impl PermissionValue {
|
||||
self.has(permission as u64)
|
||||
}
|
||||
|
||||
/// Throw if missing user permission
|
||||
pub fn throw_if_lacking_user_permission(&self, permission: UserPermission) -> Result<()> {
|
||||
if self.has_user_permission(permission) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(MissingPermission {
|
||||
permission: permission.to_string()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Throw if missing channel permission
|
||||
pub fn throw_if_lacking_channel_permission(&self, permission: ChannelPermission) -> Result<()> {
|
||||
if self.has_channel_permission(permission) {
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct DataChangeUsername {
|
||||
/// Change your username.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[patch("/@me/username", data = "<data>")]
|
||||
pub async fn req(
|
||||
pub async fn change_username(
|
||||
db: &State<Database>,
|
||||
account: Account,
|
||||
mut user: User,
|
||||
|
||||
@@ -1,79 +1,28 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use revolt_quark::models::user::{FieldsUser, PartialUser, User};
|
||||
use revolt_quark::models::File;
|
||||
use revolt_quark::{Database, Error, Ref, Result};
|
||||
|
||||
use revolt_quark::models::user::UserStatus;
|
||||
use revolt_database::FieldsUser;
|
||||
use revolt_database::{util::reference::Reference, Database, File, PartialUser, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// Regex for valid display names
|
||||
///
|
||||
/// Block zero width space
|
||||
/// Block newline and carriage return
|
||||
pub static RE_DISPLAY_NAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[^\u200B\n\r]+$").unwrap());
|
||||
|
||||
/// # Profile Data
|
||||
#[derive(Validate, Serialize, Deserialize, Debug, JsonSchema)]
|
||||
pub struct UserProfileData {
|
||||
/// Text to set as user profile description
|
||||
#[validate(length(min = 0, max = 2000))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
content: Option<String>,
|
||||
/// Attachment Id for background
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
background: Option<String>,
|
||||
}
|
||||
|
||||
/// # User Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditUser {
|
||||
/// New display name
|
||||
#[validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME")]
|
||||
display_name: Option<String>,
|
||||
/// Attachment Id for avatar
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
avatar: Option<String>,
|
||||
|
||||
/// New user status
|
||||
#[validate]
|
||||
status: Option<UserStatus>,
|
||||
/// New user profile data
|
||||
///
|
||||
/// This is applied as a partial.
|
||||
#[validate]
|
||||
profile: Option<UserProfileData>,
|
||||
|
||||
/// Bitfield of user badges
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
badges: Option<i32>,
|
||||
/// Enum of user flags
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
flags: Option<i32>,
|
||||
|
||||
/// Fields to remove from user object
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsUser>>,
|
||||
}
|
||||
|
||||
/// # Edit User
|
||||
///
|
||||
/// Edit currently authenticated user.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[patch("/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
pub async fn edit(
|
||||
db: &State<Database>,
|
||||
mut user: User,
|
||||
target: Ref,
|
||||
data: Json<DataEditUser>,
|
||||
) -> Result<Json<User>> {
|
||||
target: Reference,
|
||||
data: Json<v0::DataEditUser>,
|
||||
) -> Result<Json<v0::User>> {
|
||||
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()
|
||||
})
|
||||
})?;
|
||||
|
||||
// If we want to edit a different user than self, ensure we have
|
||||
// permissions and subsequently replace the user in question
|
||||
@@ -85,13 +34,13 @@ pub async fn req(
|
||||
.unwrap_or_default();
|
||||
|
||||
if !is_bot_owner && !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
return Err(create_error!(NotPrivileged));
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, filter out invalid edit fields
|
||||
if !user.privileged && (data.badges.is_some() || data.flags.is_some()) {
|
||||
return Err(Error::NotPrivileged);
|
||||
return Err(create_error!(NotPrivileged));
|
||||
}
|
||||
|
||||
// Exit out early if nothing is changed
|
||||
@@ -103,18 +52,18 @@ pub async fn req(
|
||||
&& data.flags.is_none()
|
||||
&& data.remove.is_none()
|
||||
{
|
||||
return Ok(Json(user));
|
||||
return Ok(Json(user.into_self().await));
|
||||
}
|
||||
|
||||
// 1. Remove fields from object
|
||||
if let Some(fields) = &data.remove {
|
||||
if fields.contains(&FieldsUser::Avatar) {
|
||||
if fields.contains(&v0::FieldsUser::Avatar) {
|
||||
if let Some(avatar) = &user.avatar {
|
||||
db.mark_attachment_as_deleted(&avatar.id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if fields.contains(&FieldsUser::ProfileBackground) {
|
||||
if fields.contains(&v0::FieldsUser::ProfileBackground) {
|
||||
if let Some(profile) = &user.profile {
|
||||
if let Some(background) = &profile.background {
|
||||
db.mark_attachment_as_deleted(&background.id).await?;
|
||||
@@ -123,7 +72,8 @@ pub async fn req(
|
||||
}
|
||||
|
||||
for field in fields {
|
||||
user.remove(field);
|
||||
let field: FieldsUser = field.clone().into();
|
||||
user.remove_field(&field);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +97,7 @@ pub async fn req(
|
||||
}
|
||||
|
||||
if let Some(presence) = status.presence {
|
||||
new_status.presence = Some(presence);
|
||||
new_status.presence = Some(presence.into());
|
||||
}
|
||||
|
||||
partial.status = Some(new_status);
|
||||
@@ -167,8 +117,14 @@ pub async fn req(
|
||||
partial.profile = Some(new_profile);
|
||||
}
|
||||
|
||||
user.update(db, partial, data.remove.unwrap_or_default())
|
||||
.await?;
|
||||
user.update(
|
||||
db,
|
||||
partial,
|
||||
data.remove
|
||||
.map(|v| v.into_iter().map(Into::into).collect())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(user.foreign()))
|
||||
Ok(Json(user.into_self().await))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Fetch Direct Message Channels
|
||||
@@ -10,6 +8,9 @@ use rocket::{serde::json::Json, State};
|
||||
/// This fetches your direct messages, including any DM and group DM conversations.
|
||||
#[openapi(tag = "Direct Messaging")]
|
||||
#[get("/dms")]
|
||||
pub async fn req(db: &State<Database>, user: User) -> Result<Json<Vec<Channel>>> {
|
||||
db.find_direct_messages(&user.id).await.map(Json)
|
||||
pub async fn direct_messages(db: &State<Database>, user: User) -> Result<Json<Vec<v0::Channel>>> {
|
||||
db.find_direct_messages(&user.id)
|
||||
.await
|
||||
.map(|v| v.into_iter().map(Into::into).collect())
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use revolt_quark::{
|
||||
models::{user::UserProfile, User},
|
||||
perms, Database, Error, Ref, Result,
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
};
|
||||
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Fetch User Profile
|
||||
@@ -12,17 +14,21 @@ use rocket::{serde::json::Json, State};
|
||||
/// Will fail if you do not have permission to access the other user's profile.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[get("/<target>/profile")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<UserProfile>> {
|
||||
pub async fn profile(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference,
|
||||
) -> Result<Json<v0::UserProfile>> {
|
||||
if user.id == target.id {
|
||||
return Ok(Json(user.profile.map(Into::into).unwrap_or_default()));
|
||||
}
|
||||
|
||||
let target = target.as_user(db).await?;
|
||||
|
||||
if perms(&user)
|
||||
.user(&target)
|
||||
.calc_user(db)
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).user(&target);
|
||||
calculate_user_permissions(&mut query)
|
||||
.await
|
||||
.get_view_profile()
|
||||
{
|
||||
Ok(Json(target.profile.unwrap_or_default()))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
.throw_if_lacking_user_permission(UserPermission::ViewProfile)?;
|
||||
|
||||
Ok(Json(target.profile.map(Into::into).unwrap_or_default()))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::Result;
|
||||
|
||||
use revolt_database::User;
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Self
|
||||
@@ -8,6 +8,6 @@ use rocket::serde::json::Json;
|
||||
/// Retrieve your user information.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[get("/@me")]
|
||||
pub async fn req(user: User) -> Result<Json<User>> {
|
||||
Ok(Json(user.foreign()))
|
||||
pub async fn fetch(user: User) -> Result<Json<v0::User>> {
|
||||
Ok(Json(user.into_self().await))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use revolt_quark::{models::User, perms, Database, Error, Ref, Result};
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Database, User,
|
||||
};
|
||||
use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Fetch User
|
||||
@@ -7,17 +13,17 @@ use rocket::{serde::json::Json, State};
|
||||
/// Retrieve a user's information.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[get("/<target>")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<User>> {
|
||||
if target.id == user.id {
|
||||
return Ok(Json(user));
|
||||
pub async fn fetch(db: &State<Database>, user: User, target: Reference) -> Result<Json<v0::User>> {
|
||||
if user.id == target.id {
|
||||
return Ok(Json(user.into_self().await));
|
||||
}
|
||||
|
||||
let target = target.as_user(db).await?;
|
||||
|
||||
let permissions = perms(&user).user(&target).calc_user(db).await;
|
||||
if permissions.get_access() {
|
||||
Ok(Json(target.with_perspective(&user, &permissions)))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).user(&target);
|
||||
calculate_user_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_user_permission(UserPermission::Access)?;
|
||||
|
||||
Ok(Json(target.into(db, &user).await))
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
use revolt_quark::{Database, Ref, Result};
|
||||
|
||||
use revolt_database::{util::reference::Reference, Database};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use serde::Serialize;
|
||||
|
||||
/// # Flag Response
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct FlagResponse {
|
||||
/// Flags
|
||||
flags: i32,
|
||||
}
|
||||
|
||||
/// # Fetch User Flags
|
||||
///
|
||||
/// Retrieve a user's flags.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[get("/<target>/flags")]
|
||||
pub async fn fetch_user_flags(db: &State<Database>, target: Ref) -> Result<Json<FlagResponse>> {
|
||||
pub async fn fetch_user_flags(
|
||||
db: &State<Database>,
|
||||
target: Reference,
|
||||
) -> Result<Json<v0::FlagResponse>> {
|
||||
let flags = if let Ok(target) = target.as_user(db).await {
|
||||
target.flags.unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Ok(Json(FlagResponse { flags }))
|
||||
Ok(Json(v0::FlagResponse { flags }))
|
||||
}
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::{perms, Database, Error, Ref, Result};
|
||||
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::util::reference::Reference;
|
||||
use revolt_database::{Database, User};
|
||||
use revolt_models::v0;
|
||||
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use serde::Serialize;
|
||||
|
||||
/// # Mutual Friends and Servers Response
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct MutualResponse {
|
||||
/// Array of mutual user IDs that both users are friends with
|
||||
users: Vec<String>,
|
||||
/// Array of mutual server IDs that both users are in
|
||||
servers: Vec<String>,
|
||||
}
|
||||
|
||||
/// # Fetch Mutual Friends And Servers
|
||||
///
|
||||
/// Retrieve a list of mutual friends and servers with another user.
|
||||
#[openapi(tag = "Relationships")]
|
||||
#[get("/<target>/mutual")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<MutualResponse>> {
|
||||
pub async fn mutual(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference,
|
||||
) -> Result<Json<v0::MutualResponse>> {
|
||||
if target.id == user.id {
|
||||
return Err(Error::InvalidOperation);
|
||||
return Err(create_error!(InvalidOperation));
|
||||
}
|
||||
|
||||
let target = target.as_user(db).await?;
|
||||
|
||||
if perms(&user)
|
||||
.user(&target)
|
||||
.calc_user(db)
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).user(&target);
|
||||
calculate_user_permissions(&mut query)
|
||||
.await
|
||||
.get_view_profile()
|
||||
{
|
||||
Ok(Json(MutualResponse {
|
||||
users: db.fetch_mutual_user_ids(&user.id, &target.id).await?,
|
||||
servers: db.fetch_mutual_server_ids(&user.id, &target.id).await?,
|
||||
}))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
.throw_if_lacking_user_permission(UserPermission::ViewProfile)?;
|
||||
|
||||
Ok(Json(v0::MutualResponse {
|
||||
users: db.fetch_mutual_user_ids(&user.id, &target.id).await?,
|
||||
servers: db.fetch_mutual_server_ids(&user.id, &target.id).await?,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl revolt_rocket_okapi::response::OpenApiResponderInner for CachedFile {
|
||||
/// This returns a default avatar based on the given id.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[get("/<target>/default_avatar")]
|
||||
pub async fn req(target: String) -> CachedFile {
|
||||
pub async fn default_avatar(target: String) -> CachedFile {
|
||||
CachedFile((
|
||||
ContentType::PNG,
|
||||
revolt_quark::util::pfp::avatar(target.chars().last().unwrap()),
|
||||
|
||||
@@ -20,18 +20,18 @@ mod unblock_user;
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
// User Information
|
||||
fetch_self::req,
|
||||
fetch_user::req,
|
||||
fetch_self::fetch,
|
||||
fetch_user::fetch,
|
||||
fetch_user_flags::fetch_user_flags,
|
||||
edit_user::req,
|
||||
change_username::req,
|
||||
get_default_avatar::req,
|
||||
fetch_profile::req,
|
||||
edit_user::edit,
|
||||
change_username::change_username,
|
||||
get_default_avatar::default_avatar,
|
||||
fetch_profile::profile,
|
||||
// Direct Messaging
|
||||
fetch_dms::req,
|
||||
open_dm::req,
|
||||
fetch_dms::direct_messages,
|
||||
open_dm::open_dm,
|
||||
// Relationships
|
||||
find_mutual::req,
|
||||
find_mutual::mutual,
|
||||
add_friend::add,
|
||||
remove_friend::remove,
|
||||
block_user::block,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms, Database, Error, Ref, Result, UserPermission,
|
||||
use revolt_database::{
|
||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||
Channel, Database, User,
|
||||
};
|
||||
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||
use revolt_result::Result;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use ulid::Ulid;
|
||||
|
||||
/// # Open Direct Message
|
||||
///
|
||||
@@ -13,43 +14,20 @@ use ulid::Ulid;
|
||||
/// If the target is oneself, a saved messages channel is returned.
|
||||
#[openapi(tag = "Direct Messaging")]
|
||||
#[get("/<target>/dm")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<Channel>> {
|
||||
pub async fn open_dm(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Reference,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
let target = target.as_user(db).await?;
|
||||
|
||||
// If the target is oneself, open saved messages.
|
||||
if target.id == user.id {
|
||||
return if let Ok(channel) = db.find_direct_message_channel(&user.id, &target.id).await {
|
||||
Ok(Json(channel))
|
||||
} else {
|
||||
let new_channel = Channel::SavedMessages {
|
||||
id: Ulid::new().to_string(),
|
||||
user: user.id,
|
||||
};
|
||||
|
||||
new_channel.create(db).await?;
|
||||
Ok(Json(new_channel))
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise try to find or create a DM.
|
||||
if let Ok(channel) = db.find_direct_message_channel(&user.id, &target.id).await {
|
||||
Ok(Json(channel))
|
||||
} else if perms(&user)
|
||||
.user(&target)
|
||||
.calc_user(db)
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).user(&target);
|
||||
calculate_user_permissions(&mut query)
|
||||
.await
|
||||
.get_send_message()
|
||||
{
|
||||
let new_channel = Channel::DirectMessage {
|
||||
id: Ulid::new().to_string(),
|
||||
active: false,
|
||||
recipients: vec![user.id, target.id],
|
||||
last_message_id: None,
|
||||
};
|
||||
.throw_if_lacking_user_permission(UserPermission::SendMessage)?;
|
||||
|
||||
new_channel.create(db).await?;
|
||||
Ok(Json(new_channel))
|
||||
} else {
|
||||
Error::from_user_permission(UserPermission::SendMessage)
|
||||
}
|
||||
Channel::create_dm(db, &user, &target)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user