mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-07-14 05:26:59 +00:00
Add group icons / profile backgrounds.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2475,7 +2475,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt"
|
||||
version = "0.4.1-alpha.2"
|
||||
version = "0.4.1-alpha.3"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tungstenite",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt"
|
||||
version = "0.4.1-alpha.2"
|
||||
version = "0.4.1-alpha.3"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum Channel {
|
||||
DirectMessage {
|
||||
#[serde(rename = "_id")]
|
||||
id: String,
|
||||
|
||||
active: bool,
|
||||
recipients: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -38,10 +39,14 @@ pub enum Channel {
|
||||
id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
nonce: Option<String>,
|
||||
|
||||
name: String,
|
||||
owner: String,
|
||||
description: String,
|
||||
recipients: Vec<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
icon: Option<File>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
last_message: Option<LastMessage>,
|
||||
},
|
||||
|
||||
@@ -45,18 +45,20 @@ pub enum Presence {
|
||||
pub struct UserStatus {
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
text: Option<String>,
|
||||
pub text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
presence: Option<Presence>,
|
||||
pub presence: Option<Presence>,
|
||||
}
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UserProfile {
|
||||
#[validate(length(min = 1, max = 2000))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
content: Option<String>,
|
||||
pub content: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub background: Option<File>,
|
||||
}
|
||||
|
||||
// When changing this struct, update notifications/payload.rs#80
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct User {
|
||||
#[serde(rename = "_id")]
|
||||
|
||||
@@ -77,7 +77,7 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
||||
}
|
||||
},
|
||||
FindOptions::builder()
|
||||
.projection(doc! { "_id": 1, "username": 1, "badges": 1, "status": 1 })
|
||||
.projection(doc! { "_id": 1, "username": 1, "avatar": 1, "badges": 1, "status": 1 })
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -15,14 +15,17 @@ pub struct Data {
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
icon: Option<String>,
|
||||
}
|
||||
|
||||
#[patch("/<target>", data = "<info>")]
|
||||
pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
if info.name.is_none() && info.description.is_none() {
|
||||
if info.name.is_none() && info.description.is_none() && info.icon.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -37,11 +40,34 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
||||
}
|
||||
|
||||
match &target {
|
||||
Channel::Group { id, .. } => {
|
||||
Channel::Group { id, icon, .. } => {
|
||||
let mut set = doc! {};
|
||||
if let Some(name) = &info.name {
|
||||
set.insert("name", name);
|
||||
}
|
||||
|
||||
if let Some(description) = info.description {
|
||||
set.insert("description", description);
|
||||
}
|
||||
|
||||
let mut remove_icon = false;
|
||||
if let Some(attachment_id) = info.icon {
|
||||
let attachment = File::find_and_use(&attachment_id, "icons", "object", &user.id).await?;
|
||||
set.insert(
|
||||
"icon",
|
||||
to_document(&attachment).map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: "attachment",
|
||||
})?,
|
||||
);
|
||||
|
||||
remove_icon = true;
|
||||
}
|
||||
|
||||
get_collection("channels")
|
||||
.update_one(
|
||||
doc! { "_id": &id },
|
||||
doc! { "$set": to_document(&info.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
|
||||
doc! { "$set": &set },
|
||||
None
|
||||
)
|
||||
.await
|
||||
@@ -49,18 +75,18 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
||||
|
||||
ClientboundNotification::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: json!(info.0),
|
||||
data: json!(set),
|
||||
}
|
||||
.publish(id.clone())
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if let Some(name) = &info.name {
|
||||
if let Some(name) = info.name {
|
||||
Message::create(
|
||||
"00000000000000000000000000".to_string(),
|
||||
id.clone(),
|
||||
Content::SystemMessage(SystemMessage::ChannelRenamed {
|
||||
name: name.clone(),
|
||||
name,
|
||||
by: user.id,
|
||||
}),
|
||||
)
|
||||
@@ -69,6 +95,12 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> {
|
||||
.ok();
|
||||
}
|
||||
|
||||
if remove_icon {
|
||||
if let Some(old_icon) = icon {
|
||||
old_icon.delete().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
|
||||
@@ -70,6 +70,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
||||
.unwrap_or_else(|| "A group.".to_string()),
|
||||
owner: user.id,
|
||||
recipients: set.into_iter().collect::<Vec<String>>(),
|
||||
icon: None,
|
||||
last_message: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use rocket_contrib::json::JsonValue;
|
||||
#[get("/")]
|
||||
pub async fn root() -> JsonValue {
|
||||
json!({
|
||||
"revolt": "0.4.1-alpha.2",
|
||||
"revolt": "0.4.1-alpha.3",
|
||||
"features": {
|
||||
"registration": !*DISABLE_REGISTRATION,
|
||||
"captcha": {
|
||||
|
||||
@@ -7,34 +7,100 @@ use rocket_contrib::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
#[derive(Validate, Serialize, Deserialize, Debug)]
|
||||
pub struct UserProfileData {
|
||||
#[validate(length(min = 0, max = 2000))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate]
|
||||
status: Option<UserStatus>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate]
|
||||
profile: Option<UserProfile>,
|
||||
content: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
background: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum RemoveField {
|
||||
ProfileContent,
|
||||
ProfileBackground,
|
||||
StatusText,
|
||||
Avatar
|
||||
}
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
#[validate]
|
||||
status: Option<UserStatus>,
|
||||
#[validate]
|
||||
profile: Option<UserProfileData>,
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
avatar: Option<String>,
|
||||
remove: Option<RemoveField>
|
||||
}
|
||||
|
||||
#[patch("/<_ignore_id>", data = "<data>")]
|
||||
pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result<()> {
|
||||
if data.0.status.is_none() && data.0.profile.is_none() && data.0.avatar.is_none() {
|
||||
pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
|
||||
let mut data = data.into_inner();
|
||||
if data.status.is_none() && data.profile.is_none() && data.avatar.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut set = to_document(&data.0).map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: "data",
|
||||
})?;
|
||||
let mut unset = doc! {};
|
||||
let mut set = doc! {};
|
||||
|
||||
let avatar = std::mem::replace(&mut data.0.avatar, None);
|
||||
let mut remove_background = false;
|
||||
let mut remove_avatar = false;
|
||||
|
||||
if let Some(remove) = data.remove {
|
||||
match remove {
|
||||
RemoveField::ProfileContent => {
|
||||
unset.insert("profile.content", 1);
|
||||
},
|
||||
RemoveField::ProfileBackground => {
|
||||
unset.insert("profile.background", 1);
|
||||
remove_background = true;
|
||||
}
|
||||
RemoveField::StatusText => {
|
||||
unset.insert("status.text", 1);
|
||||
},
|
||||
RemoveField::Avatar => {
|
||||
unset.insert("avatar", 1);
|
||||
remove_avatar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(status) = &data.status {
|
||||
set.insert(
|
||||
"status",
|
||||
to_document(&status).map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: "status",
|
||||
})?,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(profile) = data.profile {
|
||||
if let Some(content) = profile.content {
|
||||
set.insert("profile.content", content);
|
||||
}
|
||||
|
||||
if let Some(attachment_id) = profile.background {
|
||||
let attachment = File::find_and_use(&attachment_id, "backgrounds", "user", &user.id).await?;
|
||||
set.insert(
|
||||
"profile.background",
|
||||
to_document(&attachment).map_err(|_| Error::DatabaseError {
|
||||
operation: "to_document",
|
||||
with: "attachment",
|
||||
})?,
|
||||
);
|
||||
|
||||
remove_background = true;
|
||||
}
|
||||
}
|
||||
|
||||
let avatar = std::mem::replace(&mut data.avatar, None);
|
||||
let attachment = if let Some(attachment_id) = avatar {
|
||||
let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
|
||||
set.insert(
|
||||
@@ -44,6 +110,8 @@ pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result
|
||||
with: "attachment",
|
||||
})?,
|
||||
);
|
||||
|
||||
remove_avatar = true;
|
||||
Some(attachment)
|
||||
} else {
|
||||
None
|
||||
@@ -57,7 +125,7 @@ pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result
|
||||
with: "user",
|
||||
})?;
|
||||
|
||||
if let Some(status) = data.0.status {
|
||||
if let Some(status) = data.status {
|
||||
ClientboundNotification::UserUpdate {
|
||||
id: user.id.clone(),
|
||||
data: json!({ "status": status }),
|
||||
@@ -75,11 +143,21 @@ pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result
|
||||
.publish(user.id.clone())
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if remove_avatar {
|
||||
if let Some(old_avatar) = user.avatar {
|
||||
old_avatar.delete().await?;
|
||||
}
|
||||
}
|
||||
|
||||
if remove_background {
|
||||
if let Some(profile) = user.profile {
|
||||
if let Some(old_background) = profile.background {
|
||||
old_background.delete().await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user