User status / profile change route, username change route.

This commit is contained in:
Paul
2021-04-03 14:41:40 +01:00
parent 78c42fd412
commit 9492e145f9
10 changed files with 141 additions and 26 deletions

2
Cargo.lock generated
View File

@@ -2475,7 +2475,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt" name = "revolt"
version = "0.4.0-alpha.3" version = "0.4.0-alpha.4"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-tungstenite", "async-tungstenite",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "revolt" name = "revolt"
version = "0.4.0-alpha.3" version = "0.4.0-alpha.4"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"

View File

@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use mongodb::options::{Collation, FindOneOptions}; use mongodb::options::{Collation, FindOneOptions};
use crate::database::*; use crate::database::*;
use validator::Validate;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use crate::notifications::websocket::is_online; use crate::notifications::websocket::is_online;
use crate::database::permissions::user::UserPermissions; use crate::database::permissions::user::UserPermissions;
@@ -27,16 +28,23 @@ pub struct Relationship {
/* /*
pub enum Badge { pub enum Badge {
Developer = 1 Developer = 1,
Translator = 2,
} }
*/ */
#[derive(Serialize, Deserialize, Debug)] #[derive(Validate, Serialize, Deserialize, Debug)]
#[serde(tag = "type")] pub struct UserStatus {
pub enum UserStatus { #[validate(length(min = 1, max = 128))]
Text { #[serde(skip_serializing_if = "Option::is_none")]
text: String text: Option<String>
} }
#[derive(Validate, Serialize, Deserialize, Debug)]
pub struct UserProfile {
#[validate(length(min = 1, max = 2000))]
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -46,13 +54,13 @@ pub struct User {
pub username: String, pub username: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub relations: Option<Vec<Relationship>>, pub relations: Option<Vec<Relationship>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub badges: Option<i32>, pub badges: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<Vec<UserStatus>>, pub status: Option<UserStatus>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>, pub profile: Option<UserProfile>,
// ? This should never be pushed to the collection. // ? This should never be pushed to the collection.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@@ -78,6 +78,10 @@ pub enum ClientboundNotification {
user: String user: String
}, },
UserUpdate {
id: String,
data: JsonValue,
},
UserRelationship { UserRelationship {
id: String, id: String,
user: String, user: String,

View File

@@ -2,7 +2,7 @@ use crate::database::*;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use crate::notifications::events::ClientboundNotification; use crate::notifications::events::ClientboundNotification;
use mongodb::bson::doc; use mongodb::bson::{doc, to_document};
use validator::Validate; use validator::Validate;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@@ -10,8 +10,10 @@ use serde::{Serialize, Deserialize};
#[derive(Validate, Serialize, Deserialize)] #[derive(Validate, Serialize, Deserialize)]
pub struct Data { pub struct Data {
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 32))]
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>, name: Option<String>,
#[validate(length(min = 0, max = 1024))] #[validate(length(min = 0, max = 1024))]
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>, description: Option<String>,
} }
@@ -36,19 +38,10 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
match &target { match &target {
Channel::Group { id, .. } => { Channel::Group { id, .. } => {
let col = get_collection("channels"); get_collection("channels")
let mut set = doc! {}; .update_one(
if let Some(name) = &info.name {
set.insert("name", name.clone());
}
if let Some(description) = &info.description {
set.insert("description", description.clone());
}
col.update_one(
doc! { "_id": &id }, doc! { "_id": &id },
doc! { "$set": set }, doc! { "$set": to_document(&info.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
None None
) )
.await .await

View File

@@ -9,7 +9,7 @@ use rocket_contrib::json::JsonValue;
#[get("/")] #[get("/")]
pub async fn root() -> JsonValue { pub async fn root() -> JsonValue {
json!({ json!({
"revolt": "0.4.0-alpha.3", "revolt": "0.4.0-alpha.4",
"features": { "features": {
"registration": !*DISABLE_REGISTRATION, "registration": !*DISABLE_REGISTRATION,
"captcha": { "captcha": {

View File

@@ -0,0 +1,62 @@
use crate::database::*;
use crate::util::result::{Error, Result};
use crate::notifications::events::ClientboundNotification;
use rauth::auth::{Auth, Session};
use regex::Regex;
use mongodb::bson::doc;
use rocket::State;
use validator::Validate;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
// ! FIXME: should be global somewhere; maybe use config(?)
lazy_static! {
static ref RE_USERNAME: Regex = Regex::new(r"^[a-zA-Z0-9_.]+$").unwrap();
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
username: Option<String>,
#[validate(length(min = 8, max = 72))]
password: String,
}
#[patch("/username", data = "<data>")]
pub async fn req(auth: State<'_, Auth>, session: Session, user: User, data: Json<Data>) -> Result<()> {
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
auth.verify_password(&session, data.password.clone())
.await
.map_err(|_| Error::InvalidCredentials)?;
let mut set = doc! {};
if let Some(username) = &data.username {
if User::is_username_taken(&username).await? {
return Err(Error::UsernameTaken)
}
set.insert("username", username.clone());
}
get_collection("users")
.update_one(
doc! { "_id": &user.id },
doc! { "$set": set },
None
)
.await
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?;
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!(data.0)
}
.publish(user.id.clone())
.await
.ok();
Ok(())
}

View File

@@ -0,0 +1,41 @@
use crate::database::*;
use crate::util::result::{Error, Result};
use crate::notifications::events::ClientboundNotification;
use mongodb::bson::{doc, to_document};
use validator::Validate;
use rocket_contrib::json::Json;
use serde::{Serialize, Deserialize};
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<UserStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
profile: Option<UserProfile>
}
#[patch("/", data = "<data>")]
pub async fn req(user: User, data: Json<Data>) -> Result<()> {
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
get_collection("users")
.update_one(
doc! { "_id": &user.id },
doc! { "$set": to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
None
)
.await
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "user" })?;
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!(data.0)
}
.publish(user.id.clone())
.await
.ok();
Ok(())
}

View File

@@ -2,6 +2,8 @@ use rocket::Route;
mod add_friend; mod add_friend;
mod block_user; mod block_user;
mod change_username;
mod edit_user;
mod fetch_dms; mod fetch_dms;
mod fetch_profile; mod fetch_profile;
mod fetch_relationship; mod fetch_relationship;
@@ -18,6 +20,8 @@ pub fn routes() -> Vec<Route> {
routes![ routes![
// User Information // User Information
fetch_user::req, fetch_user::req,
edit_user::req,
change_username::req,
get_default_avatar::req, get_default_avatar::req,
get_avatar::req, get_avatar::req,
fetch_profile::req, fetch_profile::req,

View File

@@ -67,6 +67,8 @@ pub enum Error {
MissingPermission, MissingPermission,
#[snafu(display("Operation cannot be performed on this object."))] #[snafu(display("Operation cannot be performed on this object."))]
InvalidOperation, InvalidOperation,
#[snafu(display("Email or password is incorrect."))]
InvalidCredentials,
#[snafu(display("Already created an object with this nonce."))] #[snafu(display("Already created an object with this nonce."))]
DuplicateNonce, DuplicateNonce,
#[snafu(display("Voso is not enabled on this instance."))] #[snafu(display("Voso is not enabled on this instance."))]
@@ -108,6 +110,7 @@ impl<'r> Responder<'r, 'static> for Error {
Error::MissingPermission => Status::Forbidden, Error::MissingPermission => Status::Forbidden,
Error::InvalidOperation => Status::BadRequest, Error::InvalidOperation => Status::BadRequest,
Error::TooManyIds => Status::BadRequest, Error::TooManyIds => Status::BadRequest,
Error::InvalidCredentials => Status::Forbidden,
Error::DuplicateNonce => Status::Conflict, Error::DuplicateNonce => Status::Conflict,
Error::VosoUnavailable => Status::BadRequest, Error::VosoUnavailable => Status::BadRequest,
Error::NoEffect => Status::Ok, Error::NoEffect => Status::Ok,