Compare commits
21 commits
release/v0
...
master
Author | SHA1 | Date | |
---|---|---|---|
Gered | e9df91ff16 | ||
Gered | 4354157aab | ||
Gered | 07fda97781 | ||
Gered | e4aa4afcec | ||
Gered | 7fab84f1cb | ||
Gered | 64b8a678a4 | ||
Gered | 746f92f52b | ||
Gered | 2aba6152bd | ||
Gered | 0d75ca6caf | ||
Gered | 31fd5d444b | ||
Gered | 0b8423a64f | ||
Gered | 4cce2d4ad7 | ||
Gered | 54b431810e | ||
Gered | 672408c3eb | ||
Gered | 3427b954ed | ||
Gered | c7665fdc79 | ||
Gered | cff659b072 | ||
Gered | 7c1d42f870 | ||
Gered | d98cef8af9 | ||
Gered | 39e35c19db | ||
Gered | 328018834d |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,4 +2,4 @@
|
|||
.DS_Store
|
||||
/.idea
|
||||
/artifacts
|
||||
/release.tar.gz
|
||||
/*.tar.gz
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1355,7 +1355,7 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
|||
|
||||
[[package]]
|
||||
name = "pbe"
|
||||
version = "0.1.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "pbe"
|
||||
description = "Personal Blog Engine. Gered's umpteenth take on a custom blog."
|
||||
version = "0.1.0"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -22,4 +22,14 @@ syntect = "5.0.0"
|
|||
tera = "1.19.0"
|
||||
thiserror = "1.0.40"
|
||||
tokio = "1"
|
||||
url = "2.4.0"
|
||||
url = "2.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
chrono = "0.4.26"
|
||||
|
||||
[profile.release]
|
||||
# settings that are mainly intended to reduce the size of the release binary while keeping the release binary tuned
|
||||
# for performance. these alone reduce the binary size by over 50%, which is good enough in my opinion ...
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
|
24
README.md
24
README.md
|
@ -31,7 +31,7 @@ pbe /path/to/example-site
|
|||
|
||||
The argument provided to `pbe` is the **root site path**. If not specified, the current working directory is used.
|
||||
|
||||
Once started up successfully, you can access this site in your browser at [http://localhost:8080/].
|
||||
Once started up successfully, you can access this site in your browser at http://localhost:8080/.
|
||||
|
||||
## Overview
|
||||
|
||||
|
@ -102,7 +102,7 @@ This is the main configuration file which controls how the website is accessed a
|
|||
|
||||
| Key | Required? | Description |
|
||||
|---------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `bind_addr` | Yes | The hostname/IP to bind the HTTP server on. Usual values would be something like `0.0.0.0` or `127.0.0.1`. |
|
||||
| `bind_addr` | Yes | The IP address of the network interface to bind the HTTP server on. Usual values would be something like `0.0.0.0` or `127.0.0.1`. |
|
||||
| `bind_port` | Yes | The port to bind the HTTP server on. For example, `8080`. |
|
||||
| `static_files_path` | Yes | The **relative** path to the directory containing all public web accessible files, e.g. CSS files, images, etc. |
|
||||
| `templates_path` | Yes | The **relative** path to the directory containing all HTML templates. |
|
||||
|
@ -122,7 +122,7 @@ top-level `pages` key. Each page can contain the following:
|
|||
|------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `file_path` | Yes | The path (relative to `pages_path` found in `server.yml`) to the HTML, Markdown or plain text content for this page. |
|
||||
| `title` | Yes | The title of the page. This is what will be visible on the website itself. |
|
||||
| `url` | Yes | The URL this page can be accessed at. This is just the path component of the URL, e.g. `/my-page/`. |
|
||||
| `url` | Yes | The URL this page can be accessed at. This is just the path component of the URL, e.g. `/my-page`. |
|
||||
| `alternate_urls` | No | A list of alternate URLs this page can be accessed at. If provided, each of these URLs will result in a redirect response to the main page URL. This is provided mainly as an aide in transitioning from another website which may have served content at different URLs. |
|
||||
|
||||
An example file may look like the following:
|
||||
|
@ -132,13 +132,13 @@ pages:
|
|||
|
||||
- file_path: about.md
|
||||
title: About This Site
|
||||
url: /about/
|
||||
url: /about
|
||||
|
||||
- file_path: joke.md
|
||||
title: Joke
|
||||
url: /joke/
|
||||
url: /joke
|
||||
alternate_urls:
|
||||
- /trying-to-be-funny/
|
||||
- /trying-to-be-funny
|
||||
```
|
||||
|
||||
### `posts.yml`
|
||||
|
@ -204,7 +204,7 @@ rss:
|
|||
#### Post URLs
|
||||
|
||||
Post URLs are automatically derived from a combination of the `date` and `slug` defined for each post using the format
|
||||
`/year/month/day/slug/`.
|
||||
`/year/month/day/slug`.
|
||||
|
||||
For example for the post
|
||||
|
||||
|
@ -215,7 +215,7 @@ date: 2023-03-20 18:01
|
|||
slug: lorem-ipsum
|
||||
```
|
||||
|
||||
The URL would end up being `/2023/03/20/lorem-ipsum/` (note the trailing slash).
|
||||
The URL would end up being `/2023/03/20/lorem-ipsum`.
|
||||
|
||||
## Writing Content
|
||||
|
||||
|
@ -273,7 +273,7 @@ Displays any single page at the individual page's URL. Normally this would displ
|
|||
|
||||
### `tag.html`
|
||||
|
||||
Displays posts for a given tag, at the tag's URL `/tag/{tag-name}/`. Normally this would display the tag and then all
|
||||
Displays posts for a given tag, at the tag's URL `/tag/{tag-name}`. Normally this would display the tag and then all
|
||||
the posts in a list format.
|
||||
|
||||
| Key | Type | Description |
|
||||
|
@ -283,7 +283,7 @@ the posts in a list format.
|
|||
|
||||
### `archive.html`
|
||||
|
||||
Displays all posts, at the archive URL `/archive/`. Normally this would be a simple list of all posts showing their
|
||||
Displays all posts, at the archive URL `/archive`. Normally this would be a simple list of all posts showing their
|
||||
titles, dates, and tags.
|
||||
|
||||
### `latest_post.html`
|
||||
|
@ -304,7 +304,7 @@ Contains all information about a single post.
|
|||
|
||||
| Field | Type | Description |
|
||||
|----------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `url` | `string` | The post's URL, e.g. `/2023/06/30/hello-world/` |
|
||||
| `url` | `string` | The post's URL, e.g. `/2023/06/30/hello-world` |
|
||||
| `title` | `string` | The post's title, as defined in `posts.yml`. |
|
||||
| `date` | `int` | The date/time of the post, as defined in `posts.yml`, converted to seconds since Jan 1, 1970. You can use [Tera's `date` filter](https://tera.netlify.app/docs/#date) to display this in a formatted way. |
|
||||
| `tags` | `string[]` | The post's tags, as defined in `posts.yml`. This may be an empty list if no tags were specified for the post. |
|
||||
|
@ -316,7 +316,7 @@ Contains all information about a single page.
|
|||
|
||||
| Field | Type | Description |
|
||||
|----------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `url` | `string` | The page's URL, as defined in `pages.yml`, e.g. `/my-page/` |
|
||||
| `url` | `string` | The page's URL, as defined in `pages.yml`, e.g. `/my-page` |
|
||||
| `title` | `string` | The page's title, as defined in `pages.yml`. |
|
||||
| `content_html` | `string` | The page's content, rendered as HTML. Most of the time, you'd want to display this in your template using [Tera's `safe` filter](https://tera.netlify.app/docs/#safe) to ensure HTML tags are not escaped. |
|
||||
|
||||
|
|
10
build.rs
Normal file
10
build.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let git_output = Command::new("git").args(&["rev-parse", "--short", "HEAD"]).output().unwrap();
|
||||
let git_hash = String::from_utf8(git_output.stdout).unwrap();
|
||||
println!("cargo:rustc-env=GIT_HASH={git_hash}");
|
||||
|
||||
let build_ts = chrono::Utc::now();
|
||||
println!("cargo:rustc-env=BUILD_TS={build_ts:?}");
|
||||
}
|
|
@ -2,10 +2,10 @@ pages:
|
|||
|
||||
- file_path: about.md
|
||||
title: About This Site
|
||||
url: /about/
|
||||
url: /about
|
||||
|
||||
- file_path: joke.md
|
||||
title: Joke
|
||||
url: /joke/
|
||||
url: /joke
|
||||
alternate_urls:
|
||||
- /trying-to-be-funny/
|
||||
- /trying-to-be-funny
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<a href="{{ post.url }}">{{ post.title }}</a>
|
||||
<span class="tags">
|
||||
{%- for tag in post.tags -%}
|
||||
<span><a href="/tag/{{ tag }}/">{{ tag }}</a></span>
|
||||
<span><a href="/tag/{{ tag }}">{{ tag }}</a></span>
|
||||
{%- endfor -%}
|
||||
</span>
|
||||
</td>
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
<h2>My Site</h2>
|
||||
<nav>
|
||||
<a href="/">Home</a> |
|
||||
<a href="/archive/">Archive</a> |
|
||||
<a href="/about/">About</a>
|
||||
<a href="/archive">Archive</a> |
|
||||
<a href="/about">About</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{{ post.date | date(format="%B %e, %Y") }} —
|
||||
<span class="tags">
|
||||
{%- for tag in post.tags -%}
|
||||
<span><a href="/tag/{{ tag }}/">{{ tag }}</a></span>
|
||||
<span><a href="/tag/{{ tag }}">{{ tag }}</a></span>
|
||||
{%- endfor -%}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
package_version=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
|
||||
current_target=$(rustc -vV | sed -n 's|host: ||p')
|
||||
|
||||
package_filename="pbe-v${package_version}-${current_target}.tar.gz"
|
||||
|
||||
rm -f ./artifacts/bin/pbe
|
||||
rm -f ./artifacts/bin/syntax_to_css
|
||||
|
||||
cargo clean
|
||||
cargo install --path . --root ./artifacts/
|
||||
cargo install --path ./syntax_to_css/ --root ./artifacts/
|
||||
|
||||
cd ./artifacts/bin
|
||||
tar -cvf ../../release.tar.gz ./pbe ./syntax_to_css
|
||||
tar -cvf $package_filename \
|
||||
README.md \
|
||||
LICENSE \
|
||||
example-site \
|
||||
-C ./artifacts/bin \
|
||||
pbe \
|
||||
syntax_to_css
|
||||
|
||||
echo "Packaged release as ${package_filename}"
|
||||
|
|
|
@ -2,6 +2,8 @@ use std::fs::File;
|
|||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::util::drop_trailing_slash;
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct Server {
|
||||
pub bind_addr: String,
|
||||
|
@ -94,11 +96,22 @@ pub fn load_content(
|
|||
let mut pages: Pages = load_config(pages_path)?;
|
||||
for page in pages.pages.iter_mut() {
|
||||
page.file_path = [&server_config.pages_path, &page.file_path].iter().collect();
|
||||
drop_trailing_slash(&mut page.url);
|
||||
if let Some(alternate_urls) = &mut page.alternate_urls {
|
||||
for alternate_url in alternate_urls.iter_mut() {
|
||||
drop_trailing_slash(alternate_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("Loading posts config from {:?}", posts_path);
|
||||
let mut posts: Posts = load_config(posts_path)?;
|
||||
for post in posts.posts.iter_mut() {
|
||||
post.file_path = [&server_config.posts_path, &post.file_path].iter().collect();
|
||||
if let Some(alternate_urls) = &mut post.alternate_urls {
|
||||
for alternate_url in alternate_urls.iter_mut() {
|
||||
drop_trailing_slash(alternate_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((pages, posts))
|
||||
}
|
||||
|
|
59
src/main.rs
59
src/main.rs
|
@ -2,54 +2,16 @@ use std::env;
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use actix_files::Files;
|
||||
use actix_web::web::Redirect;
|
||||
use actix_web::{web, App, Either, HttpRequest, HttpResponse, HttpServer, Responder};
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use anyhow::Context;
|
||||
|
||||
mod config;
|
||||
mod markdown;
|
||||
mod routes;
|
||||
mod site;
|
||||
mod util;
|
||||
mod watcher;
|
||||
|
||||
fn not_found() -> HttpResponse {
|
||||
HttpResponse::NotFound().body("not found")
|
||||
}
|
||||
|
||||
#[actix_web::get("/")]
|
||||
async fn latest_posts(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET / -> latest_posts()");
|
||||
data.serve_latest_post()
|
||||
}
|
||||
|
||||
#[actix_web::get("/tag/{tag}/")]
|
||||
async fn latest_posts_by_tag(path: web::Path<(String,)>, data: web::Data<site::SiteService>) -> impl Responder {
|
||||
let tag = path.into_inner().0;
|
||||
log::debug!("GET /tag/{0}/ -> latest_posts_by_tag(), tag = {0}", tag);
|
||||
data.serve_posts_by_tag(&tag)
|
||||
}
|
||||
|
||||
#[actix_web::get("/archive/")]
|
||||
async fn posts_archive(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET /archive/ -> posts_archive()");
|
||||
data.serve_posts_archive()
|
||||
}
|
||||
|
||||
#[actix_web::get("/rss/")]
|
||||
async fn rss_feed(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET /rss/ -> rss_feed()");
|
||||
data.serve_rss_feed()
|
||||
}
|
||||
|
||||
async fn site_content(req: HttpRequest, data: web::Data<site::SiteService>) -> Either<HttpResponse, Redirect> {
|
||||
log::debug!("GET {} -> fallback to site_content()", req.path());
|
||||
if let Some(response) = data.serve_content_by_url(&req) {
|
||||
response
|
||||
} else {
|
||||
Either::Left(not_found())
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_watcher(
|
||||
watch_paths: Vec<PathBuf>,
|
||||
pages_config_path: PathBuf,
|
||||
|
@ -112,6 +74,12 @@ async fn main() -> anyhow::Result<()> {
|
|||
.map_err(|err| anyhow::anyhow!(err))?;
|
||||
|
||||
println!("PBE - Personal Blog Engine - https://github.com/gered/pbe");
|
||||
println!(
|
||||
"Build version {0}, git hash {1}, built at {2}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("GIT_HASH"),
|
||||
env!("BUILD_TS")
|
||||
);
|
||||
|
||||
// manually handling args because
|
||||
// 1) i have very simple needs
|
||||
|
@ -170,12 +138,13 @@ async fn main() -> anyhow::Result<()> {
|
|||
HttpServer::new(move || {
|
||||
App::new() //
|
||||
.app_data(data.clone())
|
||||
.service(latest_posts)
|
||||
.service(latest_posts_by_tag)
|
||||
.service(posts_archive)
|
||||
.service(rss_feed)
|
||||
.wrap(actix_web::middleware::NormalizePath::trim())
|
||||
.service(routes::latest_posts)
|
||||
.service(routes::latest_posts_by_tag)
|
||||
.service(routes::posts_archive)
|
||||
.service(routes::rss_feed)
|
||||
.service(Files::new("/", &server_config.static_files_path))
|
||||
.default_service(web::get().to(site_content))
|
||||
.default_service(web::get().to(routes::site_content))
|
||||
})
|
||||
.bind((server_config.bind_addr.clone(), server_config.bind_port))
|
||||
.with_context(|| format!("Binding HTTP server on {}:{}", server_config.bind_addr, server_config.bind_port))?
|
||||
|
|
|
@ -107,7 +107,9 @@ impl MarkdownRenderer {
|
|||
}
|
||||
|
||||
pub fn render_to_html(&self, s: &str) -> Result<String, MarkdownError> {
|
||||
let parser = Parser::new_ext(s, pulldown_cmark::Options::all());
|
||||
let mut options = pulldown_cmark::Options::all();
|
||||
options.set(pulldown_cmark::Options::ENABLE_SMART_PUNCTUATION, false);
|
||||
let parser = Parser::new_ext(s, options);
|
||||
let events = self.highlight_codeblocks(parser)?;
|
||||
let mut output = String::new();
|
||||
pulldown_cmark::html::push_html(&mut output, events);
|
||||
|
|
45
src/routes.rs
Normal file
45
src/routes.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use actix_web::web::Redirect;
|
||||
use actix_web::{web, Either, HttpRequest, HttpResponse, Responder};
|
||||
|
||||
use crate::site;
|
||||
|
||||
fn not_found() -> HttpResponse {
|
||||
HttpResponse::NotFound().body("not found")
|
||||
}
|
||||
|
||||
#[actix_web::route("/", method = "GET", method = "HEAD")]
|
||||
pub async fn latest_posts(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET / -> latest_posts()");
|
||||
data.serve_latest_post()
|
||||
}
|
||||
|
||||
#[actix_web::route("/tag/{tag}", method = "GET", method = "HEAD")]
|
||||
pub async fn latest_posts_by_tag(path: web::Path<(String,)>, data: web::Data<site::SiteService>) -> impl Responder {
|
||||
let tag = path.into_inner().0;
|
||||
log::debug!("GET /tag/{0} -> latest_posts_by_tag(), tag = {0}", tag);
|
||||
data.serve_posts_by_tag(&tag)
|
||||
}
|
||||
|
||||
#[actix_web::route("/archive", method = "GET", method = "HEAD")]
|
||||
pub async fn posts_archive(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET /archive -> posts_archive()");
|
||||
data.serve_posts_archive()
|
||||
}
|
||||
|
||||
#[actix_web::route("/rss", method = "GET", method = "HEAD")]
|
||||
pub async fn rss_feed(data: web::Data<site::SiteService>) -> impl Responder {
|
||||
log::debug!("GET /rss -> rss_feed()");
|
||||
data.serve_rss_feed()
|
||||
}
|
||||
|
||||
pub async fn site_content(
|
||||
req: HttpRequest,
|
||||
data: web::Data<site::SiteService>,
|
||||
) -> Result<Either<HttpResponse, Redirect>, site::SiteError> {
|
||||
log::debug!("GET {} -> fallback to site_content()", req.path());
|
||||
if let Some(response) = data.serve_content_by_url(&req)? {
|
||||
Ok(response)
|
||||
} else {
|
||||
Ok(Either::Left(not_found()))
|
||||
}
|
||||
}
|
55
src/site.rs
55
src/site.rs
|
@ -3,6 +3,9 @@ use std::ops::Deref;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Redirect;
|
||||
use actix_web::{Either, HttpRequest, HttpResponse};
|
||||
use chrono::{Datelike, TimeZone};
|
||||
|
@ -62,6 +65,16 @@ pub enum SiteError {
|
|||
TeraError(#[from] tera::Error),
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for SiteError {
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
log::error!("Error response: {:?}", self);
|
||||
let status_code = self.status_code();
|
||||
HttpResponse::build(status_code) //
|
||||
.content_type(ContentType::plaintext())
|
||||
.body(format!("{status_code}\n\n{:#?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AlternateUrlMappings {
|
||||
mapping: HashMap<UriPath, UriPath>,
|
||||
}
|
||||
|
@ -129,7 +142,13 @@ pub struct Post {
|
|||
|
||||
impl Post {
|
||||
pub fn try_from(value: config::Post, content_renderer: &ContentRenderer) -> Result<Self, SiteError> {
|
||||
let url = format!("/{:04}/{:02}/{:02}/{}", value.date.year(), value.date.month(), value.date.day(), value.slug);
|
||||
let url = format!(
|
||||
"/{:04}/{:02}/{:02}/{}", //
|
||||
value.date.year(),
|
||||
value.date.month(),
|
||||
value.date.day(),
|
||||
value.slug
|
||||
);
|
||||
let content_html = content_renderer.render(&value.file_path)?;
|
||||
let tags = value.tags.map_or_else(|| Vec::new(), |x| x.clone());
|
||||
Ok(Post {
|
||||
|
@ -347,34 +366,37 @@ impl SiteService {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serve_latest_post(&self) -> HttpResponse {
|
||||
pub fn serve_latest_post(&self) -> Result<HttpResponse, SiteError> {
|
||||
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
|
||||
let post = content.get_latest_post();
|
||||
let mut context = tera::Context::new();
|
||||
if let Some(post) = post {
|
||||
context.insert("post", post);
|
||||
}
|
||||
HttpResponse::Ok().body(content.template_renderer.render("latest_post.html", &context).unwrap())
|
||||
let response_body = content.template_renderer.render("latest_post.html", &context)?;
|
||||
Ok(HttpResponse::Ok().content_type(ContentType::html()).body(response_body))
|
||||
}
|
||||
|
||||
pub fn serve_posts_by_tag(&self, tag: &Tag) -> HttpResponse {
|
||||
pub fn serve_posts_by_tag(&self, tag: &Tag) -> Result<HttpResponse, SiteError> {
|
||||
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
|
||||
let posts = content.get_posts_with_tag_ordered_by_date(tag);
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("tag", tag);
|
||||
context.insert("posts", &posts);
|
||||
HttpResponse::Ok().body(content.template_renderer.render("tag.html", &context).unwrap())
|
||||
let response_body = content.template_renderer.render("tag.html", &context)?;
|
||||
Ok(HttpResponse::Ok().content_type(ContentType::html()).body(response_body))
|
||||
}
|
||||
|
||||
pub fn serve_posts_archive(&self) -> HttpResponse {
|
||||
pub fn serve_posts_archive(&self) -> Result<HttpResponse, SiteError> {
|
||||
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
|
||||
let posts = content.get_posts_ordered_by_date();
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("posts", &posts);
|
||||
HttpResponse::Ok().body(content.template_renderer.render("archive.html", &context).unwrap())
|
||||
let response_body = content.template_renderer.render("archive.html", &context)?;
|
||||
Ok(HttpResponse::Ok().content_type(ContentType::html()).body(response_body))
|
||||
}
|
||||
|
||||
pub fn serve_rss_feed(&self) -> HttpResponse {
|
||||
pub fn serve_rss_feed(&self) -> Result<HttpResponse, SiteError> {
|
||||
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
|
||||
let base_url = url::Url::parse(&content.rss.url).unwrap();
|
||||
let posts = content.get_posts_ordered_by_date();
|
||||
|
@ -397,10 +419,11 @@ impl SiteService {
|
|||
})
|
||||
.collect::<Vec<rss::Item>>(),
|
||||
);
|
||||
HttpResponse::Ok().content_type("application/rss+xml").body(channel.to_string())
|
||||
let response_body = channel.to_string();
|
||||
Ok(HttpResponse::Ok().content_type("application/rss+xml").body(response_body))
|
||||
}
|
||||
|
||||
pub fn serve_content_by_url(&self, req: &HttpRequest) -> Option<Either<HttpResponse, Redirect>> {
|
||||
pub fn serve_content_by_url(&self, req: &HttpRequest) -> Result<Option<Either<HttpResponse, Redirect>>, SiteError> {
|
||||
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
|
||||
let url = String::from(req.path());
|
||||
match content.get_content_at(&url) {
|
||||
|
@ -408,23 +431,23 @@ impl SiteService {
|
|||
log::debug!("Found page content at {}", req.path());
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("page", page);
|
||||
let rendered = content.template_renderer.render("page.html", &context).unwrap();
|
||||
Some(Either::Left(HttpResponse::Ok().body(rendered)))
|
||||
let rendered = content.template_renderer.render("page.html", &context)?;
|
||||
Ok(Some(Either::Left(HttpResponse::Ok().content_type(ContentType::html()).body(rendered))))
|
||||
}
|
||||
Some(Content::Post(post)) => {
|
||||
log::debug!("Found post content at {}", req.path());
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("post", post);
|
||||
let rendered = content.template_renderer.render("post.html", &context).unwrap();
|
||||
Some(Either::Left(HttpResponse::Ok().body(rendered)))
|
||||
let rendered = content.template_renderer.render("post.html", &context)?;
|
||||
Ok(Some(Either::Left(HttpResponse::Ok().content_type(ContentType::html()).body(rendered))))
|
||||
}
|
||||
Some(Content::Redirect(url)) => {
|
||||
log::debug!("Found redirect at {}", req.path());
|
||||
Some(Either::Right(Redirect::to(url).permanent()))
|
||||
Ok(Some(Either::Right(Redirect::to(url).using_status_code(StatusCode::MOVED_PERMANENTLY))))
|
||||
}
|
||||
None => {
|
||||
log::debug!("No matching content at {}", req.path());
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,3 +28,9 @@ pub fn serialize_naivedatetime_to_i64<S: serde::Serializer>(
|
|||
) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_i64(value.timestamp())
|
||||
}
|
||||
|
||||
pub fn drop_trailing_slash(s: &mut String) {
|
||||
if s.ends_with("/") {
|
||||
s.pop();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue