ocre-cli 0.2.0

Command-line tool for Ocre: create, generate, migrate, run and deploy apps.
//! Realtime channels: pages subscribe with htmx's WebSocket extension
//! (`<div hx-ext="ws" ws-connect="/realtime/<channel>">`) and receive the HTML
//! that handlers and jobs send with `ocre::realtime::broadcast(&ctx, "<channel>", &html)`.
//! Generated by `ocre g scaffold Question event:references body:text votes:integer answered:boolean --realtime`.
//! Each `ocre g scaffold ... --realtime` (and `ocre g external_job ... --realtime`) adds its channels below.

use axum::{
    Router,
    extract::{Path, State},
    response::Response,
    routing::get,
};
use ocre::{Ctx, Error, OptionExt, Result, realtime::WebSocketUpgrade};

use crate::auth::OptionalUser;
use crate::models::event;

/// `/ocre/dev/realtime/sent.json` lists recent broadcasts in `ocre dev` (for
/// tests); it is a 404 in deployed builds.
pub fn routes() -> Router<Ctx> {
    Router::new().route("/realtime/{channel}", get(connect)).merge(ocre::realtime::dev_routes())
}

/// Opens a WebSocket on `channel` for whoever may listen to it:
/// `event:<public id>`, an event's room, is open to everyone with the link;
/// `host:<public id>`, the same list with moderation buttons, only to the
/// event's host. Unknown channels and events are a 404.
async fn connect(
    State(ctx): State<Ctx>,
    Path(channel): Path<String>,
    OptionalUser(user): OptionalUser,
    upgrade: WebSocketUpgrade,
) -> Result<Response> {
    match channel.as_str() {
        // ocre:channels
        name => {
            let (kind, key) = name.split_once(':').or_404()?;
            let record = event::find_by_public_id(&ctx, key).await?.or_404()?;
            match kind {
                "event" => {}
                "host" => {
                    let user = user.ok_or(Error::Unauthorized)?;
                    if user.id != record.user_id {
                        return Err(Error::Forbidden);
                    }
                }
                _ => return Err(Error::NotFound),
            }
        }
    }
    upgrade.connect(&ctx, &channel).await
}