Merge pull request 'Add paginate' (#1) from add-paginate into main
Reviewed-on: #1
This commit is contained in:
commit
57af399ace
@ -7,7 +7,7 @@ use snafu::ResultExt;
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::routes::book::BookForm;
|
||||
use crate::routes::book::BookQuery;
|
||||
use crate::routes::book::IndexQuery;
|
||||
use crate::state::AppState;
|
||||
use crate::state::error::BookSnafu;
|
||||
|
||||
@ -54,6 +54,13 @@ pub struct BookOperator {
|
||||
pub state: AppState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BooksPaginate {
|
||||
pub books: Vec<Model>,
|
||||
pub current_page: u64,
|
||||
pub total_page: u64,
|
||||
}
|
||||
|
||||
impl BookOperator {
|
||||
/// Creates a new `BookOperator` with the given application state.
|
||||
pub fn new(state: AppState) -> Self {
|
||||
@ -63,7 +70,22 @@ impl BookOperator {
|
||||
/// Lists all books matching the optional query filters.
|
||||
///
|
||||
/// Results are ordered by ID in descending order (newest first).
|
||||
pub async fn list(&self, query: Option<BookQuery>) -> Result<Vec<Model>, BookError> {
|
||||
pub async fn list(&self) -> Result<Vec<Model>, BookError> {
|
||||
Entity::find()
|
||||
.order_by_desc(Column::Id)
|
||||
.all(&self.state.db)
|
||||
.await
|
||||
.context(DBSnafu)
|
||||
}
|
||||
|
||||
pub async fn list_paginate(
|
||||
&self,
|
||||
page: u64,
|
||||
query: Option<IndexQuery>,
|
||||
) -> Result<BooksPaginate, BookError> {
|
||||
let page = if page > 0 { page } else { 1 }; // keep 1-indexed
|
||||
let page_0indexed = page - 1; // convert for SeaORM (0-based index)
|
||||
|
||||
let mut conditions = Condition::all();
|
||||
if let Some(book_query) = query {
|
||||
if let Some(title) = book_query.title {
|
||||
@ -83,12 +105,22 @@ impl BookOperator {
|
||||
}
|
||||
}
|
||||
|
||||
Entity::find()
|
||||
let book_pages = Entity::find()
|
||||
.filter(conditions)
|
||||
.order_by_desc(Column::Id)
|
||||
.all(&self.state.db)
|
||||
.paginate(&self.state.db, 1);
|
||||
|
||||
let books = book_pages
|
||||
.fetch_page(page_0indexed)
|
||||
.await
|
||||
.context(DBSnafu)
|
||||
.context(DBSnafu)?;
|
||||
let total_page = book_pages.num_pages().await.context(DBSnafu)?;
|
||||
|
||||
Ok(BooksPaginate {
|
||||
books,
|
||||
current_page: page,
|
||||
total_page,
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds a book by its ID.
|
||||
|
||||
@ -31,12 +31,15 @@ struct BookWithUser {
|
||||
|
||||
/// Query for filter search query
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct BookQuery {
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct IndexQuery {
|
||||
pub title: Option<String>,
|
||||
pub page: Option<usize>,
|
||||
pub authors: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub owner_id: Option<i32>,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub current_holder_id: Option<i32>,
|
||||
}
|
||||
@ -45,22 +48,31 @@ pub struct BookQuery {
|
||||
#[template(path = "index.html")]
|
||||
struct BookIndexTemplate {
|
||||
books_with_user: Vec<BookWithUser>,
|
||||
query: BookQuery,
|
||||
query: IndexQuery,
|
||||
users: Vec<UserModel>,
|
||||
current_page: u64,
|
||||
total_page: u64,
|
||||
base_query: String,
|
||||
}
|
||||
|
||||
pub async fn index(
|
||||
State(state): State<AppState>,
|
||||
Query(query): Query<BookQuery>,
|
||||
Query(query): Query<IndexQuery>,
|
||||
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
||||
let page: u64 = query
|
||||
.page
|
||||
.map(|p| p.max(1) as u64) // Minimum 1
|
||||
.unwrap_or(1);
|
||||
|
||||
// Get all Users
|
||||
let users = UserOperator::new(state.clone())
|
||||
.list()
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
|
||||
// Get all Book filtered with query
|
||||
let books = BookOperator::new(state)
|
||||
.list(Some(query.clone()))
|
||||
let books_paginate = BookOperator::new(state)
|
||||
.list_paginate(page, Some(query.clone()))
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
@ -73,7 +85,8 @@ pub async fn index(
|
||||
.collect();
|
||||
|
||||
// Build object of Book with his relation Owner (User) and current_holder (User)
|
||||
let result: Vec<BookWithUser> = books
|
||||
let result: Vec<BookWithUser> = books_paginate
|
||||
.books
|
||||
.into_iter()
|
||||
.filter_map(|book| {
|
||||
let owner = user_by_id.get(&book.owner_id).cloned()?;
|
||||
@ -89,10 +102,29 @@ pub async fn index(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// build original search to be sure to keep
|
||||
// search when we change page
|
||||
let mut base_query = String::new();
|
||||
if let Some(title) = &query.title {
|
||||
base_query.push_str(&format!("title={}&", title));
|
||||
}
|
||||
if let Some(authors) = &query.authors {
|
||||
base_query.push_str(&format!("authors={}&", authors));
|
||||
}
|
||||
if let Some(owner_id) = &query.owner_id {
|
||||
base_query.push_str(&format!("owner_id={}&", owner_id));
|
||||
}
|
||||
if let Some(current_holder_id) = &query.current_holder_id {
|
||||
base_query.push_str(&format!("current_holder_id={}&", current_holder_id));
|
||||
}
|
||||
|
||||
Ok(BookIndexTemplate {
|
||||
books_with_user: result,
|
||||
query,
|
||||
users,
|
||||
current_page: books_paginate.current_page,
|
||||
total_page: books_paginate.total_page,
|
||||
base_query,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ pub async fn index(
|
||||
.context(UserSnafu)?;
|
||||
|
||||
let books = BookOperator::new(state.clone())
|
||||
.list(None)
|
||||
.list()
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
{% block main %}
|
||||
{{ typography::heading("All books") }}
|
||||
|
||||
|
||||
{% call cards::card() %}
|
||||
<form method="get">
|
||||
<div class="row">
|
||||
@ -116,5 +115,27 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if total_page > 1 %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item {% if current_page <= 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={% if current_page > 1 %}{{ current_page - 1 }}{% else %}1{% endif %}">Prev</a>
|
||||
</li>
|
||||
|
||||
{% for page in 1..(total_page + 1) %}
|
||||
{% if page >= current_page - 1 && page <= current_page + 1 %}
|
||||
<li class="page-item {% if page == current_page %}active{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={{ page }}">{{ page }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if current_page == total_page %}disabled{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={{ current_page + 1 }}">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user