mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-19 08:18:39 +02:00
chore: cargo fmt with current stable rustfmt (repo-wide)
The repo pins channel=stable (no rustfmt version), so CI now pulls rustfmt 1.9.0, which reformats code last touched by an older rustfmt. main itself failed `cargo fmt --check` under the current toolchain; this is a formatting-only sweep across 24 files to make the fmt gate green. No behavior change; tests + clippy -D warnings still pass.
This commit is contained in:
+33
-14
@@ -838,15 +838,18 @@ pub async fn admin_config_put(
|
||||
let mut merged_json =
|
||||
serde_json::to_value(&persisted).map_err(|e| AppError::Internal(e.to_string()))?;
|
||||
json_merge_skip_null(&mut merged_json, &patch_json);
|
||||
let merged: crate::config_overlay::SettingsOverlay = serde_json::from_value(merged_json.clone())
|
||||
.map_err(|e| AppError::ValidationError(format!("bad settings: {e}")))?;
|
||||
let merged: crate::config_overlay::SettingsOverlay =
|
||||
serde_json::from_value(merged_json.clone())
|
||||
.map_err(|e| AppError::ValidationError(format!("bad settings: {e}")))?;
|
||||
let candidate = state.config_base.apply_overlay(&merged)?; // ValidationError -> 400
|
||||
|
||||
// Classify the CHANGED fields: which sections changed + does any need a restart?
|
||||
let mut any_restart = false;
|
||||
let mut changed_sections: Vec<String> = Vec::new();
|
||||
for (section, fields) in &patch_obj {
|
||||
let Some(fields) = fields.as_object() else { continue };
|
||||
let Some(fields) = fields.as_object() else {
|
||||
continue;
|
||||
};
|
||||
let touched: Vec<&String> = fields
|
||||
.iter()
|
||||
.filter(|(_, v)| !v.is_null())
|
||||
@@ -907,7 +910,9 @@ pub async fn admin_config_put(
|
||||
let shutdown = state.shutdown.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(750)).await;
|
||||
tracing::info!("config auto-apply: self-restart to apply a restart-required change");
|
||||
tracing::info!(
|
||||
"config auto-apply: self-restart to apply a restart-required change"
|
||||
);
|
||||
shutdown.notify_one();
|
||||
});
|
||||
}
|
||||
@@ -933,10 +938,7 @@ fn json_merge_skip_null(base: &mut serde_json::Value, patch: &serde_json::Value)
|
||||
if pv.is_null() {
|
||||
continue;
|
||||
}
|
||||
json_merge_skip_null(
|
||||
b.entry(k.clone()).or_insert(serde_json::Value::Null),
|
||||
pv,
|
||||
);
|
||||
json_merge_skip_null(b.entry(k.clone()).or_insert(serde_json::Value::Null), pv);
|
||||
}
|
||||
}
|
||||
(b, p) => *b = p.clone(),
|
||||
@@ -1076,7 +1078,11 @@ pub async fn admin_asset(Path(path): Path<String>) -> Response {
|
||||
} else {
|
||||
"application/octet-stream"
|
||||
};
|
||||
([(axum::http::header::CONTENT_TYPE, mime)], f.data.into_owned()).into_response()
|
||||
(
|
||||
[(axum::http::header::CONTENT_TYPE, mime)],
|
||||
f.data.into_owned(),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
@@ -1097,7 +1103,10 @@ pub fn routes() -> Router<Arc<AppState>> {
|
||||
.route("/admin/keys/:id", delete(admin_keys_revoke))
|
||||
.route("/admin/keys/:id/rotate", post(admin_keys_rotate))
|
||||
.route("/admin/config", get(admin_config_get).put(admin_config_put))
|
||||
.route("/admin/invites", post(admin_invites_mint).get(admin_invites_list))
|
||||
.route(
|
||||
"/admin/invites",
|
||||
post(admin_invites_mint).get(admin_invites_list),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1111,9 +1120,15 @@ mod tests {
|
||||
fn admin_console_is_embedded() {
|
||||
let index = AdminAssets::get("index.html").expect("admin-ui/dist/index.html embedded");
|
||||
let html = std::str::from_utf8(&index.data).expect("index.html is utf-8");
|
||||
assert!(html.contains("Smirk Operator Console"), "console title missing");
|
||||
assert!(
|
||||
html.contains("Smirk Operator Console"),
|
||||
"console title missing"
|
||||
);
|
||||
// Vite rewrites asset URLs to the /admin/ base; the served route matches.
|
||||
assert!(html.contains("/admin/assets/"), "asset base not rewritten to /admin/");
|
||||
assert!(
|
||||
html.contains("/admin/assets/"),
|
||||
"asset base not rewritten to /admin/"
|
||||
);
|
||||
assert!(
|
||||
AdminAssets::iter().any(|p| p.starts_with("assets/")),
|
||||
"no built JS/CSS assets embedded",
|
||||
@@ -1145,8 +1160,12 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), axum::http::StatusCode::OK);
|
||||
assert_eq!(res.headers()["content-type"], "text/html; charset=utf-8");
|
||||
let body = axum::body::to_bytes(res.into_body(), 1 << 20).await.unwrap();
|
||||
assert!(std::str::from_utf8(&body).unwrap().contains("Smirk Operator Console"));
|
||||
let body = axum::body::to_bytes(res.into_body(), 1 << 20)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(std::str::from_utf8(&body)
|
||||
.unwrap()
|
||||
.contains("Smirk Operator Console"));
|
||||
|
||||
// GET /admin/assets/<real built asset> -> served with a sane content-type.
|
||||
let asset = AdminAssets::iter()
|
||||
|
||||
+42
-18
@@ -133,9 +133,17 @@ fn parse_asset(asset: &str) -> Result<AssetType, AppError> {
|
||||
|
||||
/// Parse the wire key list into the `(AssetType, public_key, public_spend_key)`
|
||||
/// tuples the atomic-registration DB path binds directly. A bad asset is a 400.
|
||||
fn parse_keys(keys: &[AssetPublicKey]) -> Result<Vec<(AssetType, String, Option<String>)>, AppError> {
|
||||
fn parse_keys(
|
||||
keys: &[AssetPublicKey],
|
||||
) -> Result<Vec<(AssetType, String, Option<String>)>, AppError> {
|
||||
keys.iter()
|
||||
.map(|k| Ok((parse_asset(&k.asset)?, k.public_key.clone(), k.public_spend_key.clone())))
|
||||
.map(|k| {
|
||||
Ok((
|
||||
parse_asset(&k.asset)?,
|
||||
k.public_key.clone(),
|
||||
k.public_spend_key.clone(),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -178,14 +186,10 @@ fn pow_applies(state: &AppState, pubkey_hash_lc: &str) -> bool {
|
||||
/// request Host. Fail closed when unset (Nostr identity is disabled).
|
||||
fn nip98_url(state: &AppState, path: &str) -> Result<String, AppError> {
|
||||
let cfg = state.cfg();
|
||||
let base = cfg
|
||||
.identity
|
||||
.public_api_url
|
||||
.as_deref()
|
||||
.ok_or_else(|| {
|
||||
warn!("Nostr endpoint reached but PUBLIC_API_URL is unset; refusing");
|
||||
AppError::AuthError("Nostr authentication is not enabled".into())
|
||||
})?;
|
||||
let base = cfg.identity.public_api_url.as_deref().ok_or_else(|| {
|
||||
warn!("Nostr endpoint reached but PUBLIC_API_URL is unset; refusing");
|
||||
AppError::AuthError("Nostr authentication is not enabled".into())
|
||||
})?;
|
||||
Ok(format!("{}{}", base.trim_end_matches('/'), path))
|
||||
}
|
||||
|
||||
@@ -540,7 +544,10 @@ pub async fn extension_register(
|
||||
.db
|
||||
.create_user_consuming_gates(
|
||||
gates.invite_code_hash.as_deref(),
|
||||
gates.payment.as_ref().map(|(id, pkh)| (id.as_str(), pkh.as_str())),
|
||||
gates
|
||||
.payment
|
||||
.as_ref()
|
||||
.map(|(id, pkh)| (id.as_str(), pkh.as_str())),
|
||||
NewUser {
|
||||
username: req.username.clone(),
|
||||
pubkey_hash: Some(pubkey_hash.clone()),
|
||||
@@ -699,9 +706,14 @@ struct GatedRegistration {
|
||||
/// Require an invite code and return its hash, erroring if the gate needs one but
|
||||
/// none was supplied (the presence check + literal the old `enforce_invite` used).
|
||||
fn require_invite_hash(invite_code: Option<&str>) -> Result<String, AppError> {
|
||||
let code = invite_code.map(str::trim).filter(|c| !c.is_empty()).ok_or_else(|| {
|
||||
AppError::ValidationError("An invite code is required to register on this instance.".into())
|
||||
})?;
|
||||
let code = invite_code
|
||||
.map(str::trim)
|
||||
.filter(|c| !c.is_empty())
|
||||
.ok_or_else(|| {
|
||||
AppError::ValidationError(
|
||||
"An invite code is required to register on this instance.".into(),
|
||||
)
|
||||
})?;
|
||||
Ok(crate::core::invite::hash_invite_code(code))
|
||||
}
|
||||
|
||||
@@ -729,7 +741,10 @@ async fn plan_gate_consume(
|
||||
has_invite,
|
||||
has_payment,
|
||||
) {
|
||||
GatePlan::Open => Ok(GatedRegistration { invite_code_hash: None, payment: None }),
|
||||
GatePlan::Open => Ok(GatedRegistration {
|
||||
invite_code_hash: None,
|
||||
payment: None,
|
||||
}),
|
||||
GatePlan::All => {
|
||||
let invite_code_hash = if state.cfg().registration.require_invite {
|
||||
Some(require_invite_hash(invite_code)?)
|
||||
@@ -1711,7 +1726,10 @@ pub async fn nostr_register(
|
||||
.db
|
||||
.create_user_consuming_gates(
|
||||
gates.invite_code_hash.as_deref(),
|
||||
gates.payment.as_ref().map(|(id, pkh)| (id.as_str(), pkh.as_str())),
|
||||
gates
|
||||
.payment
|
||||
.as_ref()
|
||||
.map(|(id, pkh)| (id.as_str(), pkh.as_str())),
|
||||
NewUser {
|
||||
username: req.username.clone(),
|
||||
pubkey_hash: None,
|
||||
@@ -1771,7 +1789,10 @@ pub fn routes() -> Router<Arc<AppState>> {
|
||||
.route("/auth/logout", post(logout))
|
||||
.route("/auth/me", get(get_me))
|
||||
.route("/auth/nostr", post(nostr_login))
|
||||
.route("/auth/nostr/register-challenge", get(nostr_register_challenge))
|
||||
.route(
|
||||
"/auth/nostr/register-challenge",
|
||||
get(nostr_register_challenge),
|
||||
)
|
||||
.route("/auth/nostr/register", post(nostr_register))
|
||||
.route("/auth/nostr/link-challenge", get(nostr_link_challenge))
|
||||
.route("/auth/nostr/link", post(nostr_link))
|
||||
@@ -1832,7 +1853,10 @@ mod tests {
|
||||
#[test]
|
||||
fn any_mode_single_gate_is_just_that_gate() {
|
||||
assert_eq!(plan_gates(Any, true, false, false, false), GatePlan::Invite);
|
||||
assert_eq!(plan_gates(Any, false, true, false, false), GatePlan::Payment);
|
||||
assert_eq!(
|
||||
plan_gates(Any, false, true, false, false),
|
||||
GatePlan::Payment
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+1
-5
@@ -104,11 +104,7 @@ pub async fn root(State(state): State<Arc<AppState>>) -> Response {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
}
|
||||
let cfg = state.cfg();
|
||||
let title = cfg
|
||||
.landing
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or("smirk-backend-core");
|
||||
let title = cfg.landing.title.as_deref().unwrap_or("smirk-backend-core");
|
||||
let t = html_escape(title);
|
||||
let html = format!(
|
||||
"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">\
|
||||
|
||||
+10
-7
@@ -321,7 +321,9 @@ pub async fn create_social_tip(
|
||||
let encrypted_key: Option<Vec<u8>> = match req.encrypted_key.as_deref() {
|
||||
Some(h) if !h.is_empty() => {
|
||||
if h.len() > MAX_ENCRYPTED_KEY_HEX {
|
||||
return Err(AppError::ValidationError("encrypted_key is too large.".into()));
|
||||
return Err(AppError::ValidationError(
|
||||
"encrypted_key is too large.".into(),
|
||||
));
|
||||
}
|
||||
Some(
|
||||
hex::decode(h)
|
||||
@@ -560,10 +562,14 @@ pub async fn attach_funding(
|
||||
// DB length-overflow 500 (funding_txid is VARCHAR(128)).
|
||||
let funding_txid = req.funding_txid.trim();
|
||||
if funding_txid.is_empty() {
|
||||
return Err(AppError::ValidationError("funding_txid is required.".into()));
|
||||
return Err(AppError::ValidationError(
|
||||
"funding_txid is required.".into(),
|
||||
));
|
||||
}
|
||||
if funding_txid.len() > 128 {
|
||||
return Err(AppError::ValidationError("funding_txid is too long.".into()));
|
||||
return Err(AppError::ValidationError(
|
||||
"funding_txid is too long.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// The DB fn returns Ok(row) / NotFound / ValidationError; just surface it.
|
||||
@@ -716,10 +722,7 @@ pub fn routes() -> Router<Arc<AppState>> {
|
||||
.route("/tips/social/claimable", get(get_claimable_social_tips))
|
||||
.route("/tips/social/:tip_id/public", get(get_public_social_tip))
|
||||
.route("/tips/social/:tip_id/cancel", post(cancel_social_tip))
|
||||
.route(
|
||||
"/tips/social/:tip_id/attach-funding",
|
||||
post(attach_funding),
|
||||
)
|
||||
.route("/tips/social/:tip_id/attach-funding", post(attach_funding))
|
||||
.route("/tips/social/:tip_id/claim", post(claim_social_tip))
|
||||
.route("/tips/social/:tip_id/confirm-sweep", post(confirm_sweep))
|
||||
.route("/tips/social/:tip_id/clawback", post(clawback_social_tip))
|
||||
|
||||
Reference in New Issue
Block a user