A little app to serve my photography from my personal website

DB search

+92 -8
+78 -7
src/db.rs
··· 1 1 use crate::Photo; 2 - use sqlx::SqlitePool; 2 + use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs}; 3 + 4 + pub enum SortDirection { 5 + Asc, 6 + Desc, 7 + } 8 + 9 + impl SortDirection { 10 + pub fn to_sql(&self) -> String { 11 + match self { 12 + Self::Asc => "ASC", 13 + Self::Desc => "DESC", 14 + } 15 + .to_string() 16 + } 17 + } 18 + 19 + pub enum SortField { 20 + TakenAt, 21 + CreatedAt, 22 + } 23 + 24 + impl SortField { 25 + pub fn to_sql(&self) -> String { 26 + match self { 27 + Self::TakenAt => "taken_at", 28 + Self::CreatedAt => "created_at", 29 + } 30 + .to_string() 31 + } 32 + } 33 + 34 + pub struct Sort { 35 + pub direction: SortDirection, 36 + pub field: SortField, 37 + } 38 + 39 + impl Sort { 40 + pub fn to_sql(&self) -> String { 41 + let field = self.field.to_sql(); 42 + let direction = self.direction.to_sql(); 43 + format!("{field} {direction}") 44 + } 45 + } 46 + 47 + pub struct Pagination { 48 + pub limit: u32, 49 + pub page: u32, 50 + } 51 + 52 + pub struct PhotoQuery { 53 + pub sort: Sort, 54 + pub pagination: Pagination, 55 + pub tag: Option<String>, 56 + } 3 57 4 - pub async fn get_photos(pool: &SqlitePool) -> anyhow::Result<Vec<Photo>> { 5 - let photos = sqlx::query_as::<_, Photo>( 6 - "SELECT id, caption, filename, taken_at, created_at FROM photos", 7 - ) 8 - .fetch_all(pool) 9 - .await?; 58 + pub async fn get_photos(pool: &SqlitePool, pq: PhotoQuery) -> anyhow::Result<Vec<Photo>> { 59 + let offset = pq.pagination.limit * (pq.pagination.page - 1); 60 + 61 + let mut query: QueryBuilder<Sqlite> = QueryBuilder::new("SELECT * FROM photos "); 62 + 63 + if let Some(tag) = pq.tag { 64 + query 65 + .push("JOIN photo_tags ON photo_tags.tag = ") 66 + .push_bind(tag) 67 + .push(" AND photo_tags.photo_id = photos.id "); 68 + }; 69 + 70 + let sort_sql = pq.sort.to_sql(); 71 + query.push(format!("ORDER BY {sort_sql}")); 72 + 73 + query 74 + .push(" LIMIT ") 75 + .push_bind(pq.pagination.limit) 76 + .push(" OFFSET ") 77 + .push_bind(offset); 78 + 79 + let query: QueryAs<'_, Sqlite, Photo, _> = query.build_query_as(); 80 + let photos = query.fetch_all(pool).await?; 10 81 11 82 Ok(photos) 12 83 }
+14 -1
src/main.rs
··· 13 13 use templates::load_templates; 14 14 use tower_http::services::ServeDir; 15 15 16 + use crate::db::{Pagination, PhotoQuery, Sort, SortDirection, SortField}; 17 + 16 18 struct AppState { 17 19 template_env: Environment<'static>, 18 20 pool: SqlitePool, 19 21 } 20 22 21 23 async fn root(State(state): State<Arc<AppState>>) -> Result<Html<String>, AppError> { 22 - let photos = db::get_photos(&state.pool).await?; 24 + let photos = db::get_photos( 25 + &state.pool, 26 + PhotoQuery { 27 + sort: Sort { 28 + field: SortField::TakenAt, 29 + direction: SortDirection::Asc, 30 + }, 31 + pagination: Pagination { limit: 10, page: 1 }, 32 + tag: Some("me".to_string()), 33 + }, 34 + ) 35 + .await?; 23 36 let template = state.template_env.get_template("photos/index")?; 24 37 let rendered = template.render(context! { 25 38 photos => photos,