From 836596684d74ab2dc2a79a3f12a91672450aed0c Mon Sep 17 00:00:00 2001 From: javerik Date: Sat, 14 Oct 2023 01:28:09 +0200 Subject: [PATCH] Refactored db_sqlite.rs and the usage. Init of database on startup. Error handling of database error in terms of logging and HTTP error responses --- src/endpoints/create.rs | 4 +- src/endpoints/edit.rs | 21 ++++- src/endpoints/pasta.rs | 75 ++++++++++------- src/endpoints/remove.rs | 26 +++--- src/main.rs | 18 +++- src/util/db.rs | 61 ++++++++++---- src/util/db_sqlite.rs | 177 +++++++++++++++++----------------------- src/util/misc.rs | 4 +- 8 files changed, 226 insertions(+), 160 deletions(-) diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index d3d6abf..a0443ac 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -308,7 +308,9 @@ pub async fn create( for (_, pasta) in pastas.iter().enumerate() { if pasta.id == id { - insert(Some(&pastas), Some(pasta)); + if let Err(e) = insert(Some(&pastas), Some(pasta)){ + log::error!("Failed to insert pasta with id {} => {}", pasta.id, e); + } } } diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index e379b78..324b302 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -7,6 +7,7 @@ use crate::util::misc::{decrypt, encrypt, remove_expired}; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; use actix_web::{get, post, web, Error, HttpResponse}; +use actix_web::error::ErrorInternalServerError; use askama::Template; use futures::TryStreamExt; @@ -168,7 +169,10 @@ pub async fn post_edit_private( .content .replace_range(.., res.unwrap().as_str()); // save pasta in database - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pastas => {}", e); + return Err(ErrorInternalServerError("Database update error")); + } } else { return Ok(HttpResponse::Found() .append_header(( @@ -272,7 +276,10 @@ pub async fn post_submit_edit_private( .content .replace_range(.., &encrypt(&new_content, &password)); // save pasta in database - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Update error for pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")); + } } else { return Ok(HttpResponse::Found() .append_header(( @@ -339,7 +346,10 @@ pub async fn post_edit( if res.is_ok() { pastas[i].content.replace_range(.., &new_content); // save pasta in database - update(Some(&pastas), Some(&pastas[i])); + if let Err(e) = update(Some(&pastas), Some(&pastas[i])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[i].id, e); + return Err(ErrorInternalServerError("Database update error")) + } } else { return Ok(HttpResponse::Found() .append_header(( @@ -359,7 +369,10 @@ pub async fn post_edit( } else { pastas[i].content.replace_range(.., &new_content); // save pasta in database - update(Some(&pastas), Some(&pastas[i])); + if let Err(e) = update(Some(&pastas), Some(&pastas[i])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[i].id, e); + return Err(ErrorInternalServerError("Database update error")) + } } return Ok(HttpResponse::Found() diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs index ee7916d..611a017 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -12,6 +12,7 @@ use askama::Template; use futures::TryStreamExt; use magic_crypt::{new_magic_crypt, MagicCryptTrait}; use std::time::{SystemTime, UNIX_EPOCH}; +use actix_web::error::ErrorInternalServerError; #[derive(Template)] #[template(path = "upload.html", escape = "none")] @@ -24,7 +25,7 @@ fn pastaresponse( data: web::Data, id: web::Path, password: String, -) -> HttpResponse { +) -> Result { // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); @@ -50,19 +51,22 @@ fn pastaresponse( if found { if pastas[index].encrypt_server && password == *"" { - return HttpResponse::Found() + return Ok(HttpResponse::Found() .append_header(( "Location", format!("/auth/{}", pastas[index].id_as_animals()), )) - .finish(); + .finish()); } // increment read count pastas[index].read_count += 1; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(error) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, error); + return Err(ErrorInternalServerError("Database update error")) + } let original_content = pastas[index].content.to_owned(); @@ -74,12 +78,12 @@ fn pastaresponse( .content .replace_range(.., res.unwrap().as_str()); } else { - return HttpResponse::Found() + return Ok(HttpResponse::Found() .append_header(( "Location", format!("/auth/{}/incorrect", pastas[index].id_as_animals()), )) - .finish(); + .finish()); } } @@ -108,17 +112,19 @@ fn pastaresponse( // update last read time pastas[index].last_read = timenow; - // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } - return response; + return Ok(response); } // otherwise send pasta not found error - HttpResponse::Ok() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { args: &ARGS }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } #[post("/upload/{id}")] @@ -136,8 +142,7 @@ pub async fn postpasta( } } } - - Ok(pastaresponse(data, id, password)) + pastaresponse(data, id, password) } #[post("/p/{id}")] @@ -155,21 +160,20 @@ pub async fn postshortpasta( } } } - - Ok(pastaresponse(data, id, password)) + pastaresponse(data, id, password) } #[get("/upload/{id}")] -pub async fn getpasta(data: web::Data, id: web::Path) -> HttpResponse { +pub async fn getpasta(data: web::Data, id: web::Path) -> Result { pastaresponse(data, id, String::from("")) } #[get("/p/{id}")] -pub async fn getshortpasta(data: web::Data, id: web::Path) -> HttpResponse { +pub async fn getshortpasta(data: web::Data, id: web::Path) -> Result { pastaresponse(data, id, String::from("")) } -fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse { +fn urlresponse(data: web::Data, id: web::Path) -> Result { // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); @@ -199,7 +203,10 @@ fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse pastas[index].read_count += 1; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } // send redirect if it's a url pasta if pastas[index].pasta_type == "url" { @@ -220,9 +227,12 @@ fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse pastas[index].last_read = timenow; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } - return response; + return Ok(response); // send error if we're trying to open a non-url pasta as a redirect } else { HttpResponse::Ok() @@ -232,18 +242,18 @@ fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse } // otherwise send pasta not found error - HttpResponse::Ok() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { args: &ARGS }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } #[get("/url/{id}")] -pub async fn redirecturl(data: web::Data, id: web::Path) -> HttpResponse { +pub async fn redirecturl(data: web::Data, id: web::Path) -> Result { urlresponse(data, id) } #[get("/u/{id}")] -pub async fn shortredirecturl(data: web::Data, id: web::Path) -> HttpResponse { +pub async fn shortredirecturl(data: web::Data, id: web::Path) -> Result { urlresponse(data, id) } @@ -289,7 +299,10 @@ pub async fn getrawpasta( pastas[index].read_count += 1; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } // get current unix time in seconds let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) { @@ -370,7 +383,10 @@ pub async fn postrawpasta( pastas[index].read_count += 1; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } let original_content = pastas[index].content.to_owned(); @@ -404,7 +420,10 @@ pub async fn postrawpasta( pastas[index].last_read = timenow; // save the updated read count - update(Some(&pastas), Some(&pastas[index])); + if let Err(e) = update(Some(&pastas), Some(&pastas[index])) { + log::error!("Failed to update pasta with id {} => {}", &pastas[index].id, e); + return Err(ErrorInternalServerError("Database update error")) + } // send raw content of pasta let response = Ok(HttpResponse::NotFound() diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs index 644fcab..0c0daa0 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -12,9 +12,10 @@ use crate::util::misc::{decrypt, remove_expired}; use crate::AppState; use askama::Template; use std::fs; +use actix_web::error::ErrorInternalServerError; #[get("/remove/{id}")] -pub async fn remove(data: web::Data, id: web::Path) -> HttpResponse { +pub async fn remove(data: web::Data, id: web::Path) -> Result { let mut pastas = data.pastas.lock().unwrap(); let id = if ARGS.hash_ids { @@ -27,12 +28,12 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes if pasta.id == id { // if it's encrypted or read-only, it needs password to be deleted if pasta.encrypt_server || pasta.readonly { - return HttpResponse::Found() + return Ok(HttpResponse::Found() .append_header(( "Location", format!("/auth_remove_private/{}", pasta.id_as_animals()), )) - .finish(); + .finish()); } // remove the file itself @@ -63,19 +64,22 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes // remove it from in-memory pasta list pastas.remove(i); - delete(Some(&pastas), Some(id)); + if let Err(error) = delete(Some(&pastas), Some(id)) { + log::error!("Failed to delete pasta with id {} => {}", id, error); + return Err(ErrorInternalServerError("Database delete error")); + } - return HttpResponse::Found() + return Ok(HttpResponse::Found() .append_header(("Location", format!("{}/list", ARGS.public_path_as_str()))) - .finish(); + .finish()); } } remove_expired(&mut pastas); - HttpResponse::Ok() + Ok(HttpResponse::Ok() .content_type("text/html") - .body(ErrorTemplate { args: &ARGS }.render().unwrap()) + .body(ErrorTemplate { args: &ARGS }.render().unwrap())) } #[post("/remove/{id}")] @@ -137,8 +141,10 @@ pub async fn post_remove( // remove it from in-memory pasta list pastas.remove(i); - - delete(Some(&pastas), Some(id)); + if let Err(error) = delete(Some(&pastas), Some(id)) { + log::error!("Failed to delete pasta with id {} => {}", id, error); + return Err(ErrorInternalServerError("Database delete error")); + } return Ok(HttpResponse::Found() .append_header(( diff --git a/src/main.rs b/src/main.rs index 81df7b1..44b780f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use crate::endpoints::{ pasta as pasta_endpoint, qr, remove, static_resources, }; use crate::pasta::Pasta; -use crate::util::db::read_all; +use crate::util::db::{read_all}; use crate::util::telemetry::start_telemetry_thread; use actix_web::middleware::Condition; use actix_web::{middleware, web, App, HttpServer}; @@ -17,6 +17,7 @@ use log::LevelFilter; use std::fs; use std::io::Write; use std::sync::Mutex; +use crate::util::db_sqlite::init_db; pub mod args; pub mod pasta; @@ -56,6 +57,7 @@ pub struct AppState { #[actix_web::main] async fn main() -> std::io::Result<()> { + Builder::new() .format(|buf, record| { writeln!( @@ -69,6 +71,11 @@ async fn main() -> std::io::Result<()> { .filter(None, LevelFilter::Info) .init(); + if let Err(error) = init_db() { + log::error!("Failed to initialize database {}", error); + panic!("Failed to initialize database {}", error); + } + log::info!( "MicroBin starting on http://{}:{}", ARGS.bind.to_string(), @@ -90,8 +97,15 @@ async fn main() -> std::io::Result<()> { } }; + let pastas = match read_all() { + Ok(v) => v, + Err(e) => { + panic!("Could not read pastas {}", e) + } + }; + let data = web::Data::new(AppState { - pastas: Mutex::new(read_all()), + pastas: Mutex::new(pastas), }); if !ARGS.disable_telemetry { diff --git a/src/util/db.rs b/src/util/db.rs index dce751e..8dfecf1 100644 --- a/src/util/db.rs +++ b/src/util/db.rs @@ -1,41 +1,74 @@ +use std::error::Error; +use std::fmt; +use std::fmt::Formatter; use crate::{args::ARGS, pasta::Pasta}; -pub fn read_all() -> Vec { - if ARGS.json_db { - super::db_json::read_all() - } else { - super::db_sqlite::read_all() - } +pub fn read_all() -> Result, DbError> { + let pastas = match ARGS.json_db { + true => super::db_json::read_all(), + false => super::db_sqlite::read_all()? + }; + Ok(pastas) } -pub fn insert(pastas: Option<&Vec>, pasta: Option<&Pasta>) { +pub fn insert(pastas: Option<&Vec>, pasta: Option<&Pasta>) -> Result<(), DbError> { if ARGS.json_db { super::db_json::update_all(pastas.expect("Called insert() without passing Pasta vector")); } else { - super::db_sqlite::insert(pasta.expect("Called insert() without passing new Pasta")); + super::db_sqlite::insert(pasta.expect("Called insert() without passing new Pasta"))?; } + Ok(()) } -pub fn update(pastas: Option<&Vec>, pasta: Option<&Pasta>) { +pub fn update(pastas: Option<&Vec>, pasta: Option<&Pasta>) -> Result<(), DbError> { if ARGS.json_db { super::db_json::update_all(pastas.expect("Called update() without passing Pasta vector")); } else { - super::db_sqlite::update(pasta.expect("Called insert() without passing Pasta to update")); + super::db_sqlite::update(pasta.expect("Called insert() without passing Pasta to update"))?; } + Ok(()) } -pub fn update_all(pastas: &Vec) { +pub fn update_all(pastas: &Vec) -> Result<(), DbError> { if ARGS.json_db { super::db_json::update_all(pastas); } else { - super::db_sqlite::update_all(pastas); + super::db_sqlite::update_all(pastas)?; } + Ok(()) } -pub fn delete(pastas: Option<&Vec>, id: Option) { +pub fn delete(pastas: Option<&Vec>, id: Option) -> Result<(), DbError> { if ARGS.json_db { super::db_json::update_all(pastas.expect("Called delete() without passing Pasta vector")); } else { - super::db_sqlite::delete_by_id(id.expect("Called delete() without passing Pasta id")); + super::db_sqlite::delete_by_id(id.expect("Called delete() without passing Pasta id"))?; } + Ok(()) } + +#[derive(Debug)] +pub struct DbError { + sql_error: Option> +} + +impl fmt::Display for DbError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.sql_error.as_ref() { + None => write!(f, "{}", "Unknown database error occoured"), + Some(e) => write!(f, "SQL database error => {}", e) + } + } +} + +impl Error for DbError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.sql_error.as_ref().map(|e| &**e as &(dyn Error + 'static)) + } +} + +impl From for DbError { + fn from(value: rusqlite::Error) -> Self { + DbError {sql_error: Some(Box::new(value))} + } +} \ No newline at end of file diff --git a/src/util/db_sqlite.rs b/src/util/db_sqlite.rs index 5eab1f8..013499a 100644 --- a/src/util/db_sqlite.rs +++ b/src/util/db_sqlite.rs @@ -1,30 +1,14 @@ use bytesize::ByteSize; -use rusqlite::{params, Connection}; +use rusqlite::{params, Connection, Error}; use crate::{args::ARGS, pasta::PastaFile, Pasta}; -pub fn read_all() -> Vec { - select_all_from_db() -} - -pub fn update_all(pastas: &[Pasta]) { - rewrite_all_to_db(pastas); +pub fn get_connection() -> Result { + Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) } -pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { - let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) - .expect("Failed to open SQLite database!"); - - conn.execute( - " - DROP TABLE IF EXISTS pasta; - );", - params![], - ) - .expect("Failed to drop SQLite table for Pasta!"); - - conn.execute( - " +pub fn init_db() -> Result<(), Error> { + let query_create_db = " CREATE TABLE IF NOT EXISTS pasta ( id INTEGER PRIMARY KEY, content TEXT NOT NULL, @@ -43,10 +27,42 @@ pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { read_count INTEGER NOT NULL, burn_after_reads INTEGER NOT NULL, pasta_type TEXT NOT NULL - );", - params![], - ) - .expect("Failed to create SQLite table for Pasta!"); + );"; + let conn = get_connection()?; + conn.execute(query_create_db, params![])?; + match conn.close() { + Ok(_) => Ok(()), + Err(c) => Err(c.1) + } +} + +pub fn recreate_db() -> Result<(), Error> { + let query_drop = " + DROP TABLE IF EXISTS pasta; + );"; + let conn = get_connection()?; + conn.execute(query_drop, params![])?; + match conn.close() { + Ok(_) => { }, + Err(c) => return Err(c.1) + } + init_db()?; + Ok(()) +} + +pub fn read_all() -> Result, Error> { + select_all_from_db() +} + +pub fn update_all(pastas: &[Pasta]) -> Result<(), Error> { + rewrite_all_to_db(pastas)?; + Ok(()) +} + +pub fn rewrite_all_to_db(pasta_data: &[Pasta]) -> Result<(), Error> { + recreate_db()?; + let conn = get_connection()?; + for pasta in pasta_data.iter() { conn.execute( @@ -88,43 +104,19 @@ pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { pasta.burn_after_reads, pasta.pasta_type, ], - ) - .expect("Failed to insert pasta."); + )?; + } + match conn.close() { + Ok(_) => Ok(()), + Err(c) => Err(c.1) } } -pub fn select_all_from_db() -> Vec { - let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) - .expect("Failed to open SQLite database!"); - - conn.execute( - " - CREATE TABLE IF NOT EXISTS pasta ( - id INTEGER PRIMARY KEY, - content TEXT NOT NULL, - file_name TEXT, - file_size INTEGER, - extension TEXT NOT NULL, - read_only INTEGER NOT NULL, - private INTEGER NOT NULL, - editable INTEGER NOT NULL, - encrypt_server INTEGER NOT NULL, - encrypt_client INTEGER NOT NULL, - encrypted_key TEXT, - created INTEGER NOT NULL, - expiration INTEGER NOT NULL, - last_read INTEGER NOT NULL, - read_count INTEGER NOT NULL, - burn_after_reads INTEGER NOT NULL, - pasta_type TEXT NOT NULL - );", - params![], - ) - .expect("Failed to create SQLite table for Pasta!"); +pub fn select_all_from_db() -> Result, Error> { + let conn = get_connection()?; let mut stmt = conn - .prepare("SELECT * FROM pasta ORDER BY created ASC") - .expect("Failed to prepare SQL statement to load pastas"); + .prepare("SELECT * FROM pasta ORDER BY created ASC")?; let pasta_iter = stmt .query_map([], |row| { @@ -158,42 +150,19 @@ pub fn select_all_from_db() -> Vec { burn_after_reads: row.get(15)?, pasta_type: row.get(16)?, }) - }) - .expect("Failed to select Pastas from SQLite database."); + })?; + + let pastas_result: Result, Error> = pasta_iter.collect(); + let pastas = match pastas_result { + Ok(v) => v, + Err(e) => return Err(e) + }; - pasta_iter - .map(|r| r.expect("Failed to get pasta")) - .collect::>() + Ok(pastas) } -pub fn insert(pasta: &Pasta) { - let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) - .expect("Failed to open SQLite database!"); - - conn.execute( - " - CREATE TABLE IF NOT EXISTS pasta ( - id INTEGER PRIMARY KEY, - content TEXT NOT NULL, - file_name TEXT, - file_size INTEGER, - extension TEXT NOT NULL, - read_only INTEGER NOT NULL, - private INTEGER NOT NULL, - editable INTEGER NOT NULL, - encrypt_server INTEGER NOT NULL, - encrypt_client INTEGER NOT NULL, - encrypted_key TEXT, - created INTEGER NOT NULL, - expiration INTEGER NOT NULL, - last_read INTEGER NOT NULL, - read_count INTEGER NOT NULL, - burn_after_reads INTEGER NOT NULL, - pasta_type TEXT NOT NULL - );", - params![], - ) - .expect("Failed to create SQLite table for Pasta!"); +pub fn insert(pasta: &Pasta) -> Result<(), Error> { + let conn = get_connection()?; conn.execute( "INSERT INTO pasta ( @@ -234,11 +203,14 @@ pub fn insert(pasta: &Pasta) { pasta.burn_after_reads, pasta.pasta_type, ], - ) - .expect("Failed to insert pasta."); + )?; + match conn.close() { + Ok(_) => Ok(()), + Err(c) => Err(c.1) + } } -pub fn update(pasta: &Pasta) { +pub fn update(pasta: &Pasta) -> Result<(), Error> { let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) .expect("Failed to open SQLite database!"); @@ -280,18 +252,23 @@ pub fn update(pasta: &Pasta) { pasta.burn_after_reads, pasta.pasta_type, ], - ) - .expect("Failed to update pasta."); + )?; + match conn.close() { + Ok(_) => Ok(()), + Err(c) => Err(c.1) + } } -pub fn delete_by_id(id: u64) { - let conn = Connection::open(format!("{}/database.sqlite", ARGS.data_dir)) - .expect("Failed to open SQLite database!"); +pub fn delete_by_id(id: u64) -> Result<(), Error> { + let conn = get_connection()?; conn.execute( "DELETE FROM pasta WHERE id = ?1;", params![id], - ) - .expect("Failed to delete pasta."); + )?; + match conn.close() { + Ok(_) => Ok(()), + Err(c) => Err(c.1) + } } diff --git a/src/util/misc.rs b/src/util/misc.rs index 142fce7..c5c0e6b 100644 --- a/src/util/misc.rs +++ b/src/util/misc.rs @@ -36,7 +36,9 @@ pub fn remove_expired(pastas: &mut Vec) { true } else { // remove from database - delete(None, Some(p.id)); + if let Err(e) = delete(None, Some(p.id)) { + log::error!("Failed to delete pasta with id {} => {}", p.id, e); + } // remove the file itself if let Some(file) = &p.file {