use templates generated from website.git
This commit is contained in:
		
							parent
							
								
									f7f9785d8b
								
							
						
					
					
						commit
						21c8b35bba
					
				
					 6 changed files with 104 additions and 25 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
[submodule "website"]
 | 
			
		||||
	path = website
 | 
			
		||||
	url = https://git.libre-chip.org/libre-chip/website.git
 | 
			
		||||
							
								
								
									
										3
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -2507,7 +2507,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "subscribe-list"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "actix-session",
 | 
			
		||||
 "actix-web",
 | 
			
		||||
| 
						 | 
				
			
			@ -2525,6 +2525,7 @@ dependencies = [
 | 
			
		|||
 "listenfd",
 | 
			
		||||
 "log",
 | 
			
		||||
 "openidconnect",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "subscribe-list"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
authors = ["Jacob Lifshay <programmerjake@gmail.com>"]
 | 
			
		||||
description = "web server for managing subscriptions"
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +33,9 @@ tinytemplate = "1.2.1"
 | 
			
		|||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
 | 
			
		||||
toml = { version = "0.8.12", features = ["preserve_order"] }
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
regex = "1.10.4"
 | 
			
		||||
 | 
			
		||||
[package.metadata.deb]
 | 
			
		||||
depends = ["$auto", "adduser (>= 3.11)"]
 | 
			
		||||
assets = [
 | 
			
		||||
| 
						 | 
				
			
			@ -44,4 +47,4 @@ maintainer-scripts = "debian"
 | 
			
		|||
[package.metadata.deb.systemd-units]
 | 
			
		||||
unit-name = "subscribe-list"
 | 
			
		||||
unit-scripts = "."
 | 
			
		||||
enable = false
 | 
			
		||||
enable = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										80
									
								
								build.rs
									
										
									
									
									
								
							
							
						
						
									
										80
									
								
								build.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,3 +1,79 @@
 | 
			
		|||
fn main() {
 | 
			
		||||
    println!("cargo:rerun-if-changed=migrations");
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
use std::{collections::HashSet, env, error::Error, fs, path::Path, process::Command};
 | 
			
		||||
 | 
			
		||||
fn escape_template(v: &str) -> Result<String, String> {
 | 
			
		||||
    let cant_escape = "\\{";
 | 
			
		||||
    if v.contains(cant_escape) {
 | 
			
		||||
        return Err(format!("can't escape {cant_escape}"));
 | 
			
		||||
    }
 | 
			
		||||
    Ok(v.replace("{", "\\{"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    println!("cargo:rerun-if-changed=migrations");
 | 
			
		||||
    println!("cargo:rerun-if-changed=website");
 | 
			
		||||
    let out_dir = env::var_os("OUT_DIR").unwrap();
 | 
			
		||||
    let website_dir = Path::new(&out_dir).join("website");
 | 
			
		||||
    let templates_dir = Path::new(&out_dir).join("templates");
 | 
			
		||||
    fs::create_dir_all(&templates_dir)?;
 | 
			
		||||
    let status = Command::new("mdbook")
 | 
			
		||||
        .arg("build")
 | 
			
		||||
        .arg("--dest-dir")
 | 
			
		||||
        .arg(&website_dir)
 | 
			
		||||
        .current_dir("website")
 | 
			
		||||
        .status()
 | 
			
		||||
        .map_err(|e| format!("Can't run mdbook: {e}"))?;
 | 
			
		||||
    if !status.success() {
 | 
			
		||||
        return Err(format!("mdbook exited with an error: {status:?}").into());
 | 
			
		||||
    }
 | 
			
		||||
    let sub_template_path = website_dir.join("subscription/index.html");
 | 
			
		||||
    let sub_template = fs::read_to_string(&sub_template_path)
 | 
			
		||||
        .map_err(|e| format!("Error reading {}: {e}", sub_template_path.display()))?;
 | 
			
		||||
    let regex = Regex::new(r#"(?s)<div class="subscribe_list_template_section" id="(?<id>[^"]*)"/>(?<template>.*?)<div class="subscribe_list_template_section"/>"#).unwrap();
 | 
			
		||||
    let matches = Vec::from_iter(regex.captures_iter(&sub_template));
 | 
			
		||||
    let mut between_matches = vec![];
 | 
			
		||||
    let mut last_end = 0;
 | 
			
		||||
    for m in &matches {
 | 
			
		||||
        let m = m.get(0).unwrap();
 | 
			
		||||
        between_matches.push(&sub_template[last_end..m.start()]);
 | 
			
		||||
        last_end = m.end();
 | 
			
		||||
    }
 | 
			
		||||
    between_matches.push(&sub_template[last_end..]);
 | 
			
		||||
    let prefix = between_matches.remove(0);
 | 
			
		||||
    let Some(suffix) = between_matches.pop() else {
 | 
			
		||||
        return Err(format!(
 | 
			
		||||
            "no subscribe_list_template_section `div`s found: {}",
 | 
			
		||||
            sub_template_path.display()
 | 
			
		||||
        )
 | 
			
		||||
        .into());
 | 
			
		||||
    };
 | 
			
		||||
    let prefix = escape_template(prefix)?;
 | 
			
		||||
    let suffix = escape_template(suffix)?;
 | 
			
		||||
    for i in between_matches {
 | 
			
		||||
        if i.trim_start() != "" {
 | 
			
		||||
            return Err(format!(
 | 
			
		||||
                r#"can't have non-whitespace text between
 | 
			
		||||
<div class="subscribe_list_template_section"/>
 | 
			
		||||
and next
 | 
			
		||||
<div class="subscribe_list_template_section" id="..">"#
 | 
			
		||||
            )
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let mut identifiers = HashSet::new();
 | 
			
		||||
    for m in &matches {
 | 
			
		||||
        let id = &m["id"];
 | 
			
		||||
        if id.contains(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_') {
 | 
			
		||||
            return Err(format!("invalid identifier {id:?}").into());
 | 
			
		||||
        }
 | 
			
		||||
        if !identifiers.insert(id) {
 | 
			
		||||
            return Err(format!("duplicate identifier {id:?}").into());
 | 
			
		||||
        }
 | 
			
		||||
        let template = &m["template"];
 | 
			
		||||
        fs::write(
 | 
			
		||||
            templates_dir.join(format!("{id}.txt")),
 | 
			
		||||
            prefix.clone() + template + &suffix,
 | 
			
		||||
        )?;
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										35
									
								
								src/app.rs
									
										
									
									
									
								
							
							
						
						
									
										35
									
								
								src/app.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -9,6 +9,7 @@ use actix_session::Session;
 | 
			
		|||
use actix_web::{
 | 
			
		||||
    get,
 | 
			
		||||
    http::header::{ContentType, LOCATION},
 | 
			
		||||
    routes,
 | 
			
		||||
    web::{self, ServiceConfig},
 | 
			
		||||
    HttpResponse, Responder,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +52,7 @@ impl<T: Template> DynTemplate for PhantomData<T> {
 | 
			
		|||
macro_rules! templates {
 | 
			
		||||
    (
 | 
			
		||||
        $(
 | 
			
		||||
            #[text = $text:literal]
 | 
			
		||||
            #[text = $text:expr]
 | 
			
		||||
            $(#[$meta:meta])*
 | 
			
		||||
            $vis:vis struct $name:ident $fields:tt
 | 
			
		||||
        )*
 | 
			
		||||
| 
						 | 
				
			
			@ -76,28 +77,12 @@ pub struct SubscriptionLoggedOutTemplateProviders {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
templates! {
 | 
			
		||||
    #[text = r#"<head><title>Subscription</title></head>
 | 
			
		||||
<body>
 | 
			
		||||
<h1>Register or Sign In:</h1>
 | 
			
		||||
As an anti-spam measure, you need to register using a 3rd-party account:
 | 
			
		||||
<ul>
 | 
			
		||||
{{- for provider in providers -}}
 | 
			
		||||
<li><a href="{provider.url}">{provider.pretty_name}</a></li>
 | 
			
		||||
{{- endfor -}}
 | 
			
		||||
</ul>
 | 
			
		||||
</body>"#]
 | 
			
		||||
    #[text = include_str!(concat!(env!("OUT_DIR"), "/templates/SubscriptionLoggedOut.txt"))]
 | 
			
		||||
    #[derive(Debug, Serialize)]
 | 
			
		||||
    pub struct SubscriptionLoggedOutTemplate {
 | 
			
		||||
        pub providers: Vec<SubscriptionLoggedOutTemplateProviders>,
 | 
			
		||||
    }
 | 
			
		||||
    #[text = r#"<head><title>Subscription</title></head>
 | 
			
		||||
<body>
 | 
			
		||||
<p>Signed in as {email}</p>
 | 
			
		||||
<ul>
 | 
			
		||||
<li><a href="/subscription/logout">Logout</a></li>
 | 
			
		||||
<li><b><a href="/subscription/unsubscribe">Unsubscribe and Delete Account</a></b></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>"#]
 | 
			
		||||
    #[text = include_str!(concat!(env!("OUT_DIR"), "/templates/SubscriptionLoggedIn.txt"))]
 | 
			
		||||
    #[derive(Debug, Serialize)]
 | 
			
		||||
    pub struct SubscriptionLoggedInTemplate {
 | 
			
		||||
        pub email: EndUserEmail,
 | 
			
		||||
| 
						 | 
				
			
			@ -363,7 +348,10 @@ pub async fn unsubscribe(session: Session, db: web::Data<DbThread>) -> impl Resp
 | 
			
		|||
    resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[routes]
 | 
			
		||||
#[get("/subscription")]
 | 
			
		||||
#[get("/subscription/")]
 | 
			
		||||
#[get("/subscription/index.html")]
 | 
			
		||||
pub async fn subscription(
 | 
			
		||||
    config: web::Data<Config>,
 | 
			
		||||
    session: Session,
 | 
			
		||||
| 
						 | 
				
			
			@ -391,13 +379,20 @@ pub async fn subscription(
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn page_not_found() -> impl Responder {
 | 
			
		||||
    HttpResponse::NotFound()
 | 
			
		||||
        .content_type(ContentType::html())
 | 
			
		||||
        .body(include_str!(concat!(env!("OUT_DIR"), "/website/404.html")))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn all_services(cfg: &mut ServiceConfig) {
 | 
			
		||||
    cfg.service(subscription)
 | 
			
		||||
        .service(login)
 | 
			
		||||
        .service(callback)
 | 
			
		||||
        .service(logout)
 | 
			
		||||
        .service(unsubscribe)
 | 
			
		||||
        .service(email_unsubscribe);
 | 
			
		||||
        .service(email_unsubscribe)
 | 
			
		||||
        .default_service(web::to(page_not_found));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								website
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								website
									
										
									
									
									
										Submodule
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Subproject commit bfa8bd253cb9ba1bcc1358891767f8c2411a63fe
 | 
			
		||||
		Reference in a new issue