mirror of
https://github.com/4cuck/nitter-status.git
synced 2026-10-27 00:57:39 +00:00
init
This commit is contained in:
1 parent
906bfbb70c
commit
a304acc505
36 files changed
+9888
No files matched your search
@@ -0,0 +1,23 @@
|
||||
use crate::{Result, ServerError};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::State, Json};
|
||||
use entities::state::Cache;
|
||||
use hyper::http::HeaderValue;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn instances(
|
||||
State(ref cache): State<Cache>,
|
||||
State(ref config): State<Arc<crate::Config>>,
|
||||
) -> Result<axum::response::Response> {
|
||||
let mut res = {
|
||||
let guard = cache.read().map_err(|_| ServerError::MutexFailure)?;
|
||||
let res = Json(&*guard).into_response();
|
||||
drop(guard);
|
||||
res
|
||||
};
|
||||
res.headers_mut().insert(
|
||||
"cache-control",
|
||||
HeaderValue::from_str(&format!("public, max-age={}", config.max_age)).unwrap(),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use std::{borrow::Cow, net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{extract::DefaultBodyLimit, http::HeaderValue, response::Html, routing::get, Router};
|
||||
use entities::state::{scanner::ScannerConfig, Cache};
|
||||
use hyper::{header, StatusCode};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::Tera;
|
||||
use thiserror::Error;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::{
|
||||
cors::CorsLayer, limit::RequestBodyLimitLayer, services::ServeDir,
|
||||
set_header::SetResponseHeaderLayer, trace::TraceLayer,
|
||||
};
|
||||
|
||||
mod api;
|
||||
mod website;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub site_url: String,
|
||||
pub max_age: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, axum::extract::FromRef)]
|
||||
struct AppState {
|
||||
db: DatabaseConnection,
|
||||
config: Arc<Config>,
|
||||
scanner_config: ScannerConfig,
|
||||
cache: Cache,
|
||||
templates: Arc<Tera>,
|
||||
}
|
||||
|
||||
/// Start webserver
|
||||
pub async fn start(
|
||||
addr: &SocketAddr,
|
||||
db: DatabaseConnection,
|
||||
config: Config,
|
||||
scanner_config: ScannerConfig,
|
||||
cache: Cache,
|
||||
) -> Result<()> {
|
||||
let config = Arc::new(config);
|
||||
let tera = Tera::new("server/templates/*")?;
|
||||
let state = AppState {
|
||||
config: config.clone(),
|
||||
db,
|
||||
cache,
|
||||
scanner_config,
|
||||
templates: Arc::new(tera),
|
||||
};
|
||||
let router = Router::new()
|
||||
.nest_service(
|
||||
"/static",
|
||||
ServeDir::new("server/static").append_index_html_on_directories(false),
|
||||
)
|
||||
.route("/api/v1/instances", get(api::instances))
|
||||
.route("/about", get(website::about))
|
||||
.route("/", get(website::instances))
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(DefaultBodyLimit::disable())
|
||||
.layer(RequestBodyLimitLayer::new(2usize.pow(20) * 2))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors_policy(&config.site_url))
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::CONTENT_SECURITY_POLICY,
|
||||
"default-src 'self'; child-src 'none'; script-src 'self'; style-src 'self';"
|
||||
.parse::<HeaderValue>()
|
||||
.expect("Invalid CSP header value"),
|
||||
)),
|
||||
)
|
||||
.with_state(state.clone());
|
||||
tracing::debug!("Starting server with config {:?}", *config);
|
||||
tracing::info!("listening on http://{}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
|
||||
.await
|
||||
.expect("Failed to start webserver");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cors_policy(_site_url: &str) -> CorsLayer {
|
||||
CorsLayer::new()
|
||||
.allow_origin(tower_http::cors::Any)
|
||||
.allow_methods(tower_http::cors::Any)
|
||||
.allow_headers(tower_http::cors::Any)
|
||||
}
|
||||
|
||||
type Result<T = Html<String>> = std::result::Result<T, ServerError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServerError {
|
||||
#[error("Failed to access mutex")]
|
||||
MutexFailure,
|
||||
#[error("Failed to perform templating: {0:?}")]
|
||||
Templating(#[from] tera::Error),
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for ServerError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
use ServerError::*;
|
||||
let msg = match &self {
|
||||
MutexFailure | Templating(_) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Cow::Borrowed("Internal Server Error"),
|
||||
),
|
||||
};
|
||||
if msg.0 == StatusCode::INTERNAL_SERVER_ERROR {
|
||||
tracing::error!(?self);
|
||||
}
|
||||
msg.into_response()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Result;
|
||||
use crate::ServerError;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::State, response::Html};
|
||||
use entities::state::scanner::ScannerConfig;
|
||||
use entities::state::Cache;
|
||||
use hyper::http::HeaderValue;
|
||||
|
||||
pub async fn instances(
|
||||
State(ref cache): State<Cache>,
|
||||
State(ref template): State<Arc<tera::Tera>>,
|
||||
State(ref config): State<Arc<crate::Config>>,
|
||||
) -> Result<axum::response::Response> {
|
||||
let mut context = tera::Context::new();
|
||||
let mut res = {
|
||||
let guard = cache.read().map_err(|_| ServerError::MutexFailure)?;
|
||||
context.insert("instances", &guard.hosts);
|
||||
let time = guard.last_update.format("%Y.%m.%d %H:%M").to_string();
|
||||
context.insert("last_updated", &time);
|
||||
|
||||
let res = Html(template.render("instances.html.j2", &context)?).into_response();
|
||||
drop(guard);
|
||||
res
|
||||
};
|
||||
res.headers_mut().insert(
|
||||
"cache-control",
|
||||
HeaderValue::from_str(&format!("public, max-age={}", config.max_age)).unwrap(),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn about(
|
||||
State(ref template): State<Arc<tera::Tera>>,
|
||||
State(ref scanner_config): State<ScannerConfig>,
|
||||
) -> Result<axum::response::Response> {
|
||||
let mut context = tera::Context::new();
|
||||
let mut paths = Vec::with_capacity(5);
|
||||
paths.push(&scanner_config.about_path);
|
||||
paths.push(&scanner_config.rss_path);
|
||||
paths.push(&scanner_config.profile_path);
|
||||
context.insert("checked_paths", &paths);
|
||||
context.insert(
|
||||
"uptime_interval_s",
|
||||
&scanner_config.instance_check_interval.as_secs(),
|
||||
);
|
||||
context.insert(
|
||||
"wiki_interval_s",
|
||||
&scanner_config.list_fetch_interval.as_secs(),
|
||||
);
|
||||
context.insert(
|
||||
"ping_avg_interval_h",
|
||||
&scanner_config.ping_range.num_hours(),
|
||||
);
|
||||
|
||||
let mut res = Html(template.render("about.html.j2", &context)?).into_response();
|
||||
res.headers_mut().insert(
|
||||
"cache-control",
|
||||
HeaderValue::from_static("public, max-age=900"),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
Reference in new issue
Block a user