Skip to content

Commit

Permalink
Drop the "default" theme and support Zola's Hyde theme instead.
Browse files Browse the repository at this point in the history
  • Loading branch information
ibz committed Jul 30, 2024
1 parent f90f091 commit 6fb2eb6
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 779 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
sites/
target/
themes/
Cargo.lock
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ bitcoin_hashes = { version = "0.12", features = ["serde"] }
chrono = { version = "0", features = ["serde"] }
clap = { version = "4", features = ["derive"] }
femme = "2"
globset = "0.4"
grass = {version = "0.13", default-features = false, features = ["random"]}
http-types = "2"
lazy_static = "1.4"
markdown = "1.0.0-alpha.3"
Expand Down
25 changes: 7 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Things are definitely going to improve, but I am too busy building a solid found

## Themes

Not only there is no stable UI, but there are no usable themes included.
Servus currently supports Zola's [Hyde](https://github.com/getzola/hyde/) theme.

## Building

Expand Down Expand Up @@ -136,30 +136,20 @@ Each of these "sites" has the following structure:
│ └── posts
│ ├── yyyy-mm-dd-post1.md
│ └── [...]
├── _layouts
│ ├── includes
│ │ └── [...]
│ ├── base.html
│ ├── note.html
│ ├── page.html
│ └── post.html
├── favicon.ico
└── [...]
```

Files and directories starting with "." are ignored.

Files and directories starting with "_" have special meaning: `_config.toml`, `_content`, `_layouts`.
Files and directories starting with "_" have special meaning: `_config.toml`, `_content`.

Anything else will be directly served to the clients requesting it.

## _config.toml

Every site needs a config file which has one section named `site`.

All properties present under `[site]` are passed directly to the templates: `title` becomes `site.title`, `url` becomes `site.url`, etc.

`post_permalink`, if specified, is used to generate permalinks for posts by replacing `:slug` with the actual *slug* of the post. If not specified, it defaults to `/posts/:slug`.
Required: `base_url`, `theme`.
Optional: `pubkey`, `title`.

`pubkey`, if specified, is used to enable posting using the Nostr protocol. Only events from the specified pubkey will be accepted, after validating the signature.

Expand All @@ -173,14 +163,13 @@ The following variables are passed to the templates:

* `data` - any data loaded from YAML files in `_content/data/`
* `posts` - a list of all the posts
* `resource` - the current resource (post / page / note) being rendered
* `page` - the current resource (post / page / note) being rendered
* `servus.version` - the version of Servus currently running
* `site` - the `[site]` section in `_config.toml`
* `config` - the values from `_config.toml`

### Resource variables

* `resource.date` - the date associated with this resource (post / note)
* `resource.url` - the URL of this resource
* `page.date` - the date associated with this resource (post / note)

## Posting

Expand Down
40 changes: 0 additions & 40 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ fn main() {

let admin_index_html = fs::read_to_string("admin/index.html").unwrap();

let index_md = fs::read_to_string("themes/default/_content/pages/index.md").unwrap();
let posts_md = fs::read_to_string("themes/default/_content/pages/posts.md").unwrap();

let base_html = fs::read_to_string("themes/default/_layouts/base.html").unwrap();
let note_html = fs::read_to_string("themes/default/_layouts/note.html").unwrap();
let page_html = fs::read_to_string("themes/default/_layouts/page.html").unwrap();
let post_html = fs::read_to_string("themes/default/_layouts/post.html").unwrap();

let style_css = fs::read_to_string("themes/default/style.css").unwrap();

let out_dir = std::env::var_os("OUT_DIR").unwrap();

std::fs::write(
Expand All @@ -25,34 +15,4 @@ pub const INDEX_HTML: &str = r#"%%index_html%%"#;
.replace("%%index_html%%", &admin_index_html),
)
.unwrap();

std::fs::write(
std::path::Path::new(&out_dir).join("default_theme.rs"),
r##"
use std::{fs, io::Write};
fn get_path(site_path: &str, extra: &str) -> std::path::PathBuf {
[site_path, extra].iter().collect()
}
const INDEX_MD: &str = r"%%index_md%%";
const POSTS_MD: &str = r"%%posts_md%%";
const BASE_HTML: &str = r#"%%base_html%%"#;
const NOTE_HTML: &str = r#"%%note_html%%"#;
const PAGE_HTML: &str = r#"%%page_html%%"#;
const POST_HTML: &str = r#"%%post_html%%"#;
const STYLE_CSS: &str = r#"%%style_css%%"#;
pub fn generate(site_path: &str) {
fs::create_dir_all(get_path(site_path, "_content/pages")).unwrap();
fs::create_dir_all(get_path(site_path, "_layouts")).unwrap();
write!(fs::File::create(get_path(site_path, "_content/pages/index.md")).unwrap(), "{}", INDEX_MD).unwrap();
write!(fs::File::create(get_path(site_path, "_content/pages/posts.md")).unwrap(), "{}", POSTS_MD).unwrap();
write!(fs::File::create(get_path(site_path, "_layouts/base.html")).unwrap(), "{}", BASE_HTML).unwrap();
write!(fs::File::create(get_path(site_path, "_layouts/note.html")).unwrap(), "{}", NOTE_HTML).unwrap();
write!(fs::File::create(get_path(site_path, "_layouts/page.html")).unwrap(), "{}", PAGE_HTML).unwrap();
write!(fs::File::create(get_path(site_path, "_layouts/post.html")).unwrap(), "{}", POST_HTML).unwrap();
write!(fs::File::create(get_path(site_path, "style.css")).unwrap(), "{}", STYLE_CSS).unwrap();
}
"##.replace("%%index_md%%", &index_md).replace("%%posts_md%%", &posts_md).replace("%%base_html%%", &base_html).replace("%%note_html%%", &note_html).replace("%%page_html%%", &page_html).replace("%%post_html%%", &post_html).replace("%%style_css%%", &style_css),
)
.unwrap();
}
72 changes: 48 additions & 24 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ mod admin {

mod content;
mod nostr;
mod resource;
mod sass;
mod site;
mod template;
mod theme;

use site::Site;
use theme::Theme;

#[derive(Parser)]
struct Cli {
Expand All @@ -50,6 +55,7 @@ struct Cli {

#[derive(Clone)]
struct State {
themes: Arc<RwLock<HashMap<String, Theme>>>,
sites: Arc<RwLock<HashMap<String, Site>>>,
}

Expand Down Expand Up @@ -85,12 +91,12 @@ fn build_raw_response(content: Vec<u8>, mime: mime::Mime) -> Response {

fn render_and_build_response(site: &Site, resource_path: String) -> Response {
let resources = site.resources.read().unwrap();
let event_ref = resources.get(&resource_path).unwrap();
let resource = resources.get(&resource_path).unwrap();

Response::builder(StatusCode::Ok)
.content_type(mime::HTML)
.header("Access-Control-Allow-Origin", "*")
.body(&*event_ref.render(site))
.body(&*resource.render(site))
.build()
}

Expand All @@ -104,8 +110,8 @@ async fn handle_websocket(
nostr::Message::Event(cmd) => {
{
if let Some(site) = get_site(&request) {
if let Some(site_pubkey) = site.config.get("pubkey") {
if cmd.event.pubkey != site_pubkey.as_str().unwrap() {
if let Some(site_pubkey) = site.config.pubkey {
if cmd.event.pubkey != site_pubkey {
log::info!(
"Ignoring event for unknown pubkey: {}.",
cmd.event.pubkey
Expand Down Expand Up @@ -291,26 +297,37 @@ async fn handle_request(request: Request<State>) -> tide::Result<Response> {
}

if let Some(site) = get_site(&request) {
if let Some((mime, response)) = site::render_standard_resource(path, &site) {
if let Some((mime, response)) = resource::render_standard_resource(path, &site) {
return Ok(Response::builder(StatusCode::Ok)
.content_type(mime)
.header("Access-Control-Allow-Origin", "*")
.body(response)
.build());
}

let existing_posts: Vec<String>;
let site_resources: Vec<String>;
{
let posts = site.resources.read().unwrap();
existing_posts = posts.keys().cloned().collect();
let resources = site.resources.read().unwrap();
site_resources = resources.keys().cloned().collect();
}

let themes = request.state().themes.read().unwrap();
let theme = themes.get(&site.config.theme.clone().unwrap()).unwrap();

let mut resource_path = format!("/{}", &path);
if existing_posts.contains(&resource_path) {
Ok(render_and_build_response(&site, resource_path))
if site_resources.contains(&resource_path) {
return Ok(render_and_build_response(&site, resource_path));
} else {
let theme_resources = theme.resources.read().unwrap();
if theme_resources.contains_key(&resource_path) {
let content = theme_resources.get(&resource_path).unwrap();
let guess = mime_guess::from_path(resource_path);
let mime = mime::Mime::from_str(guess.first().unwrap().essence_str()).unwrap();
return Ok(build_raw_response(content.as_bytes().to_vec(), mime));
}
resource_path = format!("{}/index", &resource_path);
if existing_posts.contains(&resource_path) {
Ok(render_and_build_response(&site, resource_path))
if site_resources.contains(&resource_path) {
return Ok(render_and_build_response(&site, resource_path));
} else {
resource_path = format!("{}/{}", site.path, path);
for part in resource_path.split('/').collect::<Vec<_>>() {
Expand All @@ -324,7 +341,7 @@ async fn handle_request(request: Request<State>) -> tide::Result<Response> {
let raw_content = fs::read(&resource_path).unwrap();
let guess = mime_guess::from_path(resource_path);
let mime = mime::Mime::from_str(guess.first().unwrap().essence_str()).unwrap();
Ok(build_raw_response(raw_content, mime))
return Ok(build_raw_response(raw_content, mime));
} else {
// look for an uploaded file
if let Some(sha256) = sha256 {
Expand All @@ -340,12 +357,12 @@ async fn handle_request(request: Request<State>) -> tide::Result<Response> {
let metadata: FileMetadata =
serde_json::from_reader(metadata_reader).unwrap();
let mime = mime::Mime::from_str(&metadata.content_type).unwrap();
Ok(build_raw_response(raw_content, mime))
return Ok(build_raw_response(raw_content, mime));
} else {
Ok(Response::builder(StatusCode::NotFound).build())
return Ok(Response::builder(StatusCode::NotFound).build());
}
} else {
Ok(Response::builder(StatusCode::NotFound).build())
return Ok(Response::builder(StatusCode::NotFound).build());
}
}
}
Expand Down Expand Up @@ -418,8 +435,7 @@ async fn handle_get_sites(request: Request<State>) -> tide::Result<Response> {
let sites = all_sites
.iter()
.filter_map(|s| {
let pk = s.1.config.get("pubkey")?;
if pk.as_str().unwrap() == key {
if s.1.config.pubkey.clone().unwrap() == key {
Some(HashMap::from([("domain", s.0)]))
} else {
None
Expand All @@ -437,8 +453,8 @@ async fn handle_list_request(request: Request<State>) -> tide::Result<Response>
let site_path = {
if let Some(site) = get_site(&request) {
let pubkey = request.param("pubkey").unwrap();
if let Some(site_pubkey) = site.config.get("pubkey") {
if site_pubkey.as_str().unwrap() != pubkey {
if let Some(site_pubkey) = site.config.pubkey {
if site_pubkey != pubkey {
log::info!("Invalid key.");
return Ok(Response::builder(StatusCode::NotFound)
.header("Access-Control-Allow-Origin", "*")
Expand Down Expand Up @@ -494,8 +510,8 @@ async fn handle_upload_request(mut request: Request<State>) -> tide::Result<Resp
let site_path = {
if let Some(site) = get_site(&request) {
if let Some(pubkey) = blossom_auth(&request, "upload") {
if let Some(site_pubkey) = site.config.get("pubkey") {
if site_pubkey.as_str().unwrap() != pubkey {
if let Some(site_pubkey) = site.config.pubkey {
if site_pubkey != pubkey {
log::info!("Non-matching key.");
return Ok(Response::builder(StatusCode::Unauthorized)
.header("Access-Control-Allow-Origin", "*")
Expand Down Expand Up @@ -559,8 +575,8 @@ async fn handle_delete_request(request: Request<State>) -> tide::Result<Response
let site_path = {
if let Some(site) = get_site(&request) {
if let Some(pubkey) = blossom_auth(&request, "delete") {
if let Some(site_pubkey) = site.config.get("pubkey") {
if site_pubkey.as_str().unwrap() != pubkey {
if let Some(site_pubkey) = site.config.pubkey {
if site_pubkey != pubkey {
log::info!("Non-matching key.");
return Ok(Response::builder(StatusCode::Unauthorized)
.header("Access-Control-Allow-Origin", "*")
Expand Down Expand Up @@ -607,6 +623,13 @@ async fn main() -> Result<(), std::io::Error> {

femme::with_level(log::LevelFilter::Info);

let themes = theme::load_themes();

if themes.len() == 0 {
println!("No themes found. Exiting!");
return Ok(());
}

let sites;

let existing_sites = site::load_sites();
Expand Down Expand Up @@ -640,6 +663,7 @@ async fn main() -> Result<(), std::io::Error> {
let site_count = sites.len();

let mut app = tide::with_state(State {
themes: Arc::new(RwLock::new(themes)),
sites: Arc::new(RwLock::new(sites)),
});

Expand Down
Loading

0 comments on commit 6fb2eb6

Please sign in to comment.