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.
- Add missing context fields to logs.
- Ensure compliance with this standard.
- Preserve the startup `println!` banner and metadata in `src/backend/src/main.rs` for debug visibility.
### Agents **may not**:
- Modify business logic, algorithms, backend behavior, or API responses (other than error messages).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,10 @@ use anyhow::Result;
use sqlx::PgPool;
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> {
debug!(user_uuid = %new_perms.uuid, "insert user permissions started");
let perms = sqlx::query_as::<_, UserPermissions>(
r#"
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)
.await?;
debug!(user_uuid = %perms.uuid, "insert user permissions completed");
Ok(perms)
}
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>(
r#"
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)
.await?;
debug!(user_uuid = %uuid, "fetch user permissions by uuid completed");
Ok(perms)
}
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>(
r#"
SELECT EXISTS(
@@ -51,5 +56,6 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
.fetch_one(pool)
.await?;
debug!(user_uuid = %uuid, "check user permissions existence completed");
Ok(exists)
}

View File

@@ -7,6 +7,7 @@ use sqlx::PgPool;
use uuid::Uuid;
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>(
r#"
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)
.await?;
debug!(user_uuid = %user.uuid, "insert user completed");
Ok(user)
}
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>(
r#"
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)
.await?;
debug!(user_uuid = %uuid, "fetch user by uuid completed");
Ok(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>(
r#"
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)
.await?;
debug!(user_uuid = %uuid, "fetch safe user by uuid completed");
Ok(user)
}
pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> {
debug!("fetch all internal users started");
let users = sqlx::query_as::<_, InternalUser>(
r#"
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)
.await?;
debug!("fetch all internal users completed");
Ok(users)
}
pub async fn get_safe_all(pool: &PgPool) -> Result<Vec<User>> {
debug!("fetch all safe users started");
let users = sqlx::query_as::<_, User>(
r#"
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)
.await?;
debug!("fetch all safe users completed");
Ok(users)
}
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>(
r#"
SELECT EXISTS(
@@ -94,5 +105,6 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
.fetch_one(pool)
.await?;
debug!(user_uuid = %uuid, "check user existence completed");
Ok(exists)
}

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{Ok, Result};
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_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -38,6 +38,20 @@ async fn main() -> Result<()> {
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 config = AppCfg {
db_path: db_path.clone(),
@@ -48,7 +62,9 @@ async fn main() -> Result<()> {
let app_result = router::init_router(state.clone()).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?;
info!("http server stopped");
Ok(())
}

View File

@@ -1,22 +1,25 @@
use axum::{
Router,
extract::Request,
http::{Method, header::AUTHORIZATION},
middleware::Next,
response::IntoResponse,
};
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 {
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;
debug!("auth_middleware exit");
let status = response.status();
debug!(%method, uri, %status, "auth middleware completed");
response
}
pub fn cors() -> CorsLayer {
debug!("Generating CorsLayer");
debug!("build cors layer");
CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST])

View File

@@ -8,12 +8,15 @@ use axum::{
};
use serde_json::{Value, json};
use std::sync::Arc;
use tower::{Layer, ServiceBuilder};
use tower::ServiceBuilder;
use crate::prelude::*;
use crate::state::AppState;
pub async fn init_router(app_state: Arc<AppState>) -> Router {
Router::new()
info!("router initialization started");
let router = Router::new()
.route(
"/api/ping",
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))
.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> {
debug!("ping request received");
Ok(Json(json!({ "response": "pong"})))
}

View File

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

View File

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