paginate books

This commit is contained in:
Sebastian Hugentobler 2024-05-06 14:42:14 +02:00
parent 6d949bb21e
commit 6a79f0c1ed
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
10 changed files with 99 additions and 13 deletions

View File

@ -24,7 +24,7 @@ impl Calibre {
&self, &self,
limit: u64, limit: u64,
cursor: Option<&str>, cursor: Option<&str>,
sort_order: SortOrder, sort_order: &SortOrder,
) -> Result<Vec<Book>, DataStoreError> { ) -> Result<Vec<Book>, DataStoreError> {
let conn = self.pool.get()?; let conn = self.pool.get()?;
Book::multiple(&conn, limit, cursor, sort_order) Book::multiple(&conn, limit, cursor, sort_order)
@ -91,6 +91,16 @@ impl Calibre {
Author::has_more_authors(&conn, author_sort) Author::has_more_authors(&conn, author_sort)
} }
pub fn has_previous_books(&self, book_sort: &str) -> Result<bool, DataStoreError> {
let conn = self.pool.get()?;
Book::has_previous_books(&conn, book_sort)
}
pub fn has_more_books(&self, book_sort: &str) -> Result<bool, DataStoreError> {
let conn = self.pool.get()?;
Book::has_more_books(&conn, book_sort)
}
pub fn scalar_author(&self, id: u64) -> Result<Author, DataStoreError> { pub fn scalar_author(&self, id: u64) -> Result<Author, DataStoreError> {
let conn = self.pool.get()?; let conn = self.pool.get()?;
Author::scalar_author(&conn, id) Author::scalar_author(&conn, id)
@ -108,7 +118,7 @@ mod tests {
#[test] #[test]
fn books() { fn books() {
let c = init_calibre(); let c = init_calibre();
let books = c.books(10, None, SortOrder::ASC).unwrap(); let books = c.books(10, None, &SortOrder::ASC).unwrap();
assert_eq!(books.len(), 4); assert_eq!(books.len(), 4);
} }

View File

@ -28,9 +28,9 @@ impl Book {
conn: &Connection, conn: &Connection,
limit: u64, limit: u64,
cursor: Option<&str>, cursor: Option<&str>,
sort_order: SortOrder, sort_order: &SortOrder,
) -> Result<Vec<Self>, DataStoreError> { ) -> Result<Vec<Self>, DataStoreError> {
let pagination = Pagination::new("sort", cursor, limit, sort_order); let pagination = Pagination::new("sort", cursor, limit, *sort_order);
pagination.paginate( pagination.paginate(
conn, conn,
"SELECT id, title, sort, path FROM books", "SELECT id, title, sort, path FROM books",
@ -71,4 +71,22 @@ impl Book {
let params = named_params! { ":id": id }; let params = named_params! { ":id": id };
Ok(stmt.query_row(params, Self::from_row)?) Ok(stmt.query_row(params, Self::from_row)?)
} }
pub fn has_previous_books(conn: &Connection, sort_title: &str) -> Result<bool, DataStoreError> {
let mut stmt = conn
.prepare("SELECT Count(1) FROM books WHERE sort < (:sort_title) ORDER BY sort DESC")?;
let params = named_params! { ":sort_title": sort_title};
let count: u64 = stmt.query_row(params, |x| x.get(0))?;
Ok(count > 0)
}
pub fn has_more_books(conn: &Connection, sort_title: &str) -> Result<bool, DataStoreError> {
let mut stmt = conn
.prepare("SELECT Count(1) FROM books WHERE sort > (:sort_title) ORDER BY sort ASC")?;
let params = named_params! { ":sort_title": sort_title};
let count: u64 = stmt.query_row(params, |x| x.get(0))?;
Ok(count > 0)
}
} }

View File

@ -33,7 +33,7 @@ pub async fn handler(
context.insert("books", &books); context.insert("books", &books);
TEMPLATES TEMPLATES
.render("books", &context) .render("book_list", &context)
.map_err(InternalServerError) .map_err(InternalServerError)
.map(Html) .map(Html)
} }

View File

@ -1,10 +1,44 @@
use std::sync::Arc; use std::sync::Arc;
use poem::{handler, web::Data}; use calibre_db::data::pagination::SortOrder;
use poem::{
handler,
web::{Data, Html, Path},
};
use crate::app_state::AppState; use crate::{app_state::AppState, data::book::Book};
use super::paginated;
#[handler] #[handler]
pub async fn handler(state: Data<&Arc<AppState>>) -> Result<String, poem::Error> { pub async fn handler_init(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::Error> {
Ok("books".to_string()) books(&state, None, &SortOrder::ASC)
}
#[handler]
pub async fn handler(
Path((cursor, sort_order)): Path<(String, SortOrder)>,
state: Data<&Arc<AppState>>,
) -> Result<Html<String>, poem::Error> {
books(&state, Some(&cursor), &sort_order)
}
fn books(
state: &Arc<AppState>,
cursor: Option<&str>,
sort_order: &SortOrder,
) -> Result<Html<String>, 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),
)
} }

View File

@ -1,3 +1,5 @@
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};
use serde::Serialize; use serde::Serialize;
@ -7,7 +9,7 @@ use crate::templates::TEMPLATES;
use super::error::SqliteError; use super::error::SqliteError;
pub fn render<T: Serialize, F, S, P, M>( pub fn render<T: Serialize + Debug, F, S, P, M>(
template: &str, template: &str,
fetcher: F, fetcher: F,
sort_field: S, sort_field: S,
@ -39,6 +41,7 @@ where
context.insert("forward_cursor", &forward_cursor); context.insert("forward_cursor", &forward_cursor);
context.insert("nav", template); context.insert("nav", template);
context.insert(template, &items); context.insert(template, &items);
TEMPLATES TEMPLATES
.render(template, &context) .render(template, &context)
.map_err(InternalServerError) .map_err(InternalServerError)

View File

@ -24,7 +24,7 @@ pub async fn handler(state: Data<&Arc<AppState>>) -> Result<Html<String>, poem::
context.insert("nav", "recent"); context.insert("nav", "recent");
context.insert("books", &recent_books); context.insert("books", &recent_books);
TEMPLATES TEMPLATES
.render("books", &context) .render("book_list", &context)
.map_err(InternalServerError) .map_err(InternalServerError)
.map(Html) .map(Html)
} }

View File

@ -49,7 +49,8 @@ async fn main() -> Result<(), std::io::Error> {
let app = Route::new() let app = Route::new()
.at("/", get(handlers::recents::handler)) .at("/", get(handlers::recents::handler))
.at("/books", get(handlers::books::handler)) .at("/books", get(handlers::books::handler_init))
.at("/books/:cursor/:sort_order", get(handlers::books::handler))
.at("/authors", get(handlers::authors::handler_init)) .at("/authors", get(handlers::authors::handler_init))
.at("/authors/:id", get(handlers::author::handler)) .at("/authors/:id", get(handlers::author::handler))
.at( .at(

View File

@ -7,6 +7,7 @@ pub static TEMPLATES: Lazy<Tera> = Lazy::new(|| {
("base", include_str!("../templates/base.html")), ("base", include_str!("../templates/base.html")),
("book_card", include_str!("../templates/book_card.html")), ("book_card", include_str!("../templates/book_card.html")),
("authors", include_str!("../templates/authors.html")), ("authors", include_str!("../templates/authors.html")),
("book_list", include_str!("../templates/book_list.html")),
("books", include_str!("../templates/books.html")), ("books", include_str!("../templates/books.html")),
]) ])
.expect("failed to parse tera templates"); .expect("failed to parse tera templates");

View File

@ -0,0 +1,8 @@
{% extends "base" %}
{% block content %}
<div class="grid-container">
{% for book in books %}
{% include "book_card" %}
{% endfor %}
</div>
{% endblock content %}

View File

@ -1,8 +1,19 @@
{% extends "base" %} {% extends "base" %}
{% block title %}
{% if has_previous %}
<a class="secondary" href="/books/{{ backward_cursor }}/DESC">← back</a>
{% endif %}
{% if has_previous and has_more %}|{% endif%}
{% if has_more %}
<a class="secondary" href="/books/{{ forward_cursor }}/ASC">more →</a>
{% endif %}
{% endblock title %}
{% block content %} {% block content %}
<div class="grid-container"> <div class="grid-container">
{% for book in books %} {% for book in books %}
{% include "book_card" %} {% include "book_card" %}
{% endfor %} {% endfor %}
</div> </div>
{% endblock content %} {% endblock content %}