feat(core/database): basic implementation of User and File models
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2822,6 +2822,7 @@ dependencies = [
|
|||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"authifier",
|
"authifier",
|
||||||
|
"bson",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
@@ -2877,6 +2878,7 @@ name = "revolt-models"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"revolt-database",
|
"revolt-database",
|
||||||
|
"revolt-presence",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ description = "Revolt Backend: Database Implementation"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Databases
|
# Databases
|
||||||
mongodb = [ "dep:mongodb" ]
|
mongodb = [ "dep:mongodb", "bson" ]
|
||||||
|
|
||||||
# ... Other
|
# ... Other
|
||||||
async-std-runtime = [ "async-std" ]
|
async-std-runtime = [ "async-std" ]
|
||||||
@@ -33,6 +33,7 @@ revolt_optional_struct = "0.2.0"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
|
bson = { optional = true, version = "2.1.0" }
|
||||||
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||||
|
|
||||||
# Async Language Features
|
# Async Language Features
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
|
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use crate::Bot;
|
use crate::{Bot, User};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
/// Reference implementation
|
/// Reference implementation
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ReferenceDb {
|
pub struct ReferenceDb {
|
||||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||||
|
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ extern crate revolt_result;
|
|||||||
#[cfg(feature = "mongodb")]
|
#[cfg(feature = "mongodb")]
|
||||||
pub use mongodb;
|
pub use mongodb;
|
||||||
|
|
||||||
|
#[cfg(feature = "mongodb")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bson;
|
||||||
|
|
||||||
macro_rules! database_derived {
|
macro_rules! database_derived {
|
||||||
( $( $item:item )+ ) => {
|
( $( $item:item )+ ) => {
|
||||||
$(
|
$(
|
||||||
|
|||||||
3
crates/core/database/src/models/files/mod.rs
Normal file
3
crates/core/database/src/models/files/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod model;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
59
crates/core/database/src/models/files/model.rs
Normal file
59
crates/core/database/src/models/files/model.rs
Normal file
@@ -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<bool>,
|
||||||
|
/// Whether this file was reported
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reported: Option<bool>,
|
||||||
|
|
||||||
|
// 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<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server_id: Option<String>,
|
||||||
|
|
||||||
|
/// Id of the object this file is associated with
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub object_id: Option<String>,
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
mod admin_migrations;
|
mod admin_migrations;
|
||||||
mod bots;
|
mod bots;
|
||||||
|
mod files;
|
||||||
|
mod users;
|
||||||
|
|
||||||
pub use admin_migrations::*;
|
pub use admin_migrations::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
|
pub use files::*;
|
||||||
|
pub use users::*;
|
||||||
|
|
||||||
use crate::{Database, MongoDb, ReferenceDb};
|
use crate::{Database, MongoDb, ReferenceDb};
|
||||||
|
|
||||||
pub trait AbstractDatabase:
|
pub trait AbstractDatabase:
|
||||||
Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots
|
Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots + users::AbstractUsers
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
crates/core/database/src/models/users/mod.rs
Normal file
8
crates/core/database/src/models/users/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
mod rocket;
|
||||||
|
|
||||||
|
pub use self::rocket::*;
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
99
crates/core/database/src/models/users/model.rs
Normal file
99
crates/core/database/src/models/users/model.rs
Normal file
@@ -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<File>,
|
||||||
|
/// Relationships with other users
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub relations: Option<Vec<Relationship>>,
|
||||||
|
|
||||||
|
/// Bitfield of user badges
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub badges: Option<i32>,
|
||||||
|
/// User's current status
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub status: Option<UserStatus>,
|
||||||
|
/// User's profile page
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub profile: Option<UserProfile>,
|
||||||
|
|
||||||
|
/// Enum of user flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
/// 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<BotInformation>,
|
||||||
|
},
|
||||||
|
"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<Presence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot information for if the user is a bot
|
||||||
|
pub struct BotInformation {
|
||||||
|
/// Id of the owner of this bot
|
||||||
|
pub owner: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
12
crates/core/database/src/models/users/ops.rs
Normal file
12
crates/core/database/src/models/users/ops.rs
Normal file
@@ -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<User>;
|
||||||
|
}
|
||||||
16
crates/core/database/src/models/users/ops/mongodb.rs
Normal file
16
crates/core/database/src/models/users/ops/mongodb.rs
Normal file
@@ -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<User> {
|
||||||
|
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
18
crates/core/database/src/models/users/ops/reference.rs
Normal file
18
crates/core/database/src/models/users/ops/reference.rs
Normal file
@@ -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<User> {
|
||||||
|
let users = self.users.lock().await;
|
||||||
|
users
|
||||||
|
.get(id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
44
crates/core/database/src/models/users/rocket.rs
Normal file
44
crates/core/database/src/models/users/rocket.rs
Normal file
@@ -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<Self, Self::Error> {
|
||||||
|
let user: &Option<User> = request
|
||||||
|
.local_cache_async(async {
|
||||||
|
let db = request.rocket().state::<Database>().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::<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, authifier::Error::InvalidSession))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user