Can download files
This commit is contained in:
parent
61a4ea62c6
commit
6f18aafc33
1
moneymgr_backend/Cargo.lock
generated
1
moneymgr_backend/Cargo.lock
generated
@ -2098,6 +2098,7 @@ dependencies = [
|
|||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"httpdate",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"jwt-simple",
|
"jwt-simple",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
|
@ -30,3 +30,4 @@ jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-
|
|||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
rust-embed = { version = "8.6.0" }
|
rust-embed = { version = "8.6.0" }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
httpdate = "1.0.3"
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::extractors::auth_extractor::AuthExtractor;
|
use crate::extractors::auth_extractor::AuthExtractor;
|
||||||
use crate::extractors::file_extractor::FileExtractor;
|
use crate::extractors::file_extractor::FileExtractor;
|
||||||
|
use crate::extractors::file_id_extractor::FileIdExtractor;
|
||||||
|
use crate::models::files::File;
|
||||||
use crate::services::files_service;
|
use crate::services::files_service;
|
||||||
use actix_web::HttpResponse;
|
use crate::utils::time_utils;
|
||||||
|
use actix_web::http::header;
|
||||||
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Upload a new file
|
/// Upload a new file
|
||||||
pub async fn upload(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
|
pub async fn upload(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
|
||||||
@ -16,3 +22,39 @@ pub async fn upload(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok().json(file))
|
Ok(HttpResponse::Ok().json(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Download an uploaded file
|
||||||
|
pub async fn download(req: HttpRequest, file_extractor: FileIdExtractor) -> HttpResult {
|
||||||
|
serve_file(req, file_extractor.as_ref()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serve a file, returning 304 status code if the requested file already exists
|
||||||
|
pub async fn serve_file(req: HttpRequest, file: &File) -> HttpResult {
|
||||||
|
// Check if the browser already knows the etag
|
||||||
|
if let Some(c) = req.headers().get(header::IF_NONE_MATCH) {
|
||||||
|
if c.to_str().unwrap_or("") == file.sha512.as_str() {
|
||||||
|
return Ok(HttpResponse::NotModified().finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the browser already knows the file by date
|
||||||
|
if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
|
||||||
|
let date_str = c.to_str().unwrap_or("");
|
||||||
|
if let Ok(date) = httpdate::parse_http_date(date_str) {
|
||||||
|
if date.add(Duration::from_secs(1))
|
||||||
|
>= time_utils::unix_to_system_time(file.time_create as u64)
|
||||||
|
{
|
||||||
|
return Ok(HttpResponse::NotModified().finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type(file.mime_type.as_str())
|
||||||
|
.insert_header(("etag", file.sha512.as_str()))
|
||||||
|
.insert_header((
|
||||||
|
"last-modified",
|
||||||
|
time_utils::unix_to_http_date(file.time_create as u64),
|
||||||
|
))
|
||||||
|
.body(files_service::get_file_content(file).await?))
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ pub struct CreateTokenBody {
|
|||||||
right_account: bool,
|
right_account: bool,
|
||||||
right_movement: bool,
|
right_movement: bool,
|
||||||
right_inbox: bool,
|
right_inbox: bool,
|
||||||
right_attachment: bool,
|
right_file: bool,
|
||||||
right_auth: bool,
|
right_auth: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ pub async fn create(auth: AuthExtractor, req: web::Json<CreateTokenBody>) -> Htt
|
|||||||
right_account: req.right_account,
|
right_account: req.right_account,
|
||||||
right_movement: req.right_movement,
|
right_movement: req.right_movement,
|
||||||
right_inbox: req.right_inbox,
|
right_inbox: req.right_inbox,
|
||||||
right_file: req.right_attachment,
|
right_file: req.right_file,
|
||||||
right_auth: req.right_auth,
|
right_auth: req.right_auth,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
61
moneymgr_backend/src/extractors/file_id_extractor.rs
Normal file
61
moneymgr_backend/src/extractors/file_id_extractor.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use crate::extractors::auth_extractor::AuthExtractor;
|
||||||
|
use crate::models::files::{File, FileID};
|
||||||
|
use crate::services::files_service;
|
||||||
|
use actix_web::dev::Payload;
|
||||||
|
use actix_web::{FromRequest, HttpRequest};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FileIdInPath {
|
||||||
|
file_id: FileID,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
enum FileIdExtractorError {
|
||||||
|
#[error("Current user does not own the file!")]
|
||||||
|
UserDoesNotOwnFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileIdExtractor(File);
|
||||||
|
|
||||||
|
impl FileIdExtractor {
|
||||||
|
pub async fn load_file_from_path(auth: &AuthExtractor, id: FileID) -> anyhow::Result<Self> {
|
||||||
|
let file = files_service::get_file_with_id(id)?;
|
||||||
|
|
||||||
|
if file.user_id() != auth.user_id() {
|
||||||
|
return Err(FileIdExtractorError::UserDoesNotOwnFile.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for FileIdExtractor {
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||||
|
let req = req.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let auth = AuthExtractor::extract(&req).await?;
|
||||||
|
|
||||||
|
let file_id =
|
||||||
|
actix_web::web::Path::<FileIdInPath>::from_request(&req, &mut Payload::None)
|
||||||
|
.await?
|
||||||
|
.file_id;
|
||||||
|
|
||||||
|
Self::load_file_from_path(&auth, file_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to extract file ID from URL! {}", e);
|
||||||
|
actix_web::error::ErrorNotFound("Could not fetch file information!")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<File> for FileIdExtractor {
|
||||||
|
fn as_ref(&self) -> &File {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod account_extractor;
|
pub mod account_extractor;
|
||||||
pub mod auth_extractor;
|
pub mod auth_extractor;
|
||||||
pub mod file_extractor;
|
pub mod file_extractor;
|
||||||
|
pub mod file_id_extractor;
|
||||||
pub mod money_session;
|
pub mod money_session;
|
||||||
|
@ -121,6 +121,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
)
|
)
|
||||||
// Files controller
|
// Files controller
|
||||||
.route("/api/file", web::post().to(files_controller::upload))
|
.route("/api/file", web::post().to(files_controller::upload))
|
||||||
|
.route(
|
||||||
|
"/api/file/{file_id}",
|
||||||
|
web::get().to(files_controller::download),
|
||||||
|
)
|
||||||
|
// TODO Delete file
|
||||||
// Static assets
|
// Static assets
|
||||||
.route("/", web::get().to(static_controller::root_index))
|
.route("/", web::get().to(static_controller::root_index))
|
||||||
.route(
|
.route(
|
||||||
|
@ -25,7 +25,7 @@ impl File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_id(&self) -> UserID {
|
pub fn user_id(&self) -> UserID {
|
||||||
UserID(self.id)
|
UserID(self.user_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! # Time utilities
|
//! # Time utilities
|
||||||
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
/// Get the current time since epoch
|
/// Get the current time since epoch
|
||||||
pub fn time() -> u64 {
|
pub fn time() -> u64 {
|
||||||
@ -9,3 +9,13 @@ pub fn time() -> u64 {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs()
|
.as_secs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format UNIX time to HTTP date
|
||||||
|
pub fn unix_to_system_time(time: u64) -> SystemTime {
|
||||||
|
UNIX_EPOCH + Duration::from_secs(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format UNIX time to HTTP date
|
||||||
|
pub fn unix_to_http_date(time: u64) -> String {
|
||||||
|
httpdate::fmt_http_date(unix_to_system_time(time))
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user