crates/server: REST API routes; RBAC auth middleware; cookie sessions; dashboard

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I5298a925bd9c11780e49d8b1c98eebd86a6a6964
This commit is contained in:
raf 2026-02-01 15:13:33 +03:00
commit 235d3d38a6
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
38 changed files with 6275 additions and 7 deletions

View file

@ -0,0 +1,125 @@
use axum::{
Json, Router,
extract::{Path, State},
routing::get,
};
use fc_common::Validate;
use fc_common::models::{CreateRemoteBuilder, RemoteBuilder, SystemStatus, UpdateRemoteBuilder};
use uuid::Uuid;
use crate::auth_middleware::RequireAdmin;
use crate::error::ApiError;
use crate::state::AppState;
async fn list_builders(
State(state): State<AppState>,
) -> Result<Json<Vec<RemoteBuilder>>, ApiError> {
let builders = fc_common::repo::remote_builders::list(&state.pool)
.await
.map_err(ApiError)?;
Ok(Json(builders))
}
async fn get_builder(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Json<RemoteBuilder>, ApiError> {
let builder = fc_common::repo::remote_builders::get(&state.pool, id)
.await
.map_err(ApiError)?;
Ok(Json(builder))
}
async fn create_builder(
_auth: RequireAdmin,
State(state): State<AppState>,
Json(input): Json<CreateRemoteBuilder>,
) -> Result<Json<RemoteBuilder>, ApiError> {
input
.validate()
.map_err(|msg| ApiError(fc_common::CiError::Validation(msg)))?;
let builder = fc_common::repo::remote_builders::create(&state.pool, input)
.await
.map_err(ApiError)?;
Ok(Json(builder))
}
async fn update_builder(
_auth: RequireAdmin,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(input): Json<UpdateRemoteBuilder>,
) -> Result<Json<RemoteBuilder>, ApiError> {
input
.validate()
.map_err(|msg| ApiError(fc_common::CiError::Validation(msg)))?;
let builder = fc_common::repo::remote_builders::update(&state.pool, id, input)
.await
.map_err(ApiError)?;
Ok(Json(builder))
}
async fn delete_builder(
_auth: RequireAdmin,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, ApiError> {
fc_common::repo::remote_builders::delete(&state.pool, id)
.await
.map_err(ApiError)?;
Ok(Json(serde_json::json!({"deleted": true})))
}
async fn system_status(
_auth: RequireAdmin,
State(state): State<AppState>,
) -> Result<Json<SystemStatus>, ApiError> {
let pool = &state.pool;
let projects: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM projects")
.fetch_one(pool)
.await
.map_err(|e| ApiError(fc_common::CiError::Database(e)))?;
let jobsets: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM jobsets")
.fetch_one(pool)
.await
.map_err(|e| ApiError(fc_common::CiError::Database(e)))?;
let evaluations: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM evaluations")
.fetch_one(pool)
.await
.map_err(|e| ApiError(fc_common::CiError::Database(e)))?;
let stats = fc_common::repo::builds::get_stats(pool)
.await
.map_err(ApiError)?;
let builders = fc_common::repo::remote_builders::count(pool)
.await
.map_err(ApiError)?;
let channels: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM channels")
.fetch_one(pool)
.await
.map_err(|e| ApiError(fc_common::CiError::Database(e)))?;
Ok(Json(SystemStatus {
projects_count: projects.0,
jobsets_count: jobsets.0,
evaluations_count: evaluations.0,
builds_pending: stats.pending_builds.unwrap_or(0),
builds_running: stats.running_builds.unwrap_or(0),
builds_completed: stats.completed_builds.unwrap_or(0),
builds_failed: stats.failed_builds.unwrap_or(0),
remote_builders: builders,
channels_count: channels.0,
}))
}
pub fn router() -> Router<AppState> {
Router::new()
.route("/admin/builders", get(list_builders).post(create_builder))
.route(
"/admin/builders/{id}",
get(get_builder).put(update_builder).delete(delete_builder),
)
.route("/admin/system", get(system_status))
}