barycenter/src/admin_graphql.rs
Till Wegmueller a1056bb237
feat: add admin GraphQL API, background jobs, and user sync CLI
Major Features:
- Admin GraphQL API with dual endpoints (Seaography + custom)
- Background job scheduler with execution tracking
- Idempotent user sync CLI for Kubernetes deployments
- Secure PUT /properties endpoint with Bearer token auth

Admin GraphQL API:
- Entity CRUD via Seaography at /admin/graphql
- Custom job management API at /admin/jobs
- Mutations: triggerJob
- Queries: jobLogs, availableJobs
- GraphiQL playgrounds for both endpoints

Background Jobs:
- tokio-cron-scheduler integration
- Automated cleanup of expired sessions (hourly)
- Automated cleanup of expired refresh tokens (hourly)
- Job execution tracking in database
- Manual job triggering via GraphQL

User Sync CLI:
- Command: barycenter sync-users --file users.json
- Idempotent user synchronization from JSON
- Creates new users with hashed passwords
- Updates existing users (enabled, email_verified, email)
- Syncs custom properties per user
- Perfect for Kubernetes init containers

Security Enhancements:
- PUT /properties endpoint requires Bearer token
- Users can only modify their own properties
- Public registration disabled by default
- Admin API on separate port for network isolation

Database:
- New job_executions table for job tracking
- User update functions (update_user, update_user_email)
- PostgreSQL + SQLite support maintained

Configuration:
- allow_public_registration setting (default: false)
- admin_port setting (default: main port + 1)

Documentation:
- Comprehensive Kubernetes deployment guide
- User sync JSON schema and examples
- Init container and CronJob examples
- Production deployment patterns

Files Added:
- src/admin_graphql.rs - GraphQL schema builders
- src/admin_mutations.rs - Custom mutations and queries
- src/jobs.rs - Job scheduler and tracking
- src/user_sync.rs - User sync logic
- src/entities/ - SeaORM entities (8 entities)
- docs/kubernetes-deployment.md - K8s deployment guide
- users.json.example - User sync example

Dependencies:
- tokio-cron-scheduler 0.13
- seaography 1.1.4
- async-graphql 7.0
- async-graphql-axum 7.0

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:06:50 +01:00

113 lines
3.6 KiB
Rust

use async_graphql::dynamic::Schema as DynamicSchema;
use async_graphql::EmptySubscription;
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use axum::{
extract::State,
response::IntoResponse,
routing::{get, post},
Router,
};
use sea_orm::DatabaseConnection;
use std::sync::Arc;
use crate::admin_mutations::{AdminMutation, AdminQuery};
use crate::entities;
/// Initialize the Seaography admin GraphQL schema with all entities
pub fn build_seaography_schema(db: DatabaseConnection) -> DynamicSchema {
use seaography::{Builder, BuilderContext};
// Create a static BuilderContext for Seaography
let context: &'static BuilderContext = Box::leak(Box::new(BuilderContext::default()));
let mut builder = Builder::new(context, db.clone());
// Register all entities
builder.register_entity::<entities::user::Entity>(vec![]);
builder.register_entity::<entities::client::Entity>(vec![]);
builder.register_entity::<entities::session::Entity>(vec![]);
builder.register_entity::<entities::access_token::Entity>(vec![]);
builder.register_entity::<entities::auth_code::Entity>(vec![]);
builder.register_entity::<entities::refresh_token::Entity>(vec![]);
builder.register_entity::<entities::property::Entity>(vec![]);
builder.register_entity::<entities::job_execution::Entity>(vec![]);
// Build and return the schema
builder.schema_builder().finish().unwrap()
}
/// Build custom job management GraphQL schema
pub fn build_jobs_schema(
db: DatabaseConnection,
) -> async_graphql::Schema<AdminQuery, AdminMutation, EmptySubscription> {
async_graphql::Schema::build(AdminQuery, AdminMutation, EmptySubscription)
.data(Arc::new(db))
.finish()
}
#[derive(Clone)]
pub struct SeaographyState {
pub schema: DynamicSchema,
}
#[derive(Clone)]
pub struct JobsState {
pub schema: async_graphql::Schema<AdminQuery, AdminMutation, EmptySubscription>,
}
/// Seaography GraphQL POST handler for entity CRUD
async fn seaography_handler(
State(state): State<Arc<SeaographyState>>,
req: GraphQLRequest,
) -> GraphQLResponse {
state.schema.execute(req.into_inner()).await.into()
}
/// Jobs GraphQL POST handler for job management
async fn jobs_handler(
State(state): State<Arc<JobsState>>,
req: GraphQLRequest,
) -> GraphQLResponse {
state.schema.execute(req.into_inner()).await.into()
}
/// Seaography GraphQL playground (GraphiQL) handler
async fn seaography_playground() -> impl IntoResponse {
axum::response::Html(
async_graphql::http::GraphiQLSource::build()
.endpoint("/admin/graphql")
.finish(),
)
}
/// Jobs GraphQL playground (GraphiQL) handler
async fn jobs_playground() -> impl IntoResponse {
axum::response::Html(
async_graphql::http::GraphiQLSource::build()
.endpoint("/admin/jobs")
.finish(),
)
}
/// Create the admin API router with both Seaography and custom job endpoints
pub fn router(
seaography_schema: DynamicSchema,
jobs_schema: async_graphql::Schema<AdminQuery, AdminMutation, EmptySubscription>,
) -> Router {
let seaography_state = Arc::new(SeaographyState {
schema: seaography_schema,
});
let jobs_state = Arc::new(JobsState {
schema: jobs_schema,
});
Router::new()
// Seaography entity CRUD
.route("/admin/graphql", post(seaography_handler))
.route("/admin/playground", get(seaography_playground))
.with_state(seaography_state)
// Custom job management
.route("/admin/jobs", post(jobs_handler))
.route("/admin/jobs/playground", get(jobs_playground))
.with_state(jobs_state)
}