likwid/backend/src/api/demo.rs
Marco Allegretti 910a6465f2 Initial commit: Likwid governance platform
- Backend: Rust/Axum with PostgreSQL, plugin architecture
- Frontend: Astro with polished UI
- Voting methods: Approval, Ranked Choice, Schulze, STAR, Quadratic
- Features: Liquid delegation, transparent moderation, structured deliberation
- Documentation: User and admin guides in /docs
- Deployment: Docker/Podman compose files for production and demo
- Demo: Seeded data with 3 communities, 13 users, 7 proposals

License: AGPLv3
2026-01-27 17:21:58 +01:00

137 lines
3.9 KiB
Rust

//! Demo API endpoints
use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use serde_json::json;
use sqlx::PgPool;
use std::sync::Arc;
use crate::config::Config;
use crate::demo::{self, DEMO_ACCOUNTS};
/// Combined state for demo endpoints
#[derive(Clone)]
pub struct DemoState {
pub pool: PgPool,
pub config: Arc<Config>,
}
/// Get demo mode status and available accounts
async fn get_demo_status(
State(state): State<DemoState>,
) -> impl IntoResponse {
Json(json!({
"demo_mode": state.config.is_demo(),
"accounts": if state.config.is_demo() {
DEMO_ACCOUNTS.iter().map(|(u, _, d)| json!({
"username": u,
"display_name": d,
"password": "demo123"
})).collect::<Vec<_>>()
} else {
vec![]
},
"restrictions": if state.config.is_demo() {
vec![
"Cannot delete communities",
"Cannot delete users",
"Cannot modify instance settings",
"Data resets periodically"
]
} else {
vec![]
}
}))
}
/// Reset demo data to initial state (only in demo mode)
async fn reset_demo(
State(state): State<DemoState>,
) -> impl IntoResponse {
if !state.config.is_demo() {
return (
StatusCode::FORBIDDEN,
Json(json!({"error": "Demo mode not enabled"}))
).into_response();
}
match demo::reset_demo_data(&state.pool).await {
Ok(_) => (
StatusCode::OK,
Json(json!({"success": true, "message": "Demo data has been reset to initial state"}))
).into_response(),
Err(e) => {
tracing::error!("Failed to reset demo data: {}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "Failed to reset demo data"}))
).into_response()
}
}
}
/// Get demo communities summary
async fn get_demo_communities(
State(state): State<DemoState>,
) -> impl IntoResponse {
if !state.config.is_demo() {
return (
StatusCode::OK,
Json(json!({"communities": []}))
).into_response();
}
let communities = sqlx::query_as::<_, (String, String, String, i64, i64)>(
r#"
SELECT
c.name,
c.slug,
COALESCE(c.description, '') as description,
(SELECT COUNT(*) FROM community_members WHERE community_id = c.id) as member_count,
(SELECT COUNT(*) FROM proposals WHERE community_id = c.id) as proposal_count
FROM communities c
WHERE c.slug IN ('aurora', 'civic-commons', 'makers')
ORDER BY c.name
"#
)
.fetch_all(&state.pool)
.await;
match communities {
Ok(rows) => {
let communities: Vec<_> = rows.iter().map(|(name, slug, desc, members, proposals)| {
json!({
"name": name,
"slug": slug,
"description": desc,
"member_count": members,
"proposal_count": proposals
})
}).collect();
(StatusCode::OK, Json(json!({"communities": communities}))).into_response()
}
Err(e) => {
tracing::error!("Failed to fetch demo communities: {}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "Failed to fetch communities"}))
).into_response()
}
}
}
pub fn router(pool: PgPool, config: Arc<Config>) -> Router {
let state = DemoState { pool, config };
Router::new()
.route("/api/demo/status", get(get_demo_status))
.route("/api/demo/reset", post(reset_demo))
.route("/api/demo/communities", get(get_demo_communities))
.with_state(state)
}