tempo_cli/models/
session.rs1use chrono::{DateTime, Duration, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum SessionContext {
6 Terminal,
7 IDE,
8 Linked,
9 Manual,
10}
11
12impl std::fmt::Display for SessionContext {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 match self {
15 SessionContext::Terminal => write!(f, "terminal"),
16 SessionContext::IDE => write!(f, "ide"),
17 SessionContext::Linked => write!(f, "linked"),
18 SessionContext::Manual => write!(f, "manual"),
19 }
20 }
21}
22
23impl std::str::FromStr for SessionContext {
24 type Err = anyhow::Error;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 match s.to_lowercase().as_str() {
28 "terminal" => Ok(SessionContext::Terminal),
29 "ide" => Ok(SessionContext::IDE),
30 "linked" => Ok(SessionContext::Linked),
31 "manual" => Ok(SessionContext::Manual),
32 _ => Err(anyhow::anyhow!("Invalid session context: {}", s)),
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum SessionStatus {
39 Active,
40 Paused,
41 Completed,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct Session {
46 pub id: Option<i64>,
47 pub project_id: i64,
48 pub start_time: DateTime<Utc>,
49 pub end_time: Option<DateTime<Utc>>,
50 pub context: SessionContext,
51 pub paused_duration: Duration,
52 pub notes: Option<String>,
53 pub created_at: DateTime<Utc>,
54}
55
56impl Session {
57 pub fn new(project_id: i64, context: SessionContext) -> Self {
58 let now = Utc::now();
59 Self {
60 id: None,
61 project_id,
62 start_time: now,
63 end_time: None,
64 context,
65 paused_duration: Duration::zero(),
66 notes: None,
67 created_at: now,
68 }
69 }
70
71 pub fn with_start_time(mut self, start_time: DateTime<Utc>) -> Self {
72 self.start_time = start_time;
73 self
74 }
75
76 pub fn with_notes(mut self, notes: Option<String>) -> Self {
77 self.notes = notes;
78 self
79 }
80
81 pub fn end_session(&mut self) -> anyhow::Result<()> {
82 if self.end_time.is_some() {
83 return Err(anyhow::anyhow!("Session is already ended"));
84 }
85
86 self.end_time = Some(Utc::now());
87 Ok(())
88 }
89
90 pub fn add_pause_duration(&mut self, duration: Duration) {
91 self.paused_duration = self.paused_duration + duration;
92 }
93
94 pub fn is_active(&self) -> bool {
95 self.end_time.is_none()
96 }
97
98 pub fn status(&self) -> SessionStatus {
99 if self.end_time.is_some() {
100 SessionStatus::Completed
101 } else {
102 SessionStatus::Active
103 }
104 }
105
106 pub fn total_duration(&self) -> Option<Duration> {
107 self.end_time.map(|end| end - self.start_time)
108 }
109
110 pub fn active_duration(&self) -> Option<Duration> {
111 self.total_duration().map(|total| total - self.paused_duration)
112 }
113
114 pub fn current_duration(&self) -> Duration {
115 let end_time = self.end_time.unwrap_or_else(Utc::now);
116 end_time - self.start_time
117 }
118
119 pub fn current_active_duration(&self) -> Duration {
120 self.current_duration() - self.paused_duration
121 }
122
123 pub fn validate(&self) -> anyhow::Result<()> {
124 if let Some(end_time) = self.end_time {
125 if end_time <= self.start_time {
126 return Err(anyhow::anyhow!("End time must be after start time"));
127 }
128 }
129
130 if self.paused_duration < Duration::zero() {
131 return Err(anyhow::anyhow!("Paused duration cannot be negative"));
132 }
133
134 let total = self.current_duration();
135 if self.paused_duration > total {
136 return Err(anyhow::anyhow!("Paused duration cannot exceed total duration"));
137 }
138
139 Ok(())
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct SessionEdit {
145 pub id: Option<i64>,
146 pub session_id: i64,
147 pub original_start_time: DateTime<Utc>,
148 pub original_end_time: Option<DateTime<Utc>>,
149 pub new_start_time: DateTime<Utc>,
150 pub new_end_time: Option<DateTime<Utc>>,
151 pub edit_reason: Option<String>,
152 pub created_at: DateTime<Utc>,
153}
154
155impl SessionEdit {
156 pub fn new(
157 session_id: i64,
158 original_start_time: DateTime<Utc>,
159 original_end_time: Option<DateTime<Utc>>,
160 new_start_time: DateTime<Utc>,
161 new_end_time: Option<DateTime<Utc>>,
162 ) -> Self {
163 Self {
164 id: None,
165 session_id,
166 original_start_time,
167 original_end_time,
168 new_start_time,
169 new_end_time,
170 edit_reason: None,
171 created_at: Utc::now(),
172 }
173 }
174
175 pub fn with_reason(mut self, reason: Option<String>) -> Self {
176 self.edit_reason = reason;
177 self
178 }
179}