canvas_cli/
lib.rs

1use colored::Colorize;
2use inquire::Select;
3use reqwest::Client;
4use serde_derive::Deserialize;
5use std::{collections::HashMap, fmt::Display};
6
7pub type DateTime = chrono::DateTime<chrono::Utc>;
8
9#[derive(Debug, Hash, Clone, PartialEq, Eq)]
10pub struct Course {
11    pub name: String,
12    pub id: u32,
13    is_favorite: bool,
14    css_color: Option<String>,
15    created_at: DateTime,
16}
17
18#[derive(Deserialize, Debug)]
19struct CourseResponse {
20    id: u32,
21    name: String,
22    is_favorite: bool,
23    created_at: DateTime,
24    concluded: bool,
25}
26
27#[derive(Deserialize, Debug)]
28struct ColorsResponse {
29    custom_colors: HashMap<String, String>,
30}
31
32impl Display for Course {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        let css_color = self.css_color.clone().unwrap_or("#000000".to_string());
35        let color = csscolorparser::parse(&css_color)
36            .unwrap()
37            .to_linear_rgba_u8();
38        write!(
39            f,
40            "{}{}{}",
41            "█ ".truecolor(color[0], color[1], color[2]),
42            self.name,
43            if self.is_favorite { " ★" } else { "" }.yellow()
44        )
45    }
46}
47
48impl Course {
49    pub async fn fetch(
50        course_id: Option<u32>,
51        base_url: &str,
52        client: &Client,
53    ) -> Result<Course, anyhow::Error> {
54        Ok(if let Some(course_id) = course_id {
55            let course_response = client
56                .get(format!(
57                    "{}/api/v1/courses/{}?include[]=favorites&include[]=concluded",
58                    base_url, course_id
59                ))
60                .send()
61                .await?
62                .json::<CourseResponse>()
63                .await?;
64            log::info!("Made REST request to get course information");
65
66            let course_colors: HashMap<u32, String> = client
67                .get(format!("{}/api/v1/users/self/colors", base_url))
68                .send()
69                .await?
70                .json::<ColorsResponse>()
71                .await?
72                .custom_colors
73                .into_iter()
74                .filter(|(k, _)| k.starts_with("course_"))
75                .map(|(k, v)| (k.trim_start_matches("course_").parse::<u32>().unwrap(), v))
76                .collect();
77            log::info!("Made REST request to get course colors");
78
79            let course = Course {
80                name: course_response.name,
81                id: course_response.id,
82                is_favorite: course_response.is_favorite,
83                css_color: course_colors.get(&course_response.id).cloned(),
84                created_at: course_response.created_at,
85            };
86
87            println!("✓ Found {course}");
88            course
89        } else {
90            let courses_response = client
91                .get(format!(
92                    "{}/api/v1/courses?per_page=1000&include[]=favorites&include[]=concluded",
93                    base_url
94                ))
95                .send()
96                .await?
97                .json::<Vec<serde_json::Value>>()
98                .await?
99                .into_iter()
100                .filter_map(|v| serde_json::from_value(v).ok())
101                .collect::<Vec<CourseResponse>>();
102
103            log::info!("Made REST request to get favorite courses");
104
105            let course_colors: HashMap<u32, String> = client
106                .get(format!("{}/api/v1/users/self/colors", base_url))
107                .send()
108                .await?
109                .json::<ColorsResponse>()
110                .await?
111                .custom_colors
112                .into_iter()
113                .filter(|(k, _)| k.starts_with("course_"))
114                .map(|(k, v)| (k.trim_start_matches("course_").parse::<u32>().unwrap(), v))
115                .collect();
116            log::info!("Made REST request to get course colors");
117
118            println!("✓ Queried course information");
119
120            let mut courses: Vec<Course> = courses_response
121                .into_iter()
122                .filter(|course| !course.concluded)
123                .map(|course| Course {
124                    name: course.name.clone(),
125                    id: course.id,
126                    is_favorite: course.is_favorite,
127                    css_color: course_colors.get(&course.id).cloned(),
128                    created_at: course.created_at,
129                })
130                .collect();
131
132            courses.sort_by(|a, b| {
133                b.is_favorite
134                    .cmp(&a.is_favorite)
135                    .then(a.created_at.cmp(&b.created_at))
136            });
137            Select::new("Course?", courses).prompt()?
138        })
139    }
140}