tangled
alpha
login
or
join now
ericwood.org
/
photos-site
1
fork
atom
A little app to serve my photography from my personal website
1
fork
atom
overview
issues
pulls
pipelines
DB search
ericwood.org
5 months ago
85cb1e9d
422f425f
+92
-8
2 changed files
expand all
collapse all
unified
split
src
db.rs
main.rs
+78
-7
src/db.rs
···
1
1
use crate::Photo;
2
2
-
use sqlx::SqlitePool;
2
2
+
use sqlx::{QueryBuilder, Sqlite, SqlitePool, query::QueryAs};
3
3
+
4
4
+
pub enum SortDirection {
5
5
+
Asc,
6
6
+
Desc,
7
7
+
}
8
8
+
9
9
+
impl SortDirection {
10
10
+
pub fn to_sql(&self) -> String {
11
11
+
match self {
12
12
+
Self::Asc => "ASC",
13
13
+
Self::Desc => "DESC",
14
14
+
}
15
15
+
.to_string()
16
16
+
}
17
17
+
}
18
18
+
19
19
+
pub enum SortField {
20
20
+
TakenAt,
21
21
+
CreatedAt,
22
22
+
}
23
23
+
24
24
+
impl SortField {
25
25
+
pub fn to_sql(&self) -> String {
26
26
+
match self {
27
27
+
Self::TakenAt => "taken_at",
28
28
+
Self::CreatedAt => "created_at",
29
29
+
}
30
30
+
.to_string()
31
31
+
}
32
32
+
}
33
33
+
34
34
+
pub struct Sort {
35
35
+
pub direction: SortDirection,
36
36
+
pub field: SortField,
37
37
+
}
38
38
+
39
39
+
impl Sort {
40
40
+
pub fn to_sql(&self) -> String {
41
41
+
let field = self.field.to_sql();
42
42
+
let direction = self.direction.to_sql();
43
43
+
format!("{field} {direction}")
44
44
+
}
45
45
+
}
46
46
+
47
47
+
pub struct Pagination {
48
48
+
pub limit: u32,
49
49
+
pub page: u32,
50
50
+
}
51
51
+
52
52
+
pub struct PhotoQuery {
53
53
+
pub sort: Sort,
54
54
+
pub pagination: Pagination,
55
55
+
pub tag: Option<String>,
56
56
+
}
3
57
4
4
-
pub async fn get_photos(pool: &SqlitePool) -> anyhow::Result<Vec<Photo>> {
5
5
-
let photos = sqlx::query_as::<_, Photo>(
6
6
-
"SELECT id, caption, filename, taken_at, created_at FROM photos",
7
7
-
)
8
8
-
.fetch_all(pool)
9
9
-
.await?;
58
58
+
pub async fn get_photos(pool: &SqlitePool, pq: PhotoQuery) -> anyhow::Result<Vec<Photo>> {
59
59
+
let offset = pq.pagination.limit * (pq.pagination.page - 1);
60
60
+
61
61
+
let mut query: QueryBuilder<Sqlite> = QueryBuilder::new("SELECT * FROM photos ");
62
62
+
63
63
+
if let Some(tag) = pq.tag {
64
64
+
query
65
65
+
.push("JOIN photo_tags ON photo_tags.tag = ")
66
66
+
.push_bind(tag)
67
67
+
.push(" AND photo_tags.photo_id = photos.id ");
68
68
+
};
69
69
+
70
70
+
let sort_sql = pq.sort.to_sql();
71
71
+
query.push(format!("ORDER BY {sort_sql}"));
72
72
+
73
73
+
query
74
74
+
.push(" LIMIT ")
75
75
+
.push_bind(pq.pagination.limit)
76
76
+
.push(" OFFSET ")
77
77
+
.push_bind(offset);
78
78
+
79
79
+
let query: QueryAs<'_, Sqlite, Photo, _> = query.build_query_as();
80
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
16
+
use crate::db::{Pagination, PhotoQuery, Sort, SortDirection, SortField};
17
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
22
-
let photos = db::get_photos(&state.pool).await?;
24
24
+
let photos = db::get_photos(
25
25
+
&state.pool,
26
26
+
PhotoQuery {
27
27
+
sort: Sort {
28
28
+
field: SortField::TakenAt,
29
29
+
direction: SortDirection::Asc,
30
30
+
},
31
31
+
pagination: Pagination { limit: 10, page: 1 },
32
32
+
tag: Some("me".to_string()),
33
33
+
},
34
34
+
)
35
35
+
.await?;
23
36
let template = state.template_env.get_template("photos/index")?;
24
37
let rendered = template.render(context! {
25
38
photos => photos,