A little app to serve my photography from my personal website

better pagination

+94 -45
+1
Cargo.lock
··· 1070 1070 "dotenv", 1071 1071 "minijinja", 1072 1072 "serde", 1073 + "serde_urlencoded", 1073 1074 "sqlx", 1074 1075 "tokio", 1075 1076 "tower-http",
+1
Cargo.toml
··· 10 10 dotenv = "0.15.0" 11 11 minijinja = { version = "2.12.0", features = ["loader"] } 12 12 serde = "1.0.228" 13 + serde_urlencoded = "0.7.1" 13 14 sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] } 14 15 tokio = { version = "1.47.1", features = ["rt-multi-thread"] } 15 16 tower-http = { version = "0.6.6", features = ["fs"] }
+25 -7
src/db.rs
··· 1 + use std::convert; 2 + 1 3 use crate::{Photo, Tag}; 2 4 use serde::{Deserialize, Serialize}; 3 5 use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs}; 4 6 5 - #[derive(Deserialize, Serialize, Clone, Copy)] 7 + #[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] 6 8 #[serde(rename_all = "snake_case")] 7 9 pub enum SortDirection { 8 10 Asc, 9 11 Desc, 10 12 } 11 13 14 + impl convert::From<SortDirection> for String { 15 + fn from(value: SortDirection) -> String { 16 + match value { 17 + SortDirection::Asc => "asc", 18 + SortDirection::Desc => "desc", 19 + } 20 + .to_string() 21 + } 22 + } 23 + 12 24 impl SortDirection { 13 25 pub fn to_sql(self) -> String { 14 26 match self { ··· 19 31 } 20 32 } 21 33 22 - #[derive(Deserialize, Serialize, Clone, Copy)] 34 + #[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] 23 35 #[serde(rename_all = "snake_case")] 24 36 pub enum SortField { 25 37 TakenAt, 26 38 CreatedAt, 27 39 } 28 40 29 - impl SortField { 30 - pub fn to_sql(self) -> String { 31 - match self { 32 - Self::TakenAt => "taken_at", 33 - Self::CreatedAt => "created_at", 41 + impl convert::From<SortField> for String { 42 + fn from(value: SortField) -> String { 43 + match value { 44 + SortField::TakenAt => "taken_at", 45 + SortField::CreatedAt => "created_at", 34 46 } 35 47 .to_string() 48 + } 49 + } 50 + 51 + impl SortField { 52 + pub fn to_sql(self) -> String { 53 + self.into() 36 54 } 37 55 } 38 56
+62 -25
src/routes/photos.rs
··· 16 16 pub struct Pagination { 17 17 page: u32, 18 18 num_pages: u32, 19 + prev_query: Option<String>, 20 + next_query: Option<String>, 19 21 } 20 22 21 - #[derive(Deserialize)] 22 - #[serde(default)] 23 + #[derive(Deserialize, Serialize, Clone)] 23 24 pub struct IndexParams { 24 - page: u32, 25 - limit: u32, 25 + page: Option<u32>, 26 + limit: Option<u32>, 26 27 tag: Option<String>, 27 - sort: SortField, 28 - dir: SortDirection, 28 + sort: Option<SortField>, 29 + dir: Option<SortDirection>, 29 30 } 30 31 31 32 impl Default for IndexParams { 32 33 fn default() -> Self { 33 34 Self { 34 - page: 1, 35 - limit: 10, 35 + page: Some(1), 36 + limit: Some(10), 36 37 tag: None, 37 - sort: SortField::TakenAt, 38 - dir: SortDirection::Desc, 38 + sort: Some(SortField::TakenAt), 39 + dir: Some(SortDirection::Desc), 39 40 } 40 41 } 41 42 } ··· 46 47 query: Query<IndexParams>, 47 48 State(state): State<Arc<AppState>>, 48 49 ) -> Result<Html<String>, AppError> { 50 + let query = query.0; 51 + let page = query.page.unwrap_or(1); 52 + let limit = query.limit.unwrap_or(10); 53 + let tag = query.tag.clone(); 54 + let sort = query.sort.unwrap_or(SortField::TakenAt); 55 + let dir = query.dir.unwrap_or(SortDirection::Desc); 49 56 let photos = db::get_photos( 50 57 &state.pool, 51 58 PhotoQuery { 52 59 sort: Sort { 53 - field: query.sort, 54 - direction: query.dir, 55 - }, 56 - pagination: QueryPagination { 57 - limit: query.limit, 58 - page: query.page, 60 + field: sort, 61 + direction: dir, 59 62 }, 60 - tag: query.tag.to_owned(), 63 + pagination: QueryPagination { limit, page }, 64 + tag: tag.to_owned(), 61 65 }, 62 66 ) 63 67 .await?; 64 68 65 69 let total_photos = db::get_photo_count(&state.pool).await?; 66 70 67 - let current_tag = query.tag.clone(); 71 + let current_tag = tag.clone(); 68 72 let mut tags = db::get_tags(&state.pool).await?; 69 - if let Some(tag) = &query.tag { 70 - tags.retain(|t| t.name != *tag); 73 + if let Some(tag) = tag { 74 + tags.retain(|t| t.name != tag); 71 75 } 72 76 73 77 let sort_dir = query.dir; 74 78 let sort_field = query.sort; 75 79 let template = state.template_env.get_template("photos/index")?; 76 - let pagination = Pagination { 77 - page: query.page, 78 - num_pages: total_photos / query.limit, 79 - }; 80 + let num_pages = total_photos / limit; 81 + let pagination = get_pagination(query, num_pages)?; 80 82 let rendered = template.render(context! { 81 83 photos => photos, 82 84 tags => tags, 83 - current_tag => current_tag, 84 85 current_tag => current_tag, 85 86 sort_dir => sort_dir, 86 87 sort_field => sort_field, ··· 103 104 104 105 Ok(Html(rendered)) 105 106 } 107 + 108 + fn get_pagination(query: IndexParams, num_pages: u32) -> anyhow::Result<Pagination> { 109 + let page = query.page.unwrap_or(1); 110 + let prev_query = if page > 1 { 111 + let prev_page = if page == 1 { None } else { Some(page - 1) }; 112 + Some(serde_urlencoded::to_string(IndexParams { 113 + page: prev_page, 114 + tag: query.tag.clone(), 115 + ..query 116 + })?) 117 + } else { 118 + None 119 + }; 120 + 121 + let next_query = if page < num_pages { 122 + let next_page = if page < num_pages { 123 + Some(page + 1) 124 + } else { 125 + None 126 + }; 127 + Some(serde_urlencoded::to_string(IndexParams { 128 + page: next_page, 129 + tag: query.tag.clone(), 130 + ..query 131 + })?) 132 + } else { 133 + None 134 + }; 135 + 136 + Ok(Pagination { 137 + page, 138 + num_pages, 139 + prev_query, 140 + next_query, 141 + }) 142 + }
+5 -13
templates/photos/index.jinja
··· 47 47 </div> 48 48 49 49 <ul class="pagination"> 50 - {% if pagination.page > 1 %} 51 - <a href="?page={{ pagination.page - 1 }}">←</a> 50 + {% if pagination.prev_query %} 51 + <a href="?{{ pagination.prev_query }}">←</a> 52 52 {% endif %} 53 - {% for i in range(1, pagination.num_pages+1) %} 54 - <li> 55 - {% if pagination.page == i %} 56 - {{ i }} 57 - {% else %} 58 - <a href="?page={{ i }}">{{ i }}</a> 59 - {% endif %} 60 - </li> 61 - {% endfor %} 62 - {% if pagination.page != pagination.num_pages %} 63 - <a href="?page={{ pagination.page + 1 }}">→</a> 53 + <li>{{ pagination.page }} / {{ pagination.num_pages }}</li> 54 + {% if pagination.next_query %} 55 + <a href="?{{ pagination.next_query }}">→</a> 64 56 {% endif %} 65 57 </ul> 66 58 {% endblock %}