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]]
|
||||
name = "calibre-db"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
|
@ -1222,7 +1222,7 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
|||
|
||||
[[package]]
|
||||
name = "little-hesinde"
|
||||
version = "0.3.5"
|
||||
version = "0.3.4"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"calibre-db",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "calibre-db"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "little-hesinde"
|
||||
version = "0.3.5"
|
||||
version = "0.3.4"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
@ -9,7 +9,7 @@ description = "A very simple ebook server for a calibre library, providing a htm
|
|||
|
||||
[dependencies]
|
||||
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"] }
|
||||
image = { version = "0.25.6", default-features = false, features = ["jpeg", "rayon"] }
|
||||
mime_guess = "2.0.5"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Handlers for OPDS feeds.
|
||||
pub mod authors;
|
||||
pub mod books;
|
||||
pub mod feed;
|
||||
pub mod recent;
|
||||
pub mod search;
|
||||
pub mod series;
|
||||
|
|
|
@ -12,9 +12,10 @@ use time::OffsetDateTime;
|
|||
use crate::{
|
||||
APP_NAME,
|
||||
api::{
|
||||
OPDS_TAG, SortOrder,
|
||||
SortOrder,
|
||||
authors::{self, SingleAuthorError},
|
||||
error::{ErrorResponse, HttpStatus},
|
||||
OPDS_TAG,
|
||||
},
|
||||
app_state::AppState,
|
||||
http_error,
|
||||
|
|
|
@ -12,8 +12,9 @@ use time::OffsetDateTime;
|
|||
use crate::{
|
||||
APP_NAME,
|
||||
api::{
|
||||
OPDS_TAG, SortOrder,
|
||||
SortOrder,
|
||||
error::{ErrorResponse, HttpStatus},
|
||||
OPDS_TAG,
|
||||
},
|
||||
app_state::AppState,
|
||||
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::{
|
||||
APP_NAME,
|
||||
api::{
|
||||
OPDS_TAG,
|
||||
books::{self, RecentBooksError},
|
||||
error::{ErrorResponse, HttpStatus},
|
||||
OPDS_TAG,
|
||||
},
|
||||
app_state::AppState,
|
||||
http_error,
|
||||
|
|
|
@ -12,9 +12,9 @@ use super::books::{RenderError, render_books};
|
|||
use crate::{
|
||||
APP_NAME,
|
||||
api::{
|
||||
OPDS_TAG,
|
||||
error::{ErrorResponse, HttpStatus},
|
||||
search::{self, SearchQueryError},
|
||||
OPDS_TAG,
|
||||
},
|
||||
app_state::AppState,
|
||||
http_error,
|
||||
|
|
|
@ -12,9 +12,10 @@ use time::OffsetDateTime;
|
|||
use crate::{
|
||||
APP_NAME,
|
||||
api::{
|
||||
OPDS_TAG, SortOrder,
|
||||
SortOrder,
|
||||
error::{ErrorResponse, HttpStatus},
|
||||
series::{self, SingleSeriesError},
|
||||
OPDS_TAG,
|
||||
},
|
||||
app_state::AppState,
|
||||
http_error,
|
||||
|
|
|
@ -16,7 +16,6 @@ pub fn router(state: AppState) -> OpenApiRouter {
|
|||
let store = Arc::new(state);
|
||||
|
||||
let opds_routes = OpenApiRouter::new()
|
||||
.routes(routes!(opds::feed::handler))
|
||||
.routes(routes!(opds::books::handler))
|
||||
.routes(routes!(opds::recent::handler))
|
||||
.routes(routes!(opds::series::handler))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue