proper error handling

This commit is contained in:
Sebastian Hugentobler 2024-05-06 09:09:40 +02:00
parent 687c33829f
commit ac7b0e7e88
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
15 changed files with 245 additions and 25 deletions

View file

@ -9,7 +9,11 @@ clap = { version = "4.5.4", features = ["derive"] }
once_cell = "1.19.0"
poem = { version = "3.0.0", features = ["embed", "static-files"] }
rust-embed = "8.3.0"
serde = { workspace = true }
serde_json = "1.0.116"
tera = "1.19.1"
thiserror = { workspace = true }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
uuid = { version = "1.8.0", features = ["v4", "fast-rng"] }

29
cops-web/src/data/book.rs Normal file
View file

@ -0,0 +1,29 @@
use calibre_db::data::{book::Book as DbBook, series::Series as DbSeries};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct Book {
pub id: u64,
pub title: String,
pub sort: String,
pub path: String,
pub author: String,
pub series: Option<(String, f64)>,
}
impl Book {
pub fn from_db_book(
db_book: &DbBook,
db_series: Option<(DbSeries, f64)>,
author: &str,
) -> Self {
Self {
id: db_book.id,
title: db_book.title.clone(),
sort: db_book.sort.clone(),
path: db_book.path.clone(),
author: author.to_string(),
series: db_series.map(|x| (x.0.name, x.1)),
}
}
}

View file

@ -0,0 +1,40 @@
use calibre_db::data::error::DataStoreError;
use poem::{error::ResponseError, http::StatusCode, Body, Response};
use tracing::error;
use uuid::Uuid;
#[derive(Debug, thiserror::Error)]
#[error("sqlite error")]
pub struct SqliteError(pub DataStoreError);
impl From<DataStoreError> for SqliteError {
fn from(item: DataStoreError) -> Self {
SqliteError(item)
}
}
impl ResponseError for SqliteError {
fn status(&self) -> StatusCode {
match &self.0 {
DataStoreError::NoResults(_) => StatusCode::NOT_FOUND,
_ => StatusCode::BAD_GATEWAY,
}
}
fn as_response(&self) -> Response {
let id = Uuid::new_v4();
let internal_msg = self.to_string();
let external_msg = match &self.0 {
DataStoreError::NoResults(_) => "item not found",
_ => "internal server error",
};
error!("{id}: {internal_msg}");
let body = Body::from_json(serde_json::json!({
"id": id.to_string(),
"message": external_msg,
}))
.unwrap();
Response::builder().status(self.status()).body(body)
}
}

View file

@ -7,11 +7,21 @@ use poem::{
};
use tera::Context;
use crate::{app_state::AppState, templates::TEMPLATES};
use crate::{
app_state::AppState, data::book::Book, handlers::error::SqliteError, templates::TEMPLATES,
};
#[handler]
pub async fn handler(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
let recent_books = state.calibre.recent_books(50).unwrap();
let recent_books = state.calibre.recent_books(50).map_err(SqliteError)?;
let recent_books = recent_books
.iter()
.filter_map(|x| {
let author = state.calibre.book_author(x.id).ok()?;
let series = state.calibre.book_series(x.id).ok()?;
Some(Book::from_db_book(x, series, &author.name))
})
.collect::<Vec<Book>>();
let mut context = Context::new();
context.insert("books", &recent_books);

View file

@ -14,8 +14,12 @@ mod app_state;
mod basic_auth;
mod cli;
mod config;
mod data {
pub mod book;
}
mod handlers {
pub mod cover;
pub mod error;
pub mod recents;
}
mod templates;

View file

@ -4,6 +4,10 @@
text-overflow: ellipsis;
}
.book-card hgroup {
margin-bottom: 0;
}
.cover {
width: 100%;
object-fit: contain;

View file

@ -3,10 +3,30 @@
<h1>Recent Books</h1>
<div class="grid-container">
{% for book in books %}
<article class="book-card grid-item">
<header>{{ book.title }}</header>
<article class="book-card">
<header class="grid-item">
<hgroup>
<h5>{{ book.title }}</h5>
<!-- {% if book.series %}<p>{{ book.series.0 }} ({{ book.series.1 }})</p>{% endif %} -->
<p>{{ book.author }}</p>
</hgroup>
</header>
<img class="cover" src="/cover/{{ book.id }}" alt="book cover">
<!-- <footer>{{ book.title }}</footer> -->
<footer>
<form>
<fieldset role="group">
<details class="dropdown">
<summary role="button" class="outline">
Download
</summary>
<ul>
<li><a href="#">Epub</a></li>
<li><a href="#">Pdf</a></li>
</ul>
</details>
</fieldset>
</form>
</footer>
</article>
{% endfor %}
</div>