refactor opds to something usable
This commit is contained in:
parent
cccd3cbdc9
commit
a41dcab889
@ -2,21 +2,19 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use calibre_db::data::pagination::SortOrder;
|
use calibre_db::data::pagination::SortOrder;
|
||||||
use poem::{
|
use poem::{
|
||||||
error::InternalServerError,
|
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Html, Path},
|
web::{Data, Path},
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
use tera::Context;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{app_state::AppState, data::book::Book, handlers::error::HandlerError, Accept};
|
||||||
app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
id: Path<u64>,
|
id: Path<u64>,
|
||||||
|
accept: Data<&Accept>,
|
||||||
state: Data<&Arc<AppState>>,
|
state: Data<&Arc<AppState>>,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
let author = state
|
let author = state
|
||||||
.calibre
|
.calibre
|
||||||
.scalar_author(*id)
|
.scalar_author(*id)
|
||||||
@ -30,13 +28,8 @@ pub async fn handler(
|
|||||||
.filter_map(|x| Book::full_book(x, &state))
|
.filter_map(|x| Book::full_book(x, &state))
|
||||||
.collect::<Vec<Book>>();
|
.collect::<Vec<Book>>();
|
||||||
|
|
||||||
let mut context = Context::new();
|
match accept.0 {
|
||||||
context.insert("title", &author.name);
|
Accept::Html => crate::handlers::html::author::handler(author, books).await,
|
||||||
context.insert("nav", "authors");
|
Accept::Opds => crate::handlers::opds::author::handler(author, books).await,
|
||||||
context.insert("books", &books);
|
}
|
||||||
|
|
||||||
TEMPLATES
|
|
||||||
.render("book_list", &context)
|
|
||||||
.map_err(InternalServerError)
|
|
||||||
.map(Html)
|
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,37 @@ use std::sync::Arc;
|
|||||||
use calibre_db::{calibre::Calibre, data::pagination::SortOrder};
|
use calibre_db::{calibre::Calibre, data::pagination::SortOrder};
|
||||||
use poem::{
|
use poem::{
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Html, Path},
|
web::{Data, Path},
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{app_state::AppState, handlers::paginated};
|
use crate::{app_state::AppState, Accept};
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler_init(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
|
pub async fn handler_init(
|
||||||
authors(&state.calibre, None, &SortOrder::ASC)
|
accept: Data<&Accept>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
authors(&accept, &state.calibre, None, &SortOrder::ASC).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
||||||
|
accept: Data<&Accept>,
|
||||||
state: Data<&Arc<AppState>>,
|
state: Data<&Arc<AppState>>,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
authors(&state.calibre, Some(&cursor), &sort_order)
|
authors(&accept, &state.calibre, Some(&cursor), &sort_order).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn authors(
|
async fn authors(
|
||||||
|
acccept: &Accept,
|
||||||
calibre: &Calibre,
|
calibre: &Calibre,
|
||||||
cursor: Option<&str>,
|
cursor: Option<&str>,
|
||||||
sort_order: &SortOrder,
|
sort_order: &SortOrder,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
paginated::render(
|
match acccept {
|
||||||
"authors",
|
Accept::Html => crate::handlers::html::authors::handler(calibre, cursor, sort_order).await,
|
||||||
|| calibre.authors(25, cursor, sort_order),
|
Accept::Opds => crate::handlers::opds::authors::handler(calibre, cursor, sort_order).await,
|
||||||
|author| author.sort.clone(),
|
}
|
||||||
|cursor| calibre.has_previous_authors(cursor),
|
|
||||||
|cursor| calibre.has_more_authors(cursor),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -3,41 +3,37 @@ use std::sync::Arc;
|
|||||||
use calibre_db::data::pagination::SortOrder;
|
use calibre_db::data::pagination::SortOrder;
|
||||||
use poem::{
|
use poem::{
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Html, Path},
|
web::{Data, Path},
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{app_state::AppState, data::book::Book};
|
use crate::{app_state::AppState, Accept};
|
||||||
|
|
||||||
use super::paginated;
|
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler_init(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
|
pub async fn handler_init(
|
||||||
books(&state, None, &SortOrder::ASC)
|
accept: Data<&Accept>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
books(&accept, &state, None, &SortOrder::ASC).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
||||||
|
accept: Data<&Accept>,
|
||||||
state: Data<&Arc<AppState>>,
|
state: Data<&Arc<AppState>>,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
books(&state, Some(&cursor), &sort_order)
|
books(&accept, &state, Some(&cursor), &sort_order).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn books(
|
async fn books(
|
||||||
|
accept: &Accept,
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
cursor: Option<&str>,
|
cursor: Option<&str>,
|
||||||
sort_order: &SortOrder,
|
sort_order: &SortOrder,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
paginated::render(
|
match accept {
|
||||||
"books",
|
Accept::Html => crate::handlers::html::books::handler(state, cursor, sort_order).await,
|
||||||
|| {
|
Accept::Opds => crate::handlers::opds::books::handler(state, cursor, sort_order).await,
|
||||||
state
|
}
|
||||||
.calibre
|
|
||||||
.books(25, cursor, sort_order)
|
|
||||||
.map(|x| x.iter().filter_map(|y| Book::full_book(y, state)).collect())
|
|
||||||
},
|
|
||||||
|book| book.sort.clone(),
|
|
||||||
|cursor| state.calibre.has_previous_books(cursor),
|
|
||||||
|cursor| state.calibre.has_more_books(cursor),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
18
rusty-library/src/handlers/html/author.rs
Normal file
18
rusty-library/src/handlers/html/author.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use calibre_db::data::author::Author;
|
||||||
|
use poem::{error::InternalServerError, web::Html, IntoResponse, Response};
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
|
use crate::{data::book::Book, templates::TEMPLATES};
|
||||||
|
|
||||||
|
pub async fn handler(author: Author, books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("title", &author.name);
|
||||||
|
context.insert("nav", "authors");
|
||||||
|
context.insert("books", &books);
|
||||||
|
|
||||||
|
Ok(TEMPLATES
|
||||||
|
.render("book_list", &context)
|
||||||
|
.map_err(InternalServerError)
|
||||||
|
.map(Html)?
|
||||||
|
.into_response())
|
||||||
|
}
|
18
rusty-library/src/handlers/html/authors.rs
Normal file
18
rusty-library/src/handlers/html/authors.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use calibre_db::{calibre::Calibre, data::pagination::SortOrder};
|
||||||
|
use poem::Response;
|
||||||
|
|
||||||
|
use crate::handlers::paginated;
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
calibre: &Calibre,
|
||||||
|
cursor: Option<&str>,
|
||||||
|
sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
paginated::render(
|
||||||
|
"authors",
|
||||||
|
|| calibre.authors(25, cursor, sort_order),
|
||||||
|
|author| author.sort.clone(),
|
||||||
|
|cursor| calibre.has_previous_authors(cursor),
|
||||||
|
|cursor| calibre.has_more_authors(cursor),
|
||||||
|
)
|
||||||
|
}
|
23
rusty-library/src/handlers/html/books.rs
Normal file
23
rusty-library/src/handlers/html/books.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use calibre_db::data::pagination::SortOrder;
|
||||||
|
use poem::Response;
|
||||||
|
|
||||||
|
use crate::{app_state::AppState, data::book::Book, handlers::paginated};
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
state: &AppState,
|
||||||
|
cursor: Option<&str>,
|
||||||
|
sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
paginated::render(
|
||||||
|
"books",
|
||||||
|
|| {
|
||||||
|
state
|
||||||
|
.calibre
|
||||||
|
.books(25, cursor, sort_order)
|
||||||
|
.map(|x| x.iter().filter_map(|y| Book::full_book(y, state)).collect())
|
||||||
|
},
|
||||||
|
|book| book.sort.clone(),
|
||||||
|
|cursor| state.calibre.has_previous_books(cursor),
|
||||||
|
|cursor| state.calibre.has_more_books(cursor),
|
||||||
|
)
|
||||||
|
}
|
17
rusty-library/src/handlers/html/recent.rs
Normal file
17
rusty-library/src/handlers/html/recent.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use poem::{error::InternalServerError, web::Html, IntoResponse, Response};
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
|
use crate::{data::book::Book, templates::TEMPLATES};
|
||||||
|
|
||||||
|
pub async fn handler(recent_books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("title", "Recent Books");
|
||||||
|
context.insert("nav", "recent");
|
||||||
|
context.insert("books", &recent_books);
|
||||||
|
|
||||||
|
Ok(TEMPLATES
|
||||||
|
.render("book_list", &context)
|
||||||
|
.map_err(InternalServerError)
|
||||||
|
.map(Html)?
|
||||||
|
.into_response())
|
||||||
|
}
|
18
rusty-library/src/handlers/html/series.rs
Normal file
18
rusty-library/src/handlers/html/series.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use calibre_db::{calibre::Calibre, data::pagination::SortOrder};
|
||||||
|
use poem::Response;
|
||||||
|
|
||||||
|
use crate::handlers::paginated;
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
calibre: &Calibre,
|
||||||
|
cursor: Option<&str>,
|
||||||
|
sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
paginated::render(
|
||||||
|
"series",
|
||||||
|
|| calibre.series(25, cursor, sort_order),
|
||||||
|
|series| series.sort.clone(),
|
||||||
|
|cursor| calibre.has_previous_series(cursor),
|
||||||
|
|cursor| calibre.has_more_series(cursor),
|
||||||
|
)
|
||||||
|
}
|
18
rusty-library/src/handlers/html/series_single.rs
Normal file
18
rusty-library/src/handlers/html/series_single.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use calibre_db::data::series::Series;
|
||||||
|
use poem::{error::InternalServerError, web::Html, IntoResponse, Response};
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
|
use crate::{data::book::Book, templates::TEMPLATES};
|
||||||
|
|
||||||
|
pub async fn handler(series: Series, books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("title", &series.name);
|
||||||
|
context.insert("nav", "series");
|
||||||
|
context.insert("books", &books);
|
||||||
|
|
||||||
|
Ok(TEMPLATES
|
||||||
|
.render("book_list", &context)
|
||||||
|
.map_err(InternalServerError)
|
||||||
|
.map(Html)?
|
||||||
|
.into_response())
|
||||||
|
}
|
@ -1,363 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use calibre_db::data::{author::Author as DbAuthor, pagination::SortOrder};
|
|
||||||
use poem::{
|
|
||||||
handler,
|
|
||||||
web::{Data, Path, WithContentType},
|
|
||||||
IntoResponse,
|
|
||||||
};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app_state::AppState,
|
|
||||||
data::book::Book,
|
|
||||||
handlers::error::HandlerError,
|
|
||||||
opds::{
|
|
||||||
author::Author, content::Content, entry::Entry, feed::Feed, link::Link,
|
|
||||||
media_type::MediaType, relation::Relation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn create_feed(
|
|
||||||
now: OffsetDateTime,
|
|
||||||
id: &str,
|
|
||||||
title: &str,
|
|
||||||
self_link: Link,
|
|
||||||
mut additional_links: Vec<Link>,
|
|
||||||
entries: Vec<Entry>,
|
|
||||||
) -> Feed {
|
|
||||||
let author = Author {
|
|
||||||
name: "Thallian".to_string(),
|
|
||||||
uri: "https://code.vanwa.ch/shu/rusty-library".to_string(),
|
|
||||||
email: None,
|
|
||||||
};
|
|
||||||
let mut links = vec![
|
|
||||||
Link {
|
|
||||||
href: "/opds".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Start,
|
|
||||||
title: Some("Home".to_string()),
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
self_link,
|
|
||||||
];
|
|
||||||
links.append(&mut additional_links);
|
|
||||||
|
|
||||||
Feed {
|
|
||||||
title: title.to_string(),
|
|
||||||
id: id.to_string(),
|
|
||||||
updated: now,
|
|
||||||
icon: "favicon.ico".to_string(),
|
|
||||||
author,
|
|
||||||
links,
|
|
||||||
entries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn recents_handler(
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let books = state
|
|
||||||
.calibre
|
|
||||||
.recent_books(25)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let books = books
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| Book::full_book(x, &state))
|
|
||||||
.collect::<Vec<Book>>();
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: "/opds/recent".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(
|
|
||||||
now,
|
|
||||||
"rusty:recentbooks",
|
|
||||||
"Recent Books",
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn series_single_handler(
|
|
||||||
id: Path<u64>,
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let series = state
|
|
||||||
.calibre
|
|
||||||
.scalar_series(*id)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let books = state
|
|
||||||
.calibre
|
|
||||||
.series_books(*id)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let books = books
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| Book::full_book(x, &state))
|
|
||||||
.collect::<Vec<Book>>();
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: format!("/opds/series/{}", *id),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(
|
|
||||||
now,
|
|
||||||
&format!("rusty:series:{}", *id),
|
|
||||||
&series.name,
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn series_handler(
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let series = state
|
|
||||||
.calibre
|
|
||||||
.series(u32::MAX.into(), None, &SortOrder::ASC)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = series.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: "/opds/series".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(
|
|
||||||
now,
|
|
||||||
"rusty:series",
|
|
||||||
"All Series",
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn author_handler(
|
|
||||||
id: Path<u64>,
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let author = state
|
|
||||||
.calibre
|
|
||||||
.scalar_author(*id)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let books = state
|
|
||||||
.calibre
|
|
||||||
.author_books(*id, u32::MAX.into(), None, SortOrder::ASC)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let books = books
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| Book::full_book(x, &state))
|
|
||||||
.collect::<Vec<Book>>();
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: format!("/opds/authors/{}", author.id),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(
|
|
||||||
now,
|
|
||||||
&format!("rusty:author:{}", author.id),
|
|
||||||
&author.name,
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn authors_handler(
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let authors: Vec<DbAuthor> = state
|
|
||||||
.calibre
|
|
||||||
.authors(u32::MAX.into(), None, &SortOrder::ASC)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = authors.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: "/opds/authors".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(
|
|
||||||
now,
|
|
||||||
"rusty:authors",
|
|
||||||
"All Authors",
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn books_handler(
|
|
||||||
state: Data<&Arc<AppState>>,
|
|
||||||
) -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
let books: Vec<Book> = state
|
|
||||||
.calibre
|
|
||||||
.books(u32::MAX.into(), None, &SortOrder::ASC)
|
|
||||||
.map(|x| {
|
|
||||||
x.iter()
|
|
||||||
.filter_map(|y| Book::full_book(y, &state))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
|
|
||||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let self_link = Link {
|
|
||||||
href: "/opds/books".to_string(),
|
|
||||||
media_type: MediaType::Navigation,
|
|
||||||
rel: Relation::Myself,
|
|
||||||
title: None,
|
|
||||||
count: None,
|
|
||||||
};
|
|
||||||
let feed = create_feed(now, "rusty:books", "All Books", self_link, vec![], entries);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn handler() -> Result<WithContentType<String>, poem::Error> {
|
|
||||||
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: "rusty:books".to_string(),
|
|
||||||
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: "rusty:authors".to_string(),
|
|
||||||
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: "rusty:series".to_string(),
|
|
||||||
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: "rusty:recentbooks".to_string(),
|
|
||||||
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 = create_feed(
|
|
||||||
now,
|
|
||||||
"rusty:catalog",
|
|
||||||
"Rusty-Library",
|
|
||||||
self_link,
|
|
||||||
vec![],
|
|
||||||
vec![authors_entry, series_entry, books_entry, recents_entry],
|
|
||||||
);
|
|
||||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
|
||||||
|
|
||||||
Ok(xml.with_content_type("application/atom+xml"))
|
|
||||||
}
|
|
35
rusty-library/src/handlers/opds/author.rs
Normal file
35
rusty-library/src/handlers/opds/author.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use calibre_db::data::author::Author;
|
||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::book::Book,
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(author: Author, books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: format!("/opds/authors/{}", author.id),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(
|
||||||
|
now,
|
||||||
|
&format!("rusty:author:{}", author.id),
|
||||||
|
&author.name,
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
45
rusty-library/src/handlers/opds/authors.rs
Normal file
45
rusty-library/src/handlers/opds/authors.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use calibre_db::{
|
||||||
|
calibre::Calibre,
|
||||||
|
data::{author::Author as DbAuthor, pagination::SortOrder},
|
||||||
|
};
|
||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
calibre: &Calibre,
|
||||||
|
_cursor: Option<&str>,
|
||||||
|
_sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
let authors: Vec<DbAuthor> = calibre
|
||||||
|
.authors(u32::MAX.into(), None, &SortOrder::ASC)
|
||||||
|
.map_err(HandlerError::DataError)?;
|
||||||
|
|
||||||
|
let entries: Vec<Entry> = authors.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: "/opds/authors".to_string(),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(
|
||||||
|
now,
|
||||||
|
"rusty:authors",
|
||||||
|
"All Authors",
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
39
rusty-library/src/handlers/opds/books.rs
Normal file
39
rusty-library/src/handlers/opds/books.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use calibre_db::data::pagination::SortOrder;
|
||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app_state::AppState,
|
||||||
|
data::book::Book,
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
state: &AppState,
|
||||||
|
_cursor: Option<&str>,
|
||||||
|
_sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
let books: Vec<Book> = state
|
||||||
|
.calibre
|
||||||
|
.books(u32::MAX.into(), None, &SortOrder::ASC)
|
||||||
|
.map(|x| x.iter().filter_map(|y| Book::full_book(y, state)).collect())
|
||||||
|
.map_err(HandlerError::DataError)?;
|
||||||
|
|
||||||
|
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: "/opds/books".to_string(),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(now, "rusty:books", "All Books", self_link, vec![], entries);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
106
rusty-library/src/handlers/opds/feed.rs
Normal file
106
rusty-library/src/handlers/opds/feed.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use poem::{handler, web::WithContentType, IntoResponse};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{
|
||||||
|
content::Content, entry::Entry, feed::Feed, link::Link, media_type::MediaType,
|
||||||
|
relation::Relation,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[handler]
|
||||||
|
pub async fn handler() -> Result<WithContentType<String>, poem::Error> {
|
||||||
|
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: "rusty:books".to_string(),
|
||||||
|
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: "rusty:authors".to_string(),
|
||||||
|
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: "rusty:series".to_string(),
|
||||||
|
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: "rusty:recentbooks".to_string(),
|
||||||
|
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,
|
||||||
|
"rusty:catalog",
|
||||||
|
"Rusty-Library",
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
vec![authors_entry, series_entry, books_entry, recents_entry],
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml.with_content_type("application/atom+xml"))
|
||||||
|
}
|
34
rusty-library/src/handlers/opds/recent.rs
Normal file
34
rusty-library/src/handlers/opds/recent.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::book::Book,
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(recent_books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let entries: Vec<Entry> = recent_books.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: "/opds/recent".to_string(),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(
|
||||||
|
now,
|
||||||
|
"rusty:recentbooks",
|
||||||
|
"Recent Books",
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
42
rusty-library/src/handlers/opds/series.rs
Normal file
42
rusty-library/src/handlers/opds/series.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use calibre_db::{calibre::Calibre, data::pagination::SortOrder};
|
||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
calibre: &Calibre,
|
||||||
|
_cursor: Option<&str>,
|
||||||
|
_sort_order: &SortOrder,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
let series = calibre
|
||||||
|
.series(u32::MAX.into(), None, &SortOrder::ASC)
|
||||||
|
.map_err(HandlerError::DataError)?;
|
||||||
|
|
||||||
|
let entries: Vec<Entry> = series.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: "/opds/series".to_string(),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(
|
||||||
|
now,
|
||||||
|
"rusty:series",
|
||||||
|
"All Series",
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
35
rusty-library/src/handlers/opds/series_single.rs
Normal file
35
rusty-library/src/handlers/opds/series_single.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use calibre_db::data::series::Series;
|
||||||
|
use poem::{IntoResponse, Response};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::book::Book,
|
||||||
|
handlers::error::HandlerError,
|
||||||
|
opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handler(series: Series, books: Vec<Book>) -> Result<Response, poem::Error> {
|
||||||
|
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let self_link = Link {
|
||||||
|
href: format!("/opds/series/{}", series.id),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Myself,
|
||||||
|
title: None,
|
||||||
|
count: None,
|
||||||
|
};
|
||||||
|
let feed = Feed::create(
|
||||||
|
now,
|
||||||
|
&format!("rusty:series:{}", series.id),
|
||||||
|
&series.name,
|
||||||
|
self_link,
|
||||||
|
vec![],
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||||
|
|
||||||
|
Ok(xml
|
||||||
|
.with_content_type("application/atom+xml")
|
||||||
|
.into_response())
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use calibre_db::data::error::DataStoreError;
|
use calibre_db::data::error::DataStoreError;
|
||||||
use poem::{error::InternalServerError, web::Html};
|
use poem::{error::InternalServerError, web::Html, IntoResponse, Response};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ pub fn render<T: Serialize + Debug, F, S, P, M>(
|
|||||||
sort_field: S,
|
sort_field: S,
|
||||||
has_previous: P,
|
has_previous: P,
|
||||||
has_more: M,
|
has_more: M,
|
||||||
) -> Result<Html<String>, poem::Error>
|
) -> Result<Response, poem::Error>
|
||||||
where
|
where
|
||||||
F: Fn() -> Result<Vec<T>, DataStoreError>,
|
F: Fn() -> Result<Vec<T>, DataStoreError>,
|
||||||
S: Fn(&T) -> String,
|
S: Fn(&T) -> String,
|
||||||
@ -42,8 +42,9 @@ where
|
|||||||
context.insert("nav", template);
|
context.insert("nav", template);
|
||||||
context.insert(template, &items);
|
context.insert(template, &items);
|
||||||
|
|
||||||
TEMPLATES
|
Ok(TEMPLATES
|
||||||
.render(template, &context)
|
.render(template, &context)
|
||||||
.map_err(InternalServerError)
|
.map_err(InternalServerError)
|
||||||
.map(Html)
|
.map(Html)?
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
25
rusty-library/src/handlers/recent.rs
Normal file
25
rusty-library/src/handlers/recent.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use poem::{handler, web::Data, Response};
|
||||||
|
|
||||||
|
use crate::{app_state::AppState, data::book::Book, handlers::error::HandlerError, Accept};
|
||||||
|
|
||||||
|
#[handler]
|
||||||
|
pub async fn handler(
|
||||||
|
accept: Data<&Accept>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
let recent_books = state
|
||||||
|
.calibre
|
||||||
|
.recent_books(25)
|
||||||
|
.map_err(HandlerError::DataError)?;
|
||||||
|
let recent_books = recent_books
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| Book::full_book(x, &state))
|
||||||
|
.collect::<Vec<Book>>();
|
||||||
|
|
||||||
|
match accept.0 {
|
||||||
|
Accept::Html => crate::handlers::html::recent::handler(recent_books).await,
|
||||||
|
Accept::Opds => crate::handlers::opds::recent::handler(recent_books).await,
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use poem::{
|
|
||||||
error::InternalServerError,
|
|
||||||
handler,
|
|
||||||
web::{Data, Html},
|
|
||||||
};
|
|
||||||
use tera::Context;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[handler]
|
|
||||||
pub async fn handler(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
|
|
||||||
let recent_books = state
|
|
||||||
.calibre
|
|
||||||
.recent_books(25)
|
|
||||||
.map_err(HandlerError::DataError)?;
|
|
||||||
let recent_books = recent_books
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| Book::full_book(x, &state))
|
|
||||||
.collect::<Vec<Book>>();
|
|
||||||
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.insert("title", "Recent Books");
|
|
||||||
context.insert("nav", "recent");
|
|
||||||
context.insert("books", &recent_books);
|
|
||||||
TEMPLATES
|
|
||||||
.render("book_list", &context)
|
|
||||||
.map_err(InternalServerError)
|
|
||||||
.map(Html)
|
|
||||||
}
|
|
@ -3,36 +3,41 @@ use std::sync::Arc;
|
|||||||
use calibre_db::data::pagination::SortOrder;
|
use calibre_db::data::pagination::SortOrder;
|
||||||
use poem::{
|
use poem::{
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Html, Path},
|
web::{Data, Path},
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::{app_state::AppState, Accept};
|
||||||
|
|
||||||
use super::paginated;
|
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler_init(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
|
pub async fn handler_init(
|
||||||
series(&state, None, &SortOrder::ASC)
|
accept: Data<&Accept>,
|
||||||
|
state: Data<&Arc<AppState>>,
|
||||||
|
) -> Result<Response, poem::Error> {
|
||||||
|
series(&accept, &state, None, &SortOrder::ASC).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
Path((cursor, sort_order)): Path<(String, SortOrder)>,
|
||||||
|
accept: Data<&Accept>,
|
||||||
state: Data<&Arc<AppState>>,
|
state: Data<&Arc<AppState>>,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
series(&state, Some(&cursor), &sort_order)
|
series(&accept, &state, Some(&cursor), &sort_order).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn series(
|
async fn series(
|
||||||
|
accept: &Accept,
|
||||||
state: &Arc<AppState>,
|
state: &Arc<AppState>,
|
||||||
cursor: Option<&str>,
|
cursor: Option<&str>,
|
||||||
sort_order: &SortOrder,
|
sort_order: &SortOrder,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
paginated::render(
|
match accept {
|
||||||
"series",
|
Accept::Html => {
|
||||||
|| state.calibre.series(25, cursor, sort_order),
|
crate::handlers::html::series::handler(&state.calibre, cursor, sort_order).await
|
||||||
|series| series.sort.clone(),
|
}
|
||||||
|cursor| state.calibre.has_previous_series(cursor),
|
Accept::Opds => {
|
||||||
|cursor| state.calibre.has_more_series(cursor),
|
crate::handlers::opds::series::handler(&state.calibre, cursor, sort_order).await
|
||||||
)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use poem::{
|
use poem::{
|
||||||
error::InternalServerError,
|
|
||||||
handler,
|
handler,
|
||||||
web::{Data, Html, Path},
|
web::{Data, Path},
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
use tera::Context;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{app_state::AppState, data::book::Book, handlers::error::HandlerError, Accept};
|
||||||
app_state::AppState, data::book::Book, handlers::error::HandlerError, templates::TEMPLATES,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
id: Path<u64>,
|
id: Path<u64>,
|
||||||
|
accept: Data<&Accept>,
|
||||||
state: Data<&Arc<AppState>>,
|
state: Data<&Arc<AppState>>,
|
||||||
) -> Result<Html<String>, poem::Error> {
|
) -> Result<Response, poem::Error> {
|
||||||
let series = state
|
let series = state
|
||||||
.calibre
|
.calibre
|
||||||
.scalar_series(*id)
|
.scalar_series(*id)
|
||||||
@ -29,13 +27,8 @@ pub async fn handler(
|
|||||||
.filter_map(|x| Book::full_book(x, &state))
|
.filter_map(|x| Book::full_book(x, &state))
|
||||||
.collect::<Vec<Book>>();
|
.collect::<Vec<Book>>();
|
||||||
|
|
||||||
let mut context = Context::new();
|
match accept.0 {
|
||||||
context.insert("title", &series.name);
|
Accept::Html => crate::handlers::html::series_single::handler(series, books).await,
|
||||||
context.insert("nav", "series");
|
Accept::Opds => crate::handlers::opds::series_single::handler(series, books).await,
|
||||||
context.insert("books", &books);
|
}
|
||||||
|
|
||||||
TEMPLATES
|
|
||||||
.render("book_list", &context)
|
|
||||||
.map_err(InternalServerError)
|
|
||||||
.map(Html)
|
|
||||||
}
|
}
|
||||||
|
@ -17,21 +17,43 @@ pub mod data {
|
|||||||
pub mod book;
|
pub mod book;
|
||||||
}
|
}
|
||||||
pub mod handlers {
|
pub mod handlers {
|
||||||
|
pub mod html {
|
||||||
|
pub mod author;
|
||||||
|
pub mod authors;
|
||||||
|
pub mod books;
|
||||||
|
pub mod recent;
|
||||||
|
pub mod series;
|
||||||
|
pub mod series_single;
|
||||||
|
}
|
||||||
|
pub mod opds {
|
||||||
|
pub mod author;
|
||||||
|
pub mod authors;
|
||||||
|
pub mod books;
|
||||||
|
pub mod feed;
|
||||||
|
pub mod recent;
|
||||||
|
pub mod series;
|
||||||
|
pub mod series_single;
|
||||||
|
}
|
||||||
pub mod author;
|
pub mod author;
|
||||||
pub mod authors;
|
pub mod authors;
|
||||||
pub mod books;
|
pub mod books;
|
||||||
pub mod cover;
|
pub mod cover;
|
||||||
pub mod download;
|
pub mod download;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod opds;
|
|
||||||
pub mod paginated;
|
pub mod paginated;
|
||||||
pub mod recents;
|
pub mod recent;
|
||||||
pub mod series;
|
pub mod series;
|
||||||
pub mod series_single;
|
pub mod series_single;
|
||||||
}
|
}
|
||||||
pub mod opds;
|
pub mod opds;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Accept {
|
||||||
|
Html,
|
||||||
|
Opds,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "static"]
|
#[folder = "static"]
|
||||||
pub struct Files;
|
pub struct Files;
|
||||||
@ -40,18 +62,8 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> {
|
|||||||
let calibre = Calibre::load(&config.metadata_path).expect("failed to load calibre database");
|
let calibre = Calibre::load(&config.metadata_path).expect("failed to load calibre database");
|
||||||
let app_state = Arc::new(AppState { calibre, config });
|
let app_state = Arc::new(AppState { calibre, config });
|
||||||
|
|
||||||
let app = Route::new()
|
let html_routes = Route::new()
|
||||||
.at("/", get(handlers::recents::handler))
|
.at("/", get(handlers::recent::handler))
|
||||||
.at("/opds", get(handlers::opds::handler))
|
|
||||||
.at("/opds/recent", get(handlers::opds::recents_handler))
|
|
||||||
.at("/opds/books", get(handlers::opds::books_handler))
|
|
||||||
.at("/opds/authors", get(handlers::opds::authors_handler))
|
|
||||||
.at("/opds/authors/:id", get(handlers::opds::author_handler))
|
|
||||||
.at("/opds/series", get(handlers::opds::series_handler))
|
|
||||||
.at(
|
|
||||||
"/opds/series/:id",
|
|
||||||
get(handlers::opds::series_single_handler),
|
|
||||||
)
|
|
||||||
.at("/books", get(handlers::books::handler_init))
|
.at("/books", get(handlers::books::handler_init))
|
||||||
.at("/books/:cursor/:sort_order", get(handlers::books::handler))
|
.at("/books/:cursor/:sort_order", get(handlers::books::handler))
|
||||||
.at("/series", get(handlers::series::handler_init))
|
.at("/series", get(handlers::series::handler_init))
|
||||||
@ -69,6 +81,21 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> {
|
|||||||
.at("/cover/:id", get(handlers::cover::handler))
|
.at("/cover/:id", get(handlers::cover::handler))
|
||||||
.at("/book/:id/:format", get(handlers::download::handler))
|
.at("/book/:id/:format", get(handlers::download::handler))
|
||||||
.nest("/static", EmbeddedFilesEndpoint::<Files>::new())
|
.nest("/static", EmbeddedFilesEndpoint::<Files>::new())
|
||||||
|
.data(Accept::Html);
|
||||||
|
|
||||||
|
let opds_routes = Route::new()
|
||||||
|
.at("/", get(handlers::opds::feed::handler))
|
||||||
|
.at("/recent", get(handlers::recent::handler))
|
||||||
|
.at("/books", get(handlers::books::handler_init))
|
||||||
|
.at("/authors", get(handlers::authors::handler_init))
|
||||||
|
.at("/authors/:id", get(handlers::author::handler))
|
||||||
|
.at("/series", get(handlers::series::handler_init))
|
||||||
|
.at("/series/:id", get(handlers::series_single::handler))
|
||||||
|
.data(Accept::Opds);
|
||||||
|
|
||||||
|
let app = Route::new()
|
||||||
|
.nest("/", html_routes)
|
||||||
|
.nest("/opds", opds_routes)
|
||||||
.data(app_state)
|
.data(app_state)
|
||||||
.with(Tracing);
|
.with(Tracing);
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub struct Entry {
|
|||||||
pub updated: OffsetDateTime,
|
pub updated: OffsetDateTime,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub content: Option<Content>,
|
pub content: Option<Content>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub author: Option<Author>,
|
pub author: Option<Author>,
|
||||||
#[serde(rename = "link")]
|
#[serde(rename = "link")]
|
||||||
pub links: Vec<Link>,
|
pub links: Vec<Link>,
|
||||||
|
@ -8,7 +8,10 @@ use quick_xml::{
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{author::Author, entry::Entry, error::OpdsError, link::Link};
|
use super::{
|
||||||
|
author::Author, entry::Entry, error::OpdsError, link::Link, media_type::MediaType,
|
||||||
|
relation::Relation,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename = "feed")]
|
#[serde(rename = "feed")]
|
||||||
@ -26,6 +29,42 @@ pub struct Feed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Feed {
|
impl Feed {
|
||||||
|
pub fn create(
|
||||||
|
now: OffsetDateTime,
|
||||||
|
id: &str,
|
||||||
|
title: &str,
|
||||||
|
self_link: Link,
|
||||||
|
mut additional_links: Vec<Link>,
|
||||||
|
entries: Vec<Entry>,
|
||||||
|
) -> Self {
|
||||||
|
let author = Author {
|
||||||
|
name: "Thallian".to_string(),
|
||||||
|
uri: "https://code.vanwa.ch/shu/rusty-library".to_string(),
|
||||||
|
email: None,
|
||||||
|
};
|
||||||
|
let mut links = vec![
|
||||||
|
Link {
|
||||||
|
href: "/opds".to_string(),
|
||||||
|
media_type: MediaType::Navigation,
|
||||||
|
rel: Relation::Start,
|
||||||
|
title: Some("Home".to_string()),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
self_link,
|
||||||
|
];
|
||||||
|
links.append(&mut additional_links);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
title: title.to_string(),
|
||||||
|
id: id.to_string(),
|
||||||
|
updated: now,
|
||||||
|
icon: "favicon.ico".to_string(),
|
||||||
|
author,
|
||||||
|
links,
|
||||||
|
entries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_xml(&self) -> Result<String, OpdsError> {
|
pub fn as_xml(&self) -> Result<String, OpdsError> {
|
||||||
let xml = to_string(&self)?;
|
let xml = to_string(&self)?;
|
||||||
let mut reader = Reader::from_str(&xml);
|
let mut reader = Reader::from_str(&xml);
|
||||||
|
Loading…
Reference in New Issue
Block a user