supernova/
models.rs

1use crate::coersion::ObjRef;
2use crate::errors::Error;
3use crate::keys::*;
4pub use crate::network::models::{ClassInfo, ClassInfoEntry, ClassInfoSources};
5use crate::Supernova;
6
7use std::cell::Cell;
8use std::cmp::Ordering;
9use std::fmt;
10use std::hash::{Hash, Hasher};
11use std::sync::Arc;
12
13use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
14use once_cell::sync::OnceCell;
15
16#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
17pub enum Weekday {
18    Monday,
19    Thursday,
20    Wednesday,
21    Tuesday,
22    Friday,
23    Saturday,
24    Sunday,
25}
26
27#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
28pub enum Period {
29    Year,
30    FirstSemester,
31    SecondSemester,
32    FirstTrimester,
33    SecondTrimester,
34    ThirdTrimester,
35    FourthTrimester,
36}
37
38#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
39pub enum Degree {
40    BSc,
41    MSc,
42    PhD,
43    IntegratedMSc,
44    PostGraduation,
45    AdvancedStudies,
46    PreGraduation,
47}
48
49#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
50pub enum ShiftType {
51    Theoretical,
52    Practical,
53    PracticalTheoretical,
54    Seminar,
55    TutorialOrientation,
56    FieldWork,
57    OnlineTheoretical,
58    OnlinePractical,
59    OnlinePracticalTheoretical,
60}
61
62#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
63pub enum FileCategory {
64    Image,
65    Slides,
66    Problems,
67    Protocol,
68    Seminar,
69    Exam,
70    Test,
71    Support,
72    Others,
73}
74
75#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
76pub enum FileLicense {
77    RightsReserved,
78    PublicDomain,
79    GPL,
80    MIT,
81    BSD,
82    CCBy,
83    CCBySa,
84    CCByNc,
85    CCBySaNc,
86    GenericPermissive,
87}
88
89#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
90pub enum FileVisibility {
91    Public,
92    Students,
93    Enrolled,
94    Nobody,
95}
96
97#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
98pub enum Season {
99    Normal,
100    Exam,
101    Special,
102}
103
104#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
105pub enum ClassEventType {
106    Test,
107    Exam,
108    Discussion,
109    FieldTrip,
110    ProjectAnnouncement,
111    ProjectDelivery,
112    AdditionalClass,
113    Presentation,
114    Seminar,
115    Talk,
116}
117
118#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd)]
119pub enum RoomType {
120    Generic,
121    Classroom,
122    Auditorium,
123    Laboratory,
124    Computer,
125    Meeting,
126    Masters,
127    Cabinet,
128}
129
130#[derive(Debug, Clone)]
131pub struct Department {
132    pub id: u32,
133    pub name: String,
134    pub description: Option<String>,
135    pub(crate) courses: Vec<ObjRef<Course, CourseKey>>,
136    pub(crate) building: Option<ObjRef<Building, BuildingKey>>,
137}
138
139#[derive(Clone)]
140pub struct Building {
141    pub id: u32,
142    pub name: String,
143    pub abbreviation: String,
144    pub thumb: Option<String>,
145    pub cover: Option<String>,
146
147    pub(crate) places: Vec<ObjRef<Place, PlaceKey>>,
148    pub(crate) client: Arc<Supernova>,
149    pub(crate) thumb_cache: OnceCell<Vec<u8>>,
150    pub(crate) cover_cache: OnceCell<Vec<u8>>,
151}
152
153#[derive(Clone)]
154pub struct Place {
155    pub id: PlaceKey,
156    pub variant: PlaceVariant,
157    pub name: String,
158    pub floor: i8,
159    pub features: Vec<PlaceFeature>,
160    pub cover: Option<String>,
161
162    pub(crate) building: Option<ObjRef<Building, BuildingKey>>,
163    pub(crate) client: Arc<Supernova>,
164    pub(crate) cover_cache: OnceCell<Vec<u8>>,
165}
166
167#[derive(Debug, Clone)]
168pub struct PlaceFeature {
169    pub name: String,
170    pub description: String,
171    pub icon: String,
172}
173
174#[derive(Debug, Clone)]
175pub enum PlaceVariant {
176    Generic,
177    Room(Room),
178}
179
180#[derive(Debug, Clone)]
181pub struct Room {
182    pub title: String,
183    pub description: Option<String>,
184    pub door_number: Option<u16>,
185    pub room_type: RoomType,
186    pub extinguished: bool,
187    pub capacity: Option<u16>,
188    pub equipment: Option<String>,
189    pub url: String,
190    pub(crate) department: Option<ObjRef<Department, DepartmentKey>>,
191}
192
193#[derive(Debug, Clone)]
194pub struct Course {
195    pub id: u32,
196    pub name: String,
197    pub abbreviation: String,
198    pub degree: Degree,
199    pub description: Option<String>,
200    pub active: bool,
201    pub url: String,
202    pub external_url: Option<String>,
203    pub(crate) coordinator: Option<ObjRef<Teacher, TeacherKey>>,
204    pub(crate) department: Option<ObjRef<Department, DepartmentKey>>,
205}
206
207#[derive(Debug, Clone)]
208pub struct Class {
209    pub id: u32,
210    pub name: String,
211    pub abbreviation: String,
212    pub credits: u32,
213    pub(crate) department: Option<ObjRef<Department, DepartmentKey>>,
214    pub(crate) instances: Vec<ObjRef<ClassInstance, ClassInstanceKey>>,
215}
216
217#[derive(Debug, Clone)]
218pub struct ClassInstance {
219    pub id: u32,
220    pub year: u32,
221    pub period: Period,
222    pub(crate) enrollments: Vec<ObjRef<Enrollment, EnrollmentKey>>,
223    pub information: Option<ClassInfo>,
224    pub avg_grade: Option<f32>,
225    pub(crate) shifts: Vec<ObjRef<ClassShift, ShiftKey>>,
226    pub(crate) department: Option<ObjRef<Department, DepartmentKey>>,
227}
228
229// pub struct ClassInfo {
230//     pub program: ClassInfoEntry,
231//     pub assistance: ClassInfoEntry,
232//     pub extra_info: ClassInfoEntry,
233//     pub objectives: ClassInfoEntry,
234//     pub competences: ClassInfoEntry,
235//     pub description: ClassInfoEntry,
236//     pub bibliography: ClassInfoEntry,
237//     pub requirements: ClassInfoEntry,
238//     pub teaching_methods: ClassInfoEntry,
239//     pub evaluation_methods: ClassInfoEntry,
240// }
241//
242//
243// pub struct ClassInfoEntry {
244//     pub pt: Option<String>,
245//     pub en: Option<String>,
246//     pub time: Option<String>,
247//     pub editor: Option<String>,
248// }
249
250#[derive(Debug, Clone)]
251pub struct ClassShift {
252    pub id: u32,
253    pub number: u16,
254    pub shift_type: ShiftType,
255    pub(crate) teachers: Vec<ObjRef<Teacher, TeacherKey>>,
256    pub instances: Vec<ClassShiftInstance>,
257}
258
259#[derive(Debug, Clone)]
260pub struct ClassShiftInstance {
261    pub weekday: Weekday,
262    pub start: u16,
263    pub duration: u16,
264    pub(crate) room: Option<ObjRef<Place, PlaceKey>>,
265}
266
267#[derive(Debug, Clone)]
268pub struct ClassInstanceFiles {
269    pub official: Vec<ClassInstanceFile>,
270    pub community: Vec<ClassInstanceFile>,
271    // pub  denied: Vec<ClassInstanceFile>,
272}
273
274#[derive(Debug, Clone)]
275pub struct ClassInstanceFile {
276    pub id: u32,
277    pub file: File,
278    pub name: String,
279    pub category: FileCategory,
280    pub upload_datetime: String, // FIXME use chrono
281    pub uploader: Option<u32>,
282    pub uploader_teacher: Option<u32>,
283    pub url: String,
284}
285
286#[derive(Debug, Clone)]
287pub struct File {
288    pub hash: String,
289    pub size: u32,
290    pub mime: String,
291    pub license: String,
292    pub url: String,
293}
294
295#[derive(Debug, Clone)]
296pub struct Student {
297    pub id: StudentKey,
298    pub name: String,
299    pub abbreviation: Option<String>,
300    pub number: u32,
301    pub(crate) enrollments: Vec<ObjRef<Enrollment, EnrollmentKey>>,
302    pub(crate) shifts: Vec<ObjRef<ClassShift, ShiftKey>>,
303    pub first_year: Option<u32>,
304    pub last_year: Option<u32>,
305    pub(crate) course: Option<ObjRef<Course, CourseKey>>,
306    pub avg_grade: Option<u32>,
307    pub url: String,
308}
309
310#[derive(Clone)]
311pub struct Teacher {
312    pub id: u32,
313    pub name: String,
314    pub abbreviation: Option<String>,
315    pub first_year: Option<u32>,
316    pub last_year: Option<u32>,
317    pub phone: Option<String>,
318    pub email: Option<String>,
319    pub thumb: Option<String>,
320    pub rank: Option<String>,
321    pub(crate) departments: Vec<ObjRef<Department, DepartmentKey>>,
322    pub(crate) shifts: Vec<ObjRef<ClassShift, ShiftKey>>,
323    pub url: String,
324
325    pub(crate) client: Arc<Supernova>,
326    pub(crate) thumb_cache: OnceCell<Vec<u8>>,
327}
328
329#[derive(Debug, Clone)]
330pub struct Enrollment {
331    pub id: EnrollmentKey,
332    pub(crate) class_instance: ObjRef<ClassInstance, ClassInstanceKey>,
333    pub(crate) student: ObjRef<Student, StudentKey>,
334    pub attendance: Option<bool>,
335    pub attendance_date: Option<String>,
336    pub normal_grade: Option<u8>,
337    pub normal_grade_date: Option<String>,
338    pub recourse_grade: Option<u8>,
339    pub recourse_grade_date: Option<String>,
340    pub special_grade: Option<u8>,
341    pub special_grade_date: Option<String>,
342    pub improvement_grade: Option<u8>,
343    pub improvement_grade_date: Option<String>,
344    pub approved: Option<bool>,
345    pub grade: Option<u8>,
346}
347
348impl Department {
349    pub async fn get_building(&self) -> Result<Option<Building>, Error> {
350        Ok(if let Some(building) = &self.building {
351            Some(building.coerce().await?)
352        } else {
353            None
354        })
355    }
356
357    pub async fn get_courses(&self) -> Result<Vec<Course>, Error> {
358        let mut result = vec![];
359        for course_ref in &self.courses {
360            result.push(course_ref.coerce().await?);
361        }
362        Ok(result)
363    }
364}
365
366impl PartialEq for Department {
367    fn eq(&self, other: &Self) -> bool {
368        self.id.eq(&other.id)
369    }
370}
371
372impl PartialOrd for Department {
373    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
374        self.name.partial_cmp(&other.name)
375    }
376}
377
378impl Hash for Department {
379    fn hash<H: Hasher>(&self, state: &mut H) {
380        self.id.hash(state);
381    }
382}
383
384impl Building {
385    pub async fn get_rooms(&self) -> Result<Vec<Place>, Error> {
386        let mut result = vec![];
387        for places_ref in &self.places {
388            result.push(places_ref.coerce().await?);
389        }
390        Ok(result)
391    }
392
393    pub async fn thumb_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
394        if let Some(thumb_url) = &self.thumb {
395            Some(if let Some(bytes) = self.thumb_cache.get() {
396                Ok(bytes.clone())
397            } else {
398                let response = self.client.base.fetch_bytes(thumb_url).await;
399                if let Ok(bytes) = &response {
400                    let _ = self.thumb_cache.set(bytes.clone());
401                }
402                response
403            })
404        } else {
405            None
406        }
407    }
408
409    pub async fn cover_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
410        if let Some(cover_url) = &self.cover {
411            Some(if let Some(bytes) = self.cover_cache.get() {
412                Ok(bytes.clone())
413            } else {
414                let response = self.client.base.fetch_bytes(cover_url).await;
415
416                if let Ok(bytes) = &response {
417                    let _ = self.cover_cache.set(bytes.clone());
418                }
419
420                response
421            })
422        } else {
423            None
424        }
425    }
426}
427
428impl PartialEq for Building {
429    fn eq(&self, other: &Self) -> bool {
430        self.id.eq(&other.id)
431    }
432}
433
434impl PartialOrd for Building {
435    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
436        self.name.partial_cmp(&other.name)
437    }
438}
439
440impl Hash for Building {
441    fn hash<H: Hasher>(&self, state: &mut H) {
442        self.id.hash(state);
443    }
444}
445
446impl fmt::Debug for Building {
447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448        f.debug_struct("Building")
449            .field("id", &self.id)
450            .field("name", &self.name)
451            .field("abbreviation", &self.abbreviation)
452            .field("thumb", &self.thumb)
453            .field("cover", &self.cover)
454            .finish()
455    }
456}
457
458impl Place {
459    pub async fn get_building(&self) -> Result<Option<Building>, Error> {
460        Ok(if let Some(building) = &self.building {
461            Some(building.coerce().await?)
462        } else {
463            None
464        })
465    }
466
467    pub async fn cover_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
468        if let Some(cover_url) = &self.cover {
469            Some(if let Some(bytes) = self.cover_cache.get() {
470                Ok(bytes.clone())
471            } else {
472                let response = self.client.base.fetch_bytes(cover_url).await;
473
474                if let Ok(bytes) = &response {
475                    let _ = self.cover_cache.set(bytes.clone());
476                }
477
478                response
479            })
480        } else {
481            None
482        }
483    }
484}
485
486impl PartialEq for Place {
487    fn eq(&self, other: &Self) -> bool {
488        self.id.eq(&other.id)
489    }
490}
491
492impl PartialOrd for Place {
493    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
494        let floor_ord = self.floor.cmp(&other.floor);
495        match floor_ord {
496            Ordering::Equal => self.name.partial_cmp(&other.name),
497            _ => Some(floor_ord),
498        }
499    }
500}
501
502impl Hash for Place {
503    fn hash<H: Hasher>(&self, state: &mut H) {
504        self.id.hash(state);
505    }
506}
507impl fmt::Debug for Place {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        f.debug_struct("Place")
510            .field("id", &self.id)
511            .field("name", &self.name)
512            .field("floor", &self.floor)
513            .field("features", &self.features)
514            .field("cover", &self.cover)
515            .field("variant", &self.variant)
516            .finish()
517    }
518}
519
520impl Room {
521    pub async fn get_department(&self) -> Result<Option<Department>, Error> {
522        Ok(if let Some(department) = &self.department {
523            Some(department.coerce().await?)
524        } else {
525            None
526        })
527    }
528}
529
530impl Course {
531    pub async fn get_department(&self) -> Result<Option<Department>, Error> {
532        Ok(if let Some(department) = &self.department {
533            Some(department.coerce().await?)
534        } else {
535            None
536        })
537    }
538
539    pub async fn get_coordinator(&self) -> Result<Option<Teacher>, Error> {
540        Ok(if let Some(coordinator) = &self.coordinator {
541            Some(coordinator.coerce().await?)
542        } else {
543            None
544        })
545    }
546}
547
548impl PartialEq for Course {
549    fn eq(&self, other: &Self) -> bool {
550        self.id.eq(&other.id)
551    }
552}
553
554impl PartialOrd for Course {
555    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
556        self.name.partial_cmp(&other.name)
557    }
558}
559
560impl Hash for Course {
561    fn hash<H: Hasher>(&self, state: &mut H) {
562        self.id.hash(state);
563    }
564}
565
566impl Class {
567    pub async fn get_department(&self) -> Result<Option<Department>, Error> {
568        Ok(if let Some(department) = &self.department {
569            Some(department.coerce().await?)
570        } else {
571            None
572        })
573    }
574
575    pub async fn get_instances(&self) -> Result<Vec<ClassInstance>, Error> {
576        let mut result = vec![];
577        for instance_ref in &self.instances {
578            result.push(instance_ref.coerce().await?);
579        }
580        Ok(result)
581    }
582}
583
584impl PartialEq for Class {
585    fn eq(&self, other: &Self) -> bool {
586        self.id.eq(&other.id)
587    }
588}
589
590impl PartialOrd for Class {
591    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
592        self.name.partial_cmp(&other.name)
593    }
594}
595
596impl Hash for Class {
597    fn hash<H: Hasher>(&self, state: &mut H) {
598        self.id.hash(state);
599    }
600}
601
602impl ClassInstance {
603    pub async fn get_department(&self) -> Result<Option<Department>, Error> {
604        Ok(if let Some(department) = &self.department {
605            Some(department.coerce().await?)
606        } else {
607            None
608        })
609    }
610
611    pub async fn get_enrollments(&self) -> Result<Vec<Enrollment>, Error> {
612        let mut result = vec![];
613        for student_ref in &self.enrollments {
614            result.push(student_ref.coerce().await?);
615        }
616        Ok(result)
617    }
618
619    pub async fn get_shifts(&self) -> Result<Vec<ClassShift>, Error> {
620        let mut result = vec![];
621        for shift_ref in &self.shifts {
622            result.push(shift_ref.coerce().await?);
623        }
624        Ok(result)
625    }
626}
627
628impl PartialEq for ClassInstance {
629    fn eq(&self, other: &Self) -> bool {
630        self.id.eq(&other.id)
631    }
632}
633
634impl PartialOrd for ClassInstance {
635    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
636        self.year.partial_cmp(&other.year)
637    }
638}
639
640impl Hash for ClassInstance {
641    fn hash<H: Hasher>(&self, state: &mut H) {
642        self.id.hash(state);
643    }
644}
645
646impl Student {
647    pub async fn get_course(&self) -> Result<Option<Course>, Error> {
648        Ok(if let Some(course_ref) = &self.course {
649            Some(course_ref.coerce().await?)
650        } else {
651            None
652        })
653    }
654
655    pub async fn get_enrollments(&self) -> Result<Vec<Enrollment>, Error> {
656        let mut result = vec![];
657        for enrollment_ref in &self.enrollments {
658            result.push(enrollment_ref.coerce().await?);
659        }
660        Ok(result)
661    }
662
663    pub async fn get_shifts(&self) -> Result<Vec<ClassShift>, Error> {
664        let mut result = vec![];
665        for shift_ref in &self.shifts {
666            result.push(shift_ref.coerce().await?);
667        }
668        Ok(result)
669    }
670}
671
672impl PartialEq for Student {
673    fn eq(&self, other: &Self) -> bool {
674        self.id.eq(&other.id)
675    }
676}
677
678impl PartialOrd for Student {
679    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
680        self.number.partial_cmp(&other.number)
681    }
682}
683
684impl Hash for Student {
685    fn hash<H: Hasher>(&self, state: &mut H) {
686        self.id.hash(state);
687    }
688}
689
690impl Teacher {
691    pub async fn get_departments(&self) -> Result<Vec<Department>, Error> {
692        let mut result = vec![];
693        for department_ref in &self.departments {
694            result.push(department_ref.coerce().await?);
695        }
696        Ok(result)
697    }
698
699    pub async fn get_shifts(&self) -> Result<Vec<ClassShift>, Error> {
700        let mut result = vec![];
701        for shift_ref in &self.shifts {
702            result.push(shift_ref.coerce().await?);
703        }
704        Ok(result)
705    }
706
707    pub async fn thumb_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
708        if let Some(thumb_url) = &self.thumb {
709            Some(if let Some(bytes) = self.thumb_cache.get() {
710                Ok(bytes.clone())
711            } else {
712                let response = self.client.base.fetch_bytes(thumb_url).await;
713
714                if let Ok(bytes) = &response {
715                    let _ = self.thumb_cache.set(bytes.clone());
716                }
717
718                response
719            })
720        } else {
721            None
722        }
723    }
724}
725
726impl PartialEq for Teacher {
727    fn eq(&self, other: &Self) -> bool {
728        self.id.eq(&other.id)
729    }
730}
731
732impl PartialOrd for Teacher {
733    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
734        self.name.partial_cmp(&other.name)
735    }
736}
737
738impl Hash for Teacher {
739    fn hash<H: Hasher>(&self, state: &mut H) {
740        self.id.hash(state);
741    }
742}
743
744impl fmt::Debug for Teacher {
745    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746        f.debug_struct("Teacher")
747            .field("id", &self.id)
748            .field("name", &self.name)
749            .field("abbreviation", &self.abbreviation)
750            .field("first_year", &self.first_year)
751            .field("last_year", &self.last_year)
752            .field("phone", &self.phone)
753            .field("email", &self.email)
754            .field("rank", &self.rank)
755            .field("url", &self.url)
756            .field("thumb", &self.thumb)
757            .finish()
758    }
759}
760
761impl Enrollment {
762    pub async fn get_student(&self) -> Result<Student, Error> {
763        self.student.coerce().await
764    }
765    pub async fn get_class_instance(&self) -> Result<ClassInstance, Error> {
766        self.class_instance.coerce().await
767    }
768}
769
770impl ClassShift {
771    pub async fn get_teachers(&self) -> Result<Vec<Teacher>, Error> {
772        let mut result = vec![];
773        for teacher_ref in &self.teachers {
774            result.push(teacher_ref.coerce().await?);
775        }
776        Ok(result)
777    }
778}
779
780impl PartialEq for ClassShift {
781    fn eq(&self, other: &Self) -> bool {
782        self.id.eq(&other.id)
783    }
784}
785
786impl PartialOrd for ClassShift {
787    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
788        let type_ord = self.shift_type.cmp(&other.shift_type);
789        match type_ord {
790            Ordering::Equal => Some(type_ord),
791            _ => self.number.partial_cmp(&other.number),
792        }
793    }
794}
795
796impl Hash for ClassShift {
797    fn hash<H: Hasher>(&self, state: &mut H) {
798        self.id.hash(state);
799    }
800}
801
802impl ClassShiftInstance {
803    pub async fn get_place(&self) -> Result<Option<Place>, Error> {
804        Ok(if let Some(room_ref) = &self.room {
805            Some(room_ref.coerce().await?)
806        } else {
807            None
808        })
809    }
810}
811
812impl PartialEq for ClassShiftInstance {
813    fn eq(&self, other: &Self) -> bool {
814        self.weekday.eq(&other.weekday)
815            && self.start.eq(&other.start)
816            && self.duration.eq(&other.duration)
817    }
818}
819
820impl PartialOrd for ClassShiftInstance {
821    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
822        let weekday_ord = self.weekday.cmp(&other.weekday);
823        Some(match weekday_ord {
824            Ordering::Equal => weekday_ord,
825            _ => {
826                let start_ord = self.start.cmp(&other.start);
827                match start_ord {
828                    Ordering::Equal => start_ord,
829                    _ => self.duration.cmp(&other.duration),
830                }
831            }
832        })
833    }
834}
835
836impl Hash for ClassShiftInstance {
837    fn hash<H: Hasher>(&self, state: &mut H) {
838        self.weekday.hash(state);
839        self.start.hash(state);
840        self.duration.hash(state);
841    }
842}
843
844impl PartialEq for ClassInstanceFile {
845    fn eq(&self, other: &Self) -> bool {
846        self.id.eq(&other.id)
847    }
848}
849
850impl PartialOrd for ClassInstanceFile {
851    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
852        self.id.partial_cmp(&other.id)
853    }
854}
855
856impl Hash for ClassInstanceFile {
857    fn hash<H: Hasher>(&self, state: &mut H) {
858        self.id.hash(state);
859    }
860}
861
862// ------------ Users ---------------
863
864#[derive(Debug, PartialEq, Copy, Clone)]
865pub struct User {
866    pub id: UserKey,
867}
868
869// ------------ Groups --------------
870
871#[derive(Debug, PartialEq, Copy, Clone)]
872pub enum GroupType {
873    Institutional,
874    Nuclei,
875    AcademicAssociation,
876    Pedagogic,
877    Praxis,
878    Community,
879}
880
881#[derive(Debug, PartialEq, Copy, Clone)]
882pub enum GroupVisibility {
883    Secret,
884    Closed,
885    Request,
886    Open,
887}
888
889#[derive(Debug, PartialEq, Copy, Clone)]
890pub enum GroupEventType {
891    Generic,
892    Talk,
893    Workshop,
894    Party,
895    Contest,
896    Fair,
897    Meeting,
898}
899
900#[derive(Clone)]
901pub struct Group {
902    pub id: GroupKey,
903    pub name: String,
904    pub abbreviation: String,
905    pub url: String,
906    pub thumb: Option<String>,
907    pub group_type: GroupType,
908    pub outsiders_openness: GroupVisibility,
909    pub official: bool,
910    pub(crate) upgraded: Cell<bool>,
911
912    pub(crate) client: Arc<Supernova>,
913    pub(crate) activities: OnceCell<Vec<GroupActivity>>,
914    pub(crate) schedulings: OnceCell<Vec<GroupScheduling>>,
915    pub(crate) events: OnceCell<Vec<Event>>,
916    pub(crate) thumb_cache: OnceCell<Vec<u8>>,
917}
918
919#[derive(Debug, Clone, PartialEq)]
920pub enum GroupActivity {
921    Announcement(GroupAnnouncement),
922    EventAnnouncement(EventAnnouncement),
923    GalleryUpload(GalleryUpload),
924}
925
926#[derive(Debug, Clone)]
927pub struct GroupAnnouncement {
928    pub(crate) author: ObjRef<User, UserKey>,
929    pub title: String,
930    pub content: String,
931    pub datetime: DateTime<Utc>,
932}
933
934#[derive(Debug, Clone)]
935pub struct EventAnnouncement {
936    pub(crate) author: ObjRef<User, UserKey>,
937    pub(crate) event: ObjRef<Event, EventKey>,
938    pub datetime: DateTime<Utc>,
939}
940
941#[derive(Debug, Clone)]
942pub struct GalleryUpload {
943    pub(crate) author: ObjRef<User, UserKey>,
944    pub datetime: DateTime<Utc>,
945    pub item: GalleryItem,
946}
947
948#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd)]
949pub struct GalleryItem {}
950
951#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd)]
952pub enum GroupScheduling {
953    Once(GroupSchedulingOnce),
954    Periodic(GroupSchedulingPeriodic),
955}
956
957#[derive(Debug, PartialEq, Eq, Hash, Clone)]
958pub struct GroupSchedulingOnce {
959    pub title: Option<String>,
960    pub datetime: DateTime<Utc>,
961    pub duration: u16,
962    pub revoked: bool,
963}
964
965#[derive(Debug, PartialEq, Eq, Hash, Clone)]
966pub struct GroupSchedulingPeriodic {
967    pub title: Option<String>,
968    pub weekday: Weekday,
969    pub time: NaiveTime,
970    pub start_date: NaiveDate,
971    pub end_date: NaiveDate,
972    pub duration: u16,
973    pub revoked: bool,
974}
975
976#[derive(Debug, Clone)]
977pub struct Event {
978    pub id: EventKey,
979    pub title: String,
980    pub description: String,
981    pub start_date: NaiveDate,
982    pub duration: Option<u16>,
983    pub(crate) place: Option<ObjRef<Place, PlaceKey>>,
984    pub capacity: Option<u32>,
985    pub cost: Option<u32>,
986    pub event_type: GroupEventType,
987}
988
989impl Event {
990    pub async fn place(&self) -> Result<Option<Place>, Error> {
991        if let Some(place) = &self.place {
992            Ok(Some(place.coerce().await?))
993        } else {
994            Ok(None)
995        }
996    }
997}
998
999#[derive(Debug, Clone)]
1000pub struct EventsPage {
1001    pub(crate) previous_page: Option<Arc<EventsPage>>,
1002    pub(crate) next_page: ObjRef<Option<Arc<EventsPage>>, EventsPageKey>,
1003    pub(crate) items: Vec<Arc<Event>>,
1004}
1005
1006impl Group {
1007    pub async fn activities(&self) -> Result<&[GroupActivity], Error> {
1008        self.upgrade().await?;
1009        Ok(self.activities.get().unwrap())
1010    }
1011
1012    pub async fn schedulings(&self) -> Result<&[GroupScheduling], Error> {
1013        self.upgrade().await?;
1014        Ok(self.schedulings.get().unwrap())
1015    }
1016
1017    pub async fn events(&self) -> Result<&[Event], Error> {
1018        self.upgrade().await?;
1019        Ok(self.events.get().unwrap().as_slice())
1020    }
1021
1022    pub async fn thumb_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
1023        if let Some(thumb_url) = &self.thumb {
1024            Some(if let Some(bytes) = self.thumb_cache.get() {
1025                Ok(bytes.clone())
1026            } else {
1027                let response = self.client.base.fetch_bytes(thumb_url).await;
1028
1029                if let Ok(bytes) = &response {
1030                    let _ = self.thumb_cache.set(bytes.clone());
1031                }
1032
1033                response
1034            })
1035        } else {
1036            None
1037        }
1038    }
1039
1040    #[allow(unused)]
1041    pub async fn upgrade(&self) -> Result<(), Error> {
1042        if !self.upgraded.get() {
1043            let group = self.client.get_group(self.id).await?;
1044
1045            self.activities.set(group.activities.get().unwrap().clone());
1046            self.schedulings
1047                .set(group.schedulings.get().unwrap().clone());
1048            self.events.set(group.events.get().unwrap().clone());
1049        }
1050        Ok(())
1051    }
1052}
1053
1054impl PartialEq for Group {
1055    fn eq(&self, other: &Self) -> bool {
1056        self.id.eq(&other.id)
1057    }
1058}
1059
1060impl fmt::Debug for Group {
1061    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1062        f.debug_struct("Group")
1063            .field("id", &self.id)
1064            .field("name", &self.name)
1065            .field("abbreviation", &self.abbreviation)
1066            .field("url", &self.url)
1067            .field("thumb", &self.thumb)
1068            .field("type", &self.group_type)
1069            .field("official", &self.official)
1070            .field("upgraded", &self.upgraded)
1071            .finish()
1072    }
1073}
1074
1075impl PartialOrd for GroupActivity {
1076    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1077        let datetime = match self {
1078            GroupActivity::Announcement(announcement) => announcement.datetime,
1079            GroupActivity::EventAnnouncement(event_announcement) => {
1080                event_announcement.datetime
1081            }
1082            GroupActivity::GalleryUpload(gallery_upload) => {
1083                gallery_upload.datetime
1084            }
1085        };
1086        match other {
1087            GroupActivity::Announcement(announcement) => {
1088                datetime.partial_cmp(&announcement.datetime)
1089            }
1090            GroupActivity::EventAnnouncement(event_announcement) => {
1091                datetime.partial_cmp(&event_announcement.datetime)
1092            }
1093            GroupActivity::GalleryUpload(gallery_upload) => {
1094                datetime.partial_cmp(&gallery_upload.datetime)
1095            }
1096        }
1097    }
1098}
1099
1100impl GroupAnnouncement {
1101    pub async fn author(&self) -> Result<User, Error> {
1102        self.author.coerce().await
1103    }
1104}
1105
1106impl PartialEq for GroupAnnouncement {
1107    fn eq(&self, other: &Self) -> bool {
1108        self.title.eq(&other.title)
1109            && self.content.eq(&other.content)
1110            && self.datetime.eq(&other.datetime)
1111    }
1112}
1113
1114impl EventAnnouncement {
1115    pub async fn author(&self) -> Result<User, Error> {
1116        self.author.coerce().await
1117    }
1118    pub async fn event(&self) -> Result<Event, Error> {
1119        self.event.coerce().await
1120    }
1121}
1122
1123impl PartialEq for EventAnnouncement {
1124    fn eq(&self, other: &Self) -> bool {
1125        self.event.identifier.eq(&other.event.identifier)
1126            && self.datetime.eq(&other.datetime)
1127    }
1128}
1129
1130impl GalleryUpload {
1131    pub async fn author(&self) -> Result<User, Error> {
1132        self.author.coerce().await
1133    }
1134}
1135
1136impl PartialEq for GalleryUpload {
1137    fn eq(&self, other: &Self) -> bool {
1138        self.item.eq(&other.item) && self.datetime.eq(&other.datetime)
1139    }
1140}
1141
1142impl PartialOrd for GroupSchedulingOnce {
1143    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1144        self.datetime.partial_cmp(&other.datetime)
1145    }
1146}
1147
1148impl PartialOrd for GroupSchedulingPeriodic {
1149    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1150        let weekday_ord = self.weekday.cmp(&other.weekday);
1151        Some(match weekday_ord {
1152            Ordering::Equal => {
1153                let time_ord = self.time.cmp(&other.time);
1154                match time_ord {
1155                    Ordering::Equal => self.duration.cmp(&other.duration),
1156                    _ => time_ord,
1157                }
1158            }
1159            _ => weekday_ord,
1160        })
1161    }
1162}
1163
1164impl PartialEq for Event {
1165    fn eq(&self, other: &Self) -> bool {
1166        self.id.eq(&other.id)
1167    }
1168}
1169
1170impl PartialOrd for Event {
1171    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1172        self.start_date.partial_cmp(&other.start_date)
1173    }
1174}
1175
1176impl Hash for Event {
1177    fn hash<H: Hasher>(&self, state: &mut H) {
1178        self.id.hash(state);
1179    }
1180}
1181
1182impl EventsPage {
1183    #[must_use]
1184    pub fn items(&self) -> &[Arc<Event>] {
1185        self.items.as_slice()
1186    }
1187
1188    #[must_use]
1189    pub fn predecessor(&self) -> Option<Arc<EventsPage>> {
1190        self.previous_page.clone()
1191    }
1192
1193    pub async fn successor(&self) -> Result<Option<Arc<EventsPage>>, Error> {
1194        self.next_page.coerce().await
1195    }
1196}
1197
1198// ------------ News --------------
1199
1200#[derive(Debug, Clone)]
1201pub struct NewsPage {
1202    pub(crate) previous_page: Option<Arc<NewsPage>>,
1203    pub(crate) next_page: ObjRef<Option<Arc<NewsPage>>, NewsPageKey>,
1204    pub(crate) items: Vec<Arc<NewsItem>>,
1205}
1206
1207#[derive(Clone)]
1208pub struct NewsItem {
1209    pub id: NewsItemKey,
1210    pub title: String,
1211    pub summary: String,
1212    pub datetime: DateTime<Utc>,
1213    pub thumb: Option<String>,
1214    pub url: String,
1215
1216    pub(crate) client: Arc<Supernova>,
1217    pub(crate) thumb_cache: OnceCell<Vec<u8>>,
1218}
1219
1220impl NewsPage {
1221    #[must_use]
1222    pub fn items(&self) -> &[Arc<NewsItem>] {
1223        self.items.as_slice()
1224    }
1225
1226    #[must_use]
1227    pub fn predecessor(&self) -> Option<Arc<NewsPage>> {
1228        self.previous_page.clone()
1229    }
1230
1231    pub async fn successor(&self) -> Result<Option<Arc<NewsPage>>, Error> {
1232        self.next_page.coerce().await
1233    }
1234}
1235
1236impl NewsItem {
1237    pub async fn thumb_bytes(&self) -> Option<Result<Vec<u8>, Error>> {
1238        if let Some(thumb_url) = &self.thumb {
1239            Some(if let Some(bytes) = self.thumb_cache.get() {
1240                Ok(bytes.clone())
1241            } else {
1242                let response = self.client.base.fetch_bytes(thumb_url).await;
1243
1244                if let Ok(bytes) = &response {
1245                    let _ = self.thumb_cache.set(bytes.clone());
1246                }
1247
1248                response
1249            })
1250        } else {
1251            None
1252        }
1253    }
1254}
1255
1256impl PartialEq for NewsItem {
1257    fn eq(&self, other: &Self) -> bool {
1258        self.id.eq(&other.id)
1259    }
1260}
1261
1262impl fmt::Debug for NewsItem {
1263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1264        f.debug_struct("NewsItem")
1265            .field("id", &self.id)
1266            .field("title", &self.title)
1267            .field("summary", &self.summary)
1268            .field("datetime", &self.datetime)
1269            .field("thumb", &self.thumb)
1270            .field("url", &self.url)
1271            .finish()
1272    }
1273}