From e6d0d44c5a7e898f6df4fa5cd4d9a09ba66fafce Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 23 Apr 2023 17:44:23 +0100 Subject: [PATCH] feat(core/database): basic implementation of User and File models --- Cargo.lock | 2 + crates/core/database/Cargo.toml | 3 +- crates/core/database/src/drivers/reference.rs | 3 +- crates/core/database/src/lib.rs | 4 + crates/core/database/src/models/files/mod.rs | 3 + .../core/database/src/models/files/model.rs | 59 +++++++++++ crates/core/database/src/models/mod.rs | 6 +- crates/core/database/src/models/users/mod.rs | 8 ++ .../core/database/src/models/users/model.rs | 99 +++++++++++++++++++ crates/core/database/src/models/users/ops.rs | 12 +++ .../database/src/models/users/ops/mongodb.rs | 16 +++ .../src/models/users/ops/reference.rs | 18 ++++ .../core/database/src/models/users/rocket.rs | 44 +++++++++ 13 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 crates/core/database/src/models/files/mod.rs create mode 100644 crates/core/database/src/models/files/model.rs create mode 100644 crates/core/database/src/models/users/mod.rs create mode 100644 crates/core/database/src/models/users/model.rs create mode 100644 crates/core/database/src/models/users/ops.rs create mode 100644 crates/core/database/src/models/users/ops/mongodb.rs create mode 100644 crates/core/database/src/models/users/ops/reference.rs create mode 100644 crates/core/database/src/models/users/rocket.rs diff --git a/Cargo.lock b/Cargo.lock index bc2cba4c..558f63b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,6 +2822,7 @@ dependencies = [ "async-std", "async-trait", "authifier", + "bson", "futures", "log", "mongodb", @@ -2877,6 +2878,7 @@ name = "revolt-models" version = "0.0.1" dependencies = [ "revolt-database", + "revolt-presence", "schemars", "serde", ] diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index ec998c16..104fa271 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -10,7 +10,7 @@ description = "Revolt Backend: Database Implementation" [features] # Databases -mongodb = [ "dep:mongodb" ] +mongodb = [ "dep:mongodb", "bson" ] # ... Other async-std-runtime = [ "async-std" ] @@ -33,6 +33,7 @@ revolt_optional_struct = "0.2.0" serde = { version = "1", features = ["derive"] } # Database +bson = { optional = true, version = "2.1.0" } mongodb = { optional = true, version = "2.1.0", default-features = false } # Async Language Features diff --git a/crates/core/database/src/drivers/reference.rs b/crates/core/database/src/drivers/reference.rs index e14ff6f3..cd4bc1a1 100644 --- a/crates/core/database/src/drivers/reference.rs +++ b/crates/core/database/src/drivers/reference.rs @@ -2,12 +2,13 @@ use std::{collections::HashMap, sync::Arc}; use futures::lock::Mutex; -use crate::Bot; +use crate::{Bot, User}; database_derived!( /// Reference implementation #[derive(Default)] pub struct ReferenceDb { pub bots: Arc>>, + pub users: Arc>>, } ); diff --git a/crates/core/database/src/lib.rs b/crates/core/database/src/lib.rs index 5a5c999b..18d5db63 100644 --- a/crates/core/database/src/lib.rs +++ b/crates/core/database/src/lib.rs @@ -19,6 +19,10 @@ extern crate revolt_result; #[cfg(feature = "mongodb")] pub use mongodb; +#[cfg(feature = "mongodb")] +#[macro_use] +extern crate bson; + macro_rules! database_derived { ( $( $item:item )+ ) => { $( diff --git a/crates/core/database/src/models/files/mod.rs b/crates/core/database/src/models/files/mod.rs new file mode 100644 index 00000000..4a7ebf60 --- /dev/null +++ b/crates/core/database/src/models/files/mod.rs @@ -0,0 +1,3 @@ +mod model; + +pub use model::*; diff --git a/crates/core/database/src/models/files/model.rs b/crates/core/database/src/models/files/model.rs new file mode 100644 index 00000000..d39e1eb3 --- /dev/null +++ b/crates/core/database/src/models/files/model.rs @@ -0,0 +1,59 @@ +auto_derived_partial!( + /// File + pub struct File { + /// Unique Id + #[serde(rename = "_id")] + pub id: String, + /// Tag / bucket this file was uploaded to + pub tag: String, + /// Original filename + pub filename: String, + /// Parsed metadata of this file + pub metadata: Metadata, + /// Raw content type of this file + pub content_type: String, + /// Size of this file (in bytes) + pub size: isize, + + /// Whether this file was deleted + #[serde(skip_serializing_if = "Option::is_none")] + pub deleted: Option, + /// Whether this file was reported + #[serde(skip_serializing_if = "Option::is_none")] + pub reported: Option, + + // TODO: migrate this mess to having: + // - author_id + // - parent: Parent { Message(id), User(id), etc } + #[serde(skip_serializing_if = "Option::is_none")] + pub message_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub user_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_id: Option, + + /// Id of the object this file is associated with + #[serde(skip_serializing_if = "Option::is_none")] + pub object_id: Option, + }, + "PartialFile" +); + +auto_derived!( + /// Metadata associated with a file + #[serde(tag = "type")] + #[derive(Default)] + pub enum Metadata { + /// File is just a generic uncategorised file + #[default] + File, + /// File contains textual data and should be displayed as such + Text, + /// File is an image with specific dimensions + Image { width: isize, height: isize }, + /// File is a video with specific dimensions + Video { width: isize, height: isize }, + /// File is audio + Audio, + } +); diff --git a/crates/core/database/src/models/mod.rs b/crates/core/database/src/models/mod.rs index d6b2a21c..9fb3ad3d 100644 --- a/crates/core/database/src/models/mod.rs +++ b/crates/core/database/src/models/mod.rs @@ -1,13 +1,17 @@ mod admin_migrations; mod bots; +mod files; +mod users; pub use admin_migrations::*; pub use bots::*; +pub use files::*; +pub use users::*; use crate::{Database, MongoDb, ReferenceDb}; pub trait AbstractDatabase: - Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots + Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots + users::AbstractUsers { } diff --git a/crates/core/database/src/models/users/mod.rs b/crates/core/database/src/models/users/mod.rs new file mode 100644 index 00000000..b56592f6 --- /dev/null +++ b/crates/core/database/src/models/users/mod.rs @@ -0,0 +1,8 @@ +mod model; +mod ops; +#[cfg(feature = "rocket-impl")] +mod rocket; + +pub use self::rocket::*; +pub use model::*; +pub use ops::*; diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs new file mode 100644 index 00000000..ebdc2ecb --- /dev/null +++ b/crates/core/database/src/models/users/model.rs @@ -0,0 +1,99 @@ +use crate::File; + +auto_derived_partial!( + /// # User + pub struct User { + /// Unique Id + #[serde(rename = "_id")] + pub id: String, + /// Username + pub username: String, + #[serde(skip_serializing_if = "Option::is_none")] + /// Avatar attachment + pub avatar: Option, + /// Relationships with other users + #[serde(skip_serializing_if = "Option::is_none")] + pub relations: Option>, + + /// Bitfield of user badges + #[serde(skip_serializing_if = "Option::is_none")] + pub badges: Option, + /// User's current status + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// User's profile page + #[serde(skip_serializing_if = "Option::is_none")] + pub profile: Option, + + /// Enum of user flags + #[serde(skip_serializing_if = "Option::is_none")] + pub flags: Option, + /// Whether this user is privileged + #[serde(skip_serializing_if = "crate::if_false", default)] + pub privileged: bool, + /// Bot information + #[serde(skip_serializing_if = "Option::is_none")] + pub bot: Option, + }, + "PartialUser" +); + +auto_derived!( + /// User's relationship with another user (or themselves) + pub enum RelationshipStatus { + None, + User, + Friend, + Outgoing, + Incoming, + Blocked, + BlockedOther, + } + + /// Relationship entry indicating current status with other user + pub struct Relationship { + #[serde(rename = "_id")] + pub id: String, + pub status: RelationshipStatus, + } + + /// Presence status + pub enum Presence { + /// User is online + Online, + /// User is not currently available + Idle, + /// User is focusing / will only receive mentions + Focus, + /// User is busy / will not receive any notifications + Busy, + /// User appears to be offline + Invisible, + } + + /// User's active status + pub struct UserStatus { + /// Custom status text + #[serde(skip_serializing_if = "String::is_empty")] + pub text: String, + /// Current presence option + #[serde(skip_serializing_if = "Option::is_none")] + pub presence: Option, + } + + /// User's profile + pub struct UserProfile { + /// Text content on user's profile + #[serde(skip_serializing_if = "String::is_empty")] + pub content: String, + /// Background visible on user's profile + #[serde(skip_serializing_if = "Option::is_none")] + pub background: Option, + } + + /// Bot information for if the user is a bot + pub struct BotInformation { + /// Id of the owner of this bot + pub owner: String, + } +); diff --git a/crates/core/database/src/models/users/ops.rs b/crates/core/database/src/models/users/ops.rs new file mode 100644 index 00000000..ca2662ed --- /dev/null +++ b/crates/core/database/src/models/users/ops.rs @@ -0,0 +1,12 @@ +use revolt_result::Result; + +use crate::User; + +mod mongodb; +mod reference; + +#[async_trait] +pub trait AbstractUsers: Sync + Send { + /// Fetch a user from the database + async fn fetch_user(&self, id: &str) -> Result; +} diff --git a/crates/core/database/src/models/users/ops/mongodb.rs b/crates/core/database/src/models/users/ops/mongodb.rs new file mode 100644 index 00000000..c6e43c98 --- /dev/null +++ b/crates/core/database/src/models/users/ops/mongodb.rs @@ -0,0 +1,16 @@ +use revolt_result::Result; + +use crate::MongoDb; +use crate::User; + +use super::AbstractUsers; + +static COL: &str = "bots"; + +#[async_trait] +impl AbstractUsers for MongoDb { + /// Fetch a user from the database + async fn fetch_user(&self, id: &str) -> Result { + query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound)) + } +} diff --git a/crates/core/database/src/models/users/ops/reference.rs b/crates/core/database/src/models/users/ops/reference.rs new file mode 100644 index 00000000..034f4867 --- /dev/null +++ b/crates/core/database/src/models/users/ops/reference.rs @@ -0,0 +1,18 @@ +use revolt_result::Result; + +use crate::ReferenceDb; +use crate::User; + +use super::AbstractUsers; + +#[async_trait] +impl AbstractUsers for ReferenceDb { + /// Fetch a user from the database + async fn fetch_user(&self, id: &str) -> Result { + let users = self.users.lock().await; + users + .get(id) + .cloned() + .ok_or_else(|| create_error!(NotFound)) + } +} diff --git a/crates/core/database/src/models/users/rocket.rs b/crates/core/database/src/models/users/rocket.rs new file mode 100644 index 00000000..4cc8cf00 --- /dev/null +++ b/crates/core/database/src/models/users/rocket.rs @@ -0,0 +1,44 @@ +use authifier::models::Session; +use rocket::http::Status; +use rocket::request::{self, FromRequest, Outcome, Request}; + +use crate::{Database, User}; + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for User { + type Error = authifier::Error; + + async fn from_request(request: &'r Request<'_>) -> request::Outcome { + let user: &Option = request + .local_cache_async(async { + let db = request.rocket().state::().expect("`Database`"); + + 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::().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, authifier::Error::InvalidSession)) + } + } +}