shared/domain/jig/
codes.rs

1//! Types for Jig short codes for sharing
2
3use std::collections::{HashMap, HashSet};
4
5use chrono::{DateTime, Utc};
6use macros::make_path_parts;
7use serde::{Deserialize, Serialize};
8
9use crate::{api::endpoints::PathPart, domain::module::StableModuleId};
10
11use super::{JigId, JigPlayerSettings, JigResponse};
12
13/// Four-digit code identifying a Jig player session
14#[derive(Serialize, Deserialize, Debug, Copy, Clone, PathPart, PartialEq, Eq)]
15#[cfg_attr(feature = "backend", derive(sqlx::Type))]
16#[cfg_attr(feature = "backend", sqlx(transparent))]
17pub struct JigCode(pub i32);
18
19impl ToString for JigCode {
20    fn to_string(&self) -> String {
21        format!("{:06}", self.0)
22    }
23}
24
25make_path_parts!(JigPlayerSessionCreatePath => "/v1/jig/codes");
26
27/// Request to create a jig code.
28#[derive(Serialize, Deserialize, Debug, Clone)]
29#[serde(rename_all = "camelCase")]
30pub struct JigPlayerSessionCreateRequest {
31    /// ID of the Jig that the session is for
32    pub jig_id: JigId,
33
34    /// Display name
35    pub name: Option<String>,
36
37    /// Settings for the session
38    pub settings: JigPlayerSettings,
39}
40
41/// Response from creating a jig code.
42#[derive(Serialize, Deserialize, Debug, Clone)]
43#[serde(rename_all = "camelCase")]
44pub struct JigPlayerSessionCreateResponse {
45    /// Four-digit code identifying a Jig player session
46    pub index: JigCode,
47}
48
49make_path_parts!(JigCodePath => "/v1/jig/codes/{}" => JigCode);
50
51/// Request to update a jig code.
52#[derive(Serialize, Deserialize, Debug, Clone, Default)]
53#[serde(rename_all = "camelCase")]
54pub struct JigCodeUpdateRequest {
55    /// Display name
56    pub name: Option<Option<String>>,
57
58    /// Settings for the session
59    pub settings: Option<JigPlayerSettings>,
60}
61
62/// Over-the-wire representation of a jig player session
63#[derive(Serialize, Deserialize, Debug, Clone)]
64pub struct JigCodeResponse {
65    /// Four-digit code identifying a Jig player session
66    pub index: JigCode,
67
68    /// Id of Jig this code is for.
69    pub jig_id: JigId,
70
71    /// Display name.
72    pub name: Option<String>,
73
74    /// Settings for the player session.
75    pub settings: JigPlayerSettings,
76
77    /// When the code was created
78    pub created_at: DateTime<Utc>,
79
80    /// When the code expires
81    pub expires_at: DateTime<Utc>,
82}
83
84make_path_parts!(JigCodeListPath => "/v1/jig/codes");
85
86/// Request for jig code list
87#[derive(Serialize, Deserialize, Clone, Debug, Default)]
88#[serde(rename_all = "camelCase")]
89pub struct JigCodeListRequest {
90    /// Jig id
91    #[serde(default)]
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub jig_id: Option<JigId>,
94}
95
96/// Lists all jig player sessions associated with a jig
97#[derive(Serialize, Deserialize, Debug, Clone)]
98pub struct JigCodeListResponse {
99    /// Vector of the jig codes
100    pub codes: Vec<JigCodeResponse>,
101}
102
103make_path_parts!(JigsWithCodesPath => "/v1/jig/codes/jig-codes");
104
105/// Lists all jig player sessions associated with a jig
106#[derive(Serialize, Deserialize, Debug, Clone)]
107pub struct JigsWithCodesResponse {
108    /// Vector of the jig that have jig codes
109    pub jigs: Vec<JigWithCodes>,
110}
111
112/// Jig with codes
113#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct JigWithCodes {
115    /// jig
116    pub jig: JigResponse,
117    /// codes
118    pub codes: Vec<JigCodeResponse>,
119}
120
121make_path_parts!(JigCodeSessionsPath => "/v1/jig/codes/{}/sessions" => JigCode);
122
123/// Lists all jig player sessions associated with a jig
124#[derive(Serialize, Deserialize, Debug, Clone)]
125pub struct JigCodeSessionsListResponse {
126    /// Vector of the jig sessions
127    pub sessions: Vec<JigCodeSessionResponse>,
128}
129
130/// Lists all jig player sessions associated with a jig
131#[derive(Serialize, Deserialize, Debug, Clone)]
132pub struct JigCodeSessionResponse {
133    /// code
134    pub code: JigCode,
135    /// Playing's name
136    pub players_name: Option<String>,
137    /// star time
138    pub started_at: DateTime<Utc>,
139    /// end time
140    pub finished_at: Option<DateTime<Utc>>,
141    /// information about the session
142    pub info: Option<JigPlaySession>,
143}
144
145/// Play session
146#[derive(Clone, Debug, Serialize, Deserialize, Default)]
147pub struct JigPlaySession {
148    /// modules
149    #[serde(default)]
150    pub modules: Vec<JigPlaySessionModule>,
151    /// Modules just visited
152    #[serde(default)]
153    pub visited: HashSet<StableModuleId>,
154}
155
156impl JigPlaySessionModuleGetPointsEarned for JigPlaySession {
157    fn get_points_earned(&self) -> PointsEarned {
158        let mut available = 0.0;
159        let mut earned = 0.0;
160        for module in &self.modules {
161            let module_points = module.get_points_earned();
162            available += module_points.available;
163            earned += module_points.earned;
164        }
165        PointsEarned { available, earned }
166    }
167}
168
169/// modules
170#[derive(Clone, Debug, Serialize, Deserialize)]
171pub enum JigPlaySessionModule {
172    /// Matching
173    Matching(JigPlaySessionMatching),
174    /// Card quiz
175    CardQuiz(JigPlaySessionCardQuiz),
176    /// Drag and drop
177    DragDrop(JigPlaySessionDragDrop),
178    /// Answer this
179    FindAnswer(JigPlaySessionFindAnswer),
180}
181
182impl JigPlaySessionModule {
183    /// get stable module id
184    pub fn stable_module_id(&self) -> StableModuleId {
185        match self {
186            Self::Matching(module) => module.stable_module_id,
187            Self::CardQuiz(module) => module.stable_module_id,
188            Self::DragDrop(module) => module.stable_module_id,
189            Self::FindAnswer(module) => module.stable_module_id,
190        }
191    }
192}
193
194impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionModule {
195    fn get_points_earned(&self) -> PointsEarned {
196        match self {
197            JigPlaySessionModule::Matching(module) => module.get_points_earned(),
198            JigPlaySessionModule::CardQuiz(module) => module.get_points_earned(),
199            JigPlaySessionModule::DragDrop(module) => module.get_points_earned(),
200            JigPlaySessionModule::FindAnswer(module) => module.get_points_earned(),
201        }
202    }
203}
204
205/// get points earned trait
206pub trait JigPlaySessionModuleGetPointsEarned {
207    /// get points earned method
208    fn get_points_earned(&self) -> PointsEarned;
209}
210
211/// Jig play session module points earned
212#[derive(Clone, Debug, Serialize, Deserialize)]
213pub struct PointsEarned {
214    /// available points to earn
215    pub available: f32,
216    /// points actually earned
217    pub earned: f32,
218}
219impl std::fmt::Display for PointsEarned {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(f, "{}|{}", self.earned, self.available)
222    }
223}
224impl PointsEarned {
225    /// get percent of points earned
226    pub fn percent(&self) -> u16 {
227        let output = (self.earned / self.available) * 100.00;
228        output as u16
229    }
230}
231
232/// matching module
233#[derive(Clone, Debug, Serialize, Deserialize)]
234pub struct JigPlaySessionMatching {
235    /// related module id
236    pub stable_module_id: StableModuleId,
237
238    /// list of rounds for this module
239    pub rounds: Vec<HashMap<usize, JigPlaySessionMatchingCard>>,
240}
241
242impl JigPlaySessionMatching {
243    /// create new from module id
244    pub fn new(stable_module_id: StableModuleId) -> Self {
245        Self {
246            stable_module_id,
247            rounds: vec![],
248        }
249    }
250}
251
252impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionMatching {
253    fn get_points_earned(&self) -> PointsEarned {
254        let mut available = 0.0;
255        let mut earned = 0.0;
256        for round in &self.rounds {
257            for (_, card) in round {
258                available += 1.0;
259                earned += match card.failed_tries {
260                    0 => 1.0,
261                    1 => 0.5,
262                    _ => 0.0,
263                };
264            }
265        }
266        PointsEarned { available, earned }
267    }
268}
269
270///
271#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
272pub struct JigPlaySessionMatchingCard {
273    /// unsuccessful try count
274    pub failed_tries: u16,
275}
276
277/// CardQuiz module
278#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct JigPlaySessionCardQuiz {
280    /// related module id
281    pub stable_module_id: StableModuleId,
282
283    /// list of rounds for this module
284    pub rounds: Vec<JigPlaySessionCardQuizRound>,
285}
286
287impl JigPlaySessionCardQuiz {
288    /// create new from module id
289    pub fn new(stable_module_id: StableModuleId) -> Self {
290        Self {
291            stable_module_id,
292            rounds: Vec::new(),
293        }
294    }
295}
296
297impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionCardQuiz {
298    fn get_points_earned(&self) -> PointsEarned {
299        let mut available = 0.0;
300        let mut earned = 0.0;
301        for card in &self.rounds {
302            available += 1.0;
303            earned += match card.failed_tries {
304                0 => 1.0,
305                1 => 0.5,
306                _ => 0.0,
307            };
308        }
309        PointsEarned { available, earned }
310    }
311}
312
313///
314#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
315pub struct JigPlaySessionCardQuizRound {
316    /// index of card
317    pub card_index: usize,
318
319    /// unsuccessful try count
320    pub failed_tries: u16,
321}
322
323/// Drag and drop module
324#[derive(Clone, Debug, Serialize, Deserialize)]
325pub struct JigPlaySessionDragDrop {
326    /// related module id
327    pub stable_module_id: StableModuleId,
328
329    /// list of rounds for this module. key is index in module.items.
330    /// HashMap instead of Vec because not all items in modules.items are interactive.
331    pub items: HashMap<usize, JigPlaySessionDragDropItem>,
332}
333
334impl JigPlaySessionDragDrop {
335    /// create new from module id
336    pub fn new(stable_module_id: StableModuleId) -> Self {
337        Self {
338            stable_module_id,
339            items: HashMap::new(),
340        }
341    }
342}
343
344impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionDragDrop {
345    fn get_points_earned(&self) -> PointsEarned {
346        let mut available = 0.0;
347        let mut earned = 0.0;
348        for card in self.items.values() {
349            available += 1.0;
350            earned += match card.failed_tries {
351                0 => 1.0,
352                1 => 0.5,
353                _ => 0.0,
354            };
355        }
356        PointsEarned { available, earned }
357    }
358}
359
360///
361#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
362pub struct JigPlaySessionDragDropItem {
363    /// unsuccessful try count
364    pub failed_tries: u16,
365}
366
367/// Drag and drop module
368#[derive(Clone, Debug, Serialize, Deserialize)]
369pub struct JigPlaySessionFindAnswer {
370    /// related module id
371    pub stable_module_id: StableModuleId,
372
373    /// list of rounds for this module
374    pub items: Vec<JigPlaySessionFindAnswerItem>,
375}
376
377impl JigPlaySessionFindAnswer {
378    /// create new from module id
379    pub fn new(stable_module_id: StableModuleId) -> Self {
380        Self {
381            stable_module_id,
382            items: Vec::new(),
383        }
384    }
385}
386
387impl JigPlaySessionModuleGetPointsEarned for JigPlaySessionFindAnswer {
388    fn get_points_earned(&self) -> PointsEarned {
389        let mut available = 0.0;
390        let mut earned = 0.0;
391        for card in &self.items {
392            available += 1.0;
393            earned += match card.failed_tries {
394                0 => 1.0,
395                1 => 0.5,
396                _ => 0.0,
397            };
398        }
399        PointsEarned { available, earned }
400    }
401}
402
403///
404#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
405pub struct JigPlaySessionFindAnswerItem {
406    /// unsuccessful try count
407    pub failed_tries: u16,
408}
409
410/// Types for Jig session instance endpoints
411pub mod instance {
412    use macros::make_path_parts;
413    use serde::{Deserialize, Serialize};
414
415    use crate::domain::jig::{
416        codes::{JigCode, JigPlaySession},
417        JigId, JigPlayerSettings,
418    };
419
420    make_path_parts!(PlayerSessionInstanceCreatePath => "/v1/jig/codes/instance");
421
422    /// Request to create a player (who is not the author) session for a JIG.
423    #[derive(Serialize, Deserialize, Debug, Clone)]
424    pub struct PlayerSessionInstanceCreateRequest {
425        /// Four-digit code identifying a JIG player session
426        pub code: JigCode,
427    }
428
429    /// Response for successfully creating an instance of a JIG player session. contains the token
430    #[derive(Serialize, Deserialize, Debug, Clone)]
431    #[serde(rename_all = "camelCase")]
432    pub struct PlayerSessionInstanceResponse {
433        /// ID of the JIG that the session is for
434        pub jig_id: JigId,
435
436        /// Settings for the player session.
437        pub settings: JigPlayerSettings,
438
439        /// Token that will be passed to confirm a JIG was played all the way through
440        pub token: String,
441    }
442
443    make_path_parts!(PlayerSessionInstanceCompletePath => "/v1/jig/codes/instance/complete");
444
445    /// Request to complete a player session for a JIG.
446    #[derive(Serialize, Deserialize, Debug, Clone)]
447    #[serde(rename_all = "camelCase")]
448    pub struct PlayerSessionInstanceCompleteRequest {
449        /// Token that will be passed to confirm a JIG was played all the way through
450        pub token: String,
451
452        /// session
453        pub session: JigPlaySession,
454
455        /// Playing's name
456        pub players_name: Option<String>,
457    }
458}