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}