feat: add role colours to masquerades

feat: add `joined_at` property to members
feat: add `timeout` property to members (non-functional)
This commit is contained in:
Paul Makles
2022-07-15 13:42:01 +01:00
parent 692081b7f0
commit 741b8ee8fd
14 changed files with 82 additions and 49 deletions

4
Cargo.lock generated
View File

@@ -2505,7 +2505,7 @@ dependencies = [
[[package]] [[package]]
name = "rauth" name = "rauth"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394" source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -2986,7 +2986,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_rauth" name = "rocket_rauth"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394" source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4"
dependencies = [ dependencies = [
"okapi", "okapi",
"rauth", "rauth",

View File

@@ -52,7 +52,7 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s
# web # web
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] } rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" } rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" }
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "67a7fa8100c7db8411fb8fb149441418b55c19ef" } rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4" }
# spec generation # spec generation
schemars = "0.8.8" schemars = "0.8.8"

View File

@@ -100,10 +100,16 @@ pub async fn message_send(
} }
// 2. Verify permissions for masquerade. // 2. Verify permissions for masquerade.
if message.masquerade.is_some() { if let Some(masq) = &message.masquerade {
permissions permissions
.throw_permission(db, Permission::Masquerade) .throw_permission(db, Permission::Masquerade)
.await?; .await?;
if masq.colour.is_some() {
permissions
.throw_permission(db, Permission::ManageRole)
.await?;
}
} }
// 3. Verify replies are valid. // 3. Verify replies are valid.

View File

@@ -5,7 +5,7 @@ use revolt_quark::{
server_member::{FieldsMember, PartialMember}, server_member::{FieldsMember, PartialMember},
File, Member, User, File, Member, User,
}, },
perms, Db, Error, Permission, Ref, Result, perms, Db, Error, Permission, Ref, Result, Timestamp,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -22,6 +22,8 @@ pub struct DataMemberEdit {
avatar: Option<String>, avatar: Option<String>,
/// Array of role ids /// Array of role ids
roles: Option<Vec<String>>, roles: Option<Vec<String>>,
/// Timestamp this member is timed out until
timeout: Option<Timestamp>,
/// Fields to remove from channel object /// Fields to remove from channel object
#[validate(length(min = 1))] #[validate(length(min = 1))]
remove: Option<Vec<FieldsMember>>, remove: Option<Vec<FieldsMember>>,
@@ -105,13 +107,7 @@ pub async fn req(
// Check permissions against roles in diff // Check permissions against roles in diff
if let Some(roles) = &data.roles { if let Some(roles) = &data.roles {
let fallback = vec![]; let current_roles = member.roles.iter().collect::<HashSet<&String>>();
let current_roles = member
.roles
.as_ref()
.unwrap_or(&fallback)
.iter()
.collect::<HashSet<&String>>();
let new_roles = roles.iter().collect::<HashSet<&String>>(); let new_roles = roles.iter().collect::<HashSet<&String>>();
let added_roles: Vec<&&String> = new_roles.difference(&current_roles).collect(); let added_roles: Vec<&&String> = new_roles.difference(&current_roles).collect();
@@ -132,12 +128,14 @@ pub async fn req(
nickname, nickname,
avatar, avatar,
roles, roles,
timeout,
remove, remove,
} = data; } = data;
let mut partial = PartialMember { let mut partial = PartialMember {
nickname, nickname,
roles, roles,
timeout,
..Default::default() ..Default::default()
}; };

View File

@@ -84,7 +84,7 @@ rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_emp
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
# rAuth # rAuth
rauth = { git = "https://github.com/insertish/rauth", rev = "67a7fa8100c7db8411fb8fb149441418b55c19ef", features = [ "async-std-runtime" ] } rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4", features = [ "async-std-runtime" ] }
# Sentry # Sentry
sentry = "0.25.0" sentry = "0.25.0"

View File

@@ -484,10 +484,8 @@ impl State {
if data.rank.is_some() || data.permissions.is_some() { if data.rank.is_some() || data.permissions.is_some() {
if let Some(member) = self.cache.members.get(id) { if let Some(member) = self.cache.members.get(id) {
if let Some(roles) = &member.roles { if member.roles.contains(role_id) {
if roles.contains(role_id) { queue_server = Some(id.clone());
queue_server = Some(id.clone());
}
} }
} }
} }
@@ -498,10 +496,8 @@ impl State {
} }
if let Some(member) = self.cache.members.get(id) { if let Some(member) = self.cache.members.get(id) {
if let Some(roles) = &member.roles { if member.roles.contains(role_id) {
if roles.contains(role_id) { queue_server = Some(id.clone());
queue_server = Some(id.clone());
}
} }
} }
} }

View File

@@ -3,6 +3,8 @@ use crate::{AbstractServerMember, Result};
use super::super::DummyDb; use super::super::DummyDb;
use iso8601_timestamp::Timestamp;
#[async_trait] #[async_trait]
impl AbstractServerMember for DummyDb { impl AbstractServerMember for DummyDb {
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> { async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
@@ -11,9 +13,11 @@ impl AbstractServerMember for DummyDb {
server: server.into(), server: server.into(),
user: user.into(), user: user.into(),
}, },
joined_at: Timestamp::now_utc(),
nickname: None, nickname: None,
avatar: None, avatar: None,
roles: None, roles: vec![],
timeout: None,
}) })
} }

View File

@@ -1,3 +1,4 @@
use iso8601_timestamp::Timestamp;
use ulid::Ulid; use ulid::Ulid;
use crate::{ use crate::{
@@ -188,7 +189,11 @@ impl Server {
server: self.id.clone(), server: self.id.clone(),
user: user.id.clone(), user: user.id.clone(),
}, },
..Default::default() joined_at: Timestamp::now_utc(),
nickname: None,
avatar: None,
roles: vec![],
timeout: None,
}; };
db.insert_member(&member).await?; db.insert_member(&member).await?;

View File

@@ -36,27 +36,24 @@ impl Member {
/// Get this user's current ranking /// Get this user's current ranking
pub fn get_ranking(&self, server: &Server) -> i64 { pub fn get_ranking(&self, server: &Server) -> i64 {
if let Some(roles) = &self.roles { let mut value = i64::MAX;
let mut value = i64::MAX; for role in &self.roles {
for role in roles { if let Some(role) = server.roles.get(role) {
if let Some(role) = server.roles.get(role) { if role.rank < value {
if role.rank < value { value = role.rank;
value = role.rank;
}
} }
} }
value
} else {
i64::MAX
} }
value
} }
pub fn remove(&mut self, field: &FieldsMember) { pub fn remove(&mut self, field: &FieldsMember) {
match field { match field {
FieldsMember::Avatar => self.avatar = None, FieldsMember::Avatar => self.avatar = None,
FieldsMember::Nickname => self.nickname = None, FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles = None, FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None,
} }
} }
} }

View File

@@ -1,7 +1,8 @@
use std::time::Duration; use std::time::Duration;
use bson::Bson; use bson::{to_bson, Bson};
use futures::StreamExt; use futures::StreamExt;
use iso8601_timestamp::Timestamp;
use mongodb::{ use mongodb::{
bson::{doc, from_bson, from_document, to_document, Document}, bson::{doc, from_bson, from_document, to_document, Document},
options::FindOptions, options::FindOptions,
@@ -16,7 +17,7 @@ struct MigrationInfo {
revision: i32, revision: i32,
} }
pub const LATEST_REVISION: i32 = 17; pub const LATEST_REVISION: i32 = 18;
pub async fn migrate_database(db: &MongoDb) { pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations"); let migrations = db.col::<Document>("migrations");
@@ -644,6 +645,23 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to create emoji parent index."); .expect("Failed to create emoji parent index.");
} }
if revision <= 17 {
info!("Running migration [revision 17 / 15-07-2022]: Initialise `joined_at` property on server members.");
db.col::<Document>("server_members")
.update_many(
doc! {},
doc! {
"$set": {
"joined_at": to_bson(&Timestamp::now_utc()).expect("to_bson")
}
},
None,
)
.await
.expect("Failed to update server members.");
}
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`. // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
// Reminder to update LATEST_REVISION when adding new migrations. // Reminder to update LATEST_REVISION when adding new migrations.

View File

@@ -129,6 +129,7 @@ impl IntoDocumentPath for FieldsMember {
FieldsMember::Avatar => "avatar", FieldsMember::Avatar => "avatar",
FieldsMember::Nickname => "nickname", FieldsMember::Nickname => "nickname",
FieldsMember::Roles => "roles", FieldsMember::Roles => "roles",
FieldsMember::Timeout => "timeout",
}) })
} }
} }

View File

@@ -74,6 +74,14 @@ pub struct Masquerade {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
pub avatar: Option<String>, pub avatar: Option<String>,
/// Replace the display role colour shown on this message
///
/// Must have `ManageRole` permission to use
///
/// This can be any valid CSS colour
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 32))]
pub colour: Option<String>,
} }
/// Representation of a Message on Revolt /// Representation of a Message on Revolt

View File

@@ -1,3 +1,4 @@
use iso8601_timestamp::Timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::models::attachment::File; use crate::models::attachment::File;
@@ -12,7 +13,7 @@ pub struct MemberCompositeKey {
} }
/// Representation of a member of a server on Revolt /// Representation of a member of a server on Revolt
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)]
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] #[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
#[optional_name = "PartialMember"] #[optional_name = "PartialMember"]
#[opt_skip_serializing_none] #[opt_skip_serializing_none]
@@ -22,6 +23,9 @@ pub struct Member {
#[serde(rename = "_id")] #[serde(rename = "_id")]
pub id: MemberCompositeKey, pub id: MemberCompositeKey,
/// Time at which this user joined the server
pub joined_at: Timestamp,
/// Member's nickname /// Member's nickname
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>, pub nickname: Option<String>,
@@ -30,8 +34,11 @@ pub struct Member {
pub avatar: Option<File>, pub avatar: Option<File>,
/// Member's roles /// Member's roles
#[serde(skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<String>,
/// Timestamp this member is timed out until
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<String>>, pub timeout: Option<Timestamp>,
} }
/// Optional fields on server member object /// Optional fields on server member object
@@ -40,6 +47,7 @@ pub enum FieldsMember {
Nickname, Nickname,
Avatar, Avatar,
Roles, Roles,
Timeout,
} }
/// Member removal intention /// Member removal intention

View File

@@ -55,11 +55,7 @@ async fn calculate_server_permission(
let mut permissions: PermissionValue = server.default_permissions.into(); let mut permissions: PermissionValue = server.default_permissions.into();
// 4. Resolve each role in order. // 4. Resolve each role in order.
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { let member_roles: HashSet<&String> = member.roles.iter().collect();
roles.iter().collect()
} else {
HashSet::new()
};
if !member_roles.is_empty() { if !member_roles.is_empty() {
let mut roles = server let mut roles = server
@@ -188,11 +184,7 @@ async fn calculate_channel_permission(
} }
// 4. Resolve each role in order. // 4. Resolve each role in order.
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { let member_roles: HashSet<&String> = member.roles.iter().collect();
roles.iter().collect()
} else {
HashSet::new()
};
if !member_roles.is_empty() { if !member_roles.is_empty() {
let mut roles = role_permissions let mut roles = role_permissions