alopex_server/http/
session.rs1use std::sync::Arc;
2
3use axum::extract::{Extension, Path};
4use axum::response::Response;
5use serde::Serialize;
6
7use crate::error::{Result, ServerError};
8use crate::http::sql::sync_catalog_to_store;
9use crate::http::{error_response, json_response, RequestContext};
10use crate::ops::distributed_read::ReadExecutionOwner;
11use crate::server::ServerState;
12use crate::session::SessionId;
13
14#[derive(Serialize)]
15struct SessionBeginResponse {
16 session_id: String,
17 expires_at: String,
18}
19
20#[derive(Serialize)]
21struct SessionActionResponse {
22 success: bool,
23}
24
25pub async fn begin(
26 Extension(state): Extension<Arc<ServerState>>,
27 Extension(ctx): Extension<RequestContext>,
28) -> Response {
29 match begin_session(state.clone()).await {
30 Ok(resp) => json_response(resp, state.config.max_response_size, &ctx),
31 Err(err) => error_response(err, &ctx),
32 }
33}
34
35pub async fn commit(
36 Extension(state): Extension<Arc<ServerState>>,
37 Extension(ctx): Extension<RequestContext>,
38 Path(id): Path<String>,
39) -> Response {
40 match session_action(state.clone(), &id, Action::Commit).await {
41 Ok(resp) => json_response(resp, state.config.max_response_size, &ctx),
42 Err(err) => error_response(err, &ctx),
43 }
44}
45
46pub async fn rollback(
47 Extension(state): Extension<Arc<ServerState>>,
48 Extension(ctx): Extension<RequestContext>,
49 Path(id): Path<String>,
50) -> Response {
51 match session_action(state.clone(), &id, Action::Rollback).await {
52 Ok(resp) => json_response(resp, state.config.max_response_size, &ctx),
53 Err(err) => error_response(err, &ctx),
54 }
55}
56
57pub async fn distributed_read_owner(
62 state: &ServerState,
63 ctx: &RequestContext,
64 session_id: Option<&str>,
65) -> Result<ReadExecutionOwner> {
66 let profile = ctx.actor.clone().ok_or_else(|| {
67 ServerError::Unauthorized("distributed read requires an authenticated profile".into())
68 })?;
69 let session_id = match session_id {
70 Some(id) => {
71 let session_id = id
72 .parse::<SessionId>()
73 .map_err(|_| ServerError::BadRequest("invalid session id".into()))?;
74 state.session_manager.get_session(&session_id).await?;
76 Some(session_id)
77 }
78 None => None,
79 };
80 ReadExecutionOwner::new(profile, session_id)
81}
82
83async fn begin_session(state: Arc<ServerState>) -> Result<SessionBeginResponse> {
84 let session_id = state.session_manager.create_session().await?;
85 state.session_manager.begin_transaction(&session_id).await?;
86 let snapshot = state.session_manager.get_session(&session_id).await?;
87 let expires_at = chrono::DateTime::<chrono::Utc>::from(snapshot.expires_at);
88 Ok(SessionBeginResponse {
89 session_id: session_id.to_string(),
90 expires_at: expires_at.to_rfc3339(),
91 })
92}
93
94enum Action {
95 Commit,
96 Rollback,
97}
98
99async fn session_action(
100 state: Arc<ServerState>,
101 id: &str,
102 action: Action,
103) -> Result<SessionActionResponse> {
104 let session_id = id
105 .parse::<SessionId>()
106 .map_err(|_| ServerError::BadRequest("invalid session id".into()))?;
107 match action {
108 Action::Commit => {
109 let effects = state.session_manager.commit(&session_id).await?;
110 if !effects.is_empty() {
111 state.apply_table_lifecycle_effects(effects)?;
112 sync_catalog_to_store(&state)?;
113 }
114 }
115 Action::Rollback => {
116 let effects = state.session_manager.rollback(&session_id).await?;
117 state.apply_catalog_rollback_effects(effects)?;
118 }
119 }
120 Ok(SessionActionResponse { success: true })
121}