Compare commits
No commits in common. "4ab8c42f69785c5bde6ed24ea3e1a9219f946771" and "9bf3a22bf568273c59bab1ed44ba1c167149a5e1" have entirely different histories.
4ab8c42f69
...
9bf3a22bf5
11 changed files with 13 additions and 152 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -305,7 +305,7 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calibre-db"
|
name = "calibre-db"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"r2d2",
|
"r2d2",
|
||||||
"r2d2_sqlite",
|
"r2d2_sqlite",
|
||||||
|
@ -1222,7 +1222,7 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "little-hesinde"
|
name = "little-hesinde"
|
||||||
version = "0.3.5"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"calibre-db",
|
"calibre-db",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "calibre-db"
|
name = "calibre-db"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "little-hesinde"
|
name = "little-hesinde"
|
||||||
version = "0.3.5"
|
version = "0.3.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
|
@ -9,7 +9,7 @@ description = "A very simple ebook server for a calibre library, providing a htm
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.4", features = ["http2", "tracing"] }
|
axum = { version = "0.8.4", features = ["http2", "tracing"] }
|
||||||
calibre-db = { path = "../calibre-db/", version = "0.1.1" }
|
calibre-db = { path = "../calibre-db/", version = "0.1.0" }
|
||||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||||
image = { version = "0.25.6", default-features = false, features = ["jpeg", "rayon"] }
|
image = { version = "0.25.6", default-features = false, features = ["jpeg", "rayon"] }
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Handlers for OPDS feeds.
|
//! Handlers for OPDS feeds.
|
||||||
pub mod authors;
|
pub mod authors;
|
||||||
pub mod books;
|
pub mod books;
|
||||||
pub mod feed;
|
|
||||||
pub mod recent;
|
pub mod recent;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod series;
|
pub mod series;
|
||||||
|
|
|
@ -12,9 +12,10 @@ use time::OffsetDateTime;
|
||||||
use crate::{
|
use crate::{
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
api::{
|
api::{
|
||||||
OPDS_TAG, SortOrder,
|
SortOrder,
|
||||||
authors::{self, SingleAuthorError},
|
authors::{self, SingleAuthorError},
|
||||||
error::{ErrorResponse, HttpStatus},
|
error::{ErrorResponse, HttpStatus},
|
||||||
|
OPDS_TAG,
|
||||||
},
|
},
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
http_error,
|
http_error,
|
||||||
|
|
|
@ -12,8 +12,9 @@ use time::OffsetDateTime;
|
||||||
use crate::{
|
use crate::{
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
api::{
|
api::{
|
||||||
OPDS_TAG, SortOrder,
|
SortOrder,
|
||||||
error::{ErrorResponse, HttpStatus},
|
error::{ErrorResponse, HttpStatus},
|
||||||
|
OPDS_TAG,
|
||||||
},
|
},
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
data::book::Book,
|
data::book::Book,
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
use axum::{
|
|
||||||
http::{StatusCode, header},
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
};
|
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
APP_NAME,
|
|
||||||
api::{
|
|
||||||
OPDS_TAG,
|
|
||||||
error::{ErrorResponse, HttpStatus},
|
|
||||||
},
|
|
||||||
http_error,
|
|
||||||
opds::{
|
|
||||||
content::Content, entry::Entry, error::AsXmlError, feed::Feed, link::Link,
|
|
||||||
media_type::MediaType, relation::Relation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
/// Errors that can occur when rendering the root OPDS feed.
|
|
||||||
pub enum OdpsFeedError {
|
|
||||||
/// A failure to create the OPDS feed.
|
|
||||||
#[snafu(display("Failed to create opds feed."))]
|
|
||||||
Feed { source: AsXmlError },
|
|
||||||
}
|
|
||||||
impl HttpStatus for OdpsFeedError {
|
|
||||||
fn status_code(&self) -> StatusCode {
|
|
||||||
match self {
|
|
||||||
OdpsFeedError::Feed { source: _ } => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http_error!(OdpsFeedError);
|
|
||||||
|
|
||||||
/// Render all books as OPDS entries embedded in a feed.
|
|
||||||
#[utoipa::path(
|
|
||||||
get,
|
|
||||||
path = "/",
|
|
||||||
tag = OPDS_TAG,
|
|
||||||
responses(
|
|
||||||
(status = OK, content_type = "application/atom+xml"),
|
|
||||||
(status = 500, description = "Server failure.", body = ErrorResponse)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub async fn handler() -> Result<Response, OdpsFeedError> {
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: "/opds".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let books_entry = Entry {
|
|
||||||
title: "Books".to_string(),
|
|
||||||
id: format!("{APP_NAME}:books"),
|
|
||||||
updated: now,
|
|
||||||
content: Some(Content {
|
|
||||||
media_type: MediaType::Text,
|
|
||||||
content: "Index of all books".to_string(),
|
|
||||||
}),
|
|
||||||
author: None,
|
|
||||||
links: vec![Link {
|
|
||||||
href: "/opds/books".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Subsection,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let authors_entry = Entry {
|
|
||||||
title: "Authors".to_string(),
|
|
||||||
id: format!("{APP_NAME}:authors"),
|
|
||||||
updated: now,
|
|
||||||
content: Some(Content {
|
|
||||||
media_type: MediaType::Text,
|
|
||||||
content: "Index of all authors".to_string(),
|
|
||||||
}),
|
|
||||||
author: None,
|
|
||||||
links: vec![Link {
|
|
||||||
href: "/opds/authors".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Subsection,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let series_entry = Entry {
|
|
||||||
title: "Series".to_string(),
|
|
||||||
id: format!("{APP_NAME}:series"),
|
|
||||||
updated: now,
|
|
||||||
content: Some(Content {
|
|
||||||
media_type: MediaType::Text,
|
|
||||||
content: "Index of all series".to_string(),
|
|
||||||
}),
|
|
||||||
author: None,
|
|
||||||
links: vec![Link {
|
|
||||||
href: "/opds/series".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Subsection,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let recents_entry = Entry {
|
|
||||||
title: "Recent Additions".to_string(),
|
|
||||||
id: format!("{APP_NAME}:recentbooks"),
|
|
||||||
updated: now,
|
|
||||||
content: Some(Content {
|
|
||||||
media_type: MediaType::Text,
|
|
||||||
content: "Recently added books".to_string(),
|
|
||||||
}),
|
|
||||||
author: None,
|
|
||||||
links: vec![Link {
|
|
||||||
href: "/opds/recent".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Subsection,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let feed = Feed::create(
|
|
||||||
now,
|
|
||||||
&format!("{APP_NAME}:catalog"),
|
|
||||||
"Little Hesinde",
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
vec![authors_entry, series_entry, books_entry, recents_entry],
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().context(FeedSnafu)?;
|
|
||||||
|
|
||||||
Ok(([(header::CONTENT_TYPE, "application/atom+xml")], xml).into_response())
|
|
||||||
}
|
|
|
@ -11,9 +11,9 @@ use time::OffsetDateTime;
|
||||||
use crate::{
|
use crate::{
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
api::{
|
api::{
|
||||||
OPDS_TAG,
|
|
||||||
books::{self, RecentBooksError},
|
books::{self, RecentBooksError},
|
||||||
error::{ErrorResponse, HttpStatus},
|
error::{ErrorResponse, HttpStatus},
|
||||||
|
OPDS_TAG,
|
||||||
},
|
},
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
http_error,
|
http_error,
|
||||||
|
|
|
@ -12,9 +12,9 @@ use super::books::{RenderError, render_books};
|
||||||
use crate::{
|
use crate::{
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
api::{
|
api::{
|
||||||
OPDS_TAG,
|
|
||||||
error::{ErrorResponse, HttpStatus},
|
error::{ErrorResponse, HttpStatus},
|
||||||
search::{self, SearchQueryError},
|
search::{self, SearchQueryError},
|
||||||
|
OPDS_TAG,
|
||||||
},
|
},
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
http_error,
|
http_error,
|
||||||
|
|
|
@ -12,9 +12,10 @@ use time::OffsetDateTime;
|
||||||
use crate::{
|
use crate::{
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
api::{
|
api::{
|
||||||
OPDS_TAG, SortOrder,
|
SortOrder,
|
||||||
error::{ErrorResponse, HttpStatus},
|
error::{ErrorResponse, HttpStatus},
|
||||||
series::{self, SingleSeriesError},
|
series::{self, SingleSeriesError},
|
||||||
|
OPDS_TAG,
|
||||||
},
|
},
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
http_error,
|
http_error,
|
||||||
|
|
|
@ -16,7 +16,6 @@ pub fn router(state: AppState) -> OpenApiRouter {
|
||||||
let store = Arc::new(state);
|
let store = Arc::new(state);
|
||||||
|
|
||||||
let opds_routes = OpenApiRouter::new()
|
let opds_routes = OpenApiRouter::new()
|
||||||
.routes(routes!(opds::feed::handler))
|
|
||||||
.routes(routes!(opds::books::handler))
|
.routes(routes!(opds::books::handler))
|
||||||
.routes(routes!(opds::recent::handler))
|
.routes(routes!(opds::recent::handler))
|
||||||
.routes(routes!(opds::series::handler))
|
.routes(routes!(opds::series::handler))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue