mirror of
https://github.com/CloudNebulaProject/webfingerd.git
synced 2026-04-10 13:10:41 +00:00
feat: project scaffold with config and error types
This commit is contained in:
parent
59d7c88707
commit
8123752c9c
9 changed files with 5250 additions and 0 deletions
5028
Cargo.lock
generated
Normal file
5028
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
42
Cargo.toml
Normal file
42
Cargo.toml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
[workspace]
|
||||||
|
members = [".", "migration"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "webfingerd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { version = "0.8", features = ["macros"] }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
sea-orm = { version = "1", features = ["sqlx-sqlite", "runtime-tokio-rustls"] }
|
||||||
|
sea-orm-migration = "1"
|
||||||
|
dashmap = "6"
|
||||||
|
governor = "0.8"
|
||||||
|
askama = "0.12"
|
||||||
|
askama_axum = "0.4"
|
||||||
|
argon2 = "0.5"
|
||||||
|
config = "0.14"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] }
|
||||||
|
metrics = "0.24"
|
||||||
|
metrics-exporter-prometheus = "0.16"
|
||||||
|
hickory-resolver = "0.25"
|
||||||
|
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
|
||||||
|
glob-match = "0.2"
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
tower = "0.5"
|
||||||
|
tower-http = { version = "0.6", features = ["cors", "request-id", "trace", "util"] }
|
||||||
|
axum-extra = { version = "0.10", features = ["cookie-signed"] }
|
||||||
|
rand = "0.8"
|
||||||
|
base64 = "0.22"
|
||||||
|
thiserror = "2"
|
||||||
|
urlencoding = "2"
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
axum-test = "16"
|
||||||
|
tempfile = "3"
|
||||||
25
config.toml
Normal file
25
config.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
[server]
|
||||||
|
listen = "0.0.0.0:8080"
|
||||||
|
base_url = "http://localhost:8080"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
path = "webfingerd.db"
|
||||||
|
wal_mode = true
|
||||||
|
|
||||||
|
[cache]
|
||||||
|
reaper_interval_secs = 30
|
||||||
|
|
||||||
|
[rate_limit]
|
||||||
|
public_rpm = 60
|
||||||
|
api_rpm = 300
|
||||||
|
batch_rpm = 10
|
||||||
|
batch_max_links = 500
|
||||||
|
|
||||||
|
[challenge]
|
||||||
|
dns_txt_prefix = "_webfinger-challenge"
|
||||||
|
http_well_known_path = ".well-known/webfinger-verify"
|
||||||
|
challenge_ttl_secs = 3600
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
enabled = false
|
||||||
|
session_secret = ""
|
||||||
6
migration/Cargo.toml
Normal file
6
migration/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
14
migration/src/lib.rs
Normal file
14
migration/src/lib.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/config.rs
Normal file
71
src/config.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub server: ServerConfig,
|
||||||
|
pub database: DatabaseConfig,
|
||||||
|
pub cache: CacheConfig,
|
||||||
|
pub rate_limit: RateLimitConfig,
|
||||||
|
pub challenge: ChallengeConfig,
|
||||||
|
pub ui: UiConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub listen: String,
|
||||||
|
pub base_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct DatabaseConfig {
|
||||||
|
pub path: String,
|
||||||
|
pub wal_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct CacheConfig {
|
||||||
|
pub reaper_interval_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct RateLimitConfig {
|
||||||
|
pub public_rpm: u32,
|
||||||
|
pub api_rpm: u32,
|
||||||
|
pub batch_rpm: u32,
|
||||||
|
pub batch_max_links: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ChallengeConfig {
|
||||||
|
pub dns_txt_prefix: String,
|
||||||
|
pub http_well_known_path: String,
|
||||||
|
pub challenge_ttl_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct UiConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub session_secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn load() -> Result<Self, config::ConfigError> {
|
||||||
|
let settings = config::Config::builder()
|
||||||
|
.add_source(config::File::with_name("config").required(false))
|
||||||
|
.add_source(
|
||||||
|
config::Environment::with_prefix("WEBFINGERD")
|
||||||
|
.separator("__"),
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let s: Self = settings.try_deserialize()?;
|
||||||
|
|
||||||
|
if s.ui.enabled && s.ui.session_secret.is_empty() {
|
||||||
|
return Err(config::ConfigError::Message(
|
||||||
|
"ui.session_secret is required when ui is enabled".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/error.rs
Normal file
49
src/error.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use axum::Json;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AppError {
|
||||||
|
#[error("not found")]
|
||||||
|
NotFound,
|
||||||
|
#[error("bad request: {0}")]
|
||||||
|
BadRequest(String),
|
||||||
|
#[error("unauthorized")]
|
||||||
|
Unauthorized,
|
||||||
|
#[error("forbidden: {0}")]
|
||||||
|
Forbidden(String),
|
||||||
|
#[error("conflict: {0}")]
|
||||||
|
Conflict(String),
|
||||||
|
#[error("rate limited")]
|
||||||
|
RateLimited,
|
||||||
|
#[error("internal error: {0}")]
|
||||||
|
Internal(String),
|
||||||
|
#[error("database error: {0}")]
|
||||||
|
Database(#[from] sea_orm::DbErr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let (status, message) = match &self {
|
||||||
|
AppError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
|
||||||
|
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
|
||||||
|
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
|
||||||
|
AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()),
|
||||||
|
AppError::Conflict(msg) => (StatusCode::CONFLICT, msg.clone()),
|
||||||
|
AppError::RateLimited => (StatusCode::TOO_MANY_REQUESTS, self.to_string()),
|
||||||
|
AppError::Internal(msg) => {
|
||||||
|
tracing::error!("internal error: {msg}");
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "internal error".into())
|
||||||
|
}
|
||||||
|
AppError::Database(err) => {
|
||||||
|
tracing::error!("database error: {err}");
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "internal error".into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(status, Json(json!({ "error": message }))).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AppResult<T> = Result<T, AppError>;
|
||||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
13
src/main.rs
Normal file
13
src/main.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
use webfingerd::config::Settings;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
fmt()
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.json()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let settings = Settings::load().expect("failed to load configuration");
|
||||||
|
tracing::info!(listen = %settings.server.listen, "starting webfingerd");
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue