bitque/crates/web-actor/src/avatar.rs

197 lines
6.2 KiB
Rust
Raw Normal View History

use std::io::Write;
2021-04-16 15:20:25 +02:00
use actix::Addr;
use actix_multipart::{Field, Multipart};
use actix_web::web::Data;
use actix_web::{post, web, Error, HttpResponse};
2023-04-01 22:31:57 +02:00
use bitque_data::msg::{WsMsg, WsMsgUser};
use bitque_data::{User, UserId};
2021-04-16 15:20:25 +02:00
use database_actor::authorize_user::AuthorizeUser;
use database_actor::user_projects::CurrentUserProject;
use database_actor::users::UpdateAvatarUrl;
use database_actor::DbExecutor;
#[cfg(feature = "local-storage")]
2021-04-16 15:20:25 +02:00
use futures::executor::block_on;
use futures::{StreamExt, TryStreamExt};
2023-04-01 22:31:57 +02:00
use tracing::error;
2021-04-16 15:20:25 +02:00
use websocket_actor::server::InnerMsg::BroadcastToChannel;
use websocket_actor::server::WsServer;
2023-04-01 22:31:57 +02:00
use crate::ServiceError;
2021-08-12 16:10:30 +02:00
#[cfg(feature = "aws-s3")]
#[post("/")]
pub async fn upload(
mut payload: Multipart,
db: Data<Addr<DbExecutor>>,
ws: Data<Addr<WsServer>>,
fs: Data<Addr<filesystem_actor::FileSystemExecutor>>,
amazon: Data<Addr<amazon_actor::AmazonExecutor>>,
) -> Result<HttpResponse, Error> {
let mut user_id: Option<UserId> = None;
let mut avatar_url: Option<String> = None;
while let Ok(Some(field)) = payload.try_next().await {
2023-04-01 22:31:57 +02:00
let disposition = field.content_disposition();
if !disposition.is_form_data() {
return Ok(HttpResponse::BadRequest().finish());
}
match disposition.get_name() {
Some("token") => {
user_id = Some(handle_token(field, db.clone()).await?);
}
Some("avatar") => {
let id = user_id.ok_or_else(|| HttpResponse::Unauthorized().finish())?;
avatar_url = Some(
crate::handlers::upload_avatar_image::handle_image(
id,
field,
2023-04-01 22:31:57 +02:00
disposition.clone(),
fs.clone(),
amazon.clone(),
)
2020-12-24 16:24:47 +01:00
.await?,
);
}
_ => continue,
};
}
let user_id = match user_id {
Some(id) => id,
_ => return Ok(HttpResponse::Unauthorized().finish()),
};
let project_id = match block_on(db.send(CurrentUserProject { user_id })) {
Ok(Ok(user_project)) => user_project.project_id,
_ => return Ok(HttpResponse::UnprocessableEntity().finish()),
};
match (user_id, avatar_url) {
(user_id, Some(avatar_url)) => {
let user = update_user_avatar(user_id, avatar_url.clone(), db).await?;
ws.send(BroadcastToChannel(
project_id,
2021-08-13 23:03:52 +02:00
WsMsgUser::AvatarUrlChanged(user.id, avatar_url).into(),
))
.await
.map_err(|_| HttpResponse::UnprocessableEntity().finish())?;
Ok(HttpResponse::NoContent().finish())
}
_ => Ok(HttpResponse::UnprocessableEntity().finish()),
}
}
2021-08-12 16:10:30 +02:00
#[cfg(not(feature = "aws-s3"))]
#[post("/")]
pub async fn upload(
mut payload: Multipart,
db: Data<Addr<DbExecutor>>,
ws: Data<Addr<WsServer>>,
fs: Data<Addr<filesystem_actor::FileSystemExecutor>>,
) -> Result<HttpResponse, Error> {
let mut user_id: Option<UserId> = None;
let mut avatar_url: Option<String> = None;
while let Ok(Some(field)) = payload.try_next().await {
2023-04-01 22:31:57 +02:00
let disposition = field.content_disposition();
2021-08-12 16:10:30 +02:00
if !disposition.is_form_data() {
return Ok(HttpResponse::BadRequest().finish());
}
match disposition.get_name() {
Some("token") => {
user_id = Some(handle_token(field, db.clone()).await?);
}
Some("avatar") => {
2023-04-01 22:31:57 +02:00
let Some(id) = user_id else { return Ok(HttpResponse::Unauthorized().finish()); };
2021-08-12 16:10:30 +02:00
avatar_url = Some(
2023-04-01 22:31:57 +02:00
crate::handlers::upload_avatar_image::handle_image(id, field, fs.clone())
.await?,
2021-08-12 16:10:30 +02:00
);
}
_ => continue,
};
}
let user_id = match user_id {
Some(id) => id,
_ => return Ok(HttpResponse::Unauthorized().finish()),
};
let project_id = match block_on(db.send(CurrentUserProject { user_id })) {
Ok(Ok(user_project)) => user_project.project_id,
_ => return Ok(HttpResponse::UnprocessableEntity().finish()),
};
match (user_id, avatar_url) {
(user_id, Some(avatar_url)) => {
let user = update_user_avatar(user_id, avatar_url.clone(), db).await?;
2023-04-01 22:31:57 +02:00
if ws
.send(BroadcastToChannel(
project_id,
WsMsg::User(WsMsgUser::AvatarUrlChanged(user.id, avatar_url)),
))
.await
.is_err()
{
return Ok(HttpResponse::UnprocessableEntity().finish());
};
2021-08-12 16:10:30 +02:00
Ok(HttpResponse::NoContent().finish())
}
_ => Ok(HttpResponse::UnprocessableEntity().finish()),
}
}
async fn update_user_avatar(
user_id: UserId,
new_url: String,
db: Data<Addr<DbExecutor>>,
2021-08-12 16:10:30 +02:00
) -> Result<User, actix_web::Error> {
match db
.send(UpdateAvatarUrl {
user_id,
avatar_url: Some(new_url),
})
.await
{
Ok(Ok(user)) => Ok(user),
Ok(Err(e)) => {
2023-03-31 23:25:20 +02:00
::tracing::error!("{:?}", e);
2023-04-01 22:31:57 +02:00
Err(ServiceError::Unauthorized.into())
}
Err(e) => {
2023-03-31 23:25:20 +02:00
::tracing::error!("{:?}", e);
2023-04-01 22:31:57 +02:00
Err(ServiceError::Unauthorized.into())
}
}
}
2023-04-01 22:31:57 +02:00
async fn handle_token(mut field: Field, db: Data<Addr<DbExecutor>>) -> Result<UserId, Error> {
let mut f: Vec<u8> = vec![];
while let Some(chunk) = field.next().await {
let data = chunk.unwrap();
2023-04-01 22:31:57 +02:00
f = web::block(move || {
if let Err(e) = f.write_all(&data) {
error!("{e}");
}
f
})
.await?;
}
let access_token = String::from_utf8(f)
.unwrap_or_default()
.parse::<uuid::Uuid>()
2023-04-01 22:31:57 +02:00
.map_err(|_| ServiceError::Unauthorized)?;
match db.send(AuthorizeUser { access_token }).await {
Ok(Ok(user)) => Ok(user.id),
Ok(Err(e)) => {
2023-03-31 23:25:20 +02:00
::tracing::error!("{:?}", e);
2023-04-01 22:31:57 +02:00
Err(ServiceError::Unauthorized.into())
}
Err(e) => {
2023-03-31 23:25:20 +02:00
::tracing::error!("{:?}", e);
2023-04-01 22:31:57 +02:00
Err(ServiceError::Unauthorized.into())
}
}
}