1use std::fmt;
2use url::Url;
3use graphql_client::{ GraphQLQuery, Response };
4
5#[doc(hidden)]
6pub enum Error {
7 Network(reqwest::Error),
8 Message(&'static str),
9 GraphQL(Vec<graphql_client::Error>),
10}
11impl fmt::Debug for Error {
12 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13 match self {
14 Error::Network(err) => write!(f, "{:?}", err),
15 Error::Message(s) => write!(f, "{}", s),
16 Error::GraphQL(err) => write!(f, "{:?}", err),
17 }
18 }
19}
20impl From<reqwest::Error> for Error {
21 fn from(err: reqwest::Error) -> Error {
22 Error::Network(err)
23 }
24}
25impl From<&'static str> for Error {
26 fn from(err: &'static str) -> Error {
27 Error::Message(err)
28 }
29}
30
31#[derive(GraphQLQuery)]
32#[graphql(
33 schema_path = "schema.graphql",
34 query_path = "api.graphql",
35 response_derives = "Debug",
36)]
37struct UserSearch;
38
39#[derive(GraphQLQuery)]
40#[graphql(
41 schema_path = "schema.graphql",
42 query_path = "api.graphql",
43 response_derives = "Debug",
44)]
45struct UserGet;
46
47#[derive(GraphQLQuery)]
48#[graphql(
49 schema_path = "schema.graphql",
50 query_path = "api.graphql",
51 response_derives = "Debug",
52)]
53struct TagsGet;
54
55#[derive(GraphQLQuery)]
56#[graphql(
57 schema_path = "schema.graphql",
58 query_path = "api.graphql",
59 response_derives = "Debug",
60)]
61struct CheckInTag;
62pub type CheckInReturn = (bool, check_in_tag::UserData, check_in_tag::TagData);
63
64pub struct CheckinAPI {
65 base_url: Url,
66 client: reqwest::blocking::Client,
67 auth_cookie: String,
68}
69
70impl CheckinAPI {
72 pub fn login(username: &str, password: &str, url: &str) -> Result<Self, Error> {
76 let client = reqwest::blocking::Client::new();
77 let base_url = Url::parse(url).expect("Invalid base URL configured");
78
79 let params = [("username", username), ("password", password)];
80 let response = client.post(base_url.join("/api/user/login").unwrap())
81 .form(¶ms)
82 .send()?;
83
84 if !response.status().is_success() {
85 return Err("Invalid username or password".into());
86 }
87
88 let cookies = response.headers().get_all(reqwest::header::SET_COOKIE);
89 let mut auth_token: Option<String> = None;
90 let auth_regex = regex::Regex::new(r"^auth=(?P<token>[a-f0-9]+);").unwrap();
91 for cookie in cookies.iter() {
92 if let Ok(cookie) = cookie.to_str() {
93 if let Some(capture) = auth_regex.captures(cookie) {
94 auth_token = Some(capture["token"].to_owned());
95 }
96 }
97 }
98
99 match auth_token {
100 Some(mut token) => {
101 token.insert_str(0, "auth=");
103 Ok(Self {
104 base_url,
105 client,
106 auth_cookie: token,
107 })
108 },
109 None => Err("No auth token set by server".into())
110 }
111 }
112
113 pub fn from_token(mut auth_token: String, url: &str) -> Self {
117 let client = reqwest::blocking::Client::new();
118 let base_url = Url::parse(url).expect("Invalid base URL configured");
119 auth_token.insert_str(0, "auth=");
121 Self { base_url, client, auth_cookie: auth_token }
122 }
123
124 pub fn auth_token(&self) -> &str {
125 &self.auth_cookie[5..]
126 }
127
128 pub fn add_user(&self, username: &str, password: &str) -> Result<(), Error> {
132 let params = [("username", username), ("password", password)];
133 let response = self.client.put(self.base_url.join("/api/user/update").unwrap())
134 .header(reqwest::header::COOKIE, self.auth_cookie.as_str())
135 .form(¶ms)
136 .send()?;
137
138 if !response.status().is_success() {
139 Err("Account creation unsuccessful".into())
140 }
141 else {
142 Ok(())
143 }
144 }
145
146 pub fn delete_user(&self, username: &str) -> Result<(), Error> {
147 let params = [("username", username)];
148 let response = self.client.delete(self.base_url.join("/api/user/update").unwrap())
149 .header(reqwest::header::COOKIE, self.auth_cookie.as_str())
150 .form(¶ms)
151 .send()?;
152
153 if !response.status().is_success() {
154 Err("Account deletion unsuccessful".into())
155 }
156 else {
157 Ok(())
158 }
159 }
160
161 fn checkin_action(&self, check_in: bool, uuid: &str, tag: &str) -> Result<CheckInReturn, Error> {
162 let body = CheckInTag::build_query(check_in_tag::Variables {
163 id: uuid.to_string(),
164 tag: tag.to_string(),
165 checkin: check_in,
166 });
167
168 let response: Response<check_in_tag::ResponseData> = self.client.post(self.base_url.join("/graphql").unwrap())
169 .header(reqwest::header::COOKIE, self.auth_cookie.as_str())
170 .json(&body)
171 .send()?
172 .json()?;
173
174 if let Some(errors) = response.errors {
175 return Err(Error::GraphQL(errors));
176 }
177 let data = match response.data {
178 Some(data) => data,
179 None => return Err("Check in API returned no data".into()),
180 };
181 let check_in_data = match data.check_in {
182 Some(check_in_data) => check_in_data,
183 None => return Err("Invalid user ID on badge".into()),
184 };
185 let user = check_in_data.user.user_data;
186 if !user.accepted || !user.confirmed {
187 return Err("User not accepted and confirmed".into());
188 }
189
190 let tag_details = check_in_data.tags.into_iter()
191 .map(|item| item.tag_data)
192 .find(|item| item.tag.name == tag)
193 .unwrap(); Ok((
196 tag_details.checkin_success,
197 user,
198 tag_details
199 ))
200 }
201
202 pub fn check_in(&self, uuid: &str, tag: &str) -> Result<CheckInReturn, Error> {
209 self.checkin_action(true, uuid, tag)
210 }
211
212 pub fn check_out(&self, uuid: &str, tag: &str) -> Result<CheckInReturn, Error> {
216 self.checkin_action(false, uuid, tag)
217 }
218
219 pub fn get_tags_names(&self, only_current: bool) -> Result<Vec<String>, Error> {
223 let body = TagsGet::build_query(tags_get::Variables {
224 only_current
225 });
226
227 let response: Response<tags_get::ResponseData> = self.client.post(self.base_url.join("/graphql").unwrap())
228 .header(reqwest::header::COOKIE, self.auth_cookie.as_str())
229 .json(&body)
230 .send()?
231 .json()?;
232
233 if let Some(errors) = response.errors {
234 return Err(Error::GraphQL(errors));
235 }
236 if response.data.is_none() {
237 return Err("Check in API returned no data".into());
238 }
239 Ok(
240 response.data.unwrap()
241 .tags.into_iter()
242 .map(|tag| tag.name)
243 .collect()
244 )
245 }
246}
247
248#[cfg(test)]
249mod checkin_api_tests {
250 use super::CheckinAPI;
251
252 #[test]
253 fn login() {
254 let username = std::env::var("CHECKIN_USERNAME").unwrap();
255 let password = std::env::var("CHECKIN_PASSWORD").unwrap();
256
257 let instance = CheckinAPI::login(&username, &password).unwrap();
258 assert_eq!(instance.auth_token().len(), 64);
259
260 instance.check_in("7dd00021-89fd-49f1-9c17-bd0ba7dcf97e", "123").unwrap();
261
262 instance.get_tags_names(true).unwrap();
263
264 instance.add_user("test_user", "just testing").unwrap();
265 instance.delete_user("test_user").unwrap();
266 }
267}