Skip to main content

fraiseql_server/routes/studio/
function_ops.rs

1//! Function operations endpoints for the Studio dashboard.
2//!
3//! Routes under `/admin/v1/functions/*` expose deployed function listing,
4//! invocation, log retrieval, and secrets management. All routes are
5//! protected by the admin bearer token middleware.
6
7use axum::{
8    Json,
9    extract::{Path, State},
10    http::StatusCode,
11    response::IntoResponse,
12};
13use fraiseql_core::db::traits::DatabaseAdapter;
14use serde::{Deserialize, Serialize};
15
16use crate::routes::graphql::app_state::AppState;
17
18// ---------------------------------------------------------------------------
19// Function record
20// ---------------------------------------------------------------------------
21
22/// A deployed function summary agreed with the Luxen UI author.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct FunctionEntry {
25    /// Function name / identifier.
26    pub name:        String,
27    /// Deployment version number.
28    pub version:     u32,
29    /// Runtime type (e.g. `"wasm"`, `"deno"`).
30    pub runtime:     String,
31    /// Deployment status (`"active"`, `"inactive"`, `"error"`).
32    pub status:      String,
33    /// Deployment timestamp (RFC 3339).
34    pub deployed_at: String,
35}
36
37// ---------------------------------------------------------------------------
38// Response types
39// ---------------------------------------------------------------------------
40
41/// Function list response agreed with the Luxen UI author.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct FunctionListResponse {
44    /// All deployed functions for this tenant.
45    pub functions: Vec<FunctionEntry>,
46}
47
48/// Function invocation result.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct InvokeResponse {
51    /// Return value from the function.
52    pub value:       serde_json::Value,
53    /// Captured log lines from the invocation.
54    pub logs:        Vec<String>,
55    /// Wall-clock duration of the invocation in milliseconds.
56    pub duration_ms: u64,
57}
58
59/// A single invocation log entry (ring-buffer record).
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct InvocationLogEntry {
62    /// Invocation outcome (`"ok"` or `"error"`).
63    pub status:      String,
64    /// Duration of this invocation in milliseconds.
65    pub duration_ms: u64,
66    /// Error message, if `status == "error"`.
67    pub error:       Option<String>,
68    /// Invocation timestamp (RFC 3339).
69    pub timestamp:   String,
70}
71
72/// Secret keys list (values are never returned).
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SecretsKeysResponse {
75    /// Secret key names for this function.
76    pub keys: Vec<String>,
77}
78
79// ---------------------------------------------------------------------------
80// Request types
81// ---------------------------------------------------------------------------
82
83/// Request body for `POST /admin/v1/functions/{name}/invoke`.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct InvokeRequest {
86    /// Event payload to pass to the function.
87    pub event: serde_json::Value,
88}
89
90/// Request body for `PUT /admin/v1/functions/{name}/secrets/{key}`.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct SecretSetRequest {
93    /// Secret value (encrypted and stored server-side).
94    pub value: String,
95}
96
97// ---------------------------------------------------------------------------
98// Handlers
99// ---------------------------------------------------------------------------
100
101/// `GET /admin/v1/functions` — list all deployed functions.
102///
103/// # Errors
104///
105/// Returns `401` without valid admin credentials (enforced by middleware).
106pub async fn list_functions_handler<A>(State(_state): State<AppState<A>>) -> impl IntoResponse
107where
108    A: DatabaseAdapter + Clone + Send + Sync + 'static,
109{
110    Json(FunctionListResponse { functions: vec![] })
111}
112
113/// `POST /admin/v1/functions/{name}/invoke` — invoke a function.
114///
115/// # Errors
116///
117/// Returns `401` without valid admin credentials (enforced by middleware).
118/// Returns `404` if the function does not exist.
119pub async fn invoke_function_handler<A>(
120    Path(_name): Path<String>,
121    State(_state): State<AppState<A>>,
122    Json(_req): Json<InvokeRequest>,
123) -> impl IntoResponse
124where
125    A: DatabaseAdapter + Clone + Send + Sync + 'static,
126{
127    (
128        StatusCode::NOT_IMPLEMENTED,
129        Json(serde_json::json!({
130            "error": "Not Implemented",
131            "message": "Function invocation endpoint available in a future release"
132        })),
133    )
134}
135
136/// `GET /admin/v1/functions/{name}/logs` — last N invocation log entries.
137///
138/// # Errors
139///
140/// Returns `401` without valid admin credentials (enforced by middleware).
141pub async fn function_logs_handler<A>(
142    Path(_name): Path<String>,
143    State(_state): State<AppState<A>>,
144) -> impl IntoResponse
145where
146    A: DatabaseAdapter + Clone + Send + Sync + 'static,
147{
148    Json(serde_json::json!({ "logs": [] }))
149}
150
151/// `GET /admin/v1/functions/{name}/secrets` — secret key names (values never returned).
152///
153/// # Errors
154///
155/// Returns `401` without valid admin credentials (enforced by middleware).
156pub async fn list_secrets_handler<A>(
157    Path(_name): Path<String>,
158    State(_state): State<AppState<A>>,
159) -> impl IntoResponse
160where
161    A: DatabaseAdapter + Clone + Send + Sync + 'static,
162{
163    Json(SecretsKeysResponse { keys: vec![] })
164}
165
166/// `PUT /admin/v1/functions/{name}/secrets/{key}` — set a secret value.
167///
168/// # Errors
169///
170/// Returns `401` without valid admin credentials (enforced by middleware).
171pub async fn set_secret_handler<A>(
172    Path((_name, _key)): Path<(String, String)>,
173    State(_state): State<AppState<A>>,
174    Json(_req): Json<SecretSetRequest>,
175) -> impl IntoResponse
176where
177    A: DatabaseAdapter + Clone + Send + Sync + 'static,
178{
179    Json(serde_json::json!({"success": true}))
180}
181
182/// `DELETE /admin/v1/functions/{name}/secrets/{key}` — delete a secret.
183///
184/// # Errors
185///
186/// Returns `401` without valid admin credentials (enforced by middleware).
187pub async fn delete_secret_handler<A>(
188    Path((_name, _key)): Path<(String, String)>,
189    State(_state): State<AppState<A>>,
190) -> impl IntoResponse
191where
192    A: DatabaseAdapter + Clone + Send + Sync + 'static,
193{
194    Json(serde_json::json!({"success": true}))
195}