bundle auxiliary files (templates/css) in the binary

This commit is contained in:
Armaël Guéneau 2024-12-22 20:36:24 +01:00
parent f50b81e8e2
commit 13f8e76ae3
6 changed files with 78 additions and 31 deletions

3
.gitignore vendored
View file

@ -3,4 +3,5 @@ classification.json
db.json
api_token
profile.json
_*.json
_*.json
env

20
Cargo.lock generated
View file

@ -694,6 +694,7 @@ dependencies = [
"actix-web",
"anyhow",
"forgejo-api",
"include_dir",
"lazy_static",
"lettre",
"rand",
@ -1260,6 +1261,25 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0"
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "indexmap"
version = "2.6.0"

View file

@ -20,6 +20,7 @@ lazy_static = "1"
actix-files = "0.6"
unicode-segmentation = "1"
lettre = { version = "0.11", features = ["builder", "smtp-transport", "rustls-tls"], default-features = false }
include_dir = "0.7.4"
[profile.profiling]
inherits = "dev"

View file

@ -1,4 +1,4 @@
# spam management for forgejo
# spam accounts management for forgejo
## Usage
@ -14,7 +14,7 @@
## Configuration
Forgery reads the following environment variables:
- `FORGEJO_URL`: url of the forgejo instance
- `FORGEJO_URL`: url of the forgejo instance (e.g. https://git.deuxfleurs.fr)
- `FORGEJO_API_TOKEN`: Forgejo API token *granting admin access*. Required. You
can generate an API token using the Forgejo web interface in `Settings ->
Applications -> Generate New Token`.
@ -22,11 +22,11 @@ Forgery reads the following environment variables:
locking accounts)
- `ADMIN_CONTACT_EMAIL`: email that can be used to contact admins of your
instance (included in the notification email sent when locking accounts)
- `ACTUALLY_BAN_USERS`: define it (e.g. to `true`) to actually lock user
accounts, send notification emails and eventually delete user accounts. If not
defined (the default), no actual action is taken, spammers are only listed in
the database. The variable should be set in production, but probably not for
testing.
- `ACTUALLY_BAN_USERS`: define it to `true` to actually lock user accounts, send
notification emails and eventually delete user accounts. If not defined (the
default) or set to `false`, no actual action is taken: spammers are only
listed in the database. The variable should be set in production, but probably
not for testing.
Environment variables that are relevant when `ACTUALLY_BAN_USERS=true`:
- `SMTP_ADDRESS`: address of the SMTP relay used to send email notifications
@ -42,4 +42,3 @@ Environment variables that are relevant when `ACTUALLY_BAN_USERS=true`:
the email could not be sent…)
- add backend to store data on garage instead of local files
- improve error handling
- bundle auxiliary files (templates, css) in the binary for easy deployment?

View file

@ -1,5 +1,5 @@
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
use anyhow::Context;
use anyhow::{anyhow, Context};
use forgejo_api::{Auth, Forgejo};
use lazy_static::lazy_static;
use rand::prelude::*;
@ -61,9 +61,19 @@ impl Config {
.context("reading the ADMIN_CONTACT_EMAIL environment variable")?;
let actually_ban = match std::env::var("ACTUALLY_BAN_USERS") {
Ok(_) => ActuallyBan::Yes {
smtp: SmtpConfig::from_env().await?,
},
Ok(s) => {
if &s == "true" {
ActuallyBan::Yes {
smtp: SmtpConfig::from_env().await?,
}
} else if &s == "false" {
ActuallyBan::No
} else {
return Err(anyhow!(
"ACTUALLY_BAN_USERS: unknown value (expected: true/false)"
));
}
}
Err(_) => ActuallyBan::No,
};
@ -231,10 +241,25 @@ async fn apply_classification(
}
}
const TEMPLATES_DIR: include_dir::Dir = include_dir::include_dir!("templates");
lazy_static! {
pub static ref TEMPLATES: Tera = {
match Tera::new("templates/**/*.html") {
Ok(t) => t,
let files: Vec<_> = TEMPLATES_DIR
.files()
.into_iter()
.map(|f| {
(
f.path().to_str().unwrap(),
std::str::from_utf8(f.contents()).unwrap(),
)
})
.collect();
let mut tera = Tera::default();
match tera.add_raw_templates(files) {
Ok(()) => tera,
Err(e) => {
eprintln!("Parsing error(s): {}", e);
::std::process::exit(1);
@ -421,6 +446,19 @@ async fn classified(
HttpResponse::Ok().body(page)
}
const STATIC_DIR: include_dir::Dir = include_dir::include_dir!("static");
#[get("/static/{filename:.*}")]
async fn static_(req: HttpRequest) -> impl Responder {
eprintln!("GET {}", req.uri());
let path: String = req.match_info().query("filename").parse().unwrap();
match STATIC_DIR.get_file(path) {
None => HttpResponse::NotFound().body("404 Not found"),
Some(page) => HttpResponse::Ok().body(page.contents()),
}
}
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
eprintln!("Eval templates");
@ -464,8 +502,8 @@ async fn main() -> anyhow::Result<()> {
HttpServer::new(move || {
App::new()
.service(actix_files::Files::new("/static/", "./static"))
.app_data(st.clone())
.service(static_)
.service(index)
.service(classified)
.service(post_classified_index)

View file

@ -83,11 +83,7 @@ pub async fn refresh_user_data(
// Worker to delete spam accounts after their grace period expired
async fn try_purge_account(
config: &Config,
forge: &Forgejo,
login: &str,
) -> anyhow::Result<()> {
async fn try_purge_account(config: &Config, forge: &Forgejo, login: &str) -> anyhow::Result<()> {
if let ActuallyBan::No = config.actually_ban {
eprintln!("[Simulating: delete account of user {login}]");
return Ok(());
@ -104,11 +100,7 @@ async fn try_purge_account(
Ok(())
}
pub async fn purge_spammer_accounts(
config: Arc<Config>,
forge: Arc<Forgejo>,
db: Arc<Mutex<Db>>,
) {
pub async fn purge_spammer_accounts(config: Arc<Config>, forge: Arc<Forgejo>, db: Arc<Mutex<Db>>) {
loop {
let mut classified_users = Vec::new();
{
@ -274,11 +266,7 @@ pub async fn try_lock_and_notify_user(
}
}
pub async fn lock_and_notify_users(
config: Arc<Config>,
forge: Arc<Forgejo>,
db: Arc<Mutex<Db>>,
) {
pub async fn lock_and_notify_users(config: Arc<Config>, forge: Arc<Forgejo>, db: Arc<Mutex<Db>>) {
let mut spammers = Vec::new();
{
let db = &db.lock().unwrap();