Skip to main content

shodh_memory/handlers/
sessions.rs

1//! Session Management Handlers
2//!
3//! Handlers for user session tracking and management.
4
5use axum::{
6    extract::{Path, Query, State},
7    response::Json,
8};
9use serde::{Deserialize, Serialize};
10
11use super::state::MultiUserMemoryManager;
12use crate::errors::{AppError, ValidationErrorExt};
13use crate::memory::{Session, SessionId, SessionStatus, SessionStoreStats, SessionSummary};
14use crate::validation;
15use std::sync::Arc;
16
17type AppState = Arc<MultiUserMemoryManager>;
18
19fn default_sessions_limit() -> usize {
20    10
21}
22
23fn default_end_reason() -> String {
24    "user_ended".to_string()
25}
26
27/// Request for listing sessions
28#[derive(Debug, Deserialize)]
29pub struct ListSessionsRequest {
30    pub user_id: String,
31    #[serde(default = "default_sessions_limit")]
32    pub limit: usize,
33}
34
35/// Response for listing sessions
36#[derive(Debug, Serialize)]
37pub struct ListSessionsResponse {
38    pub success: bool,
39    pub sessions: Vec<SessionSummary>,
40    pub count: usize,
41}
42
43/// Request for getting a specific session
44#[derive(Debug, Deserialize)]
45pub struct GetSessionRequest {
46    pub user_id: String,
47}
48
49/// Response for getting a session
50#[derive(Debug, Serialize)]
51pub struct GetSessionResponse {
52    pub success: bool,
53    pub session: Option<Session>,
54}
55
56/// Request for ending a session
57#[derive(Debug, Deserialize)]
58pub struct EndSessionRequest {
59    pub user_id: String,
60    #[serde(default = "default_end_reason")]
61    pub reason: String,
62}
63
64/// Response for ending a session
65#[derive(Debug, Serialize)]
66pub struct EndSessionResponse {
67    pub success: bool,
68    pub session: Option<Session>,
69}
70
71/// Response for session store stats
72#[derive(Debug, Serialize)]
73pub struct SessionStoreStatsResponse {
74    pub success: bool,
75    pub stats: SessionStoreStats,
76}
77
78/// POST /api/sessions - List sessions for a user
79pub async fn list_sessions(
80    State(state): State<AppState>,
81    Json(req): Json<ListSessionsRequest>,
82) -> Result<Json<ListSessionsResponse>, AppError> {
83    validation::validate_user_id(&req.user_id).map_validation_err("user_id")?;
84
85    let sessions = state
86        .session_store
87        .get_user_sessions(&req.user_id, req.limit);
88    let count = sessions.len();
89
90    Ok(Json(ListSessionsResponse {
91        success: true,
92        sessions,
93        count,
94    }))
95}
96
97/// GET /api/sessions/{session_id} - Get a specific session
98pub async fn get_session(
99    State(state): State<AppState>,
100    Path(session_id): Path<String>,
101    Query(req): Query<GetSessionRequest>,
102) -> Result<Json<GetSessionResponse>, AppError> {
103    validation::validate_user_id(&req.user_id).map_validation_err("user_id")?;
104
105    let uuid = uuid::Uuid::parse_str(&session_id).map_err(|e| AppError::InvalidInput {
106        field: "session_id".to_string(),
107        reason: format!("Invalid UUID: {e}"),
108    })?;
109    let sid = SessionId(uuid);
110    let session = state.session_store.get_session(&sid);
111
112    Ok(Json(GetSessionResponse {
113        success: session.is_some(),
114        session,
115    }))
116}
117
118/// POST /api/sessions/end - End the current/active session for a user
119pub async fn end_session(
120    State(state): State<AppState>,
121    Json(req): Json<EndSessionRequest>,
122) -> Result<Json<EndSessionResponse>, AppError> {
123    validation::validate_user_id(&req.user_id).map_validation_err("user_id")?;
124
125    let sessions = state.session_store.get_user_sessions(&req.user_id, 1);
126    let active_session = sessions
127        .into_iter()
128        .find(|s| matches!(s.status, SessionStatus::Active));
129
130    if let Some(summary) = active_session {
131        let session = state.session_store.end_session(&summary.id, &req.reason);
132        Ok(Json(EndSessionResponse {
133            success: session.is_some(),
134            session,
135        }))
136    } else {
137        Ok(Json(EndSessionResponse {
138            success: false,
139            session: None,
140        }))
141    }
142}
143
144/// GET /api/sessions/stats - Get overall session store statistics
145pub async fn get_session_stats(
146    State(state): State<AppState>,
147) -> Result<Json<SessionStoreStatsResponse>, AppError> {
148    let stats = state.session_store.stats();
149
150    Ok(Json(SessionStoreStatsResponse {
151        success: true,
152        stats,
153    }))
154}