Compare commits

..

No commits in common. "97072d54d180eedddd5b488b107a899bd9e86533" and "149b81973d0f111bd87a7c5d2cefdc815b230044" have entirely different histories.

6 changed files with 42 additions and 95 deletions

View file

@ -36,7 +36,6 @@ 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,5 +1,4 @@
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};
@ -19,7 +18,7 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
Ok(HttpResponse::Ok().json(me)) Ok(HttpResponse::Ok().json(me))
} }
#[derive(Debug, Deserialize)] #[derive(Deserialize)]
struct NewInfo { struct NewInfo {
username: Option<String>, username: Option<String>,
display_name: Option<String>, display_name: Option<String>,
@ -27,15 +26,8 @@ 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, MultipartForm(form): MultipartForm<UploadForm>, data: web::Data<Data>) -> Result<HttpResponse, Error> { pub async fn update(req: HttpRequest, new_info: web::Json<NewInfo>, 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)?;
@ -44,32 +36,22 @@ pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm<UploadF
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
let mut me = Me::get(&mut conn, uuid).await?; let me = Me::get(&mut conn, uuid).await?;
if let Some(avatar) = form.avatar { if let Some(username) = &new_info.username {
let bytes = tokio::fs::read(avatar.file).await?; todo!();
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(display_name) = &new_info.display_name {
if let Some(username) = &new_info.username { todo!();
todo!(); }
}
if let Some(display_name) = &new_info.display_name { if let Some(password) = &new_info.password {
todo!(); todo!();
} }
if let Some(password) = &new_info.password { if let Some(email) = &new_info.email {
todo!(); todo!();
}
if let Some(email) = &new_info.email {
todo!();
}
} }
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())

View file

@ -9,7 +9,6 @@ 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

@ -10,7 +10,7 @@ use diesel_async::pooled_connection::PoolError as DieselPoolError;
use tokio::task::JoinError; use tokio::task::JoinError;
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
use toml::de::Error as TomlError; use toml::de::Error as TomlError;
use log::{debug, error}; use log::error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
@ -54,7 +54,6 @@ pub enum Error {
impl ResponseError for Error { impl ResponseError for Error {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
debug!("{:?}", self);
error!("{}: {}", self.status_code(), self.to_string()); error!("{}: {}", self.status_code(), self.to_string());
HttpResponse::build(self.status_code()) HttpResponse::build(self.status_code())

View file

@ -5,8 +5,9 @@ use diesel_async::{pooled_connection::AsyncDieselConnectionManager, RunQueryDsl}
use tokio::task; use tokio::task;
use url::Url; use url::Url;
use actix_web::web::BytesMut; use actix_web::web::BytesMut;
use bindet::FileType;
use crate::{error::Error, schema::*, utils::image_check, Conn, Data}; use crate::{error::Error, Conn, Data, schema::*};
fn load_or_empty<T>(query_result: Result<Vec<T>, diesel::result::Error>) -> Result<Vec<T>, diesel::result::Error> { fn load_or_empty<T>(query_result: Result<Vec<T>, diesel::result::Error>) -> Result<Vec<T>, diesel::result::Error> {
match query_result { match query_result {
@ -255,7 +256,7 @@ impl GuildBuilder {
uuid: self.uuid, uuid: self.uuid,
name: self.name, name: self.name,
description: self.description, description: self.description,
icon: self.icon.and_then(|i| i.parse().ok()), icon: self.icon,
owner_uuid: self.owner_uuid, owner_uuid: self.owner_uuid,
roles: roles, roles: roles,
member_count: member_count, member_count: member_count,
@ -268,7 +269,7 @@ pub struct Guild {
pub uuid: Uuid, pub uuid: Uuid,
name: String, name: String,
description: Option<String>, description: Option<String>,
icon: Option<Url>, icon: Option<String>,
owner_uuid: Uuid, owner_uuid: Uuid,
pub roles: Vec<Role>, pub roles: Vec<Role>,
member_count: i64, member_count: i64,
@ -409,13 +410,29 @@ impl Guild {
// FIXME: Horrible security // FIXME: Horrible security
pub async fn set_icon(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, icon: BytesMut) -> Result<(), Error> { pub async fn set_icon(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, icon: BytesMut) -> Result<(), Error> {
let icon_clone = icon.clone(); let ico = icon.clone();
let image_type = task::spawn_blocking(move || image_check(icon_clone)).await??;
let image_type = task::spawn_blocking(move || {
let buf = std::io::Cursor::new(ico.to_vec());
let detect = bindet::detect(buf).map_err(|e| e.kind());
if let Ok(Some(file_type)) = detect {
if file_type.likely_to_be == vec![FileType::Jpg] {
return String::from("jpg")
} else if file_type.likely_to_be == vec![FileType::Png] {
return String::from("png")
}
}
String::from("unknown")
}).await?;
if image_type == "unknown" {
return Err(Error::BadRequest("Not an image".to_string()))
}
if let Some(icon) = &self.icon { if let Some(icon) = &self.icon {
let relative_url = icon let relative_url = icon.trim_start_matches("https://cdn.gorb.app/");
.path()
.trim_start_matches('/');
bunny_cdn.storage.delete(relative_url).await?; bunny_cdn.storage.delete(relative_url).await?;
} }
@ -433,7 +450,7 @@ impl Guild {
.execute(conn) .execute(conn)
.await?; .await?;
self.icon = Some(icon_url); self.icon = Some(icon_url.to_string());
Ok(()) Ok(())
} }
@ -667,38 +684,6 @@ 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)]

View file

@ -1,8 +1,7 @@
use actix_web::{ use actix_web::{
cookie::{time::Duration, Cookie, SameSite}, cookie::{Cookie, SameSite, time::Duration},
http::header::HeaderMap, web::BytesMut, http::header::HeaderMap,
}; };
use bindet::FileType;
use getrandom::fill; use getrandom::fill;
use hex::encode; use hex::encode;
use redis::RedisError; use redis::RedisError;
@ -60,22 +59,6 @@ pub fn generate_refresh_token() -> Result<String, getrandom::Error> {
Ok(encode(buf)) Ok(encode(buf))
} }
pub fn image_check(icon: BytesMut) -> Result<String, Error> {
let buf = std::io::Cursor::new(icon);
let detect = bindet::detect(buf).map_err(|e| e.kind());
if let Ok(Some(file_type)) = detect {
if file_type.likely_to_be == vec![FileType::Jpg] {
return Ok(String::from("jpg"))
} else if file_type.likely_to_be == vec![FileType::Png] {
return Ok(String::from("png"))
}
}
Err(Error::BadRequest("Uploaded file is not an image".to_string()))
}
impl Data { impl Data {
pub async fn set_cache_key( pub async fn set_cache_key(
&self, &self,