leetcode_tui_db/models/
question.rs

1use super::{topic::DbTopic, *};
2use crate::{
3    api::types::problemset_question_list::Question,
4    errors::{DBResult, DbErr},
5    get_db_client, save, save_multiple,
6};
7use std::fmt::Display;
8
9#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
10#[native_model(id = 1, version = 1)]
11#[native_db]
12pub struct DbQuestion {
13    #[primary_key]
14    pub id: u32,
15    pub title: String,
16    pub title_slug: String,
17    pub difficulty: String,
18    pub paid_only: bool,
19    pub status: Option<String>,
20    pub topics: Vec<DbTopic>,
21}
22
23impl Ord for DbQuestion {
24    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
25        self.id.cmp(&other.id)
26    }
27}
28
29impl PartialOrd for DbQuestion {
30    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
31        self.id.partial_cmp(&other.id)
32    }
33}
34
35impl Eq for DbQuestion {}
36
37impl Display for DbQuestion {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        let mut w = String::new();
40        w.push_str(if self.paid_only { "🔐" } else { "  " });
41        w.push_str(if self.status.is_none() {
42            "  "
43        } else if self.status == Some("ac".into()) {
44            "👑"
45        } else {
46            "🏃"
47        });
48        w.push_str(self.title.as_str());
49        write!(f, "{: >4}{w}", self.id)
50    }
51}
52
53impl DbQuestion {
54    pub fn is_hard(&self) -> bool {
55        self.difficulty == "Hard"
56    }
57
58    pub fn is_medium(&self) -> bool {
59        self.difficulty == "Medium"
60    }
61
62    pub fn is_easy(&self) -> bool {
63        self.difficulty == "Easy"
64    }
65}
66
67impl TryFrom<Question> for DbQuestion {
68    type Error = DbErr;
69
70    fn try_from(
71        value: crate::api::types::problemset_question_list::Question,
72    ) -> Result<Self, Self::Error> {
73        let mut db_quest = DbQuestion::new(
74            value.frontend_question_id.parse()?,
75            value.title.as_str(),
76            value.title_slug.as_str(),
77            value.difficulty,
78            value.paid_only,
79            value.status,
80        );
81        if let Some(tts) = value.topic_tags {
82            if !tts.is_empty() {
83                for topic in tts {
84                    db_quest.add_topic(topic.slug.as_str());
85                }
86            } else {
87                db_quest.add_topic("unknown");
88            }
89        }
90        Ok(db_quest)
91    }
92}
93
94impl DbQuestion {
95    pub fn new(
96        id: u32,
97        title: &str,
98        title_slug: &str,
99        difficulty: String,
100        paid_only: bool,
101        status: Option<String>,
102    ) -> Self {
103        Self {
104            id,
105            title: title.into(),
106            title_slug: title_slug.into(),
107            topics: Default::default(),
108            difficulty,
109            paid_only,
110            status,
111        }
112    }
113
114    fn add_topic(&mut self, slug: &str) {
115        self.topics.push(DbTopic::new(slug))
116    }
117
118    pub fn mark_accepted<'a>(&mut self) -> DBResult<Option<Vec<Self>>> {
119        if self.status.is_none() || self.status == Some("notac".into()) {
120            self.status = Some("ac".into());
121            return Ok(Some(self.update_in_db()?));
122        }
123        Ok(None)
124    }
125
126    pub fn mark_attempted<'a>(&mut self) -> DBResult<Option<Vec<Self>>> {
127        if self.status.is_none() {
128            self.status = Some("notac".into());
129            return Ok(Some(self.update_in_db()?));
130        }
131        Ok(None)
132    }
133
134    fn update_in_db<'a>(&self) -> DBResult<Vec<Self>> {
135        let rw = get_db_client().rw_transaction()?;
136        let old = Self::get_question_by_id(self.id)?;
137        if let Some(old_q) = old {
138            rw.update(old_q, self.clone())?;
139            rw.commit()?;
140        }
141        Ok(vec![self.clone()])
142    }
143
144    pub fn get_total_questions<'a>() -> DBResult<usize> {
145        let r = get_db_client().r_transaction()?;
146        let x = r.scan().primary::<Self>()?;
147        Ok(x.all().count())
148    }
149
150    pub fn get_question_by_id<'a>(id: u32) -> DBResult<Option<Self>> {
151        let r = get_db_client().r_transaction()?;
152        let x = r.get().primary::<DbQuestion>(id)?;
153        // x.topics = x.fetch_all_topics(db)?;
154        Ok(x)
155    }
156
157    fn save_all_topics<'a>(&mut self) -> DBResult<()> {
158        save_multiple(&self.topics)
159    }
160
161    pub(crate) fn get_topics(&self) -> &Vec<DbTopic> {
162        &self.topics
163    }
164
165    fn get_topic_question_mapping(&self) -> Vec<TopicQuestionMap> {
166        self.get_topics()
167            .iter()
168            .map(|q| TopicQuestionMap::new(&q.slug, self.id))
169            .collect::<Vec<_>>()
170    }
171
172    fn get_question_topic_mapping(&self) -> Vec<QuestionTopicMap> {
173        self.get_topics()
174            .iter()
175            .map(|q| QuestionTopicMap::new(self.id, &q.slug))
176            .collect::<Vec<_>>()
177    }
178
179    pub fn save_multiple_to_db(questions: Vec<Self>) {
180        let topic_question_map = questions
181            .iter()
182            .map(|q| q.get_topic_question_mapping())
183            .flatten()
184            .collect::<Vec<_>>();
185
186        let question_topic_map = questions
187            .iter()
188            .map(|q| q.get_question_topic_mapping())
189            .flatten()
190            .collect::<Vec<_>>();
191
192        let topics = questions
193            .iter()
194            .map(|q| q.get_topics().iter().map(|t| t.clone()))
195            .flatten()
196            .collect::<Vec<_>>();
197
198        save_multiple(&topic_question_map).unwrap();
199        save_multiple(&question_topic_map).unwrap();
200        save_multiple(&topics).unwrap();
201        save_multiple(&questions).unwrap();
202    }
203
204    pub fn save_to_db<'a>(&mut self) -> DBResult<bool> {
205        // save Topic -> Question Mapping
206        TopicQuestionMap::save_mapping(self)?;
207
208        // save Question -> Topic Mapping
209        QuestionTopicMap::save_mapping(self)?;
210
211        // save DbTopics for the question
212        self.save_all_topics()?;
213
214        // save question
215        save(self)?;
216        return Ok(true);
217    }
218}