Files
campaign-manager/backend/campaign-service/src/main.rs

119 lines
3.4 KiB
Rust

use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{any, get, post},
Json, Router,
};
use common::{issue_access_token, AppResult};
use serde::Serialize;
use sqlx::{postgres::PgPoolOptions, PgPool};
use tower_http::cors::CorsLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[derive(Clone)]
struct AppState {
users_pool: PgPool,
campaign_pool: PgPool,
jwt_secret: String,
}
#[derive(Serialize)]
struct HealthResponse {
service: &'static str,
status: &'static str,
}
#[derive(Serialize)]
struct RefreshResponse {
#[serde(rename = "accessToken")]
access_token: String,
#[serde(rename = "refreshToken")]
refresh_token: String,
}
#[tokio::main]
async fn main() {
let _ = dotenvy::dotenv();
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "campaign_service=debug,tower_http=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
let users_database_url =
std::env::var("USERS_DATABASE_URL").expect("USERS_DATABASE_URL must be set");
let campaign_database_url =
std::env::var("CAMPAIGN_DATABASE_URL").expect("CAMPAIGN_DATABASE_URL must be set");
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
let port: u16 = std::env::var("CAMPAIGN_PORT")
.or_else(|_| std::env::var("PORT"))
.unwrap_or_else(|_| "3000".to_string())
.parse()
.expect("PORT must be a valid u16");
let users_pool = PgPoolOptions::new()
.max_connections(10)
.connect_lazy(&users_database_url)
.expect("invalid USERS_DATABASE_URL");
let campaign_pool = PgPoolOptions::new()
.max_connections(10)
.connect_lazy(&campaign_database_url)
.expect("invalid CAMPAIGN_DATABASE_URL");
let state = AppState {
users_pool,
campaign_pool,
jwt_secret,
};
let app = Router::new()
.route("/health", get(health))
.route("/v1/auth/refresh", post(refresh_token))
.route("/v1/auth/login", post(not_implemented))
.route("/v1/auth/register", post(not_implemented))
.route("/v1/auth/logout", post(not_implemented))
.route("/v1/*path", any(not_implemented))
.with_state(state)
.layer(CorsLayer::permissive());
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
.await
.expect("failed to bind");
tracing::info!(
"campaign-service listening on {}",
listener.local_addr().unwrap()
);
axum::serve(listener, app).await.expect("server error");
}
async fn health(State(state): State<AppState>) -> impl IntoResponse {
let _ = &state.users_pool;
let _ = &state.campaign_pool;
Json(HealthResponse {
service: "campaign-service",
status: "ok",
})
}
async fn refresh_token(State(state): State<AppState>) -> AppResult<Json<RefreshResponse>> {
// Stub response until real refresh-token/session persistence is implemented.
let access_token = issue_access_token(&state.jwt_secret, "dev-user", vec!["user".to_string()])?;
let refresh_token = uuid::Uuid::new_v4().to_string();
Ok(Json(RefreshResponse {
access_token,
refresh_token,
}))
}
async fn not_implemented() -> impl IntoResponse {
(StatusCode::NOT_IMPLEMENTED, "not implemented")
}