Skip to main content

boarddown_schema/
task.rs

1use serde::{Deserialize, Serialize};
2use ts_rs::TS;
3
4use crate::{ColumnRef, Metadata, TaskId};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
7#[ts(export)]
8pub struct Task {
9    pub id: TaskId,
10    pub title: String,
11    pub status: Status,
12    pub column: ColumnRef,
13    pub metadata: Metadata,
14    pub dependencies: Vec<TaskId>,
15    pub references: Vec<TaskId>,
16    pub created_at: chrono::DateTime<chrono::Utc>,
17    pub updated_at: chrono::DateTime<chrono::Utc>,
18}
19
20impl Task {
21    pub fn builder() -> TaskBuilder {
22        TaskBuilder::default()
23    }
24
25    pub fn is_done(&self) -> bool {
26        self.status == Status::Done
27    }
28
29    pub fn is_blocked(&self) -> bool {
30        self.status == Status::Blocked || !self.dependencies.is_empty()
31    }
32
33    pub fn set_status(&mut self, status: Status) {
34        self.status = status;
35        self.updated_at = chrono::Utc::now();
36    }
37
38    pub fn set_assignee(&mut self, assignee: impl Into<String>) {
39        self.metadata.assign = Some(assignee.into());
40        self.updated_at = chrono::Utc::now();
41    }
42}
43
44#[derive(Debug, Clone, Default)]
45pub struct TaskBuilder {
46    id: Option<TaskId>,
47    title: Option<String>,
48    status: Option<Status>,
49    column: Option<ColumnRef>,
50    metadata: Option<Metadata>,
51    dependencies: Vec<TaskId>,
52    references: Vec<TaskId>,
53}
54
55impl TaskBuilder {
56    pub fn id(mut self, id: TaskId) -> Self {
57        self.id = Some(id);
58        self
59    }
60
61    pub fn title(mut self, title: impl Into<String>) -> Self {
62        self.title = Some(title.into());
63        self
64    }
65
66    pub fn status(mut self, status: Status) -> Self {
67        self.status = Some(status);
68        self
69    }
70
71    pub fn column(mut self, column: impl Into<String>) -> Self {
72        self.column = Some(ColumnRef::Name(column.into()));
73        self
74    }
75
76    pub fn metadata(mut self, metadata: Metadata) -> Self {
77        self.metadata = Some(metadata);
78        self
79    }
80
81    pub fn depends_on(mut self, task_id: TaskId) -> Self {
82        self.dependencies.push(task_id);
83        self
84    }
85
86    pub fn with_dependencies(mut self, deps: Vec<TaskId>) -> Self {
87        self.dependencies = deps;
88        self
89    }
90
91    pub fn references(mut self, task_id: TaskId) -> Self {
92        self.references.push(task_id);
93        self
94    }
95
96    pub fn build(self) -> Result<Task, String> {
97        let id = self.id.ok_or("Task ID is required")?;
98        let title = self.title.ok_or("Task title is required")?;
99        let now = chrono::Utc::now();
100        
101        Ok(Task {
102            id,
103            title,
104            status: self.status.unwrap_or_default(),
105            column: self.column.unwrap_or_else(|| ColumnRef::Name("Todo".to_string())),
106            metadata: self.metadata.unwrap_or_default(),
107            dependencies: self.dependencies,
108            references: self.references,
109            created_at: now,
110            updated_at: now,
111        })
112    }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
116#[ts(export)]
117#[serde(rename_all = "lowercase")]
118pub enum Status {
119    Todo,
120    InProgress,
121    Ready,
122    Done,
123    Blocked,
124    Urgent,
125}
126
127impl Default for Status {
128    fn default() -> Self {
129        Self::Todo
130    }
131}
132
133impl std::str::FromStr for Status {
134    type Err = String;
135    
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s.to_lowercase().as_str() {
138            "todo" | "t" => Ok(Self::Todo),
139            "inprogress" | "in-progress" | "ip" | "doing" => Ok(Self::InProgress),
140            "ready" | "r" => Ok(Self::Ready),
141            "done" | "d" | "completed" | "complete" => Ok(Self::Done),
142            "blocked" | "b" => Ok(Self::Blocked),
143            "urgent" | "u" => Ok(Self::Urgent),
144            _ => Err(format!("Invalid status: {}. Valid values: todo, inprogress, ready, done, blocked, urgent", s)),
145        }
146    }
147}
148
149impl std::fmt::Display for Status {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        match self {
152            Status::Todo => write!(f, "Todo"),
153            Status::InProgress => write!(f, "In Progress"),
154            Status::Ready => write!(f, "Ready"),
155            Status::Done => write!(f, "Done"),
156            Status::Blocked => write!(f, "Blocked"),
157            Status::Urgent => write!(f, "Urgent"),
158        }
159    }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
163#[ts(export)]
164pub struct Dependency {
165    pub from: TaskId,
166    pub to: TaskId,
167    pub dependency_type: DependencyType,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
171#[ts(export)]
172pub enum DependencyType {
173    Blocks,
174    DependsOn,
175    Related,
176}