mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-02-09 21:13:09 +00:00
211 lines
6.6 KiB
Rust
211 lines
6.6 KiB
Rust
|
|
//! Federation API endpoints.
|
||
|
|
|
||
|
|
use axum::{
|
||
|
|
extract::{Path, State},
|
||
|
|
http::StatusCode,
|
||
|
|
routing::{get, post},
|
||
|
|
Json, Router,
|
||
|
|
};
|
||
|
|
use serde::Deserialize;
|
||
|
|
use serde_json::{json, Value};
|
||
|
|
use sqlx::PgPool;
|
||
|
|
use uuid::Uuid;
|
||
|
|
|
||
|
|
use crate::auth::AuthUser;
|
||
|
|
use crate::plugins::builtin::federation::{
|
||
|
|
CommunityFederation, FederatedInstance, FederationService,
|
||
|
|
};
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Request Types
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
pub struct RegisterInstanceRequest {
|
||
|
|
pub url: String,
|
||
|
|
pub name: String,
|
||
|
|
pub description: Option<String>,
|
||
|
|
pub public_key: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
pub struct CreateFederationRequest {
|
||
|
|
pub remote_instance_id: Uuid,
|
||
|
|
pub remote_community_id: Uuid,
|
||
|
|
pub remote_community_name: String,
|
||
|
|
pub sync_direction: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
pub struct SetTrustLevelRequest {
|
||
|
|
pub trust_level: i32,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Deserialize)]
|
||
|
|
pub struct IncomingFederationRequest {
|
||
|
|
pub from_instance_url: String,
|
||
|
|
pub from_community_name: String,
|
||
|
|
pub message: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Handlers
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
/// Get all federated instances
|
||
|
|
async fn get_instances(
|
||
|
|
_auth: AuthUser,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
) -> Result<Json<Vec<FederatedInstance>>, (StatusCode, String)> {
|
||
|
|
let instances = FederationService::get_instances(&pool)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(instances))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Register a new federated instance
|
||
|
|
async fn register_instance(
|
||
|
|
_auth: AuthUser,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
Json(req): Json<RegisterInstanceRequest>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
let instance_id = FederationService::register_instance(
|
||
|
|
&pool,
|
||
|
|
&req.url,
|
||
|
|
&req.name,
|
||
|
|
req.description.as_deref(),
|
||
|
|
req.public_key.as_deref(),
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(json!({"id": instance_id})))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get federation statistics
|
||
|
|
async fn get_stats(
|
||
|
|
_auth: AuthUser,
|
||
|
|
Path(community_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
let stats = FederationService::get_stats(&pool, community_id)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(stats))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get community federations
|
||
|
|
async fn get_community_federations(
|
||
|
|
_auth: AuthUser,
|
||
|
|
Path(community_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
) -> Result<Json<Vec<CommunityFederation>>, (StatusCode, String)> {
|
||
|
|
let federations = FederationService::get_community_federations(&pool, community_id)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(federations))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Request federation with another community
|
||
|
|
async fn request_federation(
|
||
|
|
_auth: AuthUser,
|
||
|
|
Path(community_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
Json(req): Json<CreateFederationRequest>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
let federation_id = FederationService::create_federation(
|
||
|
|
&pool,
|
||
|
|
community_id,
|
||
|
|
req.remote_instance_id,
|
||
|
|
req.remote_community_id,
|
||
|
|
&req.remote_community_name,
|
||
|
|
&req.sync_direction,
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(json!({"id": federation_id})))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Approve a federation request
|
||
|
|
async fn approve_federation(
|
||
|
|
auth: AuthUser,
|
||
|
|
Path(federation_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
FederationService::approve_federation(&pool, federation_id, auth.user_id)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(json!({"success": true})))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Set trust level for an instance
|
||
|
|
async fn set_trust_level(
|
||
|
|
_auth: AuthUser,
|
||
|
|
Path(instance_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
Json(req): Json<SetTrustLevelRequest>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
FederationService::set_trust_level(&pool, instance_id, req.trust_level)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(json!({"success": true})))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Handle incoming federation request from another instance
|
||
|
|
/// Note: This endpoint is intentionally unauthenticated for server-to-server federation.
|
||
|
|
/// The request creates a PENDING entry that must be approved by a community admin.
|
||
|
|
/// TODO: Add rate limiting and instance signature verification for production.
|
||
|
|
async fn receive_federation_request(
|
||
|
|
Path(community_id): Path<Uuid>,
|
||
|
|
State(pool): State<PgPool>,
|
||
|
|
Json(req): Json<IncomingFederationRequest>,
|
||
|
|
) -> Result<Json<Value>, (StatusCode, String)> {
|
||
|
|
// Validate the community exists and accepts federation requests
|
||
|
|
let community_exists = sqlx::query_scalar!(
|
||
|
|
"SELECT EXISTS(SELECT 1 FROM communities WHERE id = $1 AND is_active = true) as \"exists!\"",
|
||
|
|
community_id
|
||
|
|
)
|
||
|
|
.fetch_one(&pool)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
if !community_exists {
|
||
|
|
return Err((StatusCode::NOT_FOUND, "Community not found or inactive".to_string()));
|
||
|
|
}
|
||
|
|
|
||
|
|
let request_id = FederationService::request_federation(
|
||
|
|
&pool,
|
||
|
|
&req.from_instance_url,
|
||
|
|
&req.from_community_name,
|
||
|
|
community_id,
|
||
|
|
req.message.as_deref(),
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||
|
|
|
||
|
|
Ok(Json(json!({"id": request_id})))
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// Router
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
pub fn router(pool: PgPool) -> Router {
|
||
|
|
Router::new()
|
||
|
|
// Instances
|
||
|
|
.route("/api/federation/instances", get(get_instances).post(register_instance))
|
||
|
|
.route("/api/federation/instances/{instance_id}/trust", post(set_trust_level))
|
||
|
|
// Community federations
|
||
|
|
.route("/api/communities/{community_id}/federation", get(get_community_federations).post(request_federation))
|
||
|
|
.route("/api/communities/{community_id}/federation/stats", get(get_stats))
|
||
|
|
.route("/api/communities/{community_id}/federation/request", post(receive_federation_request))
|
||
|
|
.route("/api/federation/{federation_id}/approve", post(approve_federation))
|
||
|
|
.with_state(pool)
|
||
|
|
}
|