Skip to content

Commit d4c3cf0

Browse files
feat: paginated post archive page
Added a new /posts route that shows a paginated archive of all of my posts.
1 parent 3705eee commit d4c3cf0

5 files changed

Lines changed: 130 additions & 3 deletions

File tree

src/main.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod routes;
33
mod rss;
44

55
use post::{Post, QueryPost};
6-
use routes::{about, contact, main_page, post, Static, WellKnown};
6+
use routes::{about, contact, main_page, post, posts_index, Static, WellKnown};
77
use rss::feed;
88

99
use std::env;
@@ -62,6 +62,7 @@ async fn main() {
6262

6363
let app = Router::new()
6464
.route("/", get(main_page))
65+
.route("/posts", get(posts_index))
6566
.route("/about", get(about))
6667
.route("/contact", get(contact))
6768
.route("/post/:id", get(post))
@@ -106,6 +107,28 @@ impl IntoResponse for MainPage {
106107
}
107108
}
108109

110+
#[derive(Debug, Clone)]
111+
struct PostsPage {
112+
title: String,
113+
posts: Vec<Post>,
114+
current_page: usize,
115+
total_pages: usize,
116+
}
117+
118+
impl IntoResponse for PostsPage {
119+
fn into_response(self) -> Response {
120+
let mut context = Context::new();
121+
context.insert("title", &self.title);
122+
context.insert("posts", &self.posts);
123+
context.insert("current_page", &self.current_page);
124+
context.insert("total_pages", &self.total_pages);
125+
let rendered = TEMPLATES
126+
.render("posts.html", &context)
127+
.expect("Failed to render template");
128+
Html(rendered).into_response()
129+
}
130+
}
131+
109132
#[derive(RustEmbed)]
110133
#[folder = "templates/"]
111134
struct Templates;

src/routes.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
use crate::{post::QueryPost, AppState, MainPage, Post};
1+
use crate::{post::QueryPost, AppState, MainPage, Post, PostsPage};
22
use axum::{
3-
extract::{Path, State},
3+
extract::{Path, Query, State},
44
http::StatusCode,
55
response::IntoResponse,
66
};
77
use rust_embed::RustEmbed;
8+
use serde::Deserialize;
89

910
pub async fn main_page(app: State<AppState>) -> impl IntoResponse {
1011
let queried = sqlx::query_as!(
1112
QueryPost,
1213
r#"
1314
SELECT *
1415
FROM posts
16+
WHERE content_type != 'special'
1517
ORDER BY date DESC
1618
LIMIT 5
1719
"#,
@@ -31,6 +33,56 @@ pub async fn main_page(app: State<AppState>) -> impl IntoResponse {
3133
}
3234
}
3335

36+
#[derive(Deserialize)]
37+
pub struct Pagination {
38+
page: Option<usize>,
39+
}
40+
41+
pub async fn posts_index(pagination: Query<Pagination>, app: State<AppState>) -> impl IntoResponse {
42+
let page = pagination.page.unwrap_or(1);
43+
const POSTS_PER_PAGE: i64 = 10;
44+
let offset = (page as i64 - 1) * POSTS_PER_PAGE;
45+
46+
// Fetch one page of posts
47+
let queried = sqlx::query_as!(
48+
QueryPost,
49+
r#"
50+
SELECT *
51+
FROM posts
52+
WHERE content_type != 'special'
53+
ORDER BY date DESC
54+
LIMIT ? OFFSET ?
55+
"#,
56+
POSTS_PER_PAGE,
57+
offset
58+
)
59+
.fetch_all(&app.pool)
60+
.await
61+
.unwrap();
62+
63+
let mut posts: Vec<Post> = Vec::new();
64+
for post in queried.iter() {
65+
posts.push(post.clone().into_post(app.clone()).await);
66+
}
67+
68+
// Get total number of posts to calculate total pages
69+
let total_posts: i64 =
70+
sqlx::query!("SELECT COUNT(*) as count FROM posts WHERE content_type != 'special'")
71+
.fetch_one(&app.pool)
72+
.await
73+
.unwrap()
74+
.count;
75+
76+
let total_pages = (total_posts as f64 / POSTS_PER_PAGE as f64).ceil() as usize;
77+
78+
PostsPage {
79+
title: "All Posts".to_string(),
80+
posts,
81+
current_page: page,
82+
total_pages,
83+
}
84+
}
85+
3486
pub async fn about(app: State<AppState>) -> impl IntoResponse {
3587
let query = QueryPost::fetch_special("about", app.clone())
3688
.await

static/css/style.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,20 @@ p > a[href^="/post/"] {
173173
font-size: 0.9em;
174174
color: #666;
175175
}
176+
177+
/* Pagination Styles */
178+
.pagination {
179+
text-align: center;
180+
padding-top: 20px;
181+
margin-top: 20px;
182+
border-top: 1px solid #eee;
183+
}
184+
185+
.pagination a,
186+
.pagination span {
187+
margin: 0 15px;
188+
}
189+
190+
.pagination span {
191+
color: #666;
192+
}

templates/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<h1><a href="/">Jonathan's Blog</a></h1>
2525
<nav>
2626
<a href="/">Home</a>
27+
<a href="/posts">Archive</a>
2728
<a href="/about">About</a>
2829
<a href="/contact">Contact</a>
2930
</nav>

templates/posts.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
<main>
5+
<h2>All Posts</h2>
6+
<ul>
7+
{% for post in posts %}
8+
<li>
9+
{% if post.content_type == "Post" %}
10+
{% include "partial/post.html" %}
11+
{% elif post.content_type == "Link" %}
12+
{% include "partial/link.html" %}
13+
{% elif post.content_type == "Quote" %}
14+
{% include "partial/quote.html" %}
15+
{% endif %}
16+
</li>
17+
{% endfor %}
18+
</ul>
19+
20+
{% if total_pages > 0 %}
21+
<div class="pagination">
22+
{% if current_page > 1 %}
23+
<a href="/posts?page={{ current_page - 1 }}">&laquo; Previous</a>
24+
{% endif %}
25+
26+
<span>Page {{ current_page }} of {{ total_pages }}</span>
27+
28+
{% if current_page < total_pages %}
29+
<a href="/posts?page={{ current_page + 1 }}">Next &raquo;</a>
30+
{% endif %}
31+
</div>
32+
{% endif %}
33+
</main>
34+
{% endblock content %}

0 commit comments

Comments
 (0)