1use chrono::{DateTime, NaiveDate, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum TaskStatus {
10 #[serde(rename = "incomplete")]
11 Incomplete,
12 #[serde(rename = "completed")]
13 Completed,
14 #[serde(rename = "canceled")]
15 Canceled,
16 #[serde(rename = "trashed")]
17 Trashed,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22pub enum TaskType {
23 #[serde(rename = "to-do")]
24 Todo,
25 #[serde(rename = "project")]
26 Project,
27 #[serde(rename = "heading")]
28 Heading,
29 #[serde(rename = "area")]
30 Area,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Task {
36 pub uuid: Uuid,
38 pub title: String,
40 pub task_type: TaskType,
42 pub status: TaskStatus,
44 pub notes: Option<String>,
46 pub start_date: Option<NaiveDate>,
48 pub deadline: Option<NaiveDate>,
50 pub created: DateTime<Utc>,
52 pub modified: DateTime<Utc>,
54 pub project_uuid: Option<Uuid>,
56 pub area_uuid: Option<Uuid>,
58 pub parent_uuid: Option<Uuid>,
60 pub tags: Vec<String>,
62 pub children: Vec<Task>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Project {
69 pub uuid: Uuid,
71 pub title: String,
73 pub notes: Option<String>,
75 pub start_date: Option<NaiveDate>,
77 pub deadline: Option<NaiveDate>,
79 pub created: DateTime<Utc>,
81 pub modified: DateTime<Utc>,
83 pub area_uuid: Option<Uuid>,
85 pub tags: Vec<String>,
87 pub status: TaskStatus,
89 pub tasks: Vec<Task>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct Area {
96 pub uuid: Uuid,
98 pub title: String,
100 pub notes: Option<String>,
102 pub created: DateTime<Utc>,
104 pub modified: DateTime<Utc>,
106 pub tags: Vec<String>,
108 pub projects: Vec<Project>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Tag {
115 pub uuid: Uuid,
117 pub title: String,
119 pub usage_count: u32,
121 pub tasks: Vec<Uuid>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct CreateTaskRequest {
128 pub title: String,
130 pub notes: Option<String>,
132 pub start_date: Option<NaiveDate>,
134 pub deadline: Option<NaiveDate>,
136 pub project_uuid: Option<Uuid>,
138 pub area_uuid: Option<Uuid>,
140 pub tags: Vec<String>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct UpdateTaskRequest {
147 pub uuid: Uuid,
149 pub title: Option<String>,
151 pub notes: Option<String>,
153 pub start_date: Option<NaiveDate>,
155 pub deadline: Option<NaiveDate>,
157 pub status: Option<TaskStatus>,
159 pub tags: Option<Vec<String>>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, Default)]
165pub struct TaskFilters {
166 pub status: Option<TaskStatus>,
168 pub task_type: Option<TaskType>,
170 pub project_uuid: Option<Uuid>,
172 pub area_uuid: Option<Uuid>,
174 pub tags: Option<Vec<String>>,
176 pub start_date_from: Option<NaiveDate>,
178 pub start_date_to: Option<NaiveDate>,
179 pub deadline_from: Option<NaiveDate>,
181 pub deadline_to: Option<NaiveDate>,
182 pub search_query: Option<String>,
184 pub limit: Option<usize>,
186 pub offset: Option<usize>,
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use chrono::NaiveDate;
194
195 #[test]
196 fn test_task_status_serialization() {
197 let status = TaskStatus::Incomplete;
198 let serialized = serde_json::to_string(&status).unwrap();
199 assert_eq!(serialized, "\"incomplete\"");
200
201 let status = TaskStatus::Completed;
202 let serialized = serde_json::to_string(&status).unwrap();
203 assert_eq!(serialized, "\"completed\"");
204
205 let status = TaskStatus::Canceled;
206 let serialized = serde_json::to_string(&status).unwrap();
207 assert_eq!(serialized, "\"canceled\"");
208
209 let status = TaskStatus::Trashed;
210 let serialized = serde_json::to_string(&status).unwrap();
211 assert_eq!(serialized, "\"trashed\"");
212 }
213
214 #[test]
215 fn test_task_status_deserialization() {
216 let deserialized: TaskStatus = serde_json::from_str("\"incomplete\"").unwrap();
217 assert_eq!(deserialized, TaskStatus::Incomplete);
218
219 let deserialized: TaskStatus = serde_json::from_str("\"completed\"").unwrap();
220 assert_eq!(deserialized, TaskStatus::Completed);
221
222 let deserialized: TaskStatus = serde_json::from_str("\"canceled\"").unwrap();
223 assert_eq!(deserialized, TaskStatus::Canceled);
224
225 let deserialized: TaskStatus = serde_json::from_str("\"trashed\"").unwrap();
226 assert_eq!(deserialized, TaskStatus::Trashed);
227 }
228
229 #[test]
230 fn test_task_type_serialization() {
231 let task_type = TaskType::Todo;
232 let serialized = serde_json::to_string(&task_type).unwrap();
233 assert_eq!(serialized, "\"to-do\"");
234
235 let task_type = TaskType::Project;
236 let serialized = serde_json::to_string(&task_type).unwrap();
237 assert_eq!(serialized, "\"project\"");
238
239 let task_type = TaskType::Heading;
240 let serialized = serde_json::to_string(&task_type).unwrap();
241 assert_eq!(serialized, "\"heading\"");
242
243 let task_type = TaskType::Area;
244 let serialized = serde_json::to_string(&task_type).unwrap();
245 assert_eq!(serialized, "\"area\"");
246 }
247
248 #[test]
249 fn test_task_type_deserialization() {
250 let deserialized: TaskType = serde_json::from_str("\"to-do\"").unwrap();
251 assert_eq!(deserialized, TaskType::Todo);
252
253 let deserialized: TaskType = serde_json::from_str("\"project\"").unwrap();
254 assert_eq!(deserialized, TaskType::Project);
255
256 let deserialized: TaskType = serde_json::from_str("\"heading\"").unwrap();
257 assert_eq!(deserialized, TaskType::Heading);
258
259 let deserialized: TaskType = serde_json::from_str("\"area\"").unwrap();
260 assert_eq!(deserialized, TaskType::Area);
261 }
262
263 #[test]
264 fn test_task_creation() {
265 let uuid = Uuid::new_v4();
266 let now = Utc::now();
267 let start_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
268 let deadline = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
269
270 let task = Task {
271 uuid,
272 title: "Test Task".to_string(),
273 task_type: TaskType::Todo,
274 status: TaskStatus::Incomplete,
275 notes: Some("Test notes".to_string()),
276 start_date: Some(start_date),
277 deadline: Some(deadline),
278 created: now,
279 modified: now,
280 project_uuid: None,
281 area_uuid: None,
282 parent_uuid: None,
283 tags: vec!["work".to_string(), "urgent".to_string()],
284 children: vec![],
285 };
286
287 assert_eq!(task.uuid, uuid);
288 assert_eq!(task.title, "Test Task");
289 assert_eq!(task.task_type, TaskType::Todo);
290 assert_eq!(task.status, TaskStatus::Incomplete);
291 assert_eq!(task.notes, Some("Test notes".to_string()));
292 assert_eq!(task.start_date, Some(start_date));
293 assert_eq!(task.deadline, Some(deadline));
294 assert_eq!(task.tags.len(), 2);
295 assert!(task.tags.contains(&"work".to_string()));
296 assert!(task.tags.contains(&"urgent".to_string()));
297 }
298
299 #[test]
300 fn test_task_serialization() {
301 let uuid = Uuid::new_v4();
302 let now = Utc::now();
303
304 let task = Task {
305 uuid,
306 title: "Test Task".to_string(),
307 task_type: TaskType::Todo,
308 status: TaskStatus::Incomplete,
309 notes: None,
310 start_date: None,
311 deadline: None,
312 created: now,
313 modified: now,
314 project_uuid: None,
315 area_uuid: None,
316 parent_uuid: None,
317 tags: vec![],
318 children: vec![],
319 };
320
321 let serialized = serde_json::to_string(&task).unwrap();
322 let deserialized: Task = serde_json::from_str(&serialized).unwrap();
323
324 assert_eq!(deserialized.uuid, task.uuid);
325 assert_eq!(deserialized.title, task.title);
326 assert_eq!(deserialized.task_type, task.task_type);
327 assert_eq!(deserialized.status, task.status);
328 }
329
330 #[test]
331 fn test_project_creation() {
332 let uuid = Uuid::new_v4();
333 let area_uuid = Uuid::new_v4();
334 let now = Utc::now();
335
336 let project = Project {
337 uuid,
338 title: "Test Project".to_string(),
339 notes: Some("Project notes".to_string()),
340 start_date: None,
341 deadline: None,
342 created: now,
343 modified: now,
344 area_uuid: Some(area_uuid),
345 tags: vec!["project".to_string()],
346 status: TaskStatus::Incomplete,
347 tasks: vec![],
348 };
349
350 assert_eq!(project.uuid, uuid);
351 assert_eq!(project.title, "Test Project");
352 assert_eq!(project.area_uuid, Some(area_uuid));
353 assert_eq!(project.status, TaskStatus::Incomplete);
354 assert_eq!(project.tags.len(), 1);
355 }
356
357 #[test]
358 fn test_project_serialization() {
359 let uuid = Uuid::new_v4();
360 let now = Utc::now();
361
362 let project = Project {
363 uuid,
364 title: "Test Project".to_string(),
365 notes: None,
366 start_date: None,
367 deadline: None,
368 created: now,
369 modified: now,
370 area_uuid: None,
371 tags: vec![],
372 status: TaskStatus::Incomplete,
373 tasks: vec![],
374 };
375
376 let serialized = serde_json::to_string(&project).unwrap();
377 let deserialized: Project = serde_json::from_str(&serialized).unwrap();
378
379 assert_eq!(deserialized.uuid, project.uuid);
380 assert_eq!(deserialized.title, project.title);
381 assert_eq!(deserialized.status, project.status);
382 }
383
384 #[test]
385 fn test_area_creation() {
386 let uuid = Uuid::new_v4();
387 let now = Utc::now();
388
389 let area = Area {
390 uuid,
391 title: "Test Area".to_string(),
392 notes: Some("Area notes".to_string()),
393 created: now,
394 modified: now,
395 tags: vec!["area".to_string()],
396 projects: vec![],
397 };
398
399 assert_eq!(area.uuid, uuid);
400 assert_eq!(area.title, "Test Area");
401 assert_eq!(area.notes, Some("Area notes".to_string()));
402 assert_eq!(area.tags.len(), 1);
403 }
404
405 #[test]
406 fn test_area_serialization() {
407 let uuid = Uuid::new_v4();
408 let now = Utc::now();
409
410 let area = Area {
411 uuid,
412 title: "Test Area".to_string(),
413 notes: None,
414 created: now,
415 modified: now,
416 tags: vec![],
417 projects: vec![],
418 };
419
420 let serialized = serde_json::to_string(&area).unwrap();
421 let deserialized: Area = serde_json::from_str(&serialized).unwrap();
422
423 assert_eq!(deserialized.uuid, area.uuid);
424 assert_eq!(deserialized.title, area.title);
425 }
426
427 #[test]
428 fn test_tag_creation() {
429 let uuid = Uuid::new_v4();
430 let task_uuid = Uuid::new_v4();
431
432 let tag = Tag {
433 uuid,
434 title: "work".to_string(),
435 usage_count: 5,
436 tasks: vec![task_uuid],
437 };
438
439 assert_eq!(tag.uuid, uuid);
440 assert_eq!(tag.title, "work");
441 assert_eq!(tag.usage_count, 5);
442 assert_eq!(tag.tasks.len(), 1);
443 assert_eq!(tag.tasks[0], task_uuid);
444 }
445
446 #[test]
447 fn test_tag_serialization() {
448 let uuid = Uuid::new_v4();
449
450 let tag = Tag {
451 uuid,
452 title: "test".to_string(),
453 usage_count: 0,
454 tasks: vec![],
455 };
456
457 let serialized = serde_json::to_string(&tag).unwrap();
458 let deserialized: Tag = serde_json::from_str(&serialized).unwrap();
459
460 assert_eq!(deserialized.uuid, tag.uuid);
461 assert_eq!(deserialized.title, tag.title);
462 assert_eq!(deserialized.usage_count, tag.usage_count);
463 }
464
465 #[test]
466 fn test_create_task_request() {
467 let project_uuid = Uuid::new_v4();
468 let area_uuid = Uuid::new_v4();
469 let start_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
470
471 let request = CreateTaskRequest {
472 title: "New Task".to_string(),
473 notes: Some("Task notes".to_string()),
474 start_date: Some(start_date),
475 deadline: None,
476 project_uuid: Some(project_uuid),
477 area_uuid: Some(area_uuid),
478 tags: vec!["new".to_string()],
479 };
480
481 assert_eq!(request.title, "New Task");
482 assert_eq!(request.project_uuid, Some(project_uuid));
483 assert_eq!(request.area_uuid, Some(area_uuid));
484 assert_eq!(request.start_date, Some(start_date));
485 assert_eq!(request.tags.len(), 1);
486 }
487
488 #[test]
489 fn test_create_task_request_serialization() {
490 let request = CreateTaskRequest {
491 title: "Test".to_string(),
492 notes: None,
493 start_date: None,
494 deadline: None,
495 project_uuid: None,
496 area_uuid: None,
497 tags: vec![],
498 };
499
500 let serialized = serde_json::to_string(&request).unwrap();
501 let deserialized: CreateTaskRequest = serde_json::from_str(&serialized).unwrap();
502
503 assert_eq!(deserialized.title, request.title);
504 }
505
506 #[test]
507 fn test_update_task_request() {
508 let uuid = Uuid::new_v4();
509
510 let request = UpdateTaskRequest {
511 uuid,
512 title: Some("Updated Title".to_string()),
513 notes: Some("Updated notes".to_string()),
514 start_date: None,
515 deadline: None,
516 status: Some(TaskStatus::Completed),
517 tags: Some(vec!["updated".to_string()]),
518 };
519
520 assert_eq!(request.uuid, uuid);
521 assert_eq!(request.title, Some("Updated Title".to_string()));
522 assert_eq!(request.status, Some(TaskStatus::Completed));
523 assert_eq!(request.tags, Some(vec!["updated".to_string()]));
524 }
525
526 #[test]
527 fn test_update_task_request_serialization() {
528 let uuid = Uuid::new_v4();
529
530 let request = UpdateTaskRequest {
531 uuid,
532 title: None,
533 notes: None,
534 start_date: None,
535 deadline: None,
536 status: None,
537 tags: None,
538 };
539
540 let serialized = serde_json::to_string(&request).unwrap();
541 let deserialized: UpdateTaskRequest = serde_json::from_str(&serialized).unwrap();
542
543 assert_eq!(deserialized.uuid, request.uuid);
544 }
545
546 #[test]
547 fn test_task_filters_default() {
548 let filters = TaskFilters::default();
549
550 assert!(filters.status.is_none());
551 assert!(filters.task_type.is_none());
552 assert!(filters.project_uuid.is_none());
553 assert!(filters.area_uuid.is_none());
554 assert!(filters.tags.is_none());
555 assert!(filters.start_date_from.is_none());
556 assert!(filters.start_date_to.is_none());
557 assert!(filters.deadline_from.is_none());
558 assert!(filters.deadline_to.is_none());
559 assert!(filters.search_query.is_none());
560 assert!(filters.limit.is_none());
561 assert!(filters.offset.is_none());
562 }
563
564 #[test]
565 fn test_task_filters_creation() {
566 let project_uuid = Uuid::new_v4();
567 let start_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
568
569 let filters = TaskFilters {
570 status: Some(TaskStatus::Incomplete),
571 task_type: Some(TaskType::Todo),
572 project_uuid: Some(project_uuid),
573 area_uuid: None,
574 tags: Some(vec!["work".to_string()]),
575 start_date_from: Some(start_date),
576 start_date_to: None,
577 deadline_from: None,
578 deadline_to: None,
579 search_query: Some("test".to_string()),
580 limit: Some(10),
581 offset: Some(0),
582 };
583
584 assert_eq!(filters.status, Some(TaskStatus::Incomplete));
585 assert_eq!(filters.task_type, Some(TaskType::Todo));
586 assert_eq!(filters.project_uuid, Some(project_uuid));
587 assert_eq!(filters.search_query, Some("test".to_string()));
588 assert_eq!(filters.limit, Some(10));
589 assert_eq!(filters.offset, Some(0));
590 }
591
592 #[test]
593 fn test_task_filters_serialization() {
594 let filters = TaskFilters {
595 status: Some(TaskStatus::Completed),
596 task_type: Some(TaskType::Project),
597 project_uuid: None,
598 area_uuid: None,
599 tags: None,
600 start_date_from: None,
601 start_date_to: None,
602 deadline_from: None,
603 deadline_to: None,
604 search_query: None,
605 limit: None,
606 offset: None,
607 };
608
609 let serialized = serde_json::to_string(&filters).unwrap();
610 let deserialized: TaskFilters = serde_json::from_str(&serialized).unwrap();
611
612 assert_eq!(deserialized.status, filters.status);
613 assert_eq!(deserialized.task_type, filters.task_type);
614 }
615
616 #[test]
617 fn test_task_status_equality() {
618 assert_eq!(TaskStatus::Incomplete, TaskStatus::Incomplete);
619 assert_ne!(TaskStatus::Incomplete, TaskStatus::Completed);
620 assert_ne!(TaskStatus::Completed, TaskStatus::Canceled);
621 assert_ne!(TaskStatus::Canceled, TaskStatus::Trashed);
622 }
623
624 #[test]
625 fn test_task_type_equality() {
626 assert_eq!(TaskType::Todo, TaskType::Todo);
627 assert_ne!(TaskType::Todo, TaskType::Project);
628 assert_ne!(TaskType::Project, TaskType::Heading);
629 assert_ne!(TaskType::Heading, TaskType::Area);
630 }
631
632 #[test]
633 fn test_task_with_children() {
634 let parent_uuid = Uuid::new_v4();
635 let child_uuid = Uuid::new_v4();
636 let now = Utc::now();
637
638 let child_task = Task {
639 uuid: child_uuid,
640 title: "Child Task".to_string(),
641 task_type: TaskType::Todo,
642 status: TaskStatus::Incomplete,
643 notes: None,
644 start_date: None,
645 deadline: None,
646 created: now,
647 modified: now,
648 project_uuid: None,
649 area_uuid: None,
650 parent_uuid: Some(parent_uuid),
651 tags: vec![],
652 children: vec![],
653 };
654
655 let parent_task = Task {
656 uuid: parent_uuid,
657 title: "Parent Task".to_string(),
658 task_type: TaskType::Heading,
659 status: TaskStatus::Incomplete,
660 notes: None,
661 start_date: None,
662 deadline: None,
663 created: now,
664 modified: now,
665 project_uuid: None,
666 area_uuid: None,
667 parent_uuid: None,
668 tags: vec![],
669 children: vec![child_task],
670 };
671
672 assert_eq!(parent_task.children.len(), 1);
673 assert_eq!(parent_task.children[0].parent_uuid, Some(parent_uuid));
674 assert_eq!(parent_task.children[0].title, "Child Task");
675 }
676
677 #[test]
678 fn test_project_with_tasks() {
679 let project_uuid = Uuid::new_v4();
680 let task_uuid = Uuid::new_v4();
681 let now = Utc::now();
682
683 let task = Task {
684 uuid: task_uuid,
685 title: "Project Task".to_string(),
686 task_type: TaskType::Todo,
687 status: TaskStatus::Incomplete,
688 notes: None,
689 start_date: None,
690 deadline: None,
691 created: now,
692 modified: now,
693 project_uuid: Some(project_uuid),
694 area_uuid: None,
695 parent_uuid: None,
696 tags: vec![],
697 children: vec![],
698 };
699
700 let project = Project {
701 uuid: project_uuid,
702 title: "Test Project".to_string(),
703 notes: None,
704 start_date: None,
705 deadline: None,
706 created: now,
707 modified: now,
708 area_uuid: None,
709 tags: vec![],
710 status: TaskStatus::Incomplete,
711 tasks: vec![task],
712 };
713
714 assert_eq!(project.tasks.len(), 1);
715 assert_eq!(project.tasks[0].project_uuid, Some(project_uuid));
716 assert_eq!(project.tasks[0].title, "Project Task");
717 }
718
719 #[test]
720 fn test_area_with_projects() {
721 let area_uuid = Uuid::new_v4();
722 let project_uuid = Uuid::new_v4();
723 let now = Utc::now();
724
725 let project = Project {
726 uuid: project_uuid,
727 title: "Area Project".to_string(),
728 notes: None,
729 start_date: None,
730 deadline: None,
731 created: now,
732 modified: now,
733 area_uuid: Some(area_uuid),
734 tags: vec![],
735 status: TaskStatus::Incomplete,
736 tasks: vec![],
737 };
738
739 let area = Area {
740 uuid: area_uuid,
741 title: "Test Area".to_string(),
742 notes: None,
743 created: now,
744 modified: now,
745 tags: vec![],
746 projects: vec![project],
747 };
748
749 assert_eq!(area.projects.len(), 1);
750 assert_eq!(area.projects[0].area_uuid, Some(area_uuid));
751 assert_eq!(area.projects[0].title, "Area Project");
752 }
753}