chore(monorepo): delta, january, quark
This commit is contained in:
1
crates/quark/src/impl/generic/admin/migrations.rs
Normal file
1
crates/quark/src/impl/generic/admin/migrations.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
33
crates/quark/src/impl/generic/autumn/attachment.rs
Normal file
33
crates/quark/src/impl/generic/autumn/attachment.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::{models::File, Database, Result};
|
||||
|
||||
impl File {
|
||||
pub async fn use_attachment(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "attachments", "message", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_background(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "backgrounds", "user", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_avatar(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "avatars", "user", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_icon(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "icons", "object", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_server_icon(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "icons", "object", parent)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn use_banner(db: &Database, id: &str, parent: &str) -> Result<File> {
|
||||
db.find_and_use_attachment(id, "banners", "server", parent)
|
||||
.await
|
||||
}
|
||||
}
|
||||
380
crates/quark/src/impl/generic/channels/channel.rs
Normal file
380
crates/quark/src/impl/generic/channels/channel.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
channel::{FieldsChannel, PartialChannel},
|
||||
message::SystemMessage,
|
||||
Channel,
|
||||
},
|
||||
tasks::ack::AckEvent,
|
||||
Database, Error, OverrideField, Result,
|
||||
};
|
||||
|
||||
impl Channel {
|
||||
/// Get a reference to this channel's id
|
||||
pub fn id(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Channel::DirectMessage { id, .. }
|
||||
| Channel::Group { id, .. }
|
||||
| Channel::SavedMessages { id, .. }
|
||||
| Channel::TextChannel { id, .. }
|
||||
| Channel::VoiceChannel { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent channel as its id
|
||||
pub fn as_id(self) -> String {
|
||||
match self {
|
||||
Channel::DirectMessage { id, .. }
|
||||
| Channel::Group { id, .. }
|
||||
| Channel::SavedMessages { id, .. }
|
||||
| Channel::TextChannel { id, .. }
|
||||
| Channel::VoiceChannel { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map out whether it is a direct DM
|
||||
pub fn is_direct_dm(&self) -> bool {
|
||||
matches!(self, Channel::DirectMessage { .. })
|
||||
}
|
||||
|
||||
/// Create a channel
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_channel(self).await?;
|
||||
|
||||
let event = EventV1::ChannelCreate(self.clone());
|
||||
match self {
|
||||
Self::SavedMessages { user, .. } => event.private(user.clone()).await,
|
||||
Self::DirectMessage { recipients, .. } | Self::Group { recipients, .. } => {
|
||||
for recipient in recipients {
|
||||
event.clone().private(recipient.clone()).await;
|
||||
}
|
||||
}
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => {
|
||||
event.p(server.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update channel data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialChannel,
|
||||
remove: Vec<FieldsChannel>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
let id = self.id().to_string();
|
||||
db.update_channel(&id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(match self {
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => server.clone(),
|
||||
_ => id,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a channel
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
let id = self.id().to_string();
|
||||
EventV1::ChannelDelete { id: id.clone() }.p(id).await;
|
||||
db.delete_channel(&self).await
|
||||
}
|
||||
|
||||
/// Remove a field from Channel object
|
||||
pub fn remove(&mut self, field: &FieldsChannel) {
|
||||
match field {
|
||||
FieldsChannel::Description => match self {
|
||||
Self::Group { description, .. }
|
||||
| Self::TextChannel { description, .. }
|
||||
| Self::VoiceChannel { description, .. } => {
|
||||
description.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
FieldsChannel::Icon => match self {
|
||||
Self::Group { icon, .. }
|
||||
| Self::TextChannel { icon, .. }
|
||||
| Self::VoiceChannel { icon, .. } => {
|
||||
icon.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
FieldsChannel::DefaultPermissions => match self {
|
||||
Self::TextChannel {
|
||||
default_permissions,
|
||||
..
|
||||
}
|
||||
| Self::VoiceChannel {
|
||||
default_permissions,
|
||||
..
|
||||
} => {
|
||||
default_permissions.take();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply partial channel to channel
|
||||
pub fn apply_options(&mut self, partial: PartialChannel) {
|
||||
// ! FIXME: maybe flatten channel object?
|
||||
match self {
|
||||
Self::DirectMessage { active, .. } => {
|
||||
if let Some(v) = partial.active {
|
||||
*active = v;
|
||||
}
|
||||
}
|
||||
Self::Group {
|
||||
name,
|
||||
owner,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
permissions,
|
||||
..
|
||||
} => {
|
||||
if let Some(v) = partial.name {
|
||||
*name = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.owner {
|
||||
*owner = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.description {
|
||||
description.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.icon {
|
||||
icon.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.nsfw {
|
||||
*nsfw = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.permissions {
|
||||
permissions.replace(v);
|
||||
}
|
||||
}
|
||||
Self::TextChannel {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
}
|
||||
| Self::VoiceChannel {
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
} => {
|
||||
if let Some(v) = partial.name {
|
||||
*name = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.description {
|
||||
description.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.icon {
|
||||
icon.replace(v);
|
||||
}
|
||||
|
||||
if let Some(v) = partial.nsfw {
|
||||
*nsfw = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.role_permissions {
|
||||
*role_permissions = v;
|
||||
}
|
||||
|
||||
if let Some(v) = partial.default_permissions {
|
||||
default_permissions.replace(v);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Acknowledge a message
|
||||
pub async fn ack(&self, user: &str, message: &str) -> Result<()> {
|
||||
EventV1::ChannelAck {
|
||||
id: self.id().to_string(),
|
||||
user: user.to_string(),
|
||||
message_id: message.to_string(),
|
||||
}
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
crate::tasks::ack::queue(
|
||||
self.id().to_string(),
|
||||
user.to_string(),
|
||||
AckEvent::AckMessage {
|
||||
id: message.to_string(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add user to a group
|
||||
pub async fn add_user_to_group(&mut self, db: &Database, user: &str, by: &str) -> Result<()> {
|
||||
if let Channel::Group { recipients, .. } = self {
|
||||
recipients.push(user.to_string());
|
||||
}
|
||||
|
||||
match &self {
|
||||
Channel::Group { id, .. } => {
|
||||
db.add_user_to_group(id, user).await?;
|
||||
|
||||
EventV1::ChannelGroupJoin {
|
||||
id: id.to_string(),
|
||||
user: user.to_string(),
|
||||
}
|
||||
.p(id.to_string())
|
||||
.await;
|
||||
|
||||
EventV1::ChannelCreate(self.clone())
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
SystemMessage::UserAdded {
|
||||
id: user.to_string(),
|
||||
by: by.to_string(),
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create(db, self, None)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove user from a group
|
||||
pub async fn remove_user_from_group(
|
||||
&self,
|
||||
db: &Database,
|
||||
user: &str,
|
||||
by: Option<&str>,
|
||||
) -> Result<()> {
|
||||
match &self {
|
||||
Channel::Group {
|
||||
id,
|
||||
owner,
|
||||
recipients,
|
||||
..
|
||||
} => {
|
||||
if user == owner {
|
||||
if let Some(new_owner) = recipients.iter().find(|x| *x != user) {
|
||||
db.update_channel(
|
||||
id,
|
||||
&PartialChannel {
|
||||
owner: Some(new_owner.into()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
db.delete_channel(self).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
db.remove_user_from_group(id, user).await?;
|
||||
|
||||
EventV1::ChannelGroupLeave {
|
||||
id: id.to_string(),
|
||||
user: user.to_string(),
|
||||
}
|
||||
.p(id.to_string())
|
||||
.await;
|
||||
|
||||
if let Some(by) = by {
|
||||
SystemMessage::UserRemove {
|
||||
id: user.to_string(),
|
||||
by: by.to_string(),
|
||||
}
|
||||
} else {
|
||||
SystemMessage::UserLeft {
|
||||
id: user.to_string(),
|
||||
}
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create(db, self, None)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set role permission on a channel
|
||||
pub async fn set_role_permission(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
role: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Channel::TextChannel {
|
||||
id,
|
||||
server,
|
||||
role_permissions,
|
||||
..
|
||||
}
|
||||
| Channel::VoiceChannel {
|
||||
id,
|
||||
server,
|
||||
role_permissions,
|
||||
..
|
||||
} => {
|
||||
db.set_channel_role_permission(id, role, permissions)
|
||||
.await?;
|
||||
|
||||
role_permissions.insert(role.to_string(), permissions);
|
||||
|
||||
EventV1::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: PartialChannel {
|
||||
role_permissions: Some(role_permissions.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
74
crates/quark/src/impl/generic/channels/channel_invite.rs
Normal file
74
crates/quark/src/impl/generic/channels/channel_invite.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::{
|
||||
models::{Channel, Invite, User},
|
||||
Database, Error, Result,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref ALPHABET: [char; 54] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
|
||||
];
|
||||
}
|
||||
|
||||
impl Invite {
|
||||
/// Get the invite code for this invite
|
||||
pub fn code(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { code, .. } | Invite::Group { code, .. } => code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID of the user who created this invite
|
||||
pub fn creator(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { creator, .. } | Invite::Group { creator, .. } => creator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new invite from given information
|
||||
pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result<Invite> {
|
||||
let code = nanoid!(8, &*ALPHABET);
|
||||
let invite = match &target {
|
||||
Channel::Group { id, .. } => Ok(Invite::Group {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
channel: id.clone(),
|
||||
}),
|
||||
Channel::TextChannel { id, server, .. } | Channel::VoiceChannel { id, server, .. } => {
|
||||
Ok(Invite::Server {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
server: server.clone(),
|
||||
channel: id.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}?;
|
||||
|
||||
db.insert_invite(&invite).await?;
|
||||
Ok(invite)
|
||||
}
|
||||
|
||||
/// Resolve an invite by its ID or by a public server ID
|
||||
pub async fn find(db: &Database, code: &str) -> Result<Invite> {
|
||||
if let Ok(invite) = db.fetch_invite(code).await {
|
||||
return Ok(invite);
|
||||
} else if let Ok(server) = db.fetch_server(code).await {
|
||||
if server.discoverable {
|
||||
if let Some(channel) = server.channels.into_iter().next() {
|
||||
return Ok(Invite::Server {
|
||||
code: code.to_string(),
|
||||
server: server.id,
|
||||
creator: server.owner,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/impl/generic/channels/channel_unread.rs
Normal file
1
crates/quark/src/impl/generic/channels/channel_unread.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
284
crates/quark/src/impl/generic/channels/message.rs
Normal file
284
crates/quark/src/impl/generic/channels/message.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde_json::json;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
message::{
|
||||
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage,
|
||||
},
|
||||
Channel, Message, User,
|
||||
},
|
||||
presence::presence_filter_online,
|
||||
tasks::ack::AckEvent,
|
||||
types::{
|
||||
january::{Embed, Text},
|
||||
push::PushNotification,
|
||||
},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Message {
|
||||
/// Create a message
|
||||
pub async fn create_no_web_push(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
channel: &str,
|
||||
is_direct_dm: bool,
|
||||
) -> Result<()> {
|
||||
db.insert_message(self).await?;
|
||||
|
||||
// Fan out events
|
||||
EventV1::Message(self.clone()).p(channel.to_string()).await;
|
||||
|
||||
// Update last_message_id
|
||||
crate::tasks::last_message_id::queue(
|
||||
channel.to_string(),
|
||||
self.id.to_string(),
|
||||
is_direct_dm,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Add mentions for affected users
|
||||
if let Some(mentions) = &self.mentions {
|
||||
for user in mentions {
|
||||
crate::tasks::ack::queue(
|
||||
channel.to_string(),
|
||||
user.to_string(),
|
||||
AckEvent::AddMention {
|
||||
ids: vec![self.id.to_string()],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a message and Web Push events
|
||||
pub async fn create(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
sender: Option<&User>,
|
||||
) -> Result<()> {
|
||||
self.create_no_web_push(db, channel.id(), channel.is_direct_dm())
|
||||
.await?;
|
||||
|
||||
// Push out Web Push notifications
|
||||
crate::tasks::web_push::queue(
|
||||
{
|
||||
let mut target_ids = vec![];
|
||||
match &channel {
|
||||
Channel::DirectMessage { recipients, .. }
|
||||
| Channel::Group { recipients, .. } => {
|
||||
target_ids = (&recipients.iter().cloned().collect::<HashSet<String>>()
|
||||
- &presence_filter_online(recipients).await)
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>();
|
||||
}
|
||||
Channel::TextChannel { .. } => {
|
||||
if let Some(mentions) = &self.mentions {
|
||||
target_ids.append(&mut mentions.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
target_ids
|
||||
},
|
||||
json!(PushNotification::new(self.clone(), sender, channel.id())).to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update message data
|
||||
pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> {
|
||||
self.apply_options(partial.clone());
|
||||
db.update_message(&self.id, &partial).await?;
|
||||
|
||||
EventV1::MessageUpdate {
|
||||
id: self.id.clone(),
|
||||
channel: self.channel.clone(),
|
||||
data: partial,
|
||||
}
|
||||
.p(self.channel.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Append message data
|
||||
pub async fn append(
|
||||
db: &Database,
|
||||
id: String,
|
||||
channel: String,
|
||||
append: AppendMessage,
|
||||
) -> Result<()> {
|
||||
db.append_message(&id, &append).await?;
|
||||
|
||||
EventV1::MessageAppend {
|
||||
id,
|
||||
channel: channel.to_string(),
|
||||
append,
|
||||
}
|
||||
.p(channel)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a message
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
db.delete_message(&self.id).await?;
|
||||
EventV1::MessageDelete {
|
||||
id: self.id,
|
||||
channel: self.channel.clone(),
|
||||
}
|
||||
.p(self.channel)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bulk delete messages
|
||||
pub async fn bulk_delete(db: &Database, channel: &str, ids: Vec<String>) -> Result<()> {
|
||||
db.delete_messages(channel, ids.clone()).await?;
|
||||
EventV1::BulkMessageDelete {
|
||||
channel: channel.to_string(),
|
||||
ids,
|
||||
}
|
||||
.p(channel.to_string())
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoUsers {
|
||||
fn get_user_ids(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl IntoUsers for Message {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![self.author.clone()];
|
||||
|
||||
if let Some(msg) = &self.system {
|
||||
match msg {
|
||||
SystemMessage::UserAdded { id, by, .. }
|
||||
| SystemMessage::UserRemove { id, by, .. } => {
|
||||
ids.push(id.clone());
|
||||
ids.push(by.clone());
|
||||
}
|
||||
SystemMessage::UserJoined { id, .. }
|
||||
| SystemMessage::UserLeft { id, .. }
|
||||
| SystemMessage::UserKicked { id, .. }
|
||||
| SystemMessage::UserBanned { id, .. } => ids.push(id.clone()),
|
||||
SystemMessage::ChannelRenamed { by, .. }
|
||||
| SystemMessage::ChannelDescriptionChanged { by, .. }
|
||||
| SystemMessage::ChannelIconChanged { by, .. } => ids.push(by.clone()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoUsers for Vec<Message> {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![];
|
||||
for message in self {
|
||||
ids.append(&mut message.get_user_ids());
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemMessage {
|
||||
pub fn into_message(self, channel: String) -> Message {
|
||||
Message {
|
||||
id: Ulid::new().to_string(),
|
||||
channel,
|
||||
author: "00000000000000000000000000".to_string(),
|
||||
system: Some(self),
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemMessage> for String {
|
||||
fn from(s: SystemMessage) -> String {
|
||||
match s {
|
||||
SystemMessage::Text { content } => content,
|
||||
SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
|
||||
SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
|
||||
SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
|
||||
SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
|
||||
SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
|
||||
SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
|
||||
SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
|
||||
SystemMessage::ChannelDescriptionChanged { .. } => {
|
||||
"Channel description changed.".to_string()
|
||||
}
|
||||
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SendableEmbed {
|
||||
pub async fn into_embed(self, db: &Database, message_id: String) -> Result<Embed> {
|
||||
let media = if let Some(id) = self.media {
|
||||
Some(
|
||||
db.find_and_use_attachment(&id, "attachments", "message", &message_id)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Embed::Text(Text {
|
||||
icon_url: self.icon_url,
|
||||
url: self.url,
|
||||
title: self.title,
|
||||
description: self.description,
|
||||
media,
|
||||
colour: self.colour,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl BulkMessageResponse {
|
||||
pub async fn transform(
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
messages: Vec<Message>,
|
||||
include_users: Option<bool>,
|
||||
) -> Result<BulkMessageResponse> {
|
||||
if let Some(true) = include_users {
|
||||
let user_ids = messages.get_user_ids();
|
||||
let users = db.fetch_users(&user_ids).await?;
|
||||
|
||||
Ok(match channel {
|
||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||
BulkMessageResponse::MessagesAndUsers {
|
||||
messages,
|
||||
users,
|
||||
members: Some(db.fetch_members(server, &user_ids).await?),
|
||||
}
|
||||
}
|
||||
_ => BulkMessageResponse::MessagesAndUsers {
|
||||
messages,
|
||||
users,
|
||||
members: None,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(BulkMessageResponse::JustMessages(messages))
|
||||
}
|
||||
}
|
||||
}
|
||||
28
crates/quark/src/impl/generic/mod.rs
Normal file
28
crates/quark/src/impl/generic/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! Database agnostic implementations.
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
}
|
||||
|
||||
pub mod autumn {
|
||||
pub mod attachment;
|
||||
}
|
||||
|
||||
pub mod channels {
|
||||
pub mod channel;
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
pub mod server;
|
||||
pub mod server_ban;
|
||||
pub mod server_member;
|
||||
}
|
||||
|
||||
pub mod users {
|
||||
pub mod bot;
|
||||
pub mod user;
|
||||
pub mod user_settings;
|
||||
}
|
||||
323
crates/quark/src/impl/generic/servers/server.rs
Normal file
323
crates/quark/src/impl/generic/servers/server.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
message::SystemMessage,
|
||||
server::{
|
||||
FieldsRole, FieldsServer, PartialRole, PartialServer, Role, SystemMessageChannels,
|
||||
},
|
||||
server_member::{MemberCompositeKey, RemovalIntention},
|
||||
Channel, Member, Server, ServerBan, User,
|
||||
},
|
||||
perms, Database, Error, OverrideField, Permission, Result,
|
||||
};
|
||||
|
||||
impl Role {
|
||||
/// Into optional struct
|
||||
pub fn into_optional(self) -> PartialRole {
|
||||
PartialRole {
|
||||
name: Some(self.name),
|
||||
permissions: Some(self.permissions),
|
||||
colour: self.colour,
|
||||
hoist: Some(self.hoist),
|
||||
rank: Some(self.rank),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a role
|
||||
pub async fn create(&self, db: &Database, server_id: &str) -> Result<String> {
|
||||
let role_id = Ulid::new().to_string();
|
||||
db.insert_role(server_id, &role_id, self).await?;
|
||||
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: self.clone().into_optional(),
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
Ok(role_id)
|
||||
}
|
||||
|
||||
/// Update server data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
server_id: &str,
|
||||
role_id: &str,
|
||||
partial: PartialRole,
|
||||
remove: Vec<FieldsRole>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_role(server_id, role_id, &partial, remove.clone())
|
||||
.await?;
|
||||
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: partial,
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a role
|
||||
pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
|
||||
EventV1::ServerRoleDelete {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await;
|
||||
|
||||
db.delete_role(server_id, role_id).await
|
||||
}
|
||||
|
||||
/// Remove field from Role
|
||||
pub fn remove(&mut self, field: &FieldsRole) {
|
||||
match field {
|
||||
FieldsRole::Colour => self.colour = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Create a server
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_server(self).await
|
||||
}
|
||||
|
||||
/// Update server data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialServer,
|
||||
remove: Vec<FieldsServer>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_server(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ServerUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a server
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
EventV1::ServerDelete {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
db.delete_server(&self).await
|
||||
}
|
||||
|
||||
/// Remove a field from Server
|
||||
pub fn remove(&mut self, field: &FieldsServer) {
|
||||
match field {
|
||||
FieldsServer::Description => self.description = None,
|
||||
FieldsServer::Categories => self.categories = None,
|
||||
FieldsServer::SystemMessages => self.system_messages = None,
|
||||
FieldsServer::Icon => self.icon = None,
|
||||
FieldsServer::Banner => self.banner = None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set role permission on a server
|
||||
pub async fn set_role_permission(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
role_id: &str,
|
||||
permissions: OverrideField,
|
||||
) -> Result<()> {
|
||||
if let Some(role) = self.roles.get_mut(role_id) {
|
||||
role.update(
|
||||
db,
|
||||
&self.id,
|
||||
role_id,
|
||||
PartialRole {
|
||||
permissions: Some(permissions),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new member in a server
|
||||
pub async fn create_member(
|
||||
&self,
|
||||
db: &Database,
|
||||
user: User,
|
||||
channels: Option<Vec<Channel>>,
|
||||
) -> Result<Vec<Channel>> {
|
||||
if db.fetch_ban(&self.id, &user.id).await.is_ok() {
|
||||
return Err(Error::Banned);
|
||||
}
|
||||
|
||||
let member = Member {
|
||||
id: MemberCompositeKey {
|
||||
server: self.id.clone(),
|
||||
user: user.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
db.insert_member(&member).await?;
|
||||
|
||||
let should_fetch = channels.is_none();
|
||||
let mut channels = channels.unwrap_or_default();
|
||||
|
||||
if should_fetch {
|
||||
let perm = perms(&user).server(self).member(&member);
|
||||
let existing_channels = db.fetch_channels(&self.channels).await?;
|
||||
for channel in existing_channels {
|
||||
if perm
|
||||
.clone()
|
||||
.channel(&channel)
|
||||
.has_permission(db, Permission::ViewChannel)
|
||||
.await?
|
||||
{
|
||||
channels.push(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventV1::ServerMemberJoin {
|
||||
id: self.id.clone(),
|
||||
user: user.id.clone(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await;
|
||||
|
||||
EventV1::ServerCreate {
|
||||
id: self.id.clone(),
|
||||
server: self.clone(),
|
||||
channels: channels.clone(),
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
|
||||
if let Some(id) = self
|
||||
.system_messages
|
||||
.as_ref()
|
||||
.and_then(|x| x.user_joined.as_ref())
|
||||
{
|
||||
SystemMessage::UserJoined {
|
||||
id: user.id.clone(),
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create_no_web_push(db, id, false)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
/// Remove a member from a server
|
||||
pub async fn remove_member(
|
||||
&self,
|
||||
db: &Database,
|
||||
member: Member,
|
||||
intention: RemovalIntention,
|
||||
) -> Result<()> {
|
||||
db.delete_member(&member.id).await?;
|
||||
|
||||
EventV1::ServerMemberLeave {
|
||||
id: self.id.to_string(),
|
||||
user: member.id.user.clone(),
|
||||
}
|
||||
.p(member.id.server)
|
||||
.await;
|
||||
|
||||
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
|
||||
RemovalIntention::Leave => x.user_left.as_ref(),
|
||||
RemovalIntention::Kick => x.user_kicked.as_ref(),
|
||||
RemovalIntention::Ban => x.user_banned.as_ref(),
|
||||
}) {
|
||||
match intention {
|
||||
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
|
||||
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
|
||||
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
|
||||
}
|
||||
.into_message(id.to_string())
|
||||
.create_no_web_push(db, id, false)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ban a member from a server
|
||||
pub async fn ban_member(
|
||||
self,
|
||||
db: &Database,
|
||||
member: Member,
|
||||
reason: Option<String>,
|
||||
) -> Result<ServerBan> {
|
||||
let ban = ServerBan {
|
||||
id: member.id.clone(),
|
||||
reason,
|
||||
};
|
||||
|
||||
self.remove_member(db, member, RemovalIntention::Ban)
|
||||
.await?;
|
||||
|
||||
db.insert_ban(&ban).await?;
|
||||
Ok(ban)
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemMessageChannels {
|
||||
pub fn into_channel_ids(self) -> Vec<String> {
|
||||
let mut ids = vec![];
|
||||
|
||||
if let Some(id) = self.user_joined {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_left {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_kicked {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(id) = self.user_banned {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
ids
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/impl/generic/servers/server_ban.rs
Normal file
1
crates/quark/src/impl/generic/servers/server_ban.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
62
crates/quark/src/impl/generic/servers/server_member.rs
Normal file
62
crates/quark/src/impl/generic/servers/server_member.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
server_member::{FieldsMember, PartialMember},
|
||||
Member, Server,
|
||||
},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Member {
|
||||
/// Update member data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialMember,
|
||||
remove: Vec<FieldsMember>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_member(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ServerMemberUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p(self.id.server.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get this user's current ranking
|
||||
pub fn get_ranking(&self, server: &Server) -> i64 {
|
||||
if let Some(roles) = &self.roles {
|
||||
let mut value = i64::MAX;
|
||||
for role in roles {
|
||||
if let Some(role) = server.roles.get(role) {
|
||||
if role.rank < value {
|
||||
value = role.rank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
} else {
|
||||
i64::MAX
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, field: &FieldsMember) {
|
||||
match field {
|
||||
FieldsMember::Avatar => self.avatar = None,
|
||||
FieldsMember::Nickname => self.nickname = None,
|
||||
FieldsMember::Roles => self.roles = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
24
crates/quark/src/impl/generic/users/bot.rs
Normal file
24
crates/quark/src/impl/generic/users/bot.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::{
|
||||
models::{bot::FieldsBot, Bot},
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
impl Bot {
|
||||
/// Remove a field from this object
|
||||
pub fn remove(&mut self, field: &FieldsBot) {
|
||||
match field {
|
||||
FieldsBot::Token => self.token = nanoid!(64),
|
||||
FieldsBot::InteractionsURL => {
|
||||
self.interactions_url.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete this bot
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
db.fetch_user(&self.id).await?.mark_deleted(db).await?;
|
||||
db.delete_bot(&self.id).await
|
||||
}
|
||||
}
|
||||
430
crates/quark/src/impl/generic/users/user.rs
Normal file
430
crates/quark/src/impl/generic/users/user.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
use crate::events::client::EventV1;
|
||||
use crate::models::user::{
|
||||
Badges, FieldsUser, PartialUser, Presence, RelationshipStatus, User, UserHint,
|
||||
};
|
||||
use crate::permissions::defn::UserPerms;
|
||||
use crate::permissions::r#impl::user::get_relationship;
|
||||
use crate::{perms, Database, Error, Result};
|
||||
|
||||
use futures::try_join;
|
||||
use impl_ops::impl_op_ex_commutative;
|
||||
use okapi::openapi3::{SecurityScheme, SecuritySchemeData};
|
||||
use rocket_okapi::gen::OpenApiGenerator;
|
||||
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||
use std::ops;
|
||||
|
||||
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
||||
|
||||
impl User {
|
||||
/// Update user data
|
||||
pub async fn update<'a>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialUser,
|
||||
remove: Vec<FieldsUser>,
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field);
|
||||
}
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_user(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::UserUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
}
|
||||
.p_user(self.id.clone(), db)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a field from User object
|
||||
pub fn remove(&mut self, field: &FieldsUser) {
|
||||
match field {
|
||||
FieldsUser::Avatar => self.avatar = None,
|
||||
FieldsUser::StatusText => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.text = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::StatusPresence => {
|
||||
if let Some(x) = self.status.as_mut() {
|
||||
x.presence = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileContent => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.content = None;
|
||||
}
|
||||
}
|
||||
FieldsUser::ProfileBackground => {
|
||||
if let Some(x) = self.profile.as_mut() {
|
||||
x.background = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutate the user object to remove redundant information
|
||||
pub fn foreign(mut self) -> User {
|
||||
self.profile = None;
|
||||
self.relations = None;
|
||||
|
||||
let mut badges = self.badges.unwrap_or(0);
|
||||
if let Ok(id) = ulid::Ulid::from_string(&self.id) {
|
||||
// Yes, this is hard-coded
|
||||
// No, I don't care + ratio
|
||||
if id.datetime().timestamp_millis() < 1629638578431 {
|
||||
badges = badges + Badges::EarlyAdopter;
|
||||
}
|
||||
}
|
||||
|
||||
self.badges = Some(badges);
|
||||
|
||||
if let Some(status) = &self.status {
|
||||
if let Some(presence) = &status.presence {
|
||||
if presence == &Presence::Invisible {
|
||||
self.status = None;
|
||||
self.online = Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Mutate the user object to include relationship (if it does not already exist)
|
||||
#[must_use]
|
||||
pub fn with_relationship(self, perspective: &User) -> User {
|
||||
let mut user = self.foreign();
|
||||
|
||||
if user.relationship.is_none() {
|
||||
user.relationship = Some(get_relationship(perspective, &user.id));
|
||||
}
|
||||
|
||||
user
|
||||
}
|
||||
|
||||
/// Mutate user object with given permission
|
||||
#[must_use]
|
||||
pub fn apply_permission(mut self, permission: &UserPerms) -> User {
|
||||
if !permission.get_view_profile() {
|
||||
self.status = None;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper function to apply relationship and permission
|
||||
#[must_use]
|
||||
pub fn with_perspective(self, perspective: &User, permission: &UserPerms) -> User {
|
||||
self.with_relationship(perspective)
|
||||
.apply_permission(permission)
|
||||
}
|
||||
|
||||
/// Helper function to calculate perspective
|
||||
pub async fn with_auto_perspective(self, db: &Database, perspective: &User) -> User {
|
||||
let user = self.with_relationship(perspective);
|
||||
let permissions = perms(perspective).user(&user).calc_user(db).await;
|
||||
user.apply_permission(&permissions)
|
||||
}
|
||||
|
||||
/// Check whether two users have a mutual connection
|
||||
///
|
||||
/// This will check if user and user_b share a server or a group.
|
||||
pub async fn has_mutual_connection(&self, db: &Database, user_b: &str) -> Result<bool> {
|
||||
Ok(!db
|
||||
.fetch_mutual_server_ids(&self.id, user_b)
|
||||
.await?
|
||||
.is_empty()
|
||||
|| !db
|
||||
.fetch_mutual_channel_ids(&self.id, user_b)
|
||||
.await?
|
||||
.is_empty())
|
||||
}
|
||||
|
||||
/// Check if this user can acquire another server
|
||||
pub async fn can_acquire_server(&self, db: &Database) -> Result<bool> {
|
||||
// ! FIXME: hardcoded max server count
|
||||
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
||||
}
|
||||
|
||||
/// Update a user's username
|
||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||
let username = username.trim().to_string();
|
||||
|
||||
if db.is_username_taken(&username).await? {
|
||||
return Err(Error::UsernameTaken);
|
||||
}
|
||||
|
||||
self.update(
|
||||
db,
|
||||
PartialUser {
|
||||
username: Some(username),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Apply a certain relationship between two users
|
||||
pub async fn apply_relationship(
|
||||
&self,
|
||||
db: &Database,
|
||||
target: &mut User,
|
||||
local: RelationshipStatus,
|
||||
remote: RelationshipStatus,
|
||||
) -> Result<()> {
|
||||
if try_join!(
|
||||
db.set_relationship(&self.id, &target.id, &local),
|
||||
db.set_relationship(&target.id, &self.id, &remote)
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::DatabaseError {
|
||||
operation: "update_one",
|
||||
with: "user",
|
||||
});
|
||||
}
|
||||
|
||||
EventV1::UserRelationship {
|
||||
id: target.id.clone(),
|
||||
user: self.clone().with_relationship(target),
|
||||
status: remote,
|
||||
}
|
||||
.private(target.id.clone())
|
||||
.await;
|
||||
|
||||
EventV1::UserRelationship {
|
||||
id: self.id.clone(),
|
||||
user: target.clone().with_relationship(self),
|
||||
status: local.clone(),
|
||||
}
|
||||
.private(self.id.clone())
|
||||
.await;
|
||||
|
||||
target.relationship.replace(local);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add another user as a friend
|
||||
pub async fn add_friend(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::User => Err(Error::NoEffect),
|
||||
RelationshipStatus::Friend => Err(Error::AlreadyFriends),
|
||||
RelationshipStatus::Outgoing => Err(Error::AlreadySentRequest),
|
||||
RelationshipStatus::Blocked => Err(Error::Blocked),
|
||||
RelationshipStatus::BlockedOther => Err(Error::BlockedByOther),
|
||||
RelationshipStatus::Incoming => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Friend,
|
||||
RelationshipStatus::Friend,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::None => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Outgoing,
|
||||
RelationshipStatus::Incoming,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove another user as a friend
|
||||
pub async fn remove_friend(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::Friend
|
||||
| RelationshipStatus::Outgoing
|
||||
| RelationshipStatus::Incoming => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::None,
|
||||
RelationshipStatus::None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => Err(Error::NoEffect),
|
||||
}
|
||||
}
|
||||
|
||||
/// Block another user
|
||||
pub async fn block_user(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::User | RelationshipStatus::Blocked => Err(Error::NoEffect),
|
||||
RelationshipStatus::BlockedOther => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Blocked,
|
||||
RelationshipStatus::Blocked,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::None
|
||||
| RelationshipStatus::Friend
|
||||
| RelationshipStatus::Incoming
|
||||
| RelationshipStatus::Outgoing => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::Blocked,
|
||||
RelationshipStatus::BlockedOther,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unblock another user
|
||||
pub async fn unblock_user(&self, db: &Database, target: &mut User) -> Result<()> {
|
||||
match get_relationship(self, &target.id) {
|
||||
RelationshipStatus::Blocked => match get_relationship(target, &self.id) {
|
||||
RelationshipStatus::Blocked => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::BlockedOther,
|
||||
RelationshipStatus::Blocked,
|
||||
)
|
||||
.await
|
||||
}
|
||||
RelationshipStatus::BlockedOther => {
|
||||
self.apply_relationship(
|
||||
db,
|
||||
target,
|
||||
RelationshipStatus::None,
|
||||
RelationshipStatus::None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => Err(Error::InternalError),
|
||||
},
|
||||
_ => Err(Error::NoEffect),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether this user has another user blocked
|
||||
pub fn has_blocked(&self, user: &str) -> bool {
|
||||
matches!(
|
||||
get_relationship(self, user),
|
||||
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther
|
||||
)
|
||||
}
|
||||
|
||||
/// Mark as deleted
|
||||
pub async fn mark_deleted(&mut self, db: &Database) -> Result<()> {
|
||||
self.update(
|
||||
db,
|
||||
PartialUser {
|
||||
username: Some(format!("Deleted User {}", self.id)),
|
||||
flags: Some(2),
|
||||
..Default::default()
|
||||
},
|
||||
vec![
|
||||
FieldsUser::Avatar,
|
||||
FieldsUser::StatusText,
|
||||
FieldsUser::StatusPresence,
|
||||
FieldsUser::ProfileContent,
|
||||
FieldsUser::ProfileBackground,
|
||||
],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Find a user from a given token and hint
|
||||
#[async_recursion]
|
||||
pub async fn from_token(db: &Database, token: &str, hint: UserHint) -> Result<User> {
|
||||
match hint {
|
||||
UserHint::Bot => db.fetch_user(&db.fetch_bot_by_token(token).await?.id).await,
|
||||
UserHint::User => db.fetch_user_by_token(token).await,
|
||||
UserHint::Any => {
|
||||
if let Ok(user) = User::from_token(db, token, UserHint::User).await {
|
||||
Ok(user)
|
||||
} else {
|
||||
User::from_token(db, token, UserHint::Bot).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use rauth::entities::Session;
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for User {
|
||||
type Error = rauth::util::Error;
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let user: &Option<User> = request
|
||||
.local_cache_async(async {
|
||||
let db = request
|
||||
.rocket()
|
||||
.state::<Database>()
|
||||
.expect("Database state not reachable!");
|
||||
|
||||
let header_bot_token = request
|
||||
.headers()
|
||||
.get("x-bot-token")
|
||||
.next()
|
||||
.map(|x| x.to_string());
|
||||
|
||||
if let Some(bot_token) = header_bot_token {
|
||||
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
||||
return Some(user);
|
||||
}
|
||||
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
||||
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||
return Some(user);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(user) = user {
|
||||
Outcome::Success(user.clone())
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, rauth::util::Error::InvalidSession))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> OpenApiFromRequest<'r> for User {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
_required: bool,
|
||||
) -> rocket_okapi::Result<RequestHeaderInput> {
|
||||
let mut requirements = schemars::Map::new();
|
||||
requirements.insert("Api Key".to_owned(), vec![]);
|
||||
|
||||
Ok(RequestHeaderInput::Security(
|
||||
"Api Key".to_owned(),
|
||||
SecurityScheme {
|
||||
data: SecuritySchemeData::ApiKey {
|
||||
name: "x-session-token".to_owned(),
|
||||
location: "header".to_owned(),
|
||||
},
|
||||
description: Some("Session Token".to_owned()),
|
||||
extensions: schemars::Map::new(),
|
||||
},
|
||||
requirements,
|
||||
))
|
||||
}
|
||||
}
|
||||
22
crates/quark/src/impl/generic/users/user_settings.rs
Normal file
22
crates/quark/src/impl/generic/users/user_settings.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::{events::client::EventV1, models::UserSettings, Database, Result};
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserSettingsImpl {
|
||||
async fn set(self, db: &Database, user: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserSettingsImpl for UserSettings {
|
||||
async fn set(self, db: &Database, user: &str) -> Result<()> {
|
||||
db.set_user_settings(user, &self).await?;
|
||||
|
||||
EventV1::UserSettingsUpdate {
|
||||
id: user.to_string(),
|
||||
update: self,
|
||||
}
|
||||
.private(user.to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user