tempo_cli/models/
project.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct Project {
7    pub id: Option<i64>,
8    pub name: String,
9    pub path: PathBuf,
10    pub git_hash: Option<String>,
11    pub created_at: DateTime<Utc>,
12    pub updated_at: DateTime<Utc>,
13    pub is_archived: bool,
14    pub description: Option<String>,
15}
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub enum ProjectStatus {
19    Active,
20    Archived,
21    Tracking,
22    Idle,
23}
24
25impl Project {
26    pub fn new(name: String, path: PathBuf) -> Self {
27        let now = Utc::now();
28        Self {
29            id: None,
30            name,
31            path,
32            git_hash: None,
33            created_at: now,
34            updated_at: now,
35            is_archived: false,
36            description: None,
37        }
38    }
39
40    pub fn with_git_hash(mut self, git_hash: Option<String>) -> Self {
41        self.git_hash = git_hash;
42        self
43    }
44
45    pub fn with_description(mut self, description: Option<String>) -> Self {
46        self.description = description;
47        self
48    }
49
50    pub fn archive(&mut self) {
51        self.is_archived = true;
52        self.updated_at = Utc::now();
53    }
54
55    pub fn unarchive(&mut self) {
56        self.is_archived = false;
57        self.updated_at = Utc::now();
58    }
59
60    pub fn update_path(&mut self, new_path: PathBuf) {
61        self.path = new_path;
62        self.updated_at = Utc::now();
63    }
64
65    pub fn is_git_project(&self) -> bool {
66        self.path.join(".git").exists()
67    }
68
69    pub fn has_timetrack_marker(&self) -> bool {
70        self.path.join(".timetrack").exists()
71    }
72
73    pub fn get_canonical_path(&self) -> anyhow::Result<PathBuf> {
74        Ok(self.path.canonicalize()?)
75    }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct LinkedProject {
80    pub id: Option<i64>,
81    pub name: String,
82    pub description: Option<String>,
83    pub created_at: DateTime<Utc>,
84    pub is_active: bool,
85    pub member_projects: Vec<Project>,
86}
87
88impl LinkedProject {
89    pub fn new(name: String) -> Self {
90        Self {
91            id: None,
92            name,
93            description: None,
94            created_at: Utc::now(),
95            is_active: true,
96            member_projects: Vec::new(),
97        }
98    }
99
100    pub fn add_project(&mut self, project: Project) {
101        self.member_projects.push(project);
102    }
103
104    pub fn remove_project(&mut self, project_id: i64) {
105        self.member_projects.retain(|p| p.id != Some(project_id));
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_project_new() {
115        let path = PathBuf::from("/tmp/test-project");
116        let project = Project::new("Test Project".to_string(), path.clone());
117
118        assert_eq!(project.name, "Test Project");
119        assert_eq!(project.path, path);
120        assert!(!project.is_archived);
121        assert!(project.git_hash.is_none());
122    }
123
124    #[test]
125    fn test_project_archive_unarchive() {
126        let mut project = Project::new("Test".to_string(), PathBuf::from("/tmp"));
127
128        assert!(!project.is_archived);
129
130        project.archive();
131        assert!(project.is_archived);
132
133        project.unarchive();
134        assert!(!project.is_archived);
135    }
136
137    #[test]
138    fn test_project_update_path() {
139        let mut project = Project::new("Test".to_string(), PathBuf::from("/tmp/old"));
140        let new_path = PathBuf::from("/tmp/new");
141
142        project.update_path(new_path.clone());
143        assert_eq!(project.path, new_path);
144    }
145
146    #[test]
147    fn test_linked_project_management() {
148        let mut linked = LinkedProject::new("Meta Project".to_string());
149        let p1 = Project::new("P1".to_string(), PathBuf::from("/p1"))
150            .with_git_hash(Some("hash1".to_string()));
151
152        linked.add_project(p1.clone());
153        assert_eq!(linked.member_projects.len(), 1);
154        assert_eq!(linked.member_projects[0].name, "P1");
155    }
156}