Restore startup banner output

This commit is contained in:
Hector van der Aa
2025-12-01 00:36:21 +01:00
parent 6d1434b1fe
commit 7a896c8b7c
13 changed files with 96 additions and 35 deletions

View File

@@ -95,6 +95,7 @@ Agents should insert at least:
- Improve error messages and errormapping logic. - Improve error messages and errormapping logic.
- Add missing context fields to logs. - Add missing context fields to logs.
- Ensure compliance with this standard. - Ensure compliance with this standard.
- Preserve the startup `println!` banner and metadata in `src/backend/src/main.rs` for debug visibility.
### Agents **may not**: ### Agents **may not**:
- Modify business logic, algorithms, backend behavior, or API responses (other than error messages). - Modify business logic, algorithms, backend behavior, or API responses (other than error messages).

View File

@@ -1,6 +1,6 @@
use argon2::{ use argon2::{
Argon2, Argon2,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
}; };
use tracing::debug; use tracing::debug;
@@ -10,6 +10,6 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::Error> {
let hash = argon2 let hash = argon2
.hash_password(password.as_bytes(), &salt)? .hash_password(password.as_bytes(), &salt)?
.to_string(); .to_string();
debug!("Hashed password {}", hash); debug!("password hashed");
Ok(hash) Ok(hash)
} }

View File

@@ -1,18 +1,15 @@
use crate::{ use crate::{
domain::{ domain::{user::InternalUser, user_prems::UserPermissions},
user::{self, InternalUser},
user_prems::UserPermissions,
},
infra::db, infra::db,
prelude::*, prelude::*,
}; };
use std::sync::Arc; use std::sync::Arc;
use axum::{Json, http::StatusCode}; use axum::http::StatusCode;
use uuid::Uuid; use uuid::Uuid;
use validator::Validate; use validator::Validate;
use anyhow::{Context, anyhow}; use anyhow::Context;
use crate::{ use crate::{
domain::user::{InternalNewUser, NewUser, User}, domain::user::{InternalNewUser, NewUser, User},
@@ -20,29 +17,33 @@ use crate::{
}; };
pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, StatusCode> { pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, StatusCode> {
debug!("create user started");
new_user.validate().map_err(|e| { new_user.validate().map_err(|e| {
error!("User validation failed: {e}"); error!(error = %e, "user validation failed");
StatusCode::BAD_REQUEST StatusCode::BAD_REQUEST
})?; })?;
let internal = InternalNewUser::try_from(new_user).map_err(|e| { let internal = InternalNewUser::try_from(new_user).map_err(|e| {
error!("Conversion to InternalUser failed: {e}"); error!(error = %e, "convert to internal user failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
let created_user = db::user::create(&state.db_pool, internal) let created_user = db::user::create(&state.db_pool, internal)
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to create new user: {e}"); error!(error = %e, "create user failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
info!(user_uuid = %created_user.uuid, "user created");
Ok(User::from(created_user)) Ok(User::from(created_user))
} }
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> { pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> {
debug!("fetch all users started");
let users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| { let users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| {
error!("Failed to fetch all users: {e}"); error!(error = %e, "fetch all users failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
@@ -53,16 +54,18 @@ pub async fn get_safe_by_uuid(
state: Arc<AppState>, state: Arc<AppState>,
uuid: Uuid, uuid: Uuid,
) -> Result<Option<User>, StatusCode> { ) -> Result<Option<User>, StatusCode> {
debug!(user_uuid = %uuid, "fetch user by uuid started");
let user = db::user::get_safe_by_uuid(&state.db_pool, uuid.clone()) let user = db::user::get_safe_by_uuid(&state.db_pool, uuid.clone())
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to fetch user: {e}"); error!(error = %e, user_uuid = %uuid, "fetch user failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
Ok(user) Ok(user)
} }
pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Option<InternalUser>> { pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Option<InternalUser>> {
debug!(user_uuid = %uuid, "fetch internal user started");
let mut user = match db::user::get_by_uuid(&state.db_pool, uuid) let mut user = match db::user::get_by_uuid(&state.db_pool, uuid)
.await .await
.context("failed to fetch user by uuid")? .context("failed to fetch user by uuid")?
@@ -93,23 +96,26 @@ pub async fn set_perms(
state: Arc<AppState>, state: Arc<AppState>,
user_perms: UserPermissions, user_perms: UserPermissions,
) -> Result<(), StatusCode> { ) -> Result<(), StatusCode> {
debug!(user_uuid = %user_perms.uuid, "assign permissions started");
let exists = db::user::exists_by_uuid(&state.db_pool, user_perms.uuid) let exists = db::user::exists_by_uuid(&state.db_pool, user_perms.uuid)
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to verify user exists: {e}"); error!(error = %e, user_uuid = %user_perms.uuid, "verify user exists failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
if !exists { if !exists {
warn!(user_uuid = %user_perms.uuid, "assign permissions skipped for missing user");
return Err(StatusCode::BAD_REQUEST); return Err(StatusCode::BAD_REQUEST);
} }
db::perms::create(&state.db_pool, user_perms) db::perms::create(&state.db_pool, user_perms)
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to create user permissions entry: {e}"); error!(error = %e, "create user permissions entry failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
info!("user permissions assigned");
Ok(()) Ok(())
} }

View File

@@ -1,8 +1,5 @@
use std::fmt::Display; use std::fmt::Display;
use axum::response::IntoResponse;
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::prelude::FromRow; use sqlx::prelude::FromRow;
use uuid::Uuid; use uuid::Uuid;

View File

@@ -1,4 +1,4 @@
use serde::{Deserialize, de::value}; use serde::Deserialize;
use sqlx::prelude::FromRow; use sqlx::prelude::FromRow;
use uuid::Uuid; use uuid::Uuid;

View File

@@ -5,19 +5,24 @@ use std::time::Duration;
use sqlx::{PgPool, postgres::PgPoolOptions}; use sqlx::{PgPool, postgres::PgPoolOptions};
use crate::prelude::*;
use anyhow::Result; use anyhow::Result;
pub async fn connect(database_url: &str) -> Result<PgPool> { pub async fn connect(database_url: &str) -> Result<PgPool> {
debug!("open postgres pool started");
let pool = PgPoolOptions::new() let pool = PgPoolOptions::new()
.max_connections(10) .max_connections(10)
.acquire_timeout(Duration::from_secs(5)) .acquire_timeout(Duration::from_secs(5))
.connect(database_url) .connect(database_url)
.await?; .await?;
debug!("open postgres pool completed");
Ok(pool) Ok(pool)
} }
pub async fn migrate(pool: &PgPool) -> Result<()> { pub async fn migrate(pool: &PgPool) -> Result<()> {
debug!("database migration started");
sqlx::migrate!().run(pool).await?; sqlx::migrate!().run(pool).await?;
debug!("database migration completed");
Ok(()) Ok(())
} }

View File

@@ -2,9 +2,10 @@ use anyhow::Result;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
use crate::domain::{user::User, user_prems::UserPermissions}; use crate::{domain::user_prems::UserPermissions, prelude::*};
pub async fn create(pool: &PgPool, new_perms: UserPermissions) -> Result<UserPermissions> { pub async fn create(pool: &PgPool, new_perms: UserPermissions) -> Result<UserPermissions> {
debug!(user_uuid = %new_perms.uuid, "insert user permissions started");
let perms = sqlx::query_as::<_, UserPermissions>( let perms = sqlx::query_as::<_, UserPermissions>(
r#" r#"
INSERT INTO user_permissions (uuid, root, manage_users, login) INSERT INTO user_permissions (uuid, root, manage_users, login)
@@ -19,10 +20,12 @@ pub async fn create(pool: &PgPool, new_perms: UserPermissions) -> Result<UserPer
.fetch_one(pool) .fetch_one(pool)
.await?; .await?;
debug!(user_uuid = %perms.uuid, "insert user permissions completed");
Ok(perms) Ok(perms)
} }
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermissions>> { pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermissions>> {
debug!(user_uuid = %uuid, "fetch user permissions by uuid started");
let perms = sqlx::query_as::<_, UserPermissions>( let perms = sqlx::query_as::<_, UserPermissions>(
r#" r#"
SELECT uuid, root, manage_users, login SELECT uuid, root, manage_users, login
@@ -34,10 +37,12 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermiss
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?;
debug!(user_uuid = %uuid, "fetch user permissions by uuid completed");
Ok(perms) Ok(perms)
} }
pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> { pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
debug!(user_uuid = %uuid, "check user permissions existence started");
let exists = sqlx::query_scalar::<_, bool>( let exists = sqlx::query_scalar::<_, bool>(
r#" r#"
SELECT EXISTS( SELECT EXISTS(
@@ -51,5 +56,6 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
.fetch_one(pool) .fetch_one(pool)
.await?; .await?;
debug!(user_uuid = %uuid, "check user permissions existence completed");
Ok(exists) Ok(exists)
} }

View File

@@ -7,6 +7,7 @@ use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
pub async fn create(pool: &PgPool, new_user: InternalNewUser) -> Result<InternalUser> { pub async fn create(pool: &PgPool, new_user: InternalNewUser) -> Result<InternalUser> {
debug!(user_uuid = %new_user.uuid, "insert user started");
let user = sqlx::query_as::<_, InternalUser>( let user = sqlx::query_as::<_, InternalUser>(
r#" r#"
INSERT INTO users (uuid, username, email, password_hash, first_name, last_name) INSERT INTO users (uuid, username, email, password_hash, first_name, last_name)
@@ -23,10 +24,12 @@ pub async fn create(pool: &PgPool, new_user: InternalNewUser) -> Result<Internal
.fetch_one(pool) .fetch_one(pool)
.await?; .await?;
debug!(user_uuid = %user.uuid, "insert user completed");
Ok(user) Ok(user)
} }
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<InternalUser>> { pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<InternalUser>> {
debug!(user_uuid = %uuid, "fetch user by uuid started");
let user = sqlx::query_as::<_, InternalUser>( let user = sqlx::query_as::<_, InternalUser>(
r#" r#"
SELECT uuid, username, email, password_hash, first_name, last_name FROM users WHERE uuid = $1 SELECT uuid, username, email, password_hash, first_name, last_name FROM users WHERE uuid = $1
@@ -36,10 +39,12 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<InternalUse
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?;
debug!(user_uuid = %uuid, "fetch user by uuid completed");
Ok(user) Ok(user)
} }
pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>> { pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>> {
debug!(user_uuid = %uuid, "fetch safe user by uuid started");
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
r#" r#"
SELECT uuid, username, email, first_name, last_name FROM users WHERE uuid = $1 SELECT uuid, username, email, first_name, last_name FROM users WHERE uuid = $1
@@ -49,10 +54,12 @@ pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>>
.fetch_optional(pool) .fetch_optional(pool)
.await?; .await?;
debug!(user_uuid = %uuid, "fetch safe user by uuid completed");
Ok(user) Ok(user)
} }
pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> { pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> {
debug!("fetch all internal users started");
let users = sqlx::query_as::<_, InternalUser>( let users = sqlx::query_as::<_, InternalUser>(
r#" r#"
SELECT uuid, username, email, password_hash, first_name, last_name SELECT uuid, username, email, password_hash, first_name, last_name
@@ -63,10 +70,12 @@ pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> {
.fetch_all(pool) .fetch_all(pool)
.await?; .await?;
debug!("fetch all internal users completed");
Ok(users) Ok(users)
} }
pub async fn get_safe_all(pool: &PgPool) -> Result<Vec<User>> { pub async fn get_safe_all(pool: &PgPool) -> Result<Vec<User>> {
debug!("fetch all safe users started");
let users = sqlx::query_as::<_, User>( let users = sqlx::query_as::<_, User>(
r#" r#"
SELECT uuid, username, email, first_name, last_name SELECT uuid, username, email, first_name, last_name
@@ -77,10 +86,12 @@ pub async fn get_safe_all(pool: &PgPool) -> Result<Vec<User>> {
.fetch_all(pool) .fetch_all(pool)
.await?; .await?;
debug!("fetch all safe users completed");
Ok(users) Ok(users)
} }
pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> { pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
debug!(user_uuid = %uuid, "check user existence started");
let exists = sqlx::query_scalar::<_, bool>( let exists = sqlx::query_scalar::<_, bool>(
r#" r#"
SELECT EXISTS( SELECT EXISTS(
@@ -94,5 +105,6 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
.fetch_one(pool) .fetch_one(pool)
.await?; .await?;
debug!(user_uuid = %uuid, "check user existence completed");
Ok(exists) Ok(exists)
} }

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use rustymine_daemon::{config::AppCfg, router, state::AppState}; use rustymine_daemon::{config::AppCfg, router, state::AppState};
use tracing::{Level, debug, error, info, warn}; use tracing::{Level, debug, info};
pub const APP_NAME: &str = env!("CARGO_PKG_NAME"); pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -38,6 +38,20 @@ async fn main() -> Result<()> {
tracing::subscriber::set_global_default(subscriber)?; tracing::subscriber::set_global_default(subscriber)?;
debug!(logo = ASCII_LOGO, "render application banner");
info!(
app = APP_NAME,
version = APP_VERSION,
build_mode = BUILD_MODE,
"starting application"
);
info!(
git_hash = GIT_HASH,
git_suffix = GIT_SUFFIX,
build_date = BUILD_DATE,
"build metadata"
);
let db_path: String = "postgres://rustymine:minecraft@localhost:5432/rustymine_dev".to_string(); let db_path: String = "postgres://rustymine:minecraft@localhost:5432/rustymine_dev".to_string();
let config = AppCfg { let config = AppCfg {
db_path: db_path.clone(), db_path: db_path.clone(),
@@ -48,7 +62,9 @@ async fn main() -> Result<()> {
let app_result = router::init_router(state.clone()).await; let app_result = router::init_router(state.clone()).await;
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
info!(listen_addr = "0.0.0.0:3000", "http server binding started");
axum::serve(listener, app_result).await?; axum::serve(listener, app_result).await?;
info!("http server stopped");
Ok(()) Ok(())
} }

View File

@@ -1,22 +1,25 @@
use axum::{ use axum::{
Router,
extract::Request, extract::Request,
http::{Method, header::AUTHORIZATION}, http::{Method, header::AUTHORIZATION},
middleware::Next, middleware::Next,
response::IntoResponse, response::IntoResponse,
}; };
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
use tracing::{debug, error, info, warn}; use tracing::debug;
pub async fn auth(request: Request, next: Next) -> impl IntoResponse { pub async fn auth(request: Request, next: Next) -> impl IntoResponse {
debug!("auth_middleware entry"); let method = request.method().clone();
let uri = request.uri().path().to_owned();
debug!(%method, uri, "auth middleware started");
let response = next.run(request).await; let response = next.run(request).await;
debug!("auth_middleware exit"); let status = response.status();
debug!(%method, uri, %status, "auth middleware completed");
response response
} }
pub fn cors() -> CorsLayer { pub fn cors() -> CorsLayer {
debug!("Generating CorsLayer"); debug!("build cors layer");
CorsLayer::new() CorsLayer::new()
.allow_origin(Any) .allow_origin(Any)
.allow_methods([Method::GET, Method::POST]) .allow_methods([Method::GET, Method::POST])

View File

@@ -8,12 +8,15 @@ use axum::{
}; };
use serde_json::{Value, json}; use serde_json::{Value, json};
use std::sync::Arc; use std::sync::Arc;
use tower::{Layer, ServiceBuilder}; use tower::ServiceBuilder;
use crate::prelude::*;
use crate::state::AppState; use crate::state::AppState;
pub async fn init_router(app_state: Arc<AppState>) -> Router { pub async fn init_router(app_state: Arc<AppState>) -> Router {
Router::new() info!("router initialization started");
let router = Router::new()
.route( .route(
"/api/ping", "/api/ping",
get(ping).layer( get(ping).layer(
@@ -33,9 +36,13 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
) )
.route("/api/users/{uuid}", get(user_routes::get_uuid)) .route("/api/users/{uuid}", get(user_routes::get_uuid))
.layer(ServiceBuilder::new().layer(middleware::cors())) .layer(ServiceBuilder::new().layer(middleware::cors()))
.with_state(app_state.clone()) .with_state(app_state.clone());
info!("router initialization completed");
router
} }
async fn ping() -> Result<Json<Value>, StatusCode> { async fn ping() -> Result<Json<Value>, StatusCode> {
debug!("ping request received");
Ok(Json(json!({ "response": "pong"}))) Ok(Json(json!({ "response": "pong"})))
} }

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::{ use crate::{
core, core,
domain::user::{InternalNewUser, NewUser, User}, domain::user::{NewUser, User},
state::AppState, state::AppState,
}; };
use anyhow::Result; use anyhow::Result;
@@ -18,12 +18,16 @@ pub async fn create(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Json(new_user): Json<NewUser>, Json(new_user): Json<NewUser>,
) -> Result<Json<User>, StatusCode> { ) -> Result<Json<User>, StatusCode> {
debug!("create user route started");
let user = core::user_routines::create(state, new_user).await?; let user = core::user_routines::create(state, new_user).await?;
info!("create user route completed");
Ok(Json(user)) Ok(Json(user))
} }
pub async fn get_all(State(state): State<Arc<AppState>>) -> Result<Json<Vec<User>>, StatusCode> { pub async fn get_all(State(state): State<Arc<AppState>>) -> Result<Json<Vec<User>>, StatusCode> {
debug!("list users route started");
let users = core::user_routines::get_all(state).await?; let users = core::user_routines::get_all(state).await?;
debug!(user_count = users.len(), "list users route completed");
Ok(Json(users)) Ok(Json(users))
} }
@@ -31,6 +35,8 @@ pub async fn get_uuid(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Path(uuid): Path<Uuid>, Path(uuid): Path<Uuid>,
) -> Result<Json<Option<User>>, StatusCode> { ) -> Result<Json<Option<User>>, StatusCode> {
debug!(user_uuid = %uuid, "get user by uuid route started");
let user = core::user_routines::get_safe_by_uuid(state, uuid).await?; let user = core::user_routines::get_safe_by_uuid(state, uuid).await?;
debug!("get user by uuid route completed");
Ok(Json(user)) Ok(Json(user))
} }

View File

@@ -4,7 +4,7 @@ use crate::prelude::*;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{config::AppCfg, domain::user::InternalNewUser, infra::db}; use crate::{config::AppCfg, infra::db};
pub struct AppState { pub struct AppState {
pub db_pool: PgPool, pub db_pool: PgPool,
@@ -12,23 +12,25 @@ pub struct AppState {
impl AppState { impl AppState {
pub async fn new(config: &AppCfg) -> Self { pub async fn new(config: &AppCfg) -> Self {
debug!("Initiating new AppState"); debug!("init app state");
debug!("establish database connection");
let db_pool = db::connect(&config.db_path) let db_pool = db::connect(&config.db_path)
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to connect to database: {e}"); error!(error = %e, "connect to database failed");
exit(20); exit(20);
}) })
.unwrap(); .unwrap();
debug!("run database migrations");
db::migrate(&db_pool) db::migrate(&db_pool)
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to migrade database: {e}"); error!(error = %e, "database migration failed");
exit(22); exit(22);
}) })
.unwrap(); .unwrap();
info!("DB connect and migrate sucessful"); info!("database ready after connect and migrate");
Self { db_pool: db_pool } Self { db_pool: db_pool }
} }
} }