blog post display and metadata
This commit is contained in:
parent
ae1b65957d
commit
03fefda519
@ -1 +1,2 @@
|
|||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod post;
|
||||||
|
34
axum_server/src/pages/post.rs
Normal file
34
axum_server/src/pages/post.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::{extract::Path, http::StatusCode};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::post_parser::{deserialize_date, parse_post};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct PostMetadata {
|
||||||
|
pub layout: String,
|
||||||
|
pub title: String,
|
||||||
|
pub segments: Vec<String>,
|
||||||
|
pub published: bool,
|
||||||
|
#[serde(deserialize_with = "deserialize_date")]
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
|
pub thumbnail: String,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "post.html")]
|
||||||
|
pub struct PostTemplate {
|
||||||
|
pub title: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn render_post(Path(post_id): Path<String>) -> Result<PostTemplate, StatusCode> {
|
||||||
|
let path = format!("../_posts/blog/{}.md", post_id);
|
||||||
|
let parsed = parse_post::<PostMetadata>(&path).await?;
|
||||||
|
Ok(PostTemplate {
|
||||||
|
title: parsed.metadata.title,
|
||||||
|
content: parsed.content,
|
||||||
|
})
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
use axum::{extract::Path, http::StatusCode, response::Html};
|
use axum::http::StatusCode;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use gray_matter::{engine::YAML, Matter};
|
use gray_matter::{engine::YAML, Matter};
|
||||||
use markdown::{to_html_with_options, CompileOptions, Constructs, Options, ParseOptions};
|
use markdown::{to_html_with_options, CompileOptions, Constructs, Options, ParseOptions};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{de::DeserializeOwned, Deserialize, Deserializer};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
pub fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
@ -19,20 +19,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
pub struct ParseResult<Metadata> {
|
||||||
pub struct Metadata {
|
pub content: String,
|
||||||
pub layout: String,
|
pub metadata: Metadata,
|
||||||
pub title: String,
|
|
||||||
pub segments: Vec<String>,
|
|
||||||
pub published: bool,
|
|
||||||
#[serde(deserialize_with = "deserialize_date")]
|
|
||||||
pub date: DateTime<Utc>,
|
|
||||||
pub thumbnail: String,
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn parse_post(Path(post_id): Path<String>) -> Result<Html<String>, StatusCode> {
|
pub async fn parse_post<'de, Metadata: DeserializeOwned>(
|
||||||
let path = format!("../_posts/blog/{}.md", post_id);
|
path: &str,
|
||||||
|
) -> Result<ParseResult<Metadata>, StatusCode> {
|
||||||
let contents = fs::read_to_string(path).await;
|
let contents = fs::read_to_string(path).await;
|
||||||
|
|
||||||
let raw_content = match contents {
|
let raw_content = match contents {
|
||||||
@ -58,10 +52,7 @@ pub async fn parse_post(Path(post_id): Path<String>) -> Result<Html<String>, Sta
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let matter = Matter::<YAML>::new();
|
|
||||||
let _metadata = matter.parse_with_struct::<Metadata>(&raw_content);
|
|
||||||
let parsed = to_html_with_options(&raw_content, &markdown_options);
|
let parsed = to_html_with_options(&raw_content, &markdown_options);
|
||||||
|
|
||||||
let content = match parsed {
|
let content = match parsed {
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
tracing::error!(reason);
|
tracing::error!(reason);
|
||||||
@ -70,5 +61,19 @@ pub async fn parse_post(Path(post_id): Path<String>) -> Result<Html<String>, Sta
|
|||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Html(content));
|
let matter = Matter::<YAML>::new();
|
||||||
|
let parsed_metadata = matter.parse_with_struct::<Metadata>(&raw_content);
|
||||||
|
|
||||||
|
let metadata = match parsed_metadata {
|
||||||
|
None => {
|
||||||
|
tracing::error!("Failed to parse metadata");
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
Some(metadata) => metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(ParseResult {
|
||||||
|
content,
|
||||||
|
metadata: metadata.data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{pages::index::render_index, post_parser::parse_post};
|
use crate::pages::{index::render_index, post::render_post};
|
||||||
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
@ -6,7 +6,7 @@ use tracing::info_span;
|
|||||||
pub fn get_router() -> Router {
|
pub fn get_router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(render_index))
|
.route("/", get(render_index))
|
||||||
.route("/blog/:post_id", get(parse_post))
|
.route("/blog/:post_id", get(render_post))
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||||
// Log the matched route's path (with placeholders not filled in).
|
// Log the matched route's path (with placeholders not filled in).
|
||||||
|
11
axum_server/templates/post.html
Normal file
11
axum_server/templates/post.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{title}}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
|
||||||
|
<article>{{content|escape("none")}}</article>
|
||||||
|
|
||||||
|
{# footer #}
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user