Can upload files
This commit is contained in:
parent
84e1c57dc9
commit
61a4ea62c6
1
moneymgr_backend/Cargo.lock
generated
1
moneymgr_backend/Cargo.lock
generated
@ -2110,6 +2110,7 @@ dependencies = [
|
|||||||
"rust-s3",
|
"rust-s3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -29,3 +29,4 @@ lazy-regex = "3.4.1"
|
|||||||
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
|
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
|
||||||
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"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
DROP TABLE IF EXISTS inbox;
|
DROP TABLE IF EXISTS inbox;
|
||||||
DROP TABLE IF EXISTS movements;
|
DROP TABLE IF EXISTS movements;
|
||||||
DROP TABLE IF EXISTS accounts;
|
DROP TABLE IF EXISTS accounts;
|
||||||
DROP TABLE IF EXISTS attachments;
|
DROP TABLE IF EXISTS files;
|
||||||
DROP TABLE IF EXISTS tokens;
|
DROP TABLE IF EXISTS tokens;
|
||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
@ -21,17 +21,18 @@ CREATE TABLE tokens
|
|||||||
right_account BOOLEAN NOT NULL DEFAULT false,
|
right_account BOOLEAN NOT NULL DEFAULT false,
|
||||||
right_movement BOOLEAN NOT NULL DEFAULT false,
|
right_movement BOOLEAN NOT NULL DEFAULT false,
|
||||||
right_inbox BOOLEAN NOT NULL DEFAULT false,
|
right_inbox BOOLEAN NOT NULL DEFAULT false,
|
||||||
right_attachment BOOLEAN NOT NULL DEFAULT false,
|
right_file BOOLEAN NOT NULL DEFAULT false,
|
||||||
right_auth BOOLEAN NOT NULL DEFAULT false
|
right_auth BOOLEAN NOT NULL DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE attachments
|
CREATE TABLE files
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
time_create BIGINT NOT NULL,
|
time_create BIGINT NOT NULL,
|
||||||
mime_type VARCHAR(150) NOT NULL,
|
mime_type VARCHAR(150) NOT NULL,
|
||||||
sha512 VARCHAR(130) NOT NULL,
|
sha512 VARCHAR(130) NOT NULL,
|
||||||
file_size INTEGER NOT NULL,
|
file_size INTEGER NOT NULL,
|
||||||
|
file_name VARCHAR(150) NOT NULL,
|
||||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE SET NULL
|
user_id INTEGER NOT NULL REFERENCES users ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ CREATE TABLE movements
|
|||||||
account_id INTEGER NOT NULL REFERENCES accounts ON DELETE CASCADE,
|
account_id INTEGER NOT NULL REFERENCES accounts ON DELETE CASCADE,
|
||||||
time BIGINT NOT NULL,
|
time BIGINT NOT NULL,
|
||||||
label VARCHAR(200) NOT NULL,
|
label VARCHAR(200) NOT NULL,
|
||||||
attachment_id INT REFERENCES attachments ON DELETE SET NULL,
|
file_id INT REFERENCES files ON DELETE SET NULL,
|
||||||
amount REAL NOT NULL,
|
amount REAL NOT NULL,
|
||||||
checked BOOLEAN NOT NULL DEFAULT false,
|
checked BOOLEAN NOT NULL DEFAULT false,
|
||||||
time_create BIGINT NOT NULL,
|
time_create BIGINT NOT NULL,
|
||||||
@ -61,7 +62,7 @@ CREATE TABLE movements
|
|||||||
CREATE TABLE inbox
|
CREATE TABLE inbox
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
attachment_id INTEGER NOT NULL REFERENCES attachments ON DELETE CASCADE,
|
file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE,
|
||||||
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||||
account_id INTEGER REFERENCES accounts ON DELETE CASCADE,
|
account_id INTEGER REFERENCES accounts ON DELETE CASCADE,
|
||||||
time_create BIGINT NOT NULL,
|
time_create BIGINT NOT NULL,
|
||||||
|
@ -18,3 +18,6 @@ pub mod sessions {
|
|||||||
/// Authenticated ID
|
/// Authenticated ID
|
||||||
pub const USER_ID: &str = "uid";
|
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 accounts_controller;
|
||||||
pub mod auth_controller;
|
pub mod auth_controller;
|
||||||
|
pub mod files_controller;
|
||||||
pub mod server_controller;
|
pub mod server_controller;
|
||||||
pub mod static_controller;
|
pub mod static_controller;
|
||||||
pub mod tokens_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_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_attachment: req.right_attachment,
|
right_file: req.right_attachment,
|
||||||
right_auth: req.right_auth,
|
right_auth: req.right_auth,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -144,7 +144,7 @@ impl FromRequest for AuthExtractor {
|
|||||||
let authorized = (uri.starts_with("/api/account") && token.right_account)
|
let authorized = (uri.starts_with("/api/account") && token.right_account)
|
||||||
|| (uri.starts_with("/api/movement") && token.right_movement)
|
|| (uri.starts_with("/api/movement") && token.right_movement)
|
||||||
|| (uri.starts_with("/api/inbox") && token.right_inbox)
|
|| (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);
|
|| (uri.starts_with("/api/auth/") && token.right_auth);
|
||||||
|
|
||||||
if !authorized {
|
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 account_extractor;
|
||||||
pub mod auth_extractor;
|
pub mod auth_extractor;
|
||||||
|
pub mod file_extractor;
|
||||||
pub mod money_session;
|
pub mod money_session;
|
||||||
|
@ -119,6 +119,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/account/{account_id}",
|
"/api/account/{account_id}",
|
||||||
web::delete().to(accounts_controller::delete),
|
web::delete().to(accounts_controller::delete),
|
||||||
)
|
)
|
||||||
|
// Files controller
|
||||||
|
.route("/api/file", web::post().to(files_controller::upload))
|
||||||
// Static assets
|
// Static assets
|
||||||
.route("/", web::get().to(static_controller::root_index))
|
.route("/", web::get().to(static_controller::root_index))
|
||||||
.route(
|
.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 accounts;
|
||||||
|
pub mod files;
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
@ -32,7 +32,7 @@ pub struct Token {
|
|||||||
pub right_account: bool,
|
pub right_account: bool,
|
||||||
pub right_movement: bool,
|
pub right_movement: bool,
|
||||||
pub right_inbox: bool,
|
pub right_inbox: bool,
|
||||||
pub right_attachment: bool,
|
pub right_file: bool,
|
||||||
pub right_auth: bool,
|
pub right_auth: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +76,6 @@ pub struct NewToken<'a> {
|
|||||||
pub right_account: bool,
|
pub right_account: bool,
|
||||||
pub right_movement: bool,
|
pub right_movement: bool,
|
||||||
pub right_inbox: bool,
|
pub right_inbox: bool,
|
||||||
pub right_attachment: bool,
|
pub right_file: bool,
|
||||||
pub right_auth: bool,
|
pub right_auth: bool,
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
attachments (id) {
|
files (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
time_create -> Int8,
|
time_create -> Int8,
|
||||||
#[max_length = 150]
|
#[max_length = 150]
|
||||||
@ -21,6 +21,8 @@ diesel::table! {
|
|||||||
#[max_length = 130]
|
#[max_length = 130]
|
||||||
sha512 -> Varchar,
|
sha512 -> Varchar,
|
||||||
file_size -> Int4,
|
file_size -> Int4,
|
||||||
|
#[max_length = 150]
|
||||||
|
file_name -> Varchar,
|
||||||
user_id -> Int4,
|
user_id -> Int4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +30,7 @@ diesel::table! {
|
|||||||
diesel::table! {
|
diesel::table! {
|
||||||
inbox (id) {
|
inbox (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
attachment_id -> Int4,
|
file_id -> Int4,
|
||||||
user_id -> Int4,
|
user_id -> Int4,
|
||||||
account_id -> Nullable<Int4>,
|
account_id -> Nullable<Int4>,
|
||||||
time_create -> Int8,
|
time_create -> Int8,
|
||||||
@ -43,7 +45,7 @@ diesel::table! {
|
|||||||
time -> Int8,
|
time -> Int8,
|
||||||
#[max_length = 200]
|
#[max_length = 200]
|
||||||
label -> Varchar,
|
label -> Varchar,
|
||||||
attachment_id -> Nullable<Int4>,
|
file_id -> Nullable<Int4>,
|
||||||
amount -> Float4,
|
amount -> Float4,
|
||||||
checked -> Bool,
|
checked -> Bool,
|
||||||
time_create -> Int8,
|
time_create -> Int8,
|
||||||
@ -68,7 +70,7 @@ diesel::table! {
|
|||||||
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,19 +88,12 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diesel::joinable!(accounts -> users (user_id));
|
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 -> accounts (account_id));
|
||||||
diesel::joinable!(inbox -> attachments (attachment_id));
|
diesel::joinable!(inbox -> files (file_id));
|
||||||
diesel::joinable!(inbox -> users (user_id));
|
diesel::joinable!(inbox -> users (user_id));
|
||||||
diesel::joinable!(movements -> accounts (account_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::joinable!(tokens -> users (user_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(accounts, files, inbox, movements, tokens, users,);
|
||||||
accounts,
|
|
||||||
attachments,
|
|
||||||
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 accounts_service;
|
||||||
|
pub mod files_service;
|
||||||
pub mod tokens_service;
|
pub mod tokens_service;
|
||||||
pub mod users_service;
|
pub mod users_service;
|
||||||
|
@ -17,7 +17,7 @@ pub struct NewTokenInfo {
|
|||||||
pub right_account: bool,
|
pub right_account: bool,
|
||||||
pub right_movement: bool,
|
pub right_movement: bool,
|
||||||
pub right_inbox: bool,
|
pub right_inbox: bool,
|
||||||
pub right_attachment: bool,
|
pub right_file: bool,
|
||||||
pub right_auth: 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_account: new_token.right_account,
|
||||||
right_movement: new_token.right_movement,
|
right_movement: new_token.right_movement,
|
||||||
right_inbox: new_token.right_inbox,
|
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)
|
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 rand_utils;
|
||||||
pub mod time_utils;
|
pub mod time_utils;
|
||||||
|
@ -12,7 +12,7 @@ export interface Token {
|
|||||||
right_account: boolean;
|
right_account: boolean;
|
||||||
right_movement: boolean;
|
right_movement: boolean;
|
||||||
right_inbox: boolean;
|
right_inbox: boolean;
|
||||||
right_attachment: boolean;
|
right_file: boolean;
|
||||||
right_auth: boolean;
|
right_auth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export interface NewToken {
|
|||||||
right_account: boolean;
|
right_account: boolean;
|
||||||
right_movement: boolean;
|
right_movement: boolean;
|
||||||
right_inbox: boolean;
|
right_inbox: boolean;
|
||||||
right_attachment: boolean;
|
right_file: boolean;
|
||||||
right_auth: boolean;
|
right_auth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export function CreateTokenDialog(p: {
|
|||||||
max_inactivity: 3600 * 24 * 90,
|
max_inactivity: 3600 * 24 * 90,
|
||||||
read_only: false,
|
read_only: false,
|
||||||
right_account: false,
|
right_account: false,
|
||||||
right_attachment: false,
|
right_file: false,
|
||||||
right_auth: false,
|
right_auth: false,
|
||||||
right_inbox: false,
|
right_inbox: false,
|
||||||
right_movement: false,
|
right_movement: false,
|
||||||
@ -74,7 +74,7 @@ export function CreateTokenDialog(p: {
|
|||||||
read_only: false,
|
read_only: false,
|
||||||
right_account: false,
|
right_account: false,
|
||||||
right_movement: false,
|
right_movement: false,
|
||||||
right_attachment: true,
|
right_file: true,
|
||||||
right_auth: true,
|
right_auth: true,
|
||||||
right_inbox: true,
|
right_inbox: true,
|
||||||
});
|
});
|
||||||
@ -181,12 +181,12 @@ export function CreateTokenDialog(p: {
|
|||||||
<br />
|
<br />
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
editable
|
editable
|
||||||
label="Right: attachment routes"
|
label="Right: file routes"
|
||||||
checked={newToken.right_attachment}
|
checked={newToken.right_file}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
setNewToken({
|
setNewToken({
|
||||||
...newToken,
|
...newToken,
|
||||||
right_attachment: v,
|
right_file: v,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -180,8 +180,8 @@ function TokensRouteInner(p: {
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "right_attachment",
|
field: "right_file",
|
||||||
headerName: "Attachment",
|
headerName: "File",
|
||||||
flex: 2,
|
flex: 2,
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user