A little app to serve my photography from my personal website

photo count no represents filters

+43 -30
+31 -18
src/db.rs
··· 2 2 3 3 use crate::{Photo, Tag}; 4 4 use serde::{Deserialize, Serialize}; 5 - use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs}; 5 + use sqlx::{QueryBuilder, Sqlite, SqlitePool}; 6 6 7 7 #[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] 8 8 #[serde(rename_all = "snake_case")] ··· 78 78 pub tag: Option<String>, 79 79 } 80 80 81 - pub async fn get_photo_count(pool: &SqlitePool) -> anyhow::Result<u32> { 82 - let result: (u32,) = sqlx::query_as("SELECT COUNT(*) FROM photos") 81 + pub async fn get_photos(pool: &SqlitePool, pq: PhotoQuery) -> anyhow::Result<(u32, Vec<Photo>)> { 82 + let photos = build_photo_query(&pq, false) 83 + .build_query_as() 84 + .fetch_all(pool) 85 + .await?; 86 + 87 + // I like how convenient `QueryAs` is but it doesn't make it easy for me to select a COUNT for 88 + // the results on top of it so whatever! One more query!! 89 + let (count,): (u32,) = build_photo_query(&pq, true) 90 + .build_query_as() 83 91 .fetch_one(pool) 84 92 .await?; 85 - Ok(result.0) 93 + 94 + Ok((count, photos)) 86 95 } 87 96 88 - pub async fn get_photos(pool: &SqlitePool, pq: PhotoQuery) -> anyhow::Result<Vec<Photo>> { 97 + fn build_photo_query(pq: &PhotoQuery, count: bool) -> QueryBuilder<Sqlite> { 89 98 let offset = pq.pagination.limit * (pq.pagination.page - 1); 90 99 91 - let mut query: QueryBuilder<Sqlite> = QueryBuilder::new("SELECT * FROM photos "); 100 + let select = if count { 101 + "SELECT COUNT(*) FROM photos " 102 + } else { 103 + "SELECT * FROM photos " 104 + }; 105 + let mut query = QueryBuilder::new(select); 92 106 93 - if let Some(tag) = pq.tag { 107 + if let Some(tag) = pq.tag.clone() { 94 108 query 95 109 .push("JOIN photo_tags ON photo_tags.tag = ") 96 110 .push_bind(tag) 97 111 .push(" AND photo_tags.photo_id = photos.id "); 98 112 }; 99 113 100 - let sort_sql = pq.sort.to_sql(); 101 - query.push(format!("ORDER BY {sort_sql}")); 114 + if !count { 115 + let sort_sql = pq.sort.to_sql(); 116 + query.push(format!("ORDER BY {sort_sql}")); 117 + 118 + query 119 + .push(" LIMIT ") 120 + .push_bind(pq.pagination.limit) 121 + .push(" OFFSET ") 122 + .push_bind(offset); 123 + } 102 124 103 125 query 104 - .push(" LIMIT ") 105 - .push_bind(pq.pagination.limit) 106 - .push(" OFFSET ") 107 - .push_bind(offset); 108 - 109 - let query: QueryAs<'_, Sqlite, Photo, _> = query.build_query_as(); 110 - let photos = query.fetch_all(pool).await?; 111 - 112 - Ok(photos) 113 126 } 114 127 115 128 pub async fn get_photo(pool: &SqlitePool, id: String) -> anyhow::Result<Photo> {
+1 -3
src/routes/photos.rs
··· 53 53 let tag = query.tag.clone(); 54 54 let sort = query.sort.unwrap_or(SortField::TakenAt); 55 55 let dir = query.dir.unwrap_or(SortDirection::Desc); 56 - let photos = db::get_photos( 56 + let (total_photos, photos) = db::get_photos( 57 57 &state.pool, 58 58 PhotoQuery { 59 59 sort: Sort { ··· 65 65 }, 66 66 ) 67 67 .await?; 68 - 69 - let total_photos = db::get_photo_count(&state.pool).await?; 70 68 71 69 let current_tag = tag.clone(); 72 70 let mut tags = db::get_tags(&state.pool).await?;
+11 -9
templates/photos/index.jinja
··· 46 46 {% endfor %} 47 47 </div> 48 48 49 - <ul class="pagination"> 50 - {% if pagination.prev_query %} 51 - <a href="?{{ pagination.prev_query }}">←</a> 52 - {% endif %} 53 - <li>{{ pagination.page }} / {{ pagination.num_pages }}</li> 54 - {% if pagination.next_query %} 55 - <a href="?{{ pagination.next_query }}">→</a> 56 - {% endif %} 57 - </ul> 49 + {% if pagination.num_pages > 1 %} 50 + <ul class="pagination"> 51 + {% if pagination.prev_query %} 52 + <a href="?{{ pagination.prev_query }}">←</a> 53 + {% endif %} 54 + <li>{{ pagination.page }} / {{ pagination.num_pages }}</li> 55 + {% if pagination.next_query %} 56 + <a href="?{{ pagination.next_query }}">→</a> 57 + {% endif %} 58 + </ul> 59 + {% endif %} 58 60 {% endblock %}