1use anyhow::Result;
2use serde::Deserialize;
3use std::io::BufRead;
4use std::path::Path;
5
6use crate::db;
7
8#[derive(Deserialize)]
9struct ImportTask {
10 id: String,
11 title: String,
12 #[serde(default)]
13 description: String,
14 #[serde(rename = "type", default = "default_type")]
15 task_type: String,
16 #[serde(default = "default_priority")]
17 priority: i32,
18 #[serde(default = "default_status")]
19 status: String,
20 #[serde(default = "default_effort")]
21 effort: i32,
22 #[serde(default)]
23 parent: String,
24 created: String,
25 updated: String,
26 #[serde(default)]
27 labels: Vec<String>,
28 #[serde(default)]
29 blockers: Vec<String>,
30 #[serde(default)]
31 logs: Vec<ImportLogEntry>,
32}
33
34#[derive(Deserialize)]
35struct ImportLogEntry {
36 timestamp: String,
37 body: String,
38}
39
40fn default_type() -> String {
41 "task".into()
42}
43fn default_priority() -> i32 {
44 2
45}
46fn default_status() -> String {
47 "open".into()
48}
49fn default_effort() -> i32 {
50 2
51}
52
53pub fn run(root: &Path, file: &str) -> Result<()> {
54 let conn = db::open(root)?;
55
56 eprintln!("info: importing from {file}...");
57
58 let reader: Box<dyn BufRead> = if file == "-" {
59 Box::new(std::io::stdin().lock())
60 } else {
61 Box::new(std::io::BufReader::new(std::fs::File::open(file)?))
62 };
63
64 for line in reader.lines() {
65 let line = line?;
66 if line.trim().is_empty() {
67 continue;
68 }
69
70 let t: ImportTask = serde_json::from_str(&line)?;
71
72 conn.execute(
73 "INSERT OR REPLACE INTO tasks
74 (id, title, description, type, priority, status, effort, parent, created, updated)
75 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
76 rusqlite::params![
77 t.id,
78 t.title,
79 t.description,
80 t.task_type,
81 t.priority,
82 t.status,
83 t.effort,
84 t.parent,
85 t.created,
86 t.updated,
87 ],
88 )?;
89
90 // Replace labels.
91 conn.execute("DELETE FROM labels WHERE task_id = ?1", [&t.id])?;
92 for lbl in &t.labels {
93 conn.execute(
94 "INSERT INTO labels (task_id, label) VALUES (?1, ?2)",
95 [&t.id, lbl],
96 )?;
97 }
98
99 // Replace blockers.
100 conn.execute("DELETE FROM blockers WHERE task_id = ?1", [&t.id])?;
101 for blk in &t.blockers {
102 conn.execute(
103 "INSERT INTO blockers (task_id, blocker_id) VALUES (?1, ?2)",
104 [&t.id, blk],
105 )?;
106 }
107
108 // Replace logs.
109 conn.execute("DELETE FROM task_logs WHERE task_id = ?1", [&t.id])?;
110 for log in &t.logs {
111 conn.execute(
112 "INSERT INTO task_logs (task_id, timestamp, body) VALUES (?1, ?2, ?3)",
113 rusqlite::params![&t.id, &log.timestamp, &log.body],
114 )?;
115 }
116 }
117
118 eprintln!("info: import complete");
119 Ok(())
120}