A little app to serve my photography from my personal website

additional queries

+59 -4
+18 -1
src/db.rs
··· 1 - use crate::Photo; 1 + use crate::{Photo, Tag}; 2 2 use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs}; 3 3 4 4 pub enum SortDirection { ··· 81 81 82 82 Ok(photos) 83 83 } 84 + 85 + pub async fn get_photo(pool: &SqlitePool, id: String) -> anyhow::Result<Photo> { 86 + let photo: Photo = sqlx::query_as("SELECT * FROM photos WHERE id = ?") 87 + .bind(id) 88 + .fetch_one(pool) 89 + .await?; 90 + 91 + Ok(photo) 92 + } 93 + 94 + pub async fn get_tags(pool: &SqlitePool) -> anyhow::Result<Vec<Tag>> { 95 + let tags: Vec<Tag> = sqlx::query_as("SELECT * FROM tags ORDER BY count DESC") 96 + .fetch_all(pool) 97 + .await?; 98 + 99 + Ok(tags) 100 + }
+25 -3
src/main.rs
··· 1 - use axum::{Router, extract::State, response::Html, routing::get}; 1 + use axum::{ 2 + Router, 3 + extract::{Path, State}, 4 + response::Html, 5 + routing::get, 6 + }; 2 7 use dotenv::dotenv; 3 8 use minijinja::{Environment, context}; 4 9 use std::{env, sync::Arc}; ··· 6 11 use app_error::AppError; 7 12 mod date_time; 8 13 mod db; 9 - mod photos; 14 + mod models; 10 15 mod templates; 11 - use photos::Photo; 16 + use models::{Photo, Tag}; 12 17 use sqlx::SqlitePool; 13 18 use templates::load_templates; 14 19 use tower_http::services::ServeDir; ··· 33 38 }, 34 39 ) 35 40 .await?; 41 + 42 + let tags = db::get_tags(&state.pool).await?; 36 43 let template = state.template_env.get_template("photos/index")?; 37 44 let rendered = template.render(context! { 38 45 photos => photos, 46 + tags => tags, 47 + })?; 48 + 49 + Ok(Html(rendered)) 50 + } 51 + 52 + async fn show( 53 + Path(id): Path<String>, 54 + State(state): State<Arc<AppState>>, 55 + ) -> Result<Html<String>, AppError> { 56 + let photo = db::get_photo(&state.pool, id).await?; 57 + let template = state.template_env.get_template("photos/show")?; 58 + let rendered = template.render(context! { 59 + photo => photo 39 60 })?; 40 61 41 62 Ok(Html(rendered)) ··· 52 73 let app_state = Arc::new(AppState { template_env, pool }); 53 74 let app = Router::new() 54 75 .route("/", get(root)) 76 + .route("/{id}", get(show)) 55 77 .with_state(app_state) 56 78 .nest_service("/thumbnails", ServeDir::new(&env::var("THUMBNAIL_PATH")?)) 57 79 .nest_service("/images", ServeDir::new(&env::var("IMAGE_PATH")?));
+6
src/photos.rs src/models.rs
··· 13 13 #[sqlx(try_from = "String")] 14 14 pub created_at: DateTime, 15 15 } 16 + 17 + #[derive(FromRow, Serialize)] 18 + pub struct Tag { 19 + pub name: String, 20 + pub count: u32, 21 + }
+5
templates/photos/index.jinja
··· 1 1 {% extends "layout" %} 2 2 {% block title %}Photos{% endblock %} 3 3 {% block body %} 4 + <ul> 5 + {% for tag in tags %} 6 + <li>{{ tag.name }} ({{ tag.count }})</li> 7 + {% endfor %} 8 + </ul> 4 9 {% for photo in photos %} 5 10 <div> 6 11 <p>{{ photo.taken_at }}</p>
+5
templates/photos/show.jinja
··· 1 + {% extends "layout" %} 2 + {% block title %}Photos / View{% endblock %} 3 + {% block body %} 4 + <img src="/images/{{ photo.filename }}"> 5 + {% endblock %}