Can upload files
This commit is contained in:
		
							
								
								
									
										1
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -2110,6 +2110,7 @@ dependencies = [
 | 
			
		||||
 "rust-s3",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "sha2",
 | 
			
		||||
 "thiserror 2.0.12",
 | 
			
		||||
 "tokio",
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -29,3 +29,4 @@ lazy-regex = "3.4.1"
 | 
			
		||||
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
 | 
			
		||||
mime_guess = "2.0.5"
 | 
			
		||||
rust-embed = { version = "8.6.0" }
 | 
			
		||||
sha2 = "0.10.8"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
DROP TABLE IF EXISTS inbox;
 | 
			
		||||
DROP TABLE IF EXISTS movements;
 | 
			
		||||
DROP TABLE IF EXISTS accounts;
 | 
			
		||||
DROP TABLE IF EXISTS attachments;
 | 
			
		||||
DROP TABLE IF EXISTS files;
 | 
			
		||||
DROP TABLE IF EXISTS tokens;
 | 
			
		||||
DROP TABLE IF EXISTS users;
 | 
			
		||||
@@ -9,29 +9,30 @@ CREATE TABLE users
 | 
			
		||||
 | 
			
		||||
CREATE TABLE tokens
 | 
			
		||||
(
 | 
			
		||||
    id               SERIAL PRIMARY KEY,
 | 
			
		||||
    name             VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_create      BIGINT       NOT NULL,
 | 
			
		||||
    user_id          INTEGER      NOT NULL REFERENCES users ON DELETE CASCADE,
 | 
			
		||||
    token_value      VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_used        BIGINT       NOT NULL,
 | 
			
		||||
    max_inactivity   INTEGER      NOT NULL,
 | 
			
		||||
    ip_net           VARCHAR(50),
 | 
			
		||||
    read_only        BOOLEAN      NOT NULL DEFAULT true,
 | 
			
		||||
    right_account    BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_movement   BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_inbox      BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_attachment BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_auth       BOOLEAN      NOT NULL DEFAULT false
 | 
			
		||||
    id             SERIAL PRIMARY KEY,
 | 
			
		||||
    name           VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_create    BIGINT       NOT NULL,
 | 
			
		||||
    user_id        INTEGER      NOT NULL REFERENCES users ON DELETE CASCADE,
 | 
			
		||||
    token_value    VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_used      BIGINT       NOT NULL,
 | 
			
		||||
    max_inactivity INTEGER      NOT NULL,
 | 
			
		||||
    ip_net         VARCHAR(50),
 | 
			
		||||
    read_only      BOOLEAN      NOT NULL DEFAULT true,
 | 
			
		||||
    right_account  BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_movement BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_inbox    BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_file     BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_auth     BOOLEAN      NOT NULL DEFAULT false
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE attachments
 | 
			
		||||
CREATE TABLE files
 | 
			
		||||
(
 | 
			
		||||
    id          SERIAL PRIMARY KEY,
 | 
			
		||||
    time_create BIGINT       NOT NULL,
 | 
			
		||||
    mime_type   VARCHAR(150) NOT NULL,
 | 
			
		||||
    sha512      VARCHAR(130) NOT NULL,
 | 
			
		||||
    file_size   INTEGER      NOT NULL,
 | 
			
		||||
    file_name   VARCHAR(150) NOT NULL,
 | 
			
		||||
    user_id     INTEGER      NOT NULL REFERENCES users ON DELETE SET NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@@ -47,23 +48,23 @@ CREATE TABLE accounts
 | 
			
		||||
 | 
			
		||||
CREATE TABLE movements
 | 
			
		||||
(
 | 
			
		||||
    id            SERIAL PRIMARY KEY,
 | 
			
		||||
    account_id    INTEGER      NOT NULL REFERENCES accounts ON DELETE CASCADE,
 | 
			
		||||
    time          BIGINT       NOT NULL,
 | 
			
		||||
    label         VARCHAR(200) NOT NULL,
 | 
			
		||||
    attachment_id INT          REFERENCES attachments ON DELETE SET NULL,
 | 
			
		||||
    amount        REAL         NOT NULL,
 | 
			
		||||
    checked       BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    time_create   BIGINT       NOT NULL,
 | 
			
		||||
    time_update   BIGINT       NOT NULL
 | 
			
		||||
    id          SERIAL PRIMARY KEY,
 | 
			
		||||
    account_id  INTEGER      NOT NULL REFERENCES accounts ON DELETE CASCADE,
 | 
			
		||||
    time        BIGINT       NOT NULL,
 | 
			
		||||
    label       VARCHAR(200) NOT NULL,
 | 
			
		||||
    file_id     INT          REFERENCES files ON DELETE SET NULL,
 | 
			
		||||
    amount      REAL         NOT NULL,
 | 
			
		||||
    checked     BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    time_create BIGINT       NOT NULL,
 | 
			
		||||
    time_update BIGINT       NOT NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE inbox
 | 
			
		||||
(
 | 
			
		||||
    id            SERIAL PRIMARY KEY,
 | 
			
		||||
    attachment_id INTEGER NOT NULL REFERENCES attachments ON DELETE CASCADE,
 | 
			
		||||
    user_id       INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
 | 
			
		||||
    account_id    INTEGER REFERENCES accounts ON DELETE CASCADE,
 | 
			
		||||
    time_create   BIGINT  NOT NULL,
 | 
			
		||||
    time_update   BIGINT  NOT NULL
 | 
			
		||||
    id          SERIAL PRIMARY KEY,
 | 
			
		||||
    file_id     INTEGER NOT NULL REFERENCES files ON DELETE CASCADE,
 | 
			
		||||
    user_id     INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
 | 
			
		||||
    account_id  INTEGER REFERENCES accounts ON DELETE CASCADE,
 | 
			
		||||
    time_create BIGINT  NOT NULL,
 | 
			
		||||
    time_update BIGINT  NOT NULL
 | 
			
		||||
);
 | 
			
		||||
@@ -18,3 +18,6 @@ pub mod sessions {
 | 
			
		||||
    /// Authenticated ID
 | 
			
		||||
    pub const USER_ID: &str = "uid";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Maximum uploaded file size (15MB)
 | 
			
		||||
pub const MAX_UPLOAD_FILE_SIZE: usize = 15 * 1024 * 1024;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								moneymgr_backend/src/controllers/files_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								moneymgr_backend/src/controllers/files_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
use crate::controllers::HttpResult;
 | 
			
		||||
use crate::extractors::auth_extractor::AuthExtractor;
 | 
			
		||||
use crate::extractors::file_extractor::FileExtractor;
 | 
			
		||||
use crate::services::files_service;
 | 
			
		||||
use actix_web::HttpResponse;
 | 
			
		||||
 | 
			
		||||
/// Upload a new file
 | 
			
		||||
pub async fn upload(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
 | 
			
		||||
    let file = files_service::create_file_with_mimetype(
 | 
			
		||||
        auth.user_id(),
 | 
			
		||||
        &file.name(),
 | 
			
		||||
        &file.mime,
 | 
			
		||||
        &file.buff,
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json(file))
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ use std::error::Error;
 | 
			
		||||
 | 
			
		||||
pub mod accounts_controller;
 | 
			
		||||
pub mod auth_controller;
 | 
			
		||||
pub mod files_controller;
 | 
			
		||||
pub mod server_controller;
 | 
			
		||||
pub mod static_controller;
 | 
			
		||||
pub mod tokens_controller;
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ pub async fn create(auth: AuthExtractor, req: web::Json<CreateTokenBody>) -> Htt
 | 
			
		||||
        right_account: req.right_account,
 | 
			
		||||
        right_movement: req.right_movement,
 | 
			
		||||
        right_inbox: req.right_inbox,
 | 
			
		||||
        right_attachment: req.right_attachment,
 | 
			
		||||
        right_file: req.right_attachment,
 | 
			
		||||
        right_auth: req.right_auth,
 | 
			
		||||
    })
 | 
			
		||||
    .await?;
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,7 @@ impl FromRequest for AuthExtractor {
 | 
			
		||||
                let authorized = (uri.starts_with("/api/account") && token.right_account)
 | 
			
		||||
                    || (uri.starts_with("/api/movement") && token.right_movement)
 | 
			
		||||
                    || (uri.starts_with("/api/inbox") && token.right_inbox)
 | 
			
		||||
                    || (uri.starts_with("/api/attachment") && token.right_attachment)
 | 
			
		||||
                    || (uri.starts_with("/api/file") && token.right_file)
 | 
			
		||||
                    || (uri.starts_with("/api/auth/") && token.right_auth);
 | 
			
		||||
 | 
			
		||||
                if !authorized {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								moneymgr_backend/src/extractors/file_extractor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								moneymgr_backend/src/extractors/file_extractor.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use actix_multipart::form::MultipartForm;
 | 
			
		||||
use actix_multipart::form::tempfile::TempFile;
 | 
			
		||||
use actix_web::dev::Payload;
 | 
			
		||||
use actix_web::{Error, FromRequest, HttpRequest};
 | 
			
		||||
use mime_guess::Mime;
 | 
			
		||||
use std::io::Read;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, MultipartForm)]
 | 
			
		||||
struct FileUploadForm {
 | 
			
		||||
    #[multipart(rename = "file")]
 | 
			
		||||
    file: TempFile,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct FileExtractor {
 | 
			
		||||
    pub buff: Vec<u8>,
 | 
			
		||||
    pub mime: Mime,
 | 
			
		||||
    pub name: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FileExtractor {
 | 
			
		||||
    pub fn name(&self) -> String {
 | 
			
		||||
        match &self.name {
 | 
			
		||||
            None => {
 | 
			
		||||
                let ext = self.mime.suffix().map(|s| s.as_str()).unwrap_or("bin");
 | 
			
		||||
                format!("file.{ext}")
 | 
			
		||||
            }
 | 
			
		||||
            Some(f) => f.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromRequest for FileExtractor {
 | 
			
		||||
    type Error = Error;
 | 
			
		||||
    type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
 | 
			
		||||
 | 
			
		||||
    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
 | 
			
		||||
        let form = MultipartForm::<FileUploadForm>::from_request(req, payload);
 | 
			
		||||
 | 
			
		||||
        Box::pin(async move {
 | 
			
		||||
            let mut form = form.await?;
 | 
			
		||||
 | 
			
		||||
            if form.file.size > constants::MAX_UPLOAD_FILE_SIZE {
 | 
			
		||||
                return Err(actix_web::error::ErrorPayloadTooLarge(
 | 
			
		||||
                    "Uploaded file is too large!",
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let mut buff = Vec::with_capacity(form.file.size);
 | 
			
		||||
            form.file.file.read_to_end(&mut buff)?;
 | 
			
		||||
 | 
			
		||||
            let mime = match form
 | 
			
		||||
                .file
 | 
			
		||||
                .content_type
 | 
			
		||||
                .clone()
 | 
			
		||||
                .or_else(|| Mime::from_str(form.file.file_name.as_deref().unwrap_or("")).ok())
 | 
			
		||||
            {
 | 
			
		||||
                Some(s) => s,
 | 
			
		||||
                None => {
 | 
			
		||||
                    return Err(actix_web::error::ErrorBadRequest(
 | 
			
		||||
                        "Mimetype was not specified!!",
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Ok(Self {
 | 
			
		||||
                mime,
 | 
			
		||||
                buff,
 | 
			
		||||
                name: form.file.file_name.clone(),
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
pub mod account_extractor;
 | 
			
		||||
pub mod auth_extractor;
 | 
			
		||||
pub mod file_extractor;
 | 
			
		||||
pub mod money_session;
 | 
			
		||||
 
 | 
			
		||||
@@ -119,6 +119,8 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/account/{account_id}",
 | 
			
		||||
                web::delete().to(accounts_controller::delete),
 | 
			
		||||
            )
 | 
			
		||||
            // Files controller
 | 
			
		||||
            .route("/api/file", web::post().to(files_controller::upload))
 | 
			
		||||
            // Static assets
 | 
			
		||||
            .route("/", web::get().to(static_controller::root_index))
 | 
			
		||||
            .route(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								moneymgr_backend/src/models/files.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								moneymgr_backend/src/models/files.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
use crate::models::users::UserID;
 | 
			
		||||
use crate::schema::*;
 | 
			
		||||
use diesel::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
 | 
			
		||||
pub struct FileID(pub i32);
 | 
			
		||||
 | 
			
		||||
#[derive(Queryable, Debug, serde::Serialize)]
 | 
			
		||||
pub struct File {
 | 
			
		||||
    id: i32,
 | 
			
		||||
    pub time_create: i64,
 | 
			
		||||
    pub mime_type: String,
 | 
			
		||||
    pub sha512: String,
 | 
			
		||||
    pub file_size: i32,
 | 
			
		||||
    pub file_name: String,
 | 
			
		||||
    user_id: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl File {
 | 
			
		||||
    pub fn id(&self) -> FileID {
 | 
			
		||||
        FileID(self.id)
 | 
			
		||||
    }
 | 
			
		||||
    pub fn file_path(&self) -> String {
 | 
			
		||||
        format!("blob/{}/{}", self.user_id, self.sha512)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn user_id(&self) -> UserID {
 | 
			
		||||
        UserID(self.id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Insertable)]
 | 
			
		||||
#[diesel(table_name = files)]
 | 
			
		||||
pub struct NewFile<'a> {
 | 
			
		||||
    pub time_create: i64,
 | 
			
		||||
    pub mime_type: &'a str,
 | 
			
		||||
    pub sha512: &'a str,
 | 
			
		||||
    pub file_size: i32,
 | 
			
		||||
    pub file_name: &'a str,
 | 
			
		||||
    pub user_id: i32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NewFile<'_> {
 | 
			
		||||
    pub fn file_path(&self) -> String {
 | 
			
		||||
        format!("blob/{}/{}", self.user_id, self.sha512)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
pub mod accounts;
 | 
			
		||||
pub mod files;
 | 
			
		||||
pub mod tokens;
 | 
			
		||||
pub mod users;
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ pub struct Token {
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_file: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -76,6 +76,6 @@ pub struct NewToken<'a> {
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_file: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ diesel::table! {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
diesel::table! {
 | 
			
		||||
    attachments (id) {
 | 
			
		||||
    files (id) {
 | 
			
		||||
        id -> Int4,
 | 
			
		||||
        time_create -> Int8,
 | 
			
		||||
        #[max_length = 150]
 | 
			
		||||
@@ -21,6 +21,8 @@ diesel::table! {
 | 
			
		||||
        #[max_length = 130]
 | 
			
		||||
        sha512 -> Varchar,
 | 
			
		||||
        file_size -> Int4,
 | 
			
		||||
        #[max_length = 150]
 | 
			
		||||
        file_name -> Varchar,
 | 
			
		||||
        user_id -> Int4,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -28,7 +30,7 @@ diesel::table! {
 | 
			
		||||
diesel::table! {
 | 
			
		||||
    inbox (id) {
 | 
			
		||||
        id -> Int4,
 | 
			
		||||
        attachment_id -> Int4,
 | 
			
		||||
        file_id -> Int4,
 | 
			
		||||
        user_id -> Int4,
 | 
			
		||||
        account_id -> Nullable<Int4>,
 | 
			
		||||
        time_create -> Int8,
 | 
			
		||||
@@ -43,7 +45,7 @@ diesel::table! {
 | 
			
		||||
        time -> Int8,
 | 
			
		||||
        #[max_length = 200]
 | 
			
		||||
        label -> Varchar,
 | 
			
		||||
        attachment_id -> Nullable<Int4>,
 | 
			
		||||
        file_id -> Nullable<Int4>,
 | 
			
		||||
        amount -> Float4,
 | 
			
		||||
        checked -> Bool,
 | 
			
		||||
        time_create -> Int8,
 | 
			
		||||
@@ -68,7 +70,7 @@ diesel::table! {
 | 
			
		||||
        right_account -> Bool,
 | 
			
		||||
        right_movement -> Bool,
 | 
			
		||||
        right_inbox -> Bool,
 | 
			
		||||
        right_attachment -> Bool,
 | 
			
		||||
        right_file -> Bool,
 | 
			
		||||
        right_auth -> Bool,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -86,19 +88,12 @@ diesel::table! {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
diesel::joinable!(accounts -> users (user_id));
 | 
			
		||||
diesel::joinable!(attachments -> users (user_id));
 | 
			
		||||
diesel::joinable!(files -> users (user_id));
 | 
			
		||||
diesel::joinable!(inbox -> accounts (account_id));
 | 
			
		||||
diesel::joinable!(inbox -> attachments (attachment_id));
 | 
			
		||||
diesel::joinable!(inbox -> files (file_id));
 | 
			
		||||
diesel::joinable!(inbox -> users (user_id));
 | 
			
		||||
diesel::joinable!(movements -> accounts (account_id));
 | 
			
		||||
diesel::joinable!(movements -> attachments (attachment_id));
 | 
			
		||||
diesel::joinable!(movements -> files (file_id));
 | 
			
		||||
diesel::joinable!(tokens -> users (user_id));
 | 
			
		||||
 | 
			
		||||
diesel::allow_tables_to_appear_in_same_query!(
 | 
			
		||||
    accounts,
 | 
			
		||||
    attachments,
 | 
			
		||||
    inbox,
 | 
			
		||||
    movements,
 | 
			
		||||
    tokens,
 | 
			
		||||
    users,
 | 
			
		||||
);
 | 
			
		||||
diesel::allow_tables_to_appear_in_same_query!(accounts, files, inbox, movements, tokens, users,);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										125
									
								
								moneymgr_backend/src/services/files_service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								moneymgr_backend/src/services/files_service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
use crate::connections::db_connection::db;
 | 
			
		||||
use crate::connections::s3_connection;
 | 
			
		||||
use crate::models::files::{File, FileID, NewFile};
 | 
			
		||||
use crate::models::users::UserID;
 | 
			
		||||
use crate::schema::files;
 | 
			
		||||
use crate::utils::crypt_utils::sha512;
 | 
			
		||||
use crate::utils::time_utils::time;
 | 
			
		||||
use diesel::prelude::*;
 | 
			
		||||
use mime_guess::Mime;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum FilesServiceError {
 | 
			
		||||
    #[error("UnknownMimeType!")]
 | 
			
		||||
    UnknownMimeType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn create_file_with_file_name(
 | 
			
		||||
    user_id: UserID,
 | 
			
		||||
    file_name: &str,
 | 
			
		||||
    bytes: &[u8],
 | 
			
		||||
) -> anyhow::Result<File> {
 | 
			
		||||
    let mime = mime_guess::from_path(file_name)
 | 
			
		||||
        .first()
 | 
			
		||||
        .ok_or(FilesServiceError::UnknownMimeType)?;
 | 
			
		||||
 | 
			
		||||
    create_file_with_mimetype(user_id, file_name, &mime, bytes).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn create_file_with_mimetype(
 | 
			
		||||
    user_id: UserID,
 | 
			
		||||
    file_name: &str,
 | 
			
		||||
    mime_type: &Mime,
 | 
			
		||||
    bytes: &[u8],
 | 
			
		||||
) -> anyhow::Result<File> {
 | 
			
		||||
    let sha512 = sha512(bytes);
 | 
			
		||||
 | 
			
		||||
    if let Ok(f) = get_file_with_hash(user_id, &sha512) {
 | 
			
		||||
        return Ok(f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let file = NewFile {
 | 
			
		||||
        time_create: time() as i64,
 | 
			
		||||
        mime_type: mime_type.as_ref(),
 | 
			
		||||
        sha512: &sha512,
 | 
			
		||||
        file_size: bytes.len() as i32,
 | 
			
		||||
        file_name,
 | 
			
		||||
        user_id: user_id.0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    s3_connection::upload_file(&file.file_path(), bytes).await?;
 | 
			
		||||
 | 
			
		||||
    let res = diesel::insert_into(files::table)
 | 
			
		||||
        .values(&file)
 | 
			
		||||
        .get_result(&mut db()?)?;
 | 
			
		||||
 | 
			
		||||
    Ok(res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_file_with_hash(user_id: UserID, sha512: &str) -> anyhow::Result<File> {
 | 
			
		||||
    Ok(files::table
 | 
			
		||||
        .filter(
 | 
			
		||||
            files::dsl::sha512
 | 
			
		||||
                .eq(sha512)
 | 
			
		||||
                .and(files::dsl::user_id.eq(user_id.0)),
 | 
			
		||||
        )
 | 
			
		||||
        .first(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_file_with_id(id: FileID) -> anyhow::Result<File> {
 | 
			
		||||
    Ok(files::table
 | 
			
		||||
        .filter(files::dsl::id.eq(id.0))
 | 
			
		||||
        .first(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_file_content_by_id(id: FileID) -> anyhow::Result<Vec<u8>> {
 | 
			
		||||
    let file = get_file_with_id(id)?;
 | 
			
		||||
    get_file_content(&file).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_file_content(file: &File) -> anyhow::Result<Vec<u8>> {
 | 
			
		||||
    s3_connection::get_file(&file.file_path()).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Delete the file if it is not referenced anymore in the database. Returns true
 | 
			
		||||
/// if the file was actually deleted, false otherwise
 | 
			
		||||
pub async fn delete_file_if_unused(id: FileID) -> anyhow::Result<bool> {
 | 
			
		||||
    let file = get_file_with_id(id)?;
 | 
			
		||||
 | 
			
		||||
    let res = diesel::delete(files::dsl::files.filter(files::dsl::id.eq(file.id().0)))
 | 
			
		||||
        .execute(&mut db()?);
 | 
			
		||||
 | 
			
		||||
    match res {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            s3_connection::delete_file_if_exists(&file.file_path()).await?;
 | 
			
		||||
            log::info!("File {:?} was deleted", file.id());
 | 
			
		||||
 | 
			
		||||
            Ok(true)
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::info!(
 | 
			
		||||
                "File {:?} could not be deleted, it must be used somewhere: {e}",
 | 
			
		||||
                file.id()
 | 
			
		||||
            );
 | 
			
		||||
            Ok(false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the entire list of file
 | 
			
		||||
pub async fn get_entire_list() -> anyhow::Result<Vec<File>> {
 | 
			
		||||
    Ok(files::table.get_results(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Remove unused files
 | 
			
		||||
pub async fn run_garbage_collector() -> anyhow::Result<usize> {
 | 
			
		||||
    let mut count_deleted = 0;
 | 
			
		||||
 | 
			
		||||
    for file in get_entire_list().await? {
 | 
			
		||||
        if delete_file_if_unused(file.id()).await? {
 | 
			
		||||
            count_deleted += 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(count_deleted)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
pub mod accounts_service;
 | 
			
		||||
pub mod files_service;
 | 
			
		||||
pub mod tokens_service;
 | 
			
		||||
pub mod users_service;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ pub struct NewTokenInfo {
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_file: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +38,7 @@ pub async fn create(new_token: NewTokenInfo) -> anyhow::Result<Token> {
 | 
			
		||||
        right_account: new_token.right_account,
 | 
			
		||||
        right_movement: new_token.right_movement,
 | 
			
		||||
        right_inbox: new_token.right_inbox,
 | 
			
		||||
        right_attachment: new_token.right_attachment,
 | 
			
		||||
        right_file: new_token.right_file,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let res = diesel::insert_into(tokens::table)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								moneymgr_backend/src/utils/crypt_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								moneymgr_backend/src/utils/crypt_utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
use sha2::{Digest, Sha512};
 | 
			
		||||
 | 
			
		||||
/// Compute hash of a slice of bytes (sha512)
 | 
			
		||||
pub fn sha512(bytes: &[u8]) -> String {
 | 
			
		||||
    let mut hasher = Sha512::new();
 | 
			
		||||
    hasher.update(bytes);
 | 
			
		||||
    let h = hasher.finalize();
 | 
			
		||||
    format!("{:x}", h)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
pub mod crypt_utils;
 | 
			
		||||
pub mod rand_utils;
 | 
			
		||||
pub mod time_utils;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user