A little app to serve my photography from my personal website

handle timestamps

+273 -6
+187
Cargo.lock
··· 24 24 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 25 25 26 26 [[package]] 27 + name = "android_system_properties" 28 + version = "0.1.5" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 + dependencies = [ 32 + "libc", 33 + ] 34 + 35 + [[package]] 27 36 name = "anyhow" 28 37 version = "1.0.100" 29 38 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 148 157 ] 149 158 150 159 [[package]] 160 + name = "bumpalo" 161 + version = "3.19.0" 162 + source = "registry+https://github.com/rust-lang/crates.io-index" 163 + checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 164 + 165 + [[package]] 151 166 name = "byteorder" 152 167 version = "1.5.0" 153 168 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 176 191 checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 177 192 178 193 [[package]] 194 + name = "chrono" 195 + version = "0.4.42" 196 + source = "registry+https://github.com/rust-lang/crates.io-index" 197 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 198 + dependencies = [ 199 + "iana-time-zone", 200 + "js-sys", 201 + "num-traits", 202 + "wasm-bindgen", 203 + "windows-link", 204 + ] 205 + 206 + [[package]] 179 207 name = "concurrent-queue" 180 208 version = "2.5.0" 181 209 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 189 217 version = "0.9.6" 190 218 source = "registry+https://github.com/rust-lang/crates.io-index" 191 219 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 220 + 221 + [[package]] 222 + name = "core-foundation-sys" 223 + version = "0.8.7" 224 + source = "registry+https://github.com/rust-lang/crates.io-index" 225 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 192 226 193 227 [[package]] 194 228 name = "cpufeatures" ··· 614 648 ] 615 649 616 650 [[package]] 651 + name = "iana-time-zone" 652 + version = "0.1.64" 653 + source = "registry+https://github.com/rust-lang/crates.io-index" 654 + checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 655 + dependencies = [ 656 + "android_system_properties", 657 + "core-foundation-sys", 658 + "iana-time-zone-haiku", 659 + "js-sys", 660 + "log", 661 + "wasm-bindgen", 662 + "windows-core", 663 + ] 664 + 665 + [[package]] 666 + name = "iana-time-zone-haiku" 667 + version = "0.1.2" 668 + source = "registry+https://github.com/rust-lang/crates.io-index" 669 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 670 + dependencies = [ 671 + "cc", 672 + ] 673 + 674 + [[package]] 617 675 name = "icu_collections" 618 676 version = "2.0.0" 619 677 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 748 806 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 749 807 750 808 [[package]] 809 + name = "js-sys" 810 + version = "0.3.81" 811 + source = "registry+https://github.com/rust-lang/crates.io-index" 812 + checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" 813 + dependencies = [ 814 + "once_cell", 815 + "wasm-bindgen", 816 + ] 817 + 818 + [[package]] 751 819 name = "lazy_static" 752 820 version = "1.5.0" 753 821 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 998 1066 dependencies = [ 999 1067 "anyhow", 1000 1068 "axum", 1069 + "chrono", 1001 1070 "dotenv", 1002 1071 "minijinja", 1003 1072 "serde", ··· 1145 1214 version = "0.1.26" 1146 1215 source = "registry+https://github.com/rust-lang/crates.io-index" 1147 1216 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1217 + 1218 + [[package]] 1219 + name = "rustversion" 1220 + version = "1.0.22" 1221 + source = "registry+https://github.com/rust-lang/crates.io-index" 1222 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1148 1223 1149 1224 [[package]] 1150 1225 name = "ryu" ··· 1817 1892 checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 1818 1893 1819 1894 [[package]] 1895 + name = "wasm-bindgen" 1896 + version = "0.2.104" 1897 + source = "registry+https://github.com/rust-lang/crates.io-index" 1898 + checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" 1899 + dependencies = [ 1900 + "cfg-if", 1901 + "once_cell", 1902 + "rustversion", 1903 + "wasm-bindgen-macro", 1904 + "wasm-bindgen-shared", 1905 + ] 1906 + 1907 + [[package]] 1908 + name = "wasm-bindgen-backend" 1909 + version = "0.2.104" 1910 + source = "registry+https://github.com/rust-lang/crates.io-index" 1911 + checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" 1912 + dependencies = [ 1913 + "bumpalo", 1914 + "log", 1915 + "proc-macro2", 1916 + "quote", 1917 + "syn", 1918 + "wasm-bindgen-shared", 1919 + ] 1920 + 1921 + [[package]] 1922 + name = "wasm-bindgen-macro" 1923 + version = "0.2.104" 1924 + source = "registry+https://github.com/rust-lang/crates.io-index" 1925 + checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" 1926 + dependencies = [ 1927 + "quote", 1928 + "wasm-bindgen-macro-support", 1929 + ] 1930 + 1931 + [[package]] 1932 + name = "wasm-bindgen-macro-support" 1933 + version = "0.2.104" 1934 + source = "registry+https://github.com/rust-lang/crates.io-index" 1935 + checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" 1936 + dependencies = [ 1937 + "proc-macro2", 1938 + "quote", 1939 + "syn", 1940 + "wasm-bindgen-backend", 1941 + "wasm-bindgen-shared", 1942 + ] 1943 + 1944 + [[package]] 1945 + name = "wasm-bindgen-shared" 1946 + version = "0.2.104" 1947 + source = "registry+https://github.com/rust-lang/crates.io-index" 1948 + checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" 1949 + dependencies = [ 1950 + "unicode-ident", 1951 + ] 1952 + 1953 + [[package]] 1820 1954 name = "whoami" 1821 1955 version = "1.6.1" 1822 1956 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1827 1961 ] 1828 1962 1829 1963 [[package]] 1964 + name = "windows-core" 1965 + version = "0.62.2" 1966 + source = "registry+https://github.com/rust-lang/crates.io-index" 1967 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 1968 + dependencies = [ 1969 + "windows-implement", 1970 + "windows-interface", 1971 + "windows-link", 1972 + "windows-result", 1973 + "windows-strings", 1974 + ] 1975 + 1976 + [[package]] 1977 + name = "windows-implement" 1978 + version = "0.60.2" 1979 + source = "registry+https://github.com/rust-lang/crates.io-index" 1980 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 1981 + dependencies = [ 1982 + "proc-macro2", 1983 + "quote", 1984 + "syn", 1985 + ] 1986 + 1987 + [[package]] 1988 + name = "windows-interface" 1989 + version = "0.59.3" 1990 + source = "registry+https://github.com/rust-lang/crates.io-index" 1991 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 1992 + dependencies = [ 1993 + "proc-macro2", 1994 + "quote", 1995 + "syn", 1996 + ] 1997 + 1998 + [[package]] 1830 1999 name = "windows-link" 1831 2000 version = "0.2.1" 1832 2001 source = "registry+https://github.com/rust-lang/crates.io-index" 1833 2002 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2003 + 2004 + [[package]] 2005 + name = "windows-result" 2006 + version = "0.4.1" 2007 + source = "registry+https://github.com/rust-lang/crates.io-index" 2008 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 2009 + dependencies = [ 2010 + "windows-link", 2011 + ] 2012 + 2013 + [[package]] 2014 + name = "windows-strings" 2015 + version = "0.5.1" 2016 + source = "registry+https://github.com/rust-lang/crates.io-index" 2017 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 2018 + dependencies = [ 2019 + "windows-link", 2020 + ] 1834 2021 1835 2022 [[package]] 1836 2023 name = "windows-sys"
+1
Cargo.toml
··· 6 6 [dependencies] 7 7 anyhow = "1.0.100" 8 8 axum = "0.8.6" 9 + chrono = "0.4.42" 9 10 dotenv = "0.15.0" 10 11 minijinja = { version = "2.12.0", features = ["loader"] } 11 12 serde = "1.0.228"
+69
src/date_time.rs
··· 1 + use std::{convert, fmt, str}; 2 + 3 + use chrono::{self, FixedOffset, ParseError}; 4 + use serde::{ 5 + Deserialize, Deserializer, Serialize, Serializer, 6 + de::{self, Visitor}, 7 + }; 8 + 9 + pub struct DateTime(chrono::DateTime<FixedOffset>); 10 + 11 + impl convert::TryFrom<String> for DateTime { 12 + type Error = ParseError; 13 + 14 + fn try_from(value: String) -> Result<Self, ParseError> { 15 + let dt = chrono::DateTime::parse_from_rfc3339(&value)?; 16 + Ok(DateTime(dt)) 17 + } 18 + } 19 + 20 + impl convert::From<DateTime> for String { 21 + fn from(value: DateTime) -> String { 22 + value.0.to_rfc3339() 23 + } 24 + } 25 + 26 + impl convert::From<&DateTime> for String { 27 + fn from(value: &DateTime) -> String { 28 + value.0.to_rfc3339() 29 + } 30 + } 31 + 32 + struct DateTimeVisitor; 33 + 34 + impl<'de> Visitor<'de> for DateTimeVisitor { 35 + type Value = DateTime; 36 + 37 + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 38 + formatter.write_str("an ISO 8601 string") 39 + } 40 + 41 + fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 42 + where 43 + E: de::Error, 44 + { 45 + match chrono::DateTime::parse_from_rfc3339(value) { 46 + Ok(dt) => Ok(DateTime(dt)), 47 + Err(_) => Err(E::custom(format!("unable to parse DateTime: {value}"))), 48 + } 49 + } 50 + } 51 + 52 + impl Serialize for DateTime { 53 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 54 + where 55 + S: Serializer, 56 + { 57 + let result: String = self.into(); 58 + serializer.serialize_str(&result) 59 + } 60 + } 61 + 62 + impl<'de> Deserialize<'de> for DateTime { 63 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 64 + where 65 + D: Deserializer<'de>, 66 + { 67 + deserializer.deserialize_str(DateTimeVisitor) 68 + } 69 + }
+5 -3
src/db.rs
··· 2 2 use sqlx::SqlitePool; 3 3 4 4 pub async fn get_photos(pool: &SqlitePool) -> anyhow::Result<Vec<Photo>> { 5 - let photos = sqlx::query_as::<_, Photo>("SELECT id, caption, filename FROM photos") 6 - .fetch_all(pool) 7 - .await?; 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?; 8 10 9 11 Ok(photos) 10 12 }
+1
src/main.rs
··· 4 4 use std::{env, sync::Arc}; 5 5 mod app_error; 6 6 use app_error::AppError; 7 + mod date_time; 7 8 mod db; 8 9 mod photos; 9 10 mod templates;
+6 -2
src/photos.rs
··· 1 + use crate::date_time::DateTime; 2 + 1 3 use serde::Serialize; 2 4 use sqlx::FromRow; 3 5 ··· 6 8 pub id: String, 7 9 pub caption: String, 8 10 pub filename: String, 9 - // pub taken_at TEXT, 10 - // pub created_at TEXT 11 + #[sqlx(try_from = "String")] 12 + pub taken_at: DateTime, 13 + #[sqlx(try_from = "String")] 14 + pub created_at: DateTime, 11 15 }
+4 -1
templates/photos/index.jinja
··· 2 2 {% block title %}Photos{% endblock %} 3 3 {% block body %} 4 4 {% for photo in photos %} 5 - <img src="/thumbnails/{{ photo.id }}.webp"> 5 + <div> 6 + <p>{{ photo.taken_at }}</p> 7 + <img src="/thumbnails/{{ photo.id }}.webp"> 8 + </div> 6 9 {% endfor %} 7 10 {% endblock %}