dragoon-server 0.1.0

Public-relay server for the dragoon remote-executor: axum + rusqlite + ed25519 task signing + per-user message inbox.
Documentation
//! `/v1/workers` (list) and `/v1/workers/:name/fetch` (ad-hoc file fetch).

use axum::{
    extract::{Path, State},
    http::StatusCode,
    middleware,
    routing::{get, post},
    Extension, Json, Router,
};
use serde::Deserialize;
use serde_json::{json, Value};

use dragoon_proto::models::{Task, TaskKind, TaskLimits};

use crate::{
    app::{signed_request, username_for, AppState, SignedSession},
    audit, tasks_repo, workers_repo,
};

pub fn router(state: AppState) -> Router {
    Router::new()
        .route("/v1/workers", get(list_workers))
        .route("/v1/workers/:name/fetch", post(fetch))
        .layer(middleware::from_fn_with_state(state.clone(), signed_request))
        .with_state(state)
}

async fn list_workers(
    State(state): State<AppState>,
    Extension(_sess): Extension<SignedSession>,
) -> Result<Json<Vec<Value>>, StatusCode> {
    let conn = state.conn.lock().unwrap();
    let rows = workers_repo::list_all(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    Ok(Json(
        rows.into_iter()
            .map(|w| {
                json!({
                    "name": w.name,
                    "status": w.status,
                    "current_pwd": w.current_pwd,
                    "current_task_id": w.current_task_id,
                    "last_poll_at": w.last_poll_at,
                })
            })
            .collect(),
    ))
}

#[derive(Debug, Deserialize)]
struct FetchBody {
    path: String,
    glob: String,
}

async fn fetch(
    State(state): State<AppState>,
    Path(name): Path<String>,
    Extension(sess): Extension<SignedSession>,
    Json(body): Json<FetchBody>,
) -> Result<Json<Task>, StatusCode> {
    let conn = state.conn.lock().unwrap();
    if workers_repo::lookup_by_name(&conn, &name)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
        .is_none()
    {
        return Err(StatusCode::NOT_FOUND);
    }
    let id = tasks_repo::new_task_id();
    let username = username_for(&conn, sess.0.user_id);
    let task = tasks_repo::insert_task(
        &conn,
        &id,
        &name,
        &username,
        TaskKind::Fetch,
        "",
        &[body.glob.clone()],
        &TaskLimits::default(),
        Some(&body.path),
    )
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    let _ = audit::log(
        &conn,
        Some(&username),
        "fetch",
        Some(&id),
        Some(&sess.0.fingerprint),
        &json!({"worker": name, "path": body.path, "glob": body.glob}),
    );
    Ok(Json(task))
}