Skip to content

Commit 2d5062c

Browse files
refactor: whole server refactor to consolidate post fetching
1 parent 7121153 commit 2d5062c

9 files changed

Lines changed: 302 additions & 263 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
anyhow = "1.0.98"
78
axum = "0.7.7"
89
axum-embed = "0.1.0"
910
dotenvy = "0.15.7"
1011
include_dir = "0.7.4"
1112
lazy_static = "1.5.0"
1213
rust-embed = "8.5.0"
1314
serde = { version = "1.0.217", features = ["serde_derive"] }
15+
serde_json = { version = "1.0.140", features = ["raw_value"] }
1416
sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
1517
tera = "1.20.0"
1618
tokio = { version = "1.43.1", features = ["full"] }

src/app.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use anyhow::Result;
2+
use axum::response::{IntoResponse, Response};
3+
use rust_embed::RustEmbed;
4+
use sqlx::{Pool, Sqlite};
5+
use tera::{Context, Tera};
6+
7+
#[derive(Clone)]
8+
pub struct AppState {
9+
pub post_service: crate::services::post::PostService,
10+
pub tera: Tera,
11+
}
12+
13+
impl AppState {
14+
pub fn new(pool: Pool<Sqlite>) -> Self {
15+
let tera = Self::load_templates().unwrap();
16+
AppState {
17+
post_service: crate::services::post::PostService::new(pool),
18+
tera,
19+
}
20+
}
21+
22+
fn load_templates() -> Result<Tera> {
23+
#[derive(RustEmbed)]
24+
#[folder = "templates/"]
25+
struct TemplateAssets;
26+
27+
let mut tera = Tera::default();
28+
let mut templates = vec![];
29+
30+
for path in TemplateAssets::iter() {
31+
let content_bytes = TemplateAssets::get(&path)
32+
.ok_or_else(|| anyhow::anyhow!("Template {} not found", path))?
33+
.data;
34+
35+
let content = std::str::from_utf8(&content_bytes)
36+
.map_err(|_| anyhow::anyhow!("Template {} not valid UTF-8", path))?;
37+
templates.push((path.into_owned(), content.to_owned()))
38+
}
39+
40+
tera.add_raw_templates(templates)
41+
.expect("Can't parse templates.");
42+
43+
Ok(tera)
44+
}
45+
46+
pub fn render(&self, template: &str, context: &Context) -> Result<Response> {
47+
let rendered = self.tera.render(template, context)?;
48+
Ok(axum::response::Html(rendered).into_response())
49+
}
50+
}

src/main.rs

Lines changed: 7 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,31 @@
1+
mod app;
12
mod post;
23
mod routes;
34
mod rss;
5+
mod services;
46

5-
use post::{Post, QueryPost};
6-
use routes::{about, contact, main_page, post, posts_index, Static, WellKnown};
7-
use rss::feed;
8-
7+
use crate::app::AppState;
8+
use crate::routes::{about, contact, main_page, post, posts_index, Static, WellKnown};
9+
use crate::rss::feed;
910
use std::env;
1011

1112
use axum::{
1213
extract::{MatchedPath, Request},
13-
response::{Html, IntoResponse, Response},
1414
routing::get,
1515
Router,
1616
};
17-
use lazy_static::lazy_static;
18-
use rust_embed::RustEmbed;
19-
use sqlx::{sqlite::SqlitePool, Pool, Sqlite};
20-
use tera::{Context, Tera};
17+
use sqlx::sqlite::SqlitePool;
2118
use tower_http::trace::TraceLayer;
2219
use tracing::info_span;
2320
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
2421

25-
#[derive(Debug, Clone)]
26-
pub struct AppState {
27-
pub pool: Pool<Sqlite>,
28-
}
29-
3022
#[tokio::main]
3123
async fn main() {
3224
dotenvy::dotenv().ok();
3325
let pool = SqlitePool::connect(&env::var("DATABASE_URL").expect("No DATABASE_URL set"))
3426
.await
3527
.expect("Couldn't connect to database");
36-
let state = AppState { pool };
28+
let state = AppState::new(pool);
3729

3830
let static_files = axum_embed::ServeEmbed::<Static>::with_parameters(
3931
None,
@@ -88,64 +80,3 @@ async fn main() {
8880
.expect("Failed to bind port");
8981
axum::serve(listener, app).await.unwrap();
9082
}
91-
92-
#[derive(Debug, Clone)]
93-
struct MainPage {
94-
title: String,
95-
posts: Vec<Post>,
96-
}
97-
98-
impl IntoResponse for MainPage {
99-
fn into_response(self) -> Response {
100-
let mut context = Context::new();
101-
context.insert("title", &self.title);
102-
context.insert("posts", &self.posts);
103-
let rendered = TEMPLATES
104-
.render("index.html", &context)
105-
.expect("Failed to render template");
106-
Html(rendered).into_response()
107-
}
108-
}
109-
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-
132-
#[derive(RustEmbed)]
133-
#[folder = "templates/"]
134-
struct Templates;
135-
136-
lazy_static! {
137-
pub static ref TEMPLATES: Tera = {
138-
let mut tera = Tera::default();
139-
140-
let templates_to_add = Templates::iter().map(|path| {
141-
let contents = Templates::get(path.as_ref()).unwrap().data;
142-
let content_string = String::from_utf8(contents.into_owned()).unwrap();
143-
(path, content_string)
144-
});
145-
146-
tera.add_raw_templates(templates_to_add)
147-
.expect("Failed to add raw templates");
148-
149-
return tera;
150-
};
151-
}

src/post.rs

Lines changed: 2 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
use crate::{AppState, TEMPLATES};
2-
3-
use axum::{
4-
extract::State,
5-
response::{Html, IntoResponse, Response},
6-
};
71
use serde::Serialize;
8-
use tera::Context;
92

10-
#[derive(Debug, Clone, Serialize)]
3+
#[derive(Debug, Clone, Serialize, sqlx::FromRow)]
114
pub struct Commit {
125
pub id: String,
136
pub date: String,
@@ -47,77 +40,7 @@ pub struct QueryPost {
4740
pub commits: Option<String>,
4841
}
4942

50-
impl QueryPost {
51-
pub async fn fetch(id: &str, app: State<AppState>) -> Result<QueryPost, sqlx::Error> {
52-
sqlx::query_as!(
53-
QueryPost,
54-
r#"
55-
SELECT *
56-
FROM posts
57-
WHERE id = ? AND content_type != 'special'
58-
"#,
59-
id,
60-
)
61-
.fetch_one(&app.pool)
62-
.await
63-
}
64-
65-
pub async fn fetch_special(id: &str, app: State<AppState>) -> Result<QueryPost, sqlx::Error> {
66-
sqlx::query_as!(
67-
QueryPost,
68-
r#"
69-
SELECT *
70-
FROM posts
71-
WHERE id = ? AND content_type = 'special'
72-
"#,
73-
id
74-
)
75-
.fetch_one(&app.pool)
76-
.await
77-
}
78-
79-
pub async fn into_post(self, app: State<AppState>) -> Post {
80-
let commits = match self.commits {
81-
Some(commits) => {
82-
// The commit ids for the post are stored as a whitespace-separated string
83-
let commits_clone = commits.clone();
84-
let commit_ids: Vec<&str> = commits_clone.split_whitespace().collect();
85-
let mut commits = Vec::new();
86-
for commit_id in commit_ids {
87-
let commit = sqlx::query_as!(
88-
Commit,
89-
r#"
90-
SELECT id, date, subject, body
91-
FROM commits
92-
WHERE id = ?
93-
"#,
94-
commit_id
95-
)
96-
.fetch_one(&app.pool)
97-
.await
98-
.unwrap();
99-
100-
commits.push(commit);
101-
}
102-
103-
Some(commits)
104-
}
105-
None => None,
106-
};
107-
108-
Post {
109-
id: self.id,
110-
title: self.title,
111-
content_type: self.content_type,
112-
link: self.link,
113-
via: self.via,
114-
quote_author: self.quote_author,
115-
date: self.date,
116-
content: self.content,
117-
real_commits: commits,
118-
}
119-
}
120-
}
43+
impl QueryPost {}
12144

12245
#[derive(Debug, Clone, Serialize)]
12346
pub struct Post {
@@ -131,14 +54,3 @@ pub struct Post {
13154
pub content: String,
13255
pub real_commits: Option<Vec<Commit>>,
13356
}
134-
135-
impl IntoResponse for Post {
136-
fn into_response(self) -> Response {
137-
let mut context = Context::new();
138-
context.insert("post", &self);
139-
let rendered = TEMPLATES
140-
.render("post.html", &context)
141-
.expect("Failed to render template");
142-
Html(rendered).into_response()
143-
}
144-
}

0 commit comments

Comments
 (0)