Changes to config format, move all global context into dedicated Context struct
This commit is contained in:
1 parent
054f21f664
commit
273ec529c0
12 files changed
+140
-116
No files matched your search
+14
-7
@@ -1,19 +1,21 @@
|
|||||||
bind = "127.0.0.1:8080"
|
bind = "127.0.0.1:8080"
|
||||||
concurrency_limit = 128
|
concurrency_limit = 128
|
||||||
|
fs_event_delay_millis = 2000
|
||||||
|
namespace_uuid = "00000000-0000-0000-0000-000000000000"
|
||||||
|
|
||||||
|
[site]
|
||||||
|
protocol = "http"
|
||||||
|
domain = "localhost:8080"
|
||||||
|
|
||||||
|
[content]
|
||||||
static_dir = "./content/static/"
|
static_dir = "./content/static/"
|
||||||
favicon_dir = "./content/favicon/"
|
favicon_dir = "./content/favicon/"
|
||||||
robots_path = "./content/robots.txt"
|
robots_path = "./content/robots.txt"
|
||||||
posts_dir = "./articles/src/"
|
posts_dir = "./articles/src/"
|
||||||
post_media_dir = "./articles/media/"
|
post_media_dir = "./articles/media/"
|
||||||
|
|
||||||
fs_event_delay_millis = 2000
|
[github]
|
||||||
|
source_url = "https://github.com/pantonshire/blog_content/blob/main/src"
|
||||||
namespace_uuid = "00000000-0000-0000-0000-000000000000"
|
|
||||||
|
|
||||||
[self_ref]
|
|
||||||
protocol = "http"
|
|
||||||
domain = "localhost:8080"
|
|
||||||
|
|
||||||
[rss]
|
[rss]
|
||||||
num_posts = 20
|
num_posts = 20
|
||||||
@@ -23,3 +25,8 @@ ttl = 360
|
|||||||
[atom]
|
[atom]
|
||||||
num_posts = 20
|
num_posts = 20
|
||||||
title = "Pantonshire"
|
title = "Pantonshire"
|
||||||
|
|
||||||
|
[[contact]]
|
||||||
|
name = "Twitter"
|
||||||
|
user = "@pantonshire"
|
||||||
|
url = "https://twitter.com/pantonshire"
|
||||||
@@ -7,25 +7,36 @@ use serde::{Deserialize, Deserializer};
|
|||||||
pub(crate) struct Config {
|
pub(crate) struct Config {
|
||||||
pub bind: SocketAddr,
|
pub bind: SocketAddr,
|
||||||
pub concurrency_limit: usize,
|
pub concurrency_limit: usize,
|
||||||
pub static_dir: PathBuf,
|
|
||||||
pub favicon_dir: PathBuf,
|
|
||||||
pub robots_path: PathBuf,
|
|
||||||
pub posts_dir: PathBuf,
|
|
||||||
pub post_media_dir: PathBuf,
|
|
||||||
#[serde(rename = "fs_event_delay_millis", deserialize_with = "deserialize_millis")]
|
#[serde(rename = "fs_event_delay_millis", deserialize_with = "deserialize_millis")]
|
||||||
pub fs_event_delay: Duration,
|
pub fs_event_delay: Duration,
|
||||||
pub namespace_uuid: Uuid,
|
pub namespace_uuid: Uuid,
|
||||||
pub self_ref: SelfRefConfig,
|
pub content: ContentConfig,
|
||||||
|
pub github: GithubConfig,
|
||||||
|
pub site: SiteConfig,
|
||||||
pub rss: RssConfig,
|
pub rss: RssConfig,
|
||||||
pub atom: AtomConfig,
|
pub atom: AtomConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub(crate) struct SelfRefConfig {
|
pub(crate) struct SiteConfig {
|
||||||
pub protocol: String,
|
pub protocol: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub(crate) struct ContentConfig {
|
||||||
|
pub static_dir: PathBuf,
|
||||||
|
pub favicon_dir: PathBuf,
|
||||||
|
pub robots_path: PathBuf,
|
||||||
|
pub posts_dir: PathBuf,
|
||||||
|
pub post_media_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub(crate) struct GithubConfig {
|
||||||
|
pub source_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub(crate) struct RssConfig {
|
pub(crate) struct RssConfig {
|
||||||
pub num_posts: usize,
|
pub num_posts: usize,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use blog::db::ConcurrentPostsStore;
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
pub(crate) struct Context {
|
||||||
|
config: Config,
|
||||||
|
posts: ConcurrentPostsStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn new(config: Config, posts: ConcurrentPostsStore) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
posts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn posts(&self) -> &ConcurrentPostsStore {
|
||||||
|
&self.posts
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-20
@@ -1,4 +1,5 @@
|
|||||||
mod config;
|
mod config;
|
||||||
|
mod context;
|
||||||
mod fs_watcher;
|
mod fs_watcher;
|
||||||
mod render;
|
mod render;
|
||||||
mod service;
|
mod service;
|
||||||
@@ -27,6 +28,7 @@ use blog::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use context::Context;
|
||||||
use render::Renderer;
|
use render::Renderer;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -42,7 +44,7 @@ fn run() -> Result<(), Error> {
|
|||||||
|
|
||||||
// Load the configuration from the TOML config file specified by the first command-line
|
// Load the configuration from the TOML config file specified by the first command-line
|
||||||
// argument.
|
// argument.
|
||||||
let config = Arc::new({
|
let config = {
|
||||||
let config_path = env::args().nth(1)
|
let config_path = env::args().nth(1)
|
||||||
.ok_or(Error::NoConfig)?;
|
.ok_or(Error::NoConfig)?;
|
||||||
|
|
||||||
@@ -53,27 +55,25 @@ fn run() -> Result<(), Error> {
|
|||||||
|
|
||||||
contents.parse::<Config>()
|
contents.parse::<Config>()
|
||||||
.map_err(Error::BadConfig)?
|
.map_err(Error::BadConfig)?
|
||||||
});
|
};
|
||||||
|
|
||||||
// Create the data structure used to store the rendered posts. This uses an `Arc` internally,
|
// Create the global context that will be used and modified throughout the program.
|
||||||
// so clones will point to the same underlying data.
|
let context = Arc::new(Context::new(config, ConcurrentPostsStore::new()));
|
||||||
let posts_store = ConcurrentPostsStore::new();
|
|
||||||
|
|
||||||
let code_renderer = CodeBlockRenderer::new();
|
let code_renderer = CodeBlockRenderer::new();
|
||||||
|
|
||||||
// Create the post renderer and the mpsc channel that will be used to communicate with it.
|
// Create the post renderer and the mpsc channel that will be used to communicate with it.
|
||||||
let (renderer, tx) = Renderer::new(
|
let (renderer, tx) = Renderer::new(
|
||||||
config.clone(),
|
context.clone(),
|
||||||
posts_store.clone(),
|
|
||||||
code_renderer,
|
code_renderer,
|
||||||
config.posts_dir.clone()
|
context.config().content.posts_dir.clone()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dropping the watcher stops its thread, so keep it alive until the server has stopped.
|
// Dropping the watcher stops its thread, so keep it alive until the server has stopped.
|
||||||
let watcher = fs_watcher::start_watching(
|
let watcher = fs_watcher::start_watching(
|
||||||
tx,
|
tx,
|
||||||
&config.posts_dir,
|
&context.config().content.posts_dir,
|
||||||
config.fs_event_delay
|
context.config().fs_event_delay
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let renderer_handle = thread::spawn(move || {
|
let renderer_handle = thread::spawn(move || {
|
||||||
@@ -88,7 +88,7 @@ fn run() -> Result<(), Error> {
|
|||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.map_err(Error::TokioRuntime)?
|
.map_err(Error::TokioRuntime)?
|
||||||
.block_on(run_server(config, posts_store))?;
|
.block_on(run_server(context))?;
|
||||||
|
|
||||||
info!("Stopped server");
|
info!("Stopped server");
|
||||||
|
|
||||||
@@ -105,17 +105,13 @@ fn run() -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_server(
|
async fn run_server(context: Arc<Context>) -> Result<(), Error> {
|
||||||
config: Arc<Config>,
|
let service = service::site_service(context.clone());
|
||||||
posts_store: ConcurrentPostsStore,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
{
|
|
||||||
let service = service::site_service(config.clone(), posts_store);
|
|
||||||
|
|
||||||
info!(address = %config.bind, "Starting server");
|
info!(address = %context.config().bind, "Starting server");
|
||||||
|
|
||||||
Server::try_bind(&config.bind)
|
Server::try_bind(&context.config().bind)
|
||||||
.map_err(|err| Error::Bind(config.bind, err))?
|
.map_err(|err| Error::Bind(context.config().bind, err))?
|
||||||
.serve(service.into_make_service())
|
.serve(service.into_make_service())
|
||||||
.with_graceful_shutdown(handle_interrupt())
|
.with_graceful_shutdown(handle_interrupt())
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ use tracing::{info, warn, error};
|
|||||||
use blog::{
|
use blog::{
|
||||||
codeblock::CodeBlockRenderer,
|
codeblock::CodeBlockRenderer,
|
||||||
post::{Error as ParseError, Post, Id},
|
post::{Error as ParseError, Post, Id},
|
||||||
db::ConcurrentPostsStore,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Context;
|
||||||
|
|
||||||
pub(crate) struct Renderer {
|
pub(crate) struct Renderer {
|
||||||
config: Arc<Config>,
|
context: Arc<Context>,
|
||||||
posts: ConcurrentPostsStore,
|
|
||||||
code_renderer: CodeBlockRenderer,
|
code_renderer: CodeBlockRenderer,
|
||||||
posts_dir_path: PathBuf,
|
posts_dir_path: PathBuf,
|
||||||
rx: mpsc::Receiver<DebouncedEvent>,
|
rx: mpsc::Receiver<DebouncedEvent>,
|
||||||
@@ -28,8 +26,7 @@ pub(crate) struct Renderer {
|
|||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
config: Arc<Config>,
|
context: Arc<Context>,
|
||||||
posts: ConcurrentPostsStore,
|
|
||||||
code_renderer: CodeBlockRenderer,
|
code_renderer: CodeBlockRenderer,
|
||||||
posts_dir_path: PathBuf,
|
posts_dir_path: PathBuf,
|
||||||
) -> (Self, mpsc::Sender<DebouncedEvent>)
|
) -> (Self, mpsc::Sender<DebouncedEvent>)
|
||||||
@@ -42,8 +39,7 @@ impl Renderer {
|
|||||||
tx.send(DebouncedEvent::Rescan).unwrap();
|
tx.send(DebouncedEvent::Rescan).unwrap();
|
||||||
|
|
||||||
(Self {
|
(Self {
|
||||||
config,
|
context,
|
||||||
posts,
|
|
||||||
code_renderer,
|
code_renderer,
|
||||||
posts_dir_path,
|
posts_dir_path,
|
||||||
rx,
|
rx,
|
||||||
@@ -106,7 +102,7 @@ impl Renderer {
|
|||||||
fn update(&self, target: &EventTarget) {
|
fn update(&self, target: &EventTarget) {
|
||||||
match self.parse_post_from_target(target) {
|
match self.parse_post_from_target(target) {
|
||||||
Ok(post) => {
|
Ok(post) => {
|
||||||
let mut guard = self.posts.write_blocking();
|
let mut guard = self.context.posts().write_blocking();
|
||||||
guard.insert(post);
|
guard.insert(post);
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -118,7 +114,7 @@ impl Renderer {
|
|||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn rename(&self, old_target: &EventTarget, new_target: &EventTarget) {
|
fn rename(&self, old_target: &EventTarget, new_target: &EventTarget) {
|
||||||
let post_res = self.parse_post_from_target(new_target);
|
let post_res = self.parse_post_from_target(new_target);
|
||||||
let mut guard = self.posts.write_blocking();
|
let mut guard = self.context.posts().write_blocking();
|
||||||
guard.remove(&old_target.id);
|
guard.remove(&old_target.id);
|
||||||
match post_res {
|
match post_res {
|
||||||
Ok(post) => {
|
Ok(post) => {
|
||||||
@@ -132,7 +128,7 @@ impl Renderer {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn remove(&self, target: &EventTarget) {
|
fn remove(&self, target: &EventTarget) {
|
||||||
let mut guard = self.posts.write_blocking();
|
let mut guard = self.context.posts().write_blocking();
|
||||||
guard.remove(&target.id);
|
guard.remove(&target.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +164,7 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut guard = self.posts.write_blocking();
|
let mut guard = self.context.posts().write_blocking();
|
||||||
guard.clear();
|
guard.clear();
|
||||||
for post in posts {
|
for post in posts {
|
||||||
guard.insert(post);
|
guard.insert(post);
|
||||||
@@ -204,7 +200,7 @@ impl Renderer {
|
|||||||
|
|
||||||
Post::new_from_str(
|
Post::new_from_str(
|
||||||
&self.code_renderer,
|
&self.code_renderer,
|
||||||
self.config.namespace_uuid,
|
self.context.config().namespace_uuid,
|
||||||
target.id.clone(),
|
target.id.clone(),
|
||||||
updated,
|
updated,
|
||||||
&contents
|
&contents
|
||||||
|
|||||||
@@ -3,22 +3,19 @@ use std::sync::Arc;
|
|||||||
use atom_syndication as atom;
|
use atom_syndication as atom;
|
||||||
use axum::{body::Bytes, extract::Extension};
|
use axum::{body::Bytes, extract::Extension};
|
||||||
|
|
||||||
use blog::{db::ConcurrentPostsStore, time::unix_epoch};
|
use blog::time::unix_epoch;
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Context;
|
||||||
|
|
||||||
use super::response::Atom;
|
use super::response::Atom;
|
||||||
|
|
||||||
pub(super) async fn handle(
|
pub(super) async fn handle(Extension(context): Extension<Arc<Context>>) -> Atom<Bytes> {
|
||||||
Extension(config): Extension<Arc<Config>>,
|
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>,
|
|
||||||
) -> Atom<Bytes> {
|
|
||||||
let (atom_entries, updated) = {
|
let (atom_entries, updated) = {
|
||||||
let guard = posts.read().await;
|
let guard = context.posts().read().await;
|
||||||
|
|
||||||
let atom_entries = guard
|
let atom_entries = guard
|
||||||
.iter_by_published()
|
.iter_by_published()
|
||||||
.take(config.atom.num_posts)
|
.take(context.config().atom.num_posts)
|
||||||
.map(|post| {
|
.map(|post| {
|
||||||
atom::EntryBuilder::default()
|
atom::EntryBuilder::default()
|
||||||
.id(format!("urn:uuid:{}", post.uuid()))
|
.id(format!("urn:uuid:{}", post.uuid()))
|
||||||
@@ -27,8 +24,8 @@ pub(super) async fn handle(
|
|||||||
.links(vec![atom::LinkBuilder::default()
|
.links(vec![atom::LinkBuilder::default()
|
||||||
.href(format!(
|
.href(format!(
|
||||||
"{}://{}/articles/{}",
|
"{}://{}/articles/{}",
|
||||||
config.self_ref.protocol,
|
context.config().site.protocol,
|
||||||
config.self_ref.domain,
|
context.config().site.domain,
|
||||||
post.id()
|
post.id()
|
||||||
))
|
))
|
||||||
.rel("alternate".to_owned())
|
.rel("alternate".to_owned())
|
||||||
@@ -50,21 +47,21 @@ pub(super) async fn handle(
|
|||||||
|
|
||||||
Atom(
|
Atom(
|
||||||
atom::FeedBuilder::default()
|
atom::FeedBuilder::default()
|
||||||
.id(format!("urn:uuid:{}", config.namespace_uuid))
|
.id(format!("urn:uuid:{}", context.config().namespace_uuid))
|
||||||
.title(config.atom.title.clone())
|
.title(context.config().atom.title.clone())
|
||||||
.updated(updated)
|
.updated(updated)
|
||||||
.links(vec![
|
.links(vec![
|
||||||
atom::LinkBuilder::default()
|
atom::LinkBuilder::default()
|
||||||
.href(format!(
|
.href(format!(
|
||||||
"{}://{}/atom.xml",
|
"{}://{}/atom.xml",
|
||||||
config.self_ref.protocol, config.self_ref.domain
|
context.config().site.protocol, context.config().site.domain
|
||||||
))
|
))
|
||||||
.rel("self".to_owned())
|
.rel("self".to_owned())
|
||||||
.build(),
|
.build(),
|
||||||
atom::LinkBuilder::default()
|
atom::LinkBuilder::default()
|
||||||
.href(format!(
|
.href(format!(
|
||||||
"{}://{}/articles/",
|
"{}://{}/articles/",
|
||||||
config.self_ref.protocol, config.self_ref.domain
|
context.config().site.protocol, context.config().site.domain
|
||||||
))
|
))
|
||||||
.rel("alternate".to_owned())
|
.rel("alternate".to_owned())
|
||||||
.mime_type(Some("text/html".to_owned()))
|
.mime_type(Some("text/html".to_owned()))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use blog::db::ConcurrentPostsStore;
|
use crate::{Context, template};
|
||||||
|
|
||||||
use crate::template;
|
|
||||||
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub(super) async fn handle(Extension(context): Extension<Arc<Context>>) -> Html {
|
||||||
Html::new()
|
Html::new()
|
||||||
.with_title_static("Pantonshire")
|
.with_title_static("Pantonshire")
|
||||||
.with_crawler_permissive()
|
.with_crawler_permissive()
|
||||||
@@ -49,7 +49,7 @@ pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) ->
|
|||||||
h2 { "Articles" }
|
h2 { "Articles" }
|
||||||
p { "Some recent ones:" }
|
p { "Some recent ones:" }
|
||||||
ul .articles_list {
|
ul .articles_list {
|
||||||
@for post in posts.read().await.iter_by_published().rev().take(3) {
|
@for post in context.posts().read().await.iter_by_published().rev().take(3) {
|
||||||
li {
|
li {
|
||||||
h3 { a href={"/articles/" (post.id())} { (post.title()) } }
|
h3 { a href={"/articles/" (post.id())} { (post.title()) } }
|
||||||
@if let Some(subtitle) = post.subtitle() {
|
@if let Some(subtitle) = post.subtitle() {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::{Extension, Path};
|
use axum::extract::{Extension, Path};
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use blog::db::ConcurrentPostsStore;
|
use crate::{Context, template};
|
||||||
|
|
||||||
use crate::template;
|
|
||||||
|
|
||||||
use super::response::{Error, Html};
|
use super::response::{Error, Html};
|
||||||
|
|
||||||
pub(super) async fn handle(
|
pub(super) async fn handle(
|
||||||
Path(post_id): Path<String>,
|
Path(post_id): Path<String>,
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>
|
Extension(context): Extension<Arc<Context>>,
|
||||||
) -> Result<Html, Error>
|
) -> Result<Html, Error>
|
||||||
{
|
{
|
||||||
let post = posts.get(&post_id)
|
let post = context.posts().get(&post_id)
|
||||||
.await
|
.await
|
||||||
.ok_or(Error::PostNotFound)?;
|
.ok_or(Error::PostNotFound)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use blog::db::ConcurrentPostsStore;
|
use crate::{Context, template};
|
||||||
|
|
||||||
use crate::template;
|
|
||||||
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub(super) async fn handle(Extension(context): Extension<Arc<Context>>) -> Html {
|
||||||
Html::new()
|
Html::new()
|
||||||
.with_title_static("Articles")
|
.with_title_static("Articles")
|
||||||
.with_crawler_permissive()
|
.with_crawler_permissive()
|
||||||
@@ -23,7 +23,7 @@ pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) ->
|
|||||||
"A collection of words I have written, against my better judgement."
|
"A collection of words I have written, against my better judgement."
|
||||||
}
|
}
|
||||||
ul .articles_list {
|
ul .articles_list {
|
||||||
@for post in posts.read().await.iter_by_published().rev() {
|
@for post in context.posts().read().await.iter_by_published().rev() {
|
||||||
li {
|
li {
|
||||||
h3 { a href={"/articles/" (post.id())} { (post.title()) } }
|
h3 { a href={"/articles/" (post.id())} { (post.title()) } }
|
||||||
@if let Some(subtitle) = post.subtitle() {
|
@if let Some(subtitle) = post.subtitle() {
|
||||||
|
|||||||
@@ -5,24 +5,18 @@ use axum::{
|
|||||||
extract::Extension,
|
extract::Extension,
|
||||||
};
|
};
|
||||||
|
|
||||||
use blog::{
|
use blog::time::unix_epoch;
|
||||||
db::ConcurrentPostsStore,
|
|
||||||
time::unix_epoch,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Context;
|
||||||
|
|
||||||
use super::response::Rss;
|
use super::response::Rss;
|
||||||
|
|
||||||
pub(super) async fn handle(
|
pub(super) async fn handle(Extension(context): Extension<Arc<Context>>) -> Rss<Bytes> {
|
||||||
Extension(config): Extension<Arc<Config>>,
|
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>,
|
|
||||||
) -> Rss<Bytes> {
|
|
||||||
let (rss_items, updated) = {
|
let (rss_items, updated) = {
|
||||||
let guard = posts.read().await;
|
let guard = context.posts().read().await;
|
||||||
|
|
||||||
let rss_items = guard.iter_by_published()
|
let rss_items = guard.iter_by_published()
|
||||||
.take(config.rss.num_posts)
|
.take(context.config().rss.num_posts)
|
||||||
.map(|post| {
|
.map(|post| {
|
||||||
rss::ItemBuilder::default()
|
rss::ItemBuilder::default()
|
||||||
.title(Some(post.title().to_owned()))
|
.title(Some(post.title().to_owned()))
|
||||||
@@ -32,8 +26,8 @@ pub(super) async fn handle(
|
|||||||
.build()))
|
.build()))
|
||||||
.link(Some(format!(
|
.link(Some(format!(
|
||||||
"{}://{}/articles/{}",
|
"{}://{}/articles/{}",
|
||||||
config.self_ref.protocol,
|
context.config().site.protocol,
|
||||||
config.self_ref.domain,
|
context.config().site.domain,
|
||||||
post.id()
|
post.id()
|
||||||
)))
|
)))
|
||||||
.pub_date(Some(post.published().to_rfc2822()))
|
.pub_date(Some(post.published().to_rfc2822()))
|
||||||
@@ -48,12 +42,12 @@ pub(super) async fn handle(
|
|||||||
};
|
};
|
||||||
|
|
||||||
Rss(rss::ChannelBuilder::default()
|
Rss(rss::ChannelBuilder::default()
|
||||||
.title(config.rss.title.clone())
|
.title(context.config().rss.title.clone())
|
||||||
.link(format!(
|
.link(format!(
|
||||||
"{}://{}",
|
"{}://{}",
|
||||||
config.self_ref.protocol, config.self_ref.domain
|
context.config().site.protocol, context.config().site.domain
|
||||||
))
|
))
|
||||||
.ttl(Some(config.rss.ttl.to_string()))
|
.ttl(Some(context.config().rss.ttl.to_string()))
|
||||||
.last_build_date(Some(updated.to_rfc2822()))
|
.last_build_date(Some(updated.to_rfc2822()))
|
||||||
.items(rss_items)
|
.items(rss_items)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ use tower::limit::ConcurrencyLimitLayer;
|
|||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use blog::db::ConcurrentPostsStore;
|
use crate::Context;
|
||||||
|
|
||||||
use crate::Config;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
atom,
|
atom,
|
||||||
@@ -26,11 +24,7 @@ use super::{
|
|||||||
static_content,
|
static_content,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn service(
|
pub(crate) fn service(context: Arc<Context>) -> Router {
|
||||||
config: Arc<Config>,
|
|
||||||
posts_store: ConcurrentPostsStore,
|
|
||||||
) -> Router
|
|
||||||
{
|
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(index::handle))
|
.route("/", get(index::handle))
|
||||||
.route("/contact", get(contact::handle))
|
.route("/contact", get(contact::handle))
|
||||||
@@ -38,21 +32,20 @@ pub(crate) fn service(
|
|||||||
.route("/rss.xml", get(rss::handle))
|
.route("/rss.xml", get(rss::handle))
|
||||||
.route("/atom.xml", get(atom::handle))
|
.route("/atom.xml", get(atom::handle))
|
||||||
.route("/articles/:post_id", get(post::handle))
|
.route("/articles/:post_id", get(post::handle))
|
||||||
.route("/robots.txt", static_content::file_service(&config.robots_path, None))
|
.route("/robots.txt", static_content::file_service(&context.config().content.robots_path, None))
|
||||||
.route("/favicon.ico", static_content::file_service(&config.favicon_dir.join("favicon.ico"), None))
|
.route("/favicon.ico", static_content::file_service(&context.config().content.favicon_dir.join("favicon.ico"), None))
|
||||||
.route("/favicon-16x16.png", static_content::file_service(&config.favicon_dir.join("favicon-16x16.png"), None))
|
.route("/favicon-16x16.png", static_content::file_service(&context.config().content.favicon_dir.join("favicon-16x16.png"), None))
|
||||||
.route("/favicon-32x32.png", static_content::file_service(&config.favicon_dir.join("favicon-32x32.png"), None))
|
.route("/favicon-32x32.png", static_content::file_service(&context.config().content.favicon_dir.join("favicon-32x32.png"), None))
|
||||||
.route("/apple-touch-icon.png", static_content::file_service(&config.favicon_dir.join("apple-touch-icon.png"), None))
|
.route("/apple-touch-icon.png", static_content::file_service(&context.config().content.favicon_dir.join("apple-touch-icon.png"), None))
|
||||||
.route("/android-chrome-192x192.png", static_content::file_service(&config.favicon_dir.join("android-chrome-192x192.png"), None))
|
.route("/android-chrome-192x192.png", static_content::file_service(&context.config().content.favicon_dir.join("android-chrome-192x192.png"), None))
|
||||||
.route("/android-chrome-512x512.png", static_content::file_service(&config.favicon_dir.join("android-chrome-512x512.png"), None))
|
.route("/android-chrome-512x512.png", static_content::file_service(&context.config().content.favicon_dir.join("android-chrome-512x512.png"), None))
|
||||||
.route("/site.webmanifest", static_content::file_service(&config.favicon_dir.join("site.webmanifest"), None))
|
.route("/site.webmanifest", static_content::file_service(&context.config().content.favicon_dir.join("site.webmanifest"), None))
|
||||||
.nest("/static", static_content::dir_service(&config.static_dir))
|
.nest("/static", static_content::dir_service(&context.config().content.static_dir))
|
||||||
.nest("/article_media", static_content::dir_service(&config.post_media_dir))
|
.nest("/article_media", static_content::dir_service(&context.config().content.post_media_dir))
|
||||||
.fallback(handle_fallback.into_service())
|
.fallback(handle_fallback.into_service())
|
||||||
.layer(ConcurrencyLimitLayer::new(config.concurrency_limit))
|
.layer(ConcurrencyLimitLayer::new(context.config().concurrency_limit))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
.layer(Extension(config))
|
.layer(Extension(context))
|
||||||
.layer(Extension(posts_store))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_fallback(uri: Uri) -> Error {
|
async fn handle_fallback(uri: Uri) -> Error {
|
||||||
|
|||||||
+2
-3
@@ -10,14 +10,13 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|||||||
|
|
||||||
use crate::post::{Post, Id};
|
use crate::post::{Post, Id};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ConcurrentPostsStore {
|
pub struct ConcurrentPostsStore {
|
||||||
inner: Arc<RwLock<PostsStore>>,
|
inner: RwLock<PostsStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConcurrentPostsStore {
|
impl ConcurrentPostsStore {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { inner: Arc::new(RwLock::new(PostsStore::new())) }
|
Self { inner: RwLock::new(PostsStore::new()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read(&self) -> RwLockReadGuard<'_, PostsStore> {
|
pub async fn read(&self) -> RwLockReadGuard<'_, PostsStore> {
|
||||||
|
|||||||
Reference in new issue
Block a user