1
0
Fork 0
forked from gorb/backend

feat: user avatars

This commit is contained in:
Radical 2025-05-23 20:33:58 +02:00
parent d6364a0dc0
commit 97072d54d1
4 changed files with 65 additions and 13 deletions

View file

@ -36,6 +36,7 @@ diesel = { version = "2.2", features = ["uuid"] }
diesel-async = { version = "0.5", features = ["deadpool", "postgres", "async-connection-wrapper"] } diesel-async = { version = "0.5", features = ["deadpool", "postgres", "async-connection-wrapper"] }
diesel_migrations = { version = "2.2.0", features = ["postgres"] } diesel_migrations = { version = "2.2.0", features = ["postgres"] }
thiserror = "2.0.12" thiserror = "2.0.12"
actix-multipart = "0.7.2"
[dependencies.tokio] [dependencies.tokio]
version = "1.44" version = "1.44"

View file

@ -1,4 +1,5 @@
use actix_web::{get, patch, web, HttpRequest, HttpResponse}; use actix_web::{get, patch, web, HttpRequest, HttpResponse};
use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm};
use serde::Deserialize; use serde::Deserialize;
use crate::{error::Error, structs::Me, api::v1::auth::check_access_token, utils::get_auth_header, Data}; use crate::{error::Error, structs::Me, api::v1::auth::check_access_token, utils::get_auth_header, Data};
@ -18,7 +19,7 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
Ok(HttpResponse::Ok().json(me)) Ok(HttpResponse::Ok().json(me))
} }
#[derive(Deserialize)] #[derive(Debug, Deserialize)]
struct NewInfo { struct NewInfo {
username: Option<String>, username: Option<String>,
display_name: Option<String>, display_name: Option<String>,
@ -26,8 +27,15 @@ struct NewInfo {
email: Option<String>, email: Option<String>,
} }
#[derive(Debug, MultipartForm)]
struct UploadForm {
#[multipart(limit = "100MB")]
avatar: Option<TempFile>,
json: Option<MpJson<NewInfo>>,
}
#[patch("/me")] #[patch("/me")]
pub async fn update(req: HttpRequest, new_info: web::Json<NewInfo>, data: web::Data<Data>) -> Result<HttpResponse, Error> { pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm<UploadForm>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
let headers = req.headers(); let headers = req.headers();
let auth_header = get_auth_header(headers)?; let auth_header = get_auth_header(headers)?;
@ -36,8 +44,17 @@ pub async fn update(req: HttpRequest, new_info: web::Json<NewInfo>, data: web::D
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
let me = Me::get(&mut conn, uuid).await?; let mut me = Me::get(&mut conn, uuid).await?;
if let Some(avatar) = form.avatar {
let bytes = tokio::fs::read(avatar.file).await?;
let byte_slice: &[u8] = &bytes;
me.set_avatar(&data.bunny_cdn, &mut conn, data.config.bunny.cdn_url.clone(), byte_slice.into()).await?;
}
if let Some(new_info) = form.json {
if let Some(username) = &new_info.username { if let Some(username) = &new_info.username {
todo!(); todo!();
} }
@ -53,6 +70,7 @@ pub async fn update(req: HttpRequest, new_info: web::Json<NewInfo>, data: web::D
if let Some(email) = &new_info.email { if let Some(email) = &new_info.email {
todo!(); todo!();
} }
}
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }

View file

@ -9,6 +9,7 @@ pub fn web() -> Scope {
web::scope("/users") web::scope("/users")
.service(res) .service(res)
.service(me::res) .service(me::res)
.service(me::update)
.service(uuid::res) .service(uuid::res)
} }

View file

@ -667,6 +667,38 @@ impl Me {
Ok(me) Ok(me)
} }
pub async fn set_avatar(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, avatar: BytesMut) -> Result<(), Error> {
let avatar_clone = avatar.clone();
let image_type = task::spawn_blocking(move || image_check(avatar_clone)).await??;
if let Some(avatar) = &self.avatar {
let avatar_url: Url = avatar.parse()?;
let relative_url = avatar_url
.path()
.trim_start_matches('/');
bunny_cdn.storage.delete(relative_url).await?;
}
let path = format!("avatar/{}/avatar.{}", self.uuid, image_type);
bunny_cdn.storage.upload(path.clone(), avatar.into()).await?;
let avatar_url = cdn_url.join(&path)?;
use users::dsl;
update(users::table)
.filter(dsl::uuid.eq(self.uuid))
.set(dsl::avatar.eq(avatar_url.as_str()))
.execute(conn)
.await?;
self.avatar = Some(avatar_url.to_string());
Ok(())
}
} }
#[derive(Deserialize)] #[derive(Deserialize)]