Skip to content

Commit 6d18e49

Browse files
committed
Implement the article slugs support
Signed-off-by: JmPotato <github@ipotato.me>
1 parent 4dbae30 commit 6d18e49

File tree

12 files changed

+72
-19
lines changed

12 files changed

+72
-19
lines changed

src/app.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ impl AppState {
115115
format!("{}/{}", value, uri)
116116
}
117117
});
118+
env.add_filter("get_slug", |value: &Value| {
119+
let slug = value.get_attr("slug").unwrap_or_default().to_string();
120+
if slug.is_empty() {
121+
value.get_attr("id").unwrap_or_default().to_string()
122+
} else {
123+
slug
124+
}
125+
});
118126

119127
Ok(env)
120128
}
@@ -224,7 +232,7 @@ impl App {
224232
// serve the page handlers
225233
.route("/", get(handler_home))
226234
.route("/page/:num", get(handler_page))
227-
.route("/article/:id", get(handler_article))
235+
.route("/article/:id_or_slug", get(handler_article))
228236
.route("/articles", get(handler_articles))
229237
.route("/tag/:tag", get(handler_tag))
230238
.route("/tags", get(handler_tags))

src/handlers.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,16 @@ pub async fn handler_page(
6161

6262
pub async fn handler_article(
6363
State(state): State<Arc<AppState>>,
64-
Path(id): Path<i32>,
64+
Path(id_or_slug): Path<String>,
6565
auth_session: AuthSession<AppState>,
6666
) -> Result<Html<String>, StatusCode> {
67-
if let Some(article) = Article::get_by_id(&state.db, id).await {
67+
if let Some(article) = if let Ok(id) = id_or_slug.parse::<i32>() {
68+
info!("try to get article by id: {}", id);
69+
Article::get_by_id(&state.db, id).await
70+
} else {
71+
info!("try to get article by slug: {}", id_or_slug);
72+
Article::get_by_slug(&state.db, &id_or_slug).await
73+
} {
6874
return Ok(render_template_with_context!(
6975
state,
7076
"article.html",

src/models/articles.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
#[derive(FromRow, Serialize, Deserialize, Default)]
1515
pub struct Article {
1616
id: Option<i32>,
17+
slug: String,
1718
title: String,
1819
pub content: String,
1920
pub tags: String,
@@ -22,6 +23,7 @@ pub struct Article {
2223
}
2324

2425
impl Article {
26+
// TODO: refine the error result handling.
2527
pub async fn get_all(db: &sqlx::MySqlPool) -> Vec<Self> {
2628
sqlx::query_as("SELECT * FROM articles ORDER BY id DESC")
2729
.fetch_all(db)
@@ -53,9 +55,17 @@ impl Article {
5355
.ok()
5456
}
5557

58+
pub async fn get_by_slug(db: &sqlx::MySqlPool, slug: &str) -> Option<Self> {
59+
sqlx::query_as("SELECT * FROM articles WHERE slug = ?")
60+
.bind(slug)
61+
.fetch_one(db)
62+
.await
63+
.ok()
64+
}
65+
5666
pub async fn get_by_tag(db: &sqlx::MySqlPool, tag: &str) -> Vec<Self> {
5767
sqlx::query_as(
58-
"SELECT a.id, a.title, a.content, a.tags, a.created_at, a.updated_at
68+
"SELECT a.id, a.slug, a.title, a.content, a.tags, a.created_at, a.updated_at
5969
FROM articles AS a
6070
INNER JOIN tags AS t ON a.id = t.article_id
6171
WHERE t.name = ?
@@ -103,9 +113,12 @@ impl Display for Article {
103113
#[async_trait]
104114
impl Editable for Article {
105115
fn get_redirect_url(&self) -> String {
106-
match self.id {
107-
Some(id) => format!("/article/{}", id),
108-
None => "/".to_string(),
116+
if !self.slug.is_empty() {
117+
format!("/article/{}", self.slug)
118+
} else if let Some(id) = self.id {
119+
format!("/article/{}", id)
120+
} else {
121+
"/".to_string()
109122
}
110123
}
111124

@@ -119,8 +132,9 @@ impl Editable for Article {
119132

120133
// update the articles table
121134
sqlx::query(
122-
"UPDATE articles SET title = ?, content = ?, tags = ?, updated_at = NOW() WHERE id = ?",
135+
"UPDATE articles SET slug = ?, title = ?, content = ?, tags = ?, updated_at = NOW() WHERE id = ?",
123136
)
137+
.bind(&self.slug)
124138
.bind(&self.title)
125139
.bind(&self.content)
126140
.bind(&self.tags)
@@ -189,9 +203,23 @@ impl Editable for Article {
189203

190204
impl From<EditorForm> for Article {
191205
fn from(from: EditorForm) -> Self {
206+
// Normalize the slug by replacing the whitespace with hyphen.
207+
let slug = from
208+
.slug
209+
.unwrap_or_default()
210+
.trim()
211+
.chars()
212+
.map(|c| {
213+
if c.is_whitespace() {
214+
'-'
215+
} else {
216+
c.to_ascii_lowercase()
217+
}
218+
})
219+
.collect();
192220
Article {
193221
id: from.id,
194-
// trim the title and tags to remove leading and trailing whitespace and commas
222+
slug,
195223
title: from.title.unwrap_or_default().trim().to_string(),
196224
tags: sort_out_tags(&from.tags.unwrap_or_default()),
197225
content: from.content.unwrap_or_default(),

src/models/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ use crate::Error;
1111
const CREATE_TABLE_ARTICLES_SQL: &str = r#"
1212
CREATE TABLE IF NOT EXISTS articles (
1313
id INT AUTO_INCREMENT PRIMARY KEY,
14+
slug VARCHAR(255) NOT NULL,
1415
title TEXT NOT NULL,
1516
content TEXT NOT NULL,
1617
tags VARCHAR(255) NOT NULL,
1718
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
18-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
19+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
20+
INDEX(slug)
1921
) CHARSET = utf8mb4;
2022
"#;
2123

src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub struct EditorPath {
6767
#[derive(Deserialize)]
6868
pub struct EditorForm {
6969
pub id: Option<i32>,
70+
pub slug: Option<String>,
7071
pub title: Option<String>,
7172
pub tags: Option<String>,
7273
pub content: Option<String>,

static/css/style.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ samp {
8585
}
8686

8787
q {
88-
quotes: "\201C""\201D""\2018""\2019";
88+
quotes: "\201C" "\201D" "\2018" "\2019";
8989
}
9090

9191
small {
@@ -353,7 +353,7 @@ table {
353353
text-align: center;
354354
}
355355

356-
#content .post > .date {
356+
#content .post>.date {
357357
text-align: center;
358358
}
359359

@@ -399,6 +399,10 @@ table {
399399
text-align: center;
400400
}
401401

402+
#article_editor input {
403+
width: 100%;
404+
}
405+
402406
#footer {
403407
color: gray;
404408
font-size: 0.8em;
@@ -411,4 +415,4 @@ table {
411415

412416
#footer a {
413417
color: gray;
414-
}
418+
}

templates/admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ <h1 id="title">Admin Panel</h1>
6060
{% for article in articles %}
6161
<tr>
6262
<td>{{ article.id }}</td>
63-
<td><a href="/article/{{ article.id }}">{{ article.title }}</a></td>
63+
<td><a href="/article/{{ article | get_slug }}">{{ article.title }}</a></td>
6464
<td>{{ article.created_at }}</td>
6565
<td>{{ article.updated_at }}</td>
6666
<td>

templates/articles.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ <h2>{{ year }}</h2>
99
{% for article in articles_by_year[year] %}
1010
<li>
1111
<span class="meta">{{ article.created_at[:10] }}</span>
12-
<a href="/article/{{ article.id }}">{{ article.title }}</a>
12+
<a href="/article/{{ article | get_slug }}">{{ article.title }}</a>
1313
</li>
1414
{% endfor %}
1515
</ol>

templates/editor.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ <h1 id="title">Editor</h1>
2424
<input id="article_title" name="title" type="text" style="width:100%;" value="{{ article.title }}">
2525
</div>
2626
{% if not is_page %}
27+
<div class="input">
28+
<label for="slug">Slug</label>
29+
<input id="slug" name="slug" type="text" style="width:100%;" value="{{ article.slug }}">
30+
</div>
2731
<div class="input">
2832
<label for="tags">Tags</label>
2933
<input id="tags" name="tags" type="text" style="width:100%;" value="{{ article.tags }}">

templates/feed.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
<name>{{ config.blog_author }}</name>
1616
<uri>{{ config.blog_url }}</uri>
1717
</author>
18-
<link href="{{ config.blog_url | concat_url('article') }}/{{ article.id }}"/>
18+
<link href="{{ config.blog_url | concat_url('article') }}/{{ article | get_slug }}"/>
1919
<published>{{ article.created_at }}</published>
2020
<updated>{{ article.updated_at }}</updated>
21-
<id>{{ config.blog_url | concat_url('article') }}/{{ article.id }}</id>
21+
<id>{{ config.blog_url | concat_url('article') }}/{{ article | get_slug }}</id>
2222
<content type="html">
2323
<![CDATA[{% autoescape false %}{{ article.content | md_to_html }}{% endautoescape %}]]>
2424
</content>

0 commit comments

Comments
 (0)