Working on upload images and tilemap

This commit is contained in:
Benjamin Sherriff
2023-10-23 16:17:07 -04:00
parent 8b4d4e1b1f
commit 3eb888b57d
22 changed files with 987 additions and 656 deletions

View File

@@ -29,6 +29,7 @@ jsonwebtoken = "9.0.0"
redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] }
base64 = "0.21.4"
rust-s3 = "0.33.0"
actix-multipart = "0.6.1"
[dependencies.tokio]
version = "1.32.0"

View File

@@ -6,5 +6,6 @@ CREATE TABLE IF NOT EXISTS users (
last_name TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
profile TEXT,
verified BOOLEAN NOT NULL DEFAULT FALSE
);

View File

@@ -29,6 +29,7 @@ impl RegisterUser {
last_name: self.last_name,
updated_at: chrono::Utc::now().naive_utc(),
created_at: chrono::Utc::now().naive_utc(),
profile: None,
verified: false,
})
}
@@ -50,6 +51,7 @@ pub struct QueryUser {
pub last_name: String,
pub updated_at: chrono::NaiveDateTime,
pub created_at: chrono::NaiveDateTime,
pub profile: Option<String>,
pub verified: bool,
}
@@ -75,6 +77,7 @@ pub struct InsertUser {
pub last_name: String,
pub updated_at: chrono::NaiveDateTime,
pub created_at: chrono::NaiveDateTime,
pub profile: Option<String>,
pub verified: bool,
}
@@ -86,6 +89,15 @@ impl InsertUser {
.get_result(&mut conn)?;
Ok(user)
}
pub fn update_profile(email: &str, profile: Option<&str>) -> Result<QueryUser, ServiceError> {
let mut conn = connection()?;
let user = diesel::update(users::table)
.filter(users::email.eq(&email))
.set(users::profile.eq(profile))
.get_result(&mut conn)?;
Ok(user)
}
}
#[derive(Debug, Serialize, Deserialize)]

View File

@@ -114,7 +114,12 @@ impl From<redis::RedisError> for ServiceError {
impl From<s3::error::S3Error> for ServiceError {
fn from(error: s3::error::S3Error) -> ServiceError {
ServiceError::new(500, format!("Unknown s3 error: {}", error))
match error {
s3::error::S3Error::Http(code, message) => {
ServiceError::new(code, message)
},
_ => ServiceError::new(500, format!("Unknown s3 error: {}", error))
}
}
}

View File

@@ -22,6 +22,7 @@ mod auth;
mod dnd;
mod bot;
mod storage;
mod users;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
@@ -125,8 +126,9 @@ async fn main() -> std::io::Result<()> {
.wrap(cors)
.app_data(web::Data::new(Arc::clone(&app_data)))
.configure(crate::storage::messages::init_routes)
.configure(crate::dnd::spells::init_routes)
.configure(crate::auth::init_routes)
.configure(crate::users::init_routes)
.configure(crate::dnd::spells::init_routes)
.configure(crate::bot::api::init_routes)
})
.bind(format!("{}:{}", host, port)) {

View File

@@ -112,6 +112,12 @@ pub async fn upload_file(path: &str, content: &[u8]) -> Result<ResponseData, Ser
Ok(response)
}
pub async fn get_file(path: &str) -> Result<Vec<u8>, ServiceError> {
let response = BUCKET.get_object(path).await?;
let bytes = response.bytes();
Ok(bytes.to_vec())
}
pub async fn delete_file(path: &str) -> Result<ResponseData, ServiceError> {
let response = BUCKET.delete_object(path).await?;
Ok(response)

View File

@@ -48,6 +48,7 @@ diesel::table! {
last_name -> Text,
updated_at -> Timestamp,
created_at -> Timestamp,
profile -> Nullable<Text>,
verified -> Bool,
}
}

3
service/src/users/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
mod routes;
pub use routes::init_routes;

136
service/src/users/routes.rs Normal file
View File

@@ -0,0 +1,136 @@
use actix_multipart::Multipart;
use actix_web::{web, HttpResponse, post, delete, get, ResponseError};
use log::error;
use serenity::futures::StreamExt;
use siren::ServiceError;
use crate::{auth::{JwtAuth, InsertUser, QueryUser}, storage::{upload_file, get_file, delete_file}};
#[post("/picture")]
async fn set_picture(mut payload: Multipart, auth: JwtAuth) -> HttpResponse {
while let Some(item) = payload.next().await {
let mut bytes = web::BytesMut::new();
let mut field = match item {
Ok(field) => field,
Err(err) => return ResponseError::error_response(&err)
};
let content_type = field.content_disposition();
// Get file name and construct the file path
let file_name = match content_type.get_filename() {
Some(name) => {
// Verify extension is supported
match name.split(".").last() {
Some(ext) => {
match ext {
"png" | "jpg" | "jpeg" => name,
_ => return ResponseError::error_response(&ServiceError {
status: 400,
message: "File extension is not supported".to_string()
})
}
},
None => return ResponseError::error_response(&ServiceError {
status: 400,
message: "Unknown file extension".to_string()
})
}
},
None => return ResponseError::error_response(&ServiceError {
status: 400,
message: "File name is not provided".to_string()
})
};
let path = format!("users/{}/{}", auth.user.email, file_name);
// Build the file and store it in minio
while let Some(chunk) = field.next().await {
let data = match chunk {
Ok(data) => data,
Err(err) => {
error!("Failed to get chunk: {}", err);
return ResponseError::error_response(&err);
}
};
bytes.extend_from_slice(&data);
}
match upload_file(&path, &bytes).await {
Ok(_) => {
match InsertUser::update_profile(&auth.user.email, Some(&path)) {
Ok(_) => {}
Err(err) => {
error!("Failed to update user profile: {}", err);
return ResponseError::error_response(&err);
}
};
},
Err(err) => {
error!("Failed to upload file: {}", err);
return ResponseError::error_response(&err);
}
}
};
return HttpResponse::Ok().finish();
}
#[get("/picture")]
async fn get_picture(auth: JwtAuth) -> HttpResponse {
let user = match QueryUser::get_by_email(&auth.user.email) {
Ok(user) => user,
Err(err) => {
error!("Failed to get user: {}", err);
return ResponseError::error_response(&err);
}
};
if let Some(path) = user.profile {
match get_file(&path).await {
Ok(bytes) => return HttpResponse::Ok().body(bytes),
Err(err) => {
error!("Failed to get file: {}", err);
return ResponseError::error_response(&err);
}
}
} else {
return HttpResponse::NotFound().finish();
}
}
#[delete("/picture")]
async fn delete_picture(auth: JwtAuth) -> HttpResponse {
match QueryUser::get_by_email(&auth.user.email) {
Ok(user) => {
match user.profile {
Some(path) => {
match delete_file(&path).await {
Ok(_) => {
match InsertUser::update_profile(&auth.user.email, None) {
Ok(_) => {}
Err(err) => {
error!("Failed to update user profile: {}", err);
return ResponseError::error_response(&err);
}
};
}
Err(err) => {
error!("Failed to delete file: {}", err);
return ResponseError::error_response(&err);
}
};
},
None => {}
}
},
Err(err) => {
error!("Failed to get user: {}", err);
return ResponseError::error_response(&err);
}
};
return HttpResponse::Ok().finish();
}
pub fn init_routes(config: &mut web::ServiceConfig) {
config.service(web::scope("users")
.service(set_picture)
.service(get_picture)
.service(delete_picture)
);
}