Skip to main content

hindsight/server/
dto.rs

1//! DTO (Data Transfer Object) layer for the web API
2//!
3//! Adds Serialize to existing storage structs via wrapper types with From impls.
4
5use crate::storage::{GlobalAnalytics, ProjectAnalytics, ProjectStats, SessionFile};
6use serde::{Deserialize, Serialize};
7
8/// Serializable project stats
9#[derive(Debug, Serialize, Deserialize)]
10pub struct ProjectStatsDto {
11    pub project_name: String,
12    pub session_count: usize,
13    pub total_size: u64,
14    pub last_activity: Option<i64>,
15}
16
17impl From<ProjectStats> for ProjectStatsDto {
18    fn from(s: ProjectStats) -> Self {
19        Self {
20            project_name: s.project_name,
21            session_count: s.session_count,
22            total_size: s.total_size,
23            last_activity: s.last_activity,
24        }
25    }
26}
27
28/// Serializable global analytics
29#[derive(Debug, Serialize, Deserialize)]
30pub struct GlobalAnalyticsDto {
31    pub total_sessions: usize,
32    pub sessions_this_week: usize,
33    pub sessions_today: usize,
34    pub total_size: u64,
35    pub total_projects: usize,
36    pub subagent_count: usize,
37    pub avg_session_size: u64,
38    pub most_active_project: Option<String>,
39    pub top_tools: Vec<(String, usize)>,
40    pub total_errors: usize,
41}
42
43impl From<GlobalAnalytics> for GlobalAnalyticsDto {
44    fn from(a: GlobalAnalytics) -> Self {
45        Self {
46            total_sessions: a.total_sessions,
47            sessions_this_week: a.sessions_this_week,
48            sessions_today: a.sessions_today,
49            total_size: a.total_size,
50            total_projects: a.total_projects,
51            subagent_count: a.subagent_count,
52            avg_session_size: a.avg_session_size,
53            most_active_project: a.most_active_project,
54            top_tools: a.top_tools,
55            total_errors: a.total_errors,
56        }
57    }
58}
59
60/// Serializable project analytics
61#[derive(Debug, Serialize, Deserialize)]
62pub struct ProjectAnalyticsDto {
63    pub project_name: String,
64    pub total_sessions: usize,
65    pub sessions_this_week: usize,
66    pub sessions_today: usize,
67    pub total_size: u64,
68    pub subagent_count: usize,
69    pub avg_session_size: u64,
70    pub top_tools: Vec<(String, usize)>,
71    pub last_activity: Option<i64>,
72    pub total_errors: usize,
73}
74
75impl From<ProjectAnalytics> for ProjectAnalyticsDto {
76    fn from(a: ProjectAnalytics) -> Self {
77        Self {
78            project_name: a.project_name,
79            total_sessions: a.total_sessions,
80            sessions_this_week: a.sessions_this_week,
81            sessions_today: a.sessions_today,
82            total_size: a.total_size,
83            subagent_count: a.subagent_count,
84            avg_session_size: a.avg_session_size,
85            top_tools: a.top_tools,
86            last_activity: a.last_activity,
87            total_errors: a.total_errors,
88        }
89    }
90}
91
92/// Serializable session file (path excluded — internal detail)
93#[derive(Debug, Serialize, Deserialize)]
94pub struct SessionFileDto {
95    pub session_id: String,
96    pub project_name: String,
97    pub file_size: u64,
98    pub created_at: i64,
99    pub modified_at: i64,
100    pub has_subagents: bool,
101    pub model: Option<String>,
102    pub error_count: usize,
103    pub first_message: Option<String>,
104    pub source_dir: String,
105    pub subagent_models: Option<Vec<String>>,
106}
107
108/// Cross-session prompt entry
109#[derive(Debug, Serialize, Deserialize)]
110pub struct PromptEntryDto {
111    pub session_id: String,
112    pub project_name: String,
113    pub prompt_text: String,
114    pub prompt_score: u8,
115    pub timestamp: Option<i64>,
116    pub model: Option<String>,
117}
118
119impl From<SessionFile> for SessionFileDto {
120    fn from(s: SessionFile) -> Self {
121        let subagent_models = s.subagent_models.map(|m| {
122            m.split(',').map(str::to_string).collect()
123        });
124        Self {
125            session_id: s.session_id,
126            project_name: s.project_name,
127            file_size: s.file_size,
128            created_at: s.created_at,
129            modified_at: s.modified_at,
130            has_subagents: s.has_subagents,
131            model: s.model,
132            error_count: s.error_count,
133            first_message: s.first_message,
134            source_dir: s.source_dir,
135            subagent_models,
136        }
137    }
138}