This repository has been archived on 2025-09-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
nekrochan/src/main.rs
2025-09-28 12:59:09 +02:00

189 lines
6.4 KiB
Rust

use actix_files::{Files, NamedFile};
use actix_web::{
body::MessageBody,
dev::ServiceResponse,
get,
http::header::{HeaderValue, CACHE_CONTROL, PRAGMA},
middleware::{ErrorHandlerResponse, ErrorHandlers},
web::Data,
App, HttpRequest, HttpResponse, HttpServer, ResponseError,
};
use anyhow::Error;
use askama::Template;
use log::{error, info};
use nekrochan::{
cfg::Cfg,
ctx::Ctx,
db::{cache::init_cache, models::Banner},
error::NekrochanError,
schedule::s_cleanup_files,
web::{self, template_response},
};
use sqlx::migrate;
use std::{env::var, time::Duration};
use tokio::time::sleep;
#[actix_web::main]
async fn main() {
dotenv::dotenv().ok();
env_logger::init();
if let Err(err) = run().await {
error!("{err:?}");
}
}
async fn run() -> Result<(), Error> {
let cfg_path = var("NEKROCHAN_CONFIG").unwrap_or_else(|_| "Nekrochan.toml".into());
let cfg = Cfg::load(&cfg_path).await?;
let ctx = Ctx::new(cfg).await?;
migrate!().run(ctx.db()).await?;
init_cache(&ctx).await?;
let ctx_ = ctx.clone();
tokio::spawn(async move {
loop {
match s_cleanup_files(&ctx_).await {
Ok(()) => info!("Routine file cleanup successful."),
Err(err) => error!("Routine file cleanup failed: {err:?}"),
};
sleep(Duration::from_secs(ctx_.cfg.files.cleanup_interval)).await;
}
});
let bind_addr = ctx.bind_addr();
HttpServer::new(move || {
App::new()
.app_data(Data::new(ctx.clone()))
.service(web::board::board)
.service(web::board_catalog::board_catalog)
.service(web::index::index)
.service(web::captcha::captcha)
.service(web::edit_posts::edit_posts)
.service(web::ip_posts::ip_posts)
.service(web::live::live)
.service(web::login::login_get)
.service(web::login::login_post)
.service(web::logout::logout)
.service(web::news::news)
.service(web::overboard::overboard)
.service(web::overboard_catalog::overboard_catalog)
.service(web::page::page)
.service(web::search::search)
.service(web::thread::thread)
.service(web::thread_json::thread_json)
.service(web::actions::appeal_ban::appeal_ban)
.service(web::actions::create_post::create_post)
.service(web::actions::edit_posts::edit_posts)
.service(web::actions::report_posts::report_posts)
.service(web::actions::staff_post_actions::staff_post_actions)
.service(web::actions::user_post_actions::user_post_actions)
.service(web::staff::account::account)
.service(web::staff::accounts::accounts)
.service(web::staff::bans::bans)
.service(web::staff::banners::banners)
.service(web::staff::board_config::board_config)
.service(web::staff::boards::boards)
.service(web::staff::edit_news::edit_news)
.service(web::staff::news::news)
.service(web::staff::permissions::permissions)
.service(web::staff::reports::reports)
.service(web::staff::actions::add_banners::add_banners)
.service(web::staff::actions::change_password::change_password)
.service(web::staff::actions::create_account::create_account)
.service(web::staff::actions::create_board::create_board)
.service(web::staff::actions::create_news::create_news)
.service(web::staff::actions::delete_account::delete_account)
.service(web::staff::actions::edit_news::edit_news)
.service(web::staff::actions::remove_accounts::remove_accounts)
.service(web::staff::actions::remove_banners::remove_banners)
.service(web::staff::actions::remove_bans::remove_bans)
.service(web::staff::actions::remove_boards::remove_boards)
.service(web::staff::actions::remove_news::remove_news)
.service(web::staff::actions::transfer_ownership::transfer_ownership)
.service(web::staff::actions::update_board_config::update_board_config)
.service(web::staff::actions::update_boards::update_boards)
.service(web::staff::actions::update_permissions::update_permissions)
.service(favicon)
.service(random_banner)
.service(Files::new("/static", "./static"))
.service(Files::new("/uploads", "./uploads").disable_content_disposition())
.wrap(ErrorHandlers::new().default_handler(error_handler))
})
.bind(bind_addr)?
.run()
.await?;
Ok(())
}
#[get("/favicon.ico")]
async fn favicon() -> Result<NamedFile, NekrochanError> {
let favicon = NamedFile::open("./static/favicon.ico")?;
Ok(favicon)
}
#[get("/random-banner")]
async fn random_banner(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
let file = if let Some(banner) = Banner::read_random(&ctx).await? {
let timestamp = banner.banner.timestamp;
let format = &banner.banner.format;
NamedFile::open(format!("./uploads/{timestamp}.{format}"))?
} else {
NamedFile::open("./static/default-banner.png")?
};
let mut res = file.into_response(&req);
res.headers_mut().append(
CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
res.headers_mut()
.append(PRAGMA, HeaderValue::from_static("no-cache"));
Ok(res)
}
#[derive(Template)]
#[template(path = "error.html")]
struct ErrorTempalate {
error_code: u16,
error_message: String,
}
fn error_handler<B>(res: ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>>
where
B: MessageBody,
<B as MessageBody>::Error: ResponseError + 'static,
{
let (req, res) = res.into_parts();
let status = res.status();
let error_code = status.as_u16();
let error_message = match res.into_body().try_into_bytes().ok() {
Some(bytes) => String::from_utf8(bytes.to_vec()).unwrap_or_default(),
None => String::default(),
};
let template = ErrorTempalate {
error_code,
error_message,
};
let mut res = template_response(&template)?;
*(res.status_mut()) = status;
let res = ServiceResponse::new(req, res).map_into_right_body();
Ok(ErrorHandlerResponse::Response(res))
}