task_picker/sources/
github.rs

1use anyhow::{Context, Result};
2use chrono::{DateTime, Utc};
3use json::JsonValue;
4use serde::{Deserialize, Serialize};
5use ureq::Agent;
6
7use crate::tasks::Task;
8
9use super::GITHUB_ICON;
10
11#[derive(Serialize, Deserialize, Clone)]
12#[serde(default)]
13pub struct GitHubSource {
14    #[serde(skip)]
15    agent: ureq::Agent,
16    pub name: String,
17    pub server_url: String,
18}
19
20impl Default for GitHubSource {
21    fn default() -> Self {
22        Self {
23            agent: Agent::new(),
24            name: "GitHub".to_string(),
25            server_url: "https://api.github.com".to_string(),
26        }
27    }
28}
29impl GitHubSource {
30    pub fn query_tasks<S>(&self, secret: Option<S>) -> Result<Vec<Task>>
31    where
32        S: AsRef<str>,
33    {
34        let mut result = Vec::default();
35
36        let mut request = self
37            .agent
38            .get(&format!("{}/issues", self.server_url))
39            .set("X-GitHub-Api-Version", "2022-11-28")
40            .set("Accept", "application/vnd.github+json");
41        if let Some(secret) = secret {
42            request = request.set("Authorization", &format!("Bearer {}", secret.as_ref()))
43        }
44        let response = request.call()?;
45        let body = response.into_string()?;
46        let assigned_issues = json::parse(&body)?;
47        if let JsonValue::Array(assigned_issues) = assigned_issues {
48            for issue in assigned_issues {
49                if let JsonValue::Object(issue) = issue {
50                    if Some("open") == issue["state"].as_str() {
51                        let project = if let JsonValue::Object(repo) = &issue["repository"] {
52                            repo["full_name"]
53                                .as_str()
54                                .context("Missing 'full_name' field for issue")?
55                        } else {
56                            "GitHub"
57                        };
58
59                        let title = issue["title"]
60                            .as_str()
61                            .context("Missing 'title' field for issue")?;
62                        let url = issue["html_url"]
63                            .as_str()
64                            .context("Missing 'html_url' field for issue")?;
65
66                        let created: Option<DateTime<Utc>> = issue["created_at"]
67                            .as_str()
68                            .map(|d| DateTime::parse_from_str(d, "%+"))
69                            .transpose()?
70                            .map(|d| d.into());
71
72                        let due: Option<DateTime<Utc>> =
73                            if let JsonValue::Object(milestone) = &issue["milestone"] {
74                                milestone["due_on"]
75                                    .as_str()
76                                    .map(|d| DateTime::parse_from_str(d, "%+"))
77                                    .transpose()?
78                                    .map(|d| d.into())
79                            } else {
80                                None
81                            };
82
83                        let task = Task {
84                            project: format!("{} {}", GITHUB_ICON, project),
85                            title: title.to_string(),
86                            description: url.to_string(),
87                            due,
88                            created,
89                            id: Some(url.to_string()),
90                        };
91                        result.push(task);
92                    }
93                }
94            }
95        }
96        Ok(result)
97    }
98}