telegram_raf/telegram/
contests.rs1use chrono::{DateTime, Utc};
16use log::error;
17use rusqlite::params;
18use telexide_fork::{api::types::GetChatMember, prelude::*};
19
20use crate::persistence::types::{Contest, DBKey, Rank};
21use crate::telegram::users;
22
23use std::string::ToString;
24
25#[must_use]
34pub fn get(ctx: &Context, id: i64) -> Option<Contest> {
35 let guard = ctx.data.read();
36 let map = guard.get::<DBKey>().expect("db");
37 let conn = map.get().unwrap();
38 let mut stmt = conn
39 .prepare("SELECT name, prize, end, started_at, chan, stopped FROM contests WHERE id = ?")
40 .unwrap();
41 let mut iter = stmt
42 .query_map(params![id], |row| {
43 Ok(Contest {
44 id,
45 name: row.get(0)?,
46 prize: row.get(1)?,
47 end: row.get(2)?,
48 started_at: row.get(3)?,
49 chan: row.get(4)?,
50 stopped: row.get(5)?,
51 })
52 })
53 .unwrap();
54 let c = iter.next().unwrap();
55 if let Ok(c) = c {
56 return Some(c);
57 }
58 None
59}
60
61#[must_use]
70pub fn get_all(ctx: &Context, chan: i64) -> Vec<Contest> {
71 let guard = ctx.data.read();
72 let map = guard.get::<DBKey>().expect("db");
73 let conn = map.get().unwrap();
74 let mut stmt = conn
75 .prepare(
76 "SELECT id, name, prize, end, started_at, stopped FROM contests WHERE chan = ? ORDER BY end DESC",
77 )
78 .unwrap();
79
80 let contests = stmt
81 .query_map(params![chan], |row| {
82 Ok(Contest {
83 id: row.get(0)?,
84 name: row.get(1)?,
85 prize: row.get(2)?,
86 end: row.get(3)?,
87 started_at: row.get(4)?,
88 stopped: row.get(5)?,
89 chan,
90 })
91 })
92 .unwrap()
93 .map(std::result::Result::unwrap)
94 .collect();
95 contests
96}
97
98#[must_use]
108pub fn ranking(ctx: &Context, contest: &Contest) -> Vec<Rank> {
109 let guard = ctx.data.read();
110 let map = guard.get::<DBKey>().expect("db");
111 let conn = map.get().unwrap();
112 let mut stmt = conn
115 .prepare(
116 "SELECT ROW_NUMBER() OVER (ORDER BY t.c, t.source DESC) AS r, t.c, t.source
117 FROM (SELECT COUNT(*) AS c, source FROM invitations WHERE contest = ? GROUP BY source) AS t",
118 )
119 .unwrap();
120 stmt.query_map(params![contest.id], |row| {
121 Ok(Rank {
122 rank: row.get(0)?,
123 invites: row.get(1)?,
124 user: users::get(ctx, row.get(2)?).unwrap(),
125 })
126 })
127 .unwrap()
128 .map(std::result::Result::unwrap)
129 .collect::<Vec<Rank>>()
130}
131
132#[derive(Debug, Clone)]
134pub enum Error {
135 ParseError(chrono::format::ParseError),
137 GenericError(String),
139}
140
141impl From<chrono::format::ParseError> for Error {
142 fn from(error: chrono::format::ParseError) -> Error {
144 Error::ParseError(error)
145 }
146}
147
148impl From<String> for Error {
149 fn from(error: String) -> Error {
151 Error::GenericError(error)
152 }
153}
154
155impl std::fmt::Display for Error {
156 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
158 match self {
159 Error::ParseError(error) => write!(f, "DateTime parse {error}"),
160 Error::GenericError(error) => write!(f, "{error}"),
161 }
162 }
163}
164
165pub fn from_text(text: &str, chan: i64) -> Result<Contest, Error> {
178 let rows = text
179 .split('\n')
180 .skip_while(|r| r.is_empty())
181 .collect::<Vec<&str>>();
182 if rows.len() != 3 {
183 return Err(format!("failed because row.len() != 3. Got: {}", rows.len()).into());
184 }
185 let id = -1;
186 let name = rows[0].to_string();
187 let prize = rows[2].to_string();
188 let add_seconds = |row: &str| -> String {
191 let mut elements = row
192 .split_whitespace()
193 .map(ToString::to_string)
194 .collect::<Vec<String>>();
195 if elements.len() != 3 {
196 return row.to_string();
197 }
198 elements[1] += ":00";
202 elements.join(" ")
203 };
204 let now = Utc::now();
205 let end: DateTime<Utc> =
206 DateTime::parse_from_str(&add_seconds(rows[1]), "%Y-%m-%d %H:%M:%S %#z")?.into();
207 if end < now {
208 return Err("End date can't be in the past".to_string().into());
209 }
210 Ok(Contest {
211 id,
212 end,
213 name,
214 prize,
215 chan,
216 stopped: false,
217 started_at: None,
218 })
219}
220
221#[must_use]
231pub fn count_users(ctx: &Context, contest: &Contest) -> i64 {
232 struct Counter {
233 value: i64,
234 }
235 let guard = ctx.data.read();
236 let map = guard.get::<DBKey>().expect("db");
237 let conn = map.get().unwrap();
238 let mut stmt = conn
239 .prepare("SELECT COUNT(id) FROM invitations WHERE contest = ?")
240 .unwrap();
241 let vals = stmt
242 .query_map(params![contest.id], |row| {
243 Ok(Counter { value: row.get(0)? })
244 })
245 .unwrap()
246 .map(|count| count.unwrap_or(Counter { value: -1 }).value)
247 .collect::<Vec<i64>>();
248 if vals.is_empty() {
249 return 0;
250 }
251 vals[0]
252}
253
254pub async fn validate_users(ctx: &Context, contest: &Contest) {
265 struct InnerUser {
266 id: i64,
267 }
268 let users = {
269 let guard = ctx.data.read();
270 let map = guard.get::<DBKey>().expect("db");
271 let conn = map.get().unwrap();
272 let mut stmt = conn
273 .prepare("SELECT dest FROM invitations WHERE contest = ?")
274 .unwrap();
275 stmt.query_map(params![contest.id], |row| Ok(InnerUser { id: row.get(0)? }))
276 .unwrap()
277 .map(|user| user.unwrap().id)
278 .collect::<Vec<i64>>()
279 };
280
281 for user in users {
282 let member = ctx
283 .api
284 .get_chat_member(GetChatMember {
285 chat_id: contest.chan,
286 user_id: user,
287 })
288 .await;
289
290 let in_channel = member.is_ok();
291 if !in_channel {
292 let res = {
293 let guard = ctx.data.read();
294 let map = guard.get::<DBKey>().expect("db");
295 let conn = map.get().unwrap();
296 let mut stmt = conn
297 .prepare("DELETE FROM invitations WHERE dest = ? and contest = ?")
298 .unwrap();
299 stmt.execute(params![user, contest.id])
300 };
301 if res.is_err() {
302 error!("[users validation] {}", res.err().unwrap());
303 }
304 }
305 }
306}