chore: initial foundation — server skeleton, autodocs, CI

- minimal Axum server exposing GET /health
- OpenAPI contract generated from handlers (gen_openapi -> openapi.json),
  drift-gated in CI so the spec can never silently diverge from the code
- CI: secret-scan + gitleaks, openapi drift gate, fmt/clippy/build/test/doc-test, cargo-deny
- Cargo.lock tracked for reproducible builds; publish=false; pinned toolchain
- README, SECURITY.md, deny.toml, .env.example
This commit is contained in:
jwinterm
2026-06-22 06:18:57 -04:00
commit 143f00701b
17 changed files with 4958 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
//! Health / liveness endpoint.
use axum::Json;
use serde::Serialize;
/// Liveness response.
#[derive(Serialize, utoipa::ToSchema)]
pub struct HealthResponse {
/// Always `"ok"` when the service is reachable.
pub status: String,
}
/// Liveness probe.
///
/// Returns `200` with `{ "status": "ok" }` whenever the server is up. Used by
/// load balancers and uptime checks; requires no authentication.
#[utoipa::path(
get,
path = "/health",
responses((status = 200, description = "Service is up", body = HealthResponse)),
tag = "system"
)]
pub async fn health() -> Json<HealthResponse> {
Json(HealthResponse {
status: "ok".to_string(),
})
}
+4
View File
@@ -0,0 +1,4 @@
//! HTTP API layer: handlers and the OpenAPI assembly.
pub mod health;
pub mod openapi;
+21
View File
@@ -0,0 +1,21 @@
//! OpenAPI specification assembly.
//!
//! `ApiDoc` aggregates the annotated handlers into the spec that is the single
//! source of truth for the API contract and the wallet's generated TypeScript
//! client. Add a path here once its handler carries `#[utoipa::path(...)]` and
//! its request/response types derive `utoipa::ToSchema`.
use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(
info(
title = "smirk-backend-core",
version = "0.3.0",
description = "Open, self-hostable backend for the Smirk non-custodial multi-chain wallet."
),
paths(crate::api::health::health),
components(schemas(crate::api::health::HealthResponse)),
tags((name = "system", description = "Service health and metadata."))
)]
pub struct ApiDoc;
+20
View File
@@ -0,0 +1,20 @@
//! Emit the OpenAPI 3 specification to stdout.
//!
//! The spec is derived from the handlers (`utoipa` annotations) and is the
//! single source of truth for the API contract and the generated TypeScript
//! client. Regenerate the committed contract with:
//!
//! cargo run --bin gen_openapi > openapi.json
//!
//! CI regenerates and diffs against the committed `openapi.json`; a drift is a
//! failed build, not a stale doc.
use smirk_backend_core::api::openapi::ApiDoc;
use utoipa::OpenApi;
fn main() {
let spec = ApiDoc::openapi()
.to_pretty_json()
.expect("serialize OpenAPI spec");
println!("{spec}");
}
+11
View File
@@ -0,0 +1,11 @@
//! smirk-backend-core
//!
//! Open, self-hostable backend for the Smirk non-custodial multi-chain wallet:
//! authentication, per-chain wallet access (Bitcoin, Litecoin, Monero, Wownero,
//! Grin), the Grin slatepack relay, and Nostr-based identity.
//!
//! The HTTP contract is generated from the handlers (`utoipa`) into
//! `openapi.json`, which is the single source of truth for the API and the
//! wallet's generated TypeScript client.
pub mod api;
+23
View File
@@ -0,0 +1,23 @@
//! smirk-backend-core server entry point.
//!
//! Minimal server skeleton. Configuration, application state, the full router,
//! and background tasks are added as modules land; for now it serves the
//! health endpoint so the build, the OpenAPI contract, and deployment wiring
//! are exercised end to end.
use axum::{routing::get, Router};
use smirk_backend_core::api::health;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new().route("/health", get(health::health));
let addr = "0.0.0.0:8080";
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("bind listener");
tracing::info!("smirk-backend-core listening on http://{addr}");
axum::serve(listener, app).await.expect("server error");
}