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#[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 }
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, 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#[derive(Debug, PartialEq, Copy, Clone)]
865pub struct User {
866 pub id: UserKey,
867}
868
869#[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#[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}