Skip to main content

ograf_core/
lib.rs

1//! The pure OGraf-spec Core server: graphics, renderers, actions, and the
2//! renderer WebSocket protocol. Knows nothing about zones, API keys, or
3//! admin accounts — every access decision is delegated to whatever
4//! [`access::AccessControl`] implementation the binary wires in (Dependency
5//! Inversion principle). A consumer that wants no access control at all can
6//! use [`access::AllowAllAccessControl`].
7
8pub mod access;
9pub mod config;
10pub mod directory;
11pub mod error;
12pub mod handlers;
13pub mod models;
14pub mod protocol;
15pub mod renderer_ws;
16pub mod store;
17
18use std::sync::Arc;
19
20use access::AccessControl;
21use config::Config;
22use directory::{NoDirectory, RendererDirectory};
23use store::renderers::RendererRegistry;
24
25#[derive(Clone)]
26#[non_exhaustive]
27pub struct AppState {
28    pub config: Arc<Config>,
29    pub renderers: Arc<RendererRegistry>,
30    pub access: Arc<dyn AccessControl>,
31    pub directory: Arc<dyn RendererDirectory>,
32}
33
34impl AppState {
35    /// With [`NoDirectory`] — see [`AppState::with_directory`] to list
36    /// renderers from a consumer's own storage too.
37    pub fn new(
38        config: Arc<Config>,
39        renderers: Arc<RendererRegistry>,
40        access: Arc<dyn AccessControl>,
41    ) -> Self {
42        Self { config, renderers, access, directory: Arc::new(NoDirectory) }
43    }
44
45    pub fn with_directory(mut self, directory: Arc<dyn RendererDirectory>) -> Self {
46        self.directory = directory;
47        self
48    }
49}
50
51pub use axum::Router;
52
53/// Where renderers open their WebSocket (`GET`, upgraded). Outside
54/// `/ograf/v1` on purpose, see `build_router`.
55pub const RENDERER_CONNECT_PATH: &str = "/rendererApi/v1/connect";
56
57/// Builds the `/ograf/v1/*` API router plus the internal graphic-asset route
58/// used by the renderer HTML — everything a consumer needs to nest under its
59/// own top-level router alongside its own admin routes. Static file serving
60/// (`/renderer`, admin UI) and CORS/tracing layers are the binary's own
61/// concern, not Core's.
62pub fn build_router(state: AppState) -> Router {
63    use axum::routing::{get, post, put};
64
65    let api = Router::new()
66        .route("/", get(handlers::server_info))
67        .route("/health", get(handlers::health))
68        .route("/graphics", get(handlers::graphics::list_graphics))
69        .route(
70            "/graphics/:id",
71            get(handlers::graphics::get_graphic).delete(handlers::graphics::delete_graphic),
72        )
73        .route(
74            "/graphics/:id/assets/*path",
75            get(handlers::graphics::serve_graphic_asset),
76        )
77        .route(
78            "/graphics/:id/thumbnail",
79            get(handlers::graphics::get_thumbnail),
80        )
81        .route("/renderers", get(handlers::renderers::list_renderers))
82        .route("/renderers/:id", get(handlers::renderers::get_renderer))
83        .route(
84            "/renderers/:id/target",
85            get(handlers::renderers::get_target),
86        )
87        .route(
88            "/renderers/:id/customActions/:action_id",
89            post(handlers::actions::renderer_custom_action),
90        )
91        .route(
92            "/renderers/:id/target/graphicInstance/clear",
93            put(handlers::actions::clear),
94        )
95        .route(
96            "/renderers/:id/target/graphicInstance/load",
97            post(handlers::actions::load),
98        )
99        .route(
100            "/renderers/:id/target/graphicInstance/playAction",
101            post(handlers::actions::play_action),
102        )
103        .route(
104            "/renderers/:id/target/graphicInstance/stopAction",
105            post(handlers::actions::stop_action),
106        )
107        .route(
108            "/renderers/:id/target/graphicInstance/updateAction",
109            post(handlers::actions::update_action),
110        )
111        .route(
112            "/renderers/:id/target/graphicInstance/customActions/:action_id",
113            post(handlers::actions::custom_action),
114        );
115
116    Router::new()
117        .nest("/ograf/v1", api)
118        // The spec's server info is `/` under `/ograf/v1`, i.e. `/ograf/v1/`
119        // — `nest` only matches its `/` route without the trailing slash.
120        .route("/ograf/v1/", get(handlers::server_info))
121        // The renderer WebSocket protocol isn't part of the OGraf Server API,
122        // so it lives outside `/ograf/v1` — under `/ograf/v1/renderers/` it
123        // shadowed `GET /renderers/{rendererId}` for a renderer named
124        // `connect`. Versioned on its own, since the protocol changes
125        // independently of the Server API.
126        .route(
127            RENDERER_CONNECT_PATH,
128            get(handlers::renderers::connect_renderer),
129        )
130        .route(
131            "/serverApi/internal/graphics/:graphic_id/*path",
132            get(handlers::graphics::serve_graphic_asset),
133        )
134        .with_state(state)
135}