task_picker/sources/
github.rs1use 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}