11import { initializeApp } from "firebase/app" ;
22import { getAuth , GoogleAuthProvider , onAuthStateChanged } from "firebase/auth" ;
3- import { get , getDatabase , ref , set , serverTimestamp } from "firebase/database" ;
4-
3+ import { get , getDatabase , ref , set , onValue , push } from "firebase/database" ;
4+ import { reaction , toJS } from "mobx" ;
55// Your web app's Firebase configuration
66const firebaseConfig = {
7- apiKey : "AIzaSyCBckVI9nhAP62u5jZJW3F4SLulUv7znis" ,
8- authDomain : "findmynextcourse.firebaseapp.com" ,
9- databaseURL : "https://findmynextcourse-default-rtdb.europe-west1.firebasedatabase.app" ,
10- projectId : " findmynextcourse",
11- storageBucket : "findmynextcourse.firebasestorage.app " ,
12- messagingSenderId : "893484115963 " ,
13- appId : "1: 893484115963:web:59ac087d280dec919ccd5e"
14- } ;
15-
7+ apiKey : "AIzaSyCBckVI9nhAP62u5jZJW3F4SLulUv7znis" ,
8+ authDomain : "findmynextcourse.firebaseapp.com" ,
9+ databaseURL :
10+ "https:// findmynextcourse-default-rtdb.europe-west1.firebasedatabase.app ",
11+ projectId : "findmynextcourse" ,
12+ storageBucket : "findmynextcourse.firebasestorage.app " ,
13+ messagingSenderId : "893484115963" ,
14+ appId : "1:893484115963:web:59ac087d280dec919ccd5e" ,
15+ } ;
1616
1717// Initialize Firebase
1818const app = initializeApp ( firebaseConfig ) ;
@@ -21,77 +21,183 @@ export const db = getDatabase(app);
2121export const googleProvider = new GoogleAuthProvider ( ) ;
2222googleProvider . addScope ( "profile" ) ;
2323googleProvider . addScope ( "email" ) ;
24+ let noUpload = false ;
25+
26+ export function connectToFirebase ( model ) {
27+ loadCoursesFromCacheOrFirebase ( model ) ;
28+ onAuthStateChanged ( auth , ( user ) => {
29+ if ( user ) {
30+ model . setUser ( user ) ; // Set the user ID once authenticated
31+ firebaseToModel ( model ) ; // Set up listeners for user-specific data
32+ syncModelToFirebase ( model ) ; // Start syncing changes to Firebase
33+ } else {
34+ model . setUser ( null ) ; // If no user, clear user-specific data
35+ }
36+ } ) ;
37+ }
2438
2539// fetches all relevant information to create the model
2640async function firebaseToModel ( model ) {
27- const courses = await fetchAllCourses ( ) ;
28- model . setCourses ( courses ) ;
41+ if ( ! model . user )
42+ return ;
43+ const userRef = ref ( db , `users/${ model . user . uid } ` ) ;
44+ onValue ( userRef , ( snapshot ) => {
45+ if ( ! snapshot . exists ( ) )
46+ return ;
47+ const data = snapshot . val ( ) ;
48+ noUpload = true ;
49+ if ( data . favourites )
50+ model . setFavourite ( data . favourites ) ;
51+ // if (data.currentSearch)
52+ // model.setCurrentSearch(data.currentSearch);
53+ noUpload = false ;
54+ } ) ;
2955}
3056
31- export function connectToFirebase ( model ) {
32- onAuthStateChanged ( auth , ( user ) => {
33- model . setUser ( user ) ;
34- } ) ;
35- firebaseToModel ( model ) ;
57+
58+ export function syncModelToFirebase ( model ) {
59+ reaction (
60+ ( ) => ( {
61+ userId : model ?. user . uid ,
62+ favourites : toJS ( model . favourites ) ,
63+ // currentSearch: toJS(model.currentSearch),
64+ // Add more per-user attributes here
65+ } ) ,
66+ // eslint-disable-next-line no-unused-vars
67+ ( { userId, favourites, currentSearch } ) => {
68+ if ( noUpload || ! userId )
69+ return ;
70+ const userRef = ref ( db , `users/${ userId } ` ) ;
71+ const dataToSync = {
72+ favourites,
73+ //currentSearch,
74+ } ;
75+
76+ set ( userRef , dataToSync )
77+ . then ( ( ) => console . log ( "User model synced to Firebase" ) )
78+ . catch ( console . error ) ;
79+ }
80+ ) ;
3681}
3782
38- export async function addCourse ( course ) {
39- if ( ! course ?. code )
40- return ;
41- const myRef = ref ( db , `courses/${ course . code } ` ) ;
42- await set ( myRef , course ) ;
83+ function saveCoursesInChunks ( courses , timestamp ) {
84+ const parts = 3 ; // Adjust this based on course size
85+ const chunkSize = Math . ceil ( courses . length / parts ) ;
86+
87+ for ( let i = 0 ; i < parts ; i ++ ) {
88+ const chunk = courses . slice ( i * chunkSize , ( i + 1 ) * chunkSize ) ;
89+ localStorage . setItem ( `coursesPart${ i } ` , JSON . stringify ( chunk ) ) ;
90+ }
91+ localStorage . setItem ( "coursesMetadata" , JSON . stringify ( { parts, timestamp } ) ) ;
4392}
4493
45- export async function fetchAllCourses ( ) {
46- const myRef = ref ( db , `courses` ) ;
47- const snapshot = await get ( myRef ) ;
94+ async function updateLastUpdatedTimestamp ( ) {
95+ const timestampRef = ref ( db , "metadata/lastUpdated" ) ;
96+ await set ( timestampRef , Date . now ( ) ) ;
97+ }
98+
99+ async function fetchLastUpdatedTimestamp ( ) {
100+ const timestampRef = ref ( db , "metadata/lastUpdated" ) ;
101+ const snapshot = await get ( timestampRef ) ;
102+ return snapshot . exists ( ) ? snapshot . val ( ) : 0 ;
103+ }
48104
49- if ( ! snapshot . exists ( ) ) return [ ] ;
105+ export async function addCourse ( course ) {
106+ if ( ! course ?. code ) return ;
107+ const myRef = ref ( db , `courses/${ course . code } ` ) ;
108+ await set ( myRef , course ) ;
109+ updateLastUpdatedTimestamp ( ) ;
110+ }
50111
51- const value = snapshot . val ( ) ; // Firebase returns an object where keys are course IDs
52- const courses = [ ] ;
112+ export async function fetchAllCourses ( ) {
113+ const myRef = ref ( db , `courses` ) ;
114+ const snapshot = await get ( myRef ) ;
115+ if ( ! snapshot . exists ( ) ) return [ ] ;
53116
54- for ( const id of Object . keys ( value ) ) {
55- courses . push ( { id, ...value [ id ] } ) ;
56- }
117+ const value = snapshot . val ( ) ; // Firebase returns an object where keys are course IDs
118+ const courses = [ ] ;
57119
58- return courses ;
120+ for ( const id of Object . keys ( value ) ) {
121+ courses . push ( { id, ...value [ id ] } ) ;
122+ }
123+ return courses ;
59124}
60125
61- // Before: [ {courseCode: "CS101", name: "Intro to CS"}, {...} ]
62- // After: { "CS101": { name: "Intro to CS" }, "CS102": {...} }
126+ async function loadCoursesFromCacheOrFirebase ( model ) {
127+ // Load metadata from localStorage
128+ const cachedMetadata = JSON . parse ( localStorage . getItem ( "coursesMetadata" ) ) ;
129+ const firebaseTimestamp = await fetchLastUpdatedTimestamp ( ) ;
130+ // check if up to date
131+ if ( cachedMetadata && cachedMetadata . timestamp === firebaseTimestamp ) {
132+ console . log ( "Using cached courses..." ) ;
133+ let mergedCourses = [ ] ;
134+ for ( let i = 0 ; i < cachedMetadata . parts ; i ++ ) {
135+ const part = JSON . parse ( localStorage . getItem ( `coursesPart${ i } ` ) ) ;
136+ if ( part ) mergedCourses = mergedCourses . concat ( part ) ;
137+ }
138+ model . setCourses ( mergedCourses ) ;
139+ return ;
140+ }
141+
142+ // Fetch if outdated or missing
143+ console . log ( "Fetching courses from Firebase..." ) ;
144+ const courses = await fetchAllCourses ( ) ;
145+ model . setCourses ( courses ) ;
146+ saveCoursesInChunks ( courses , firebaseTimestamp ) ;
147+ }
63148
149+ export async function saveJSONCoursesToFirebase ( model , data ) {
150+ if ( ! data || ! model ) {
151+ console . log ( "no model or data" ) ;
152+ return ;
153+ }
154+ const entries = Object . entries ( data ) ;
155+ entries . forEach ( ( entry ) => {
156+ const course = {
157+ code : entry [ 1 ] . code ,
158+ name : entry [ 1 ] ?. name ?? "" ,
159+ location : entry [ 1 ] ?. location ?? "" ,
160+ department : entry [ 1 ] ?. department ?? "" ,
161+ language : entry [ 1 ] ?. language ?? "" ,
162+ description : entry [ 1 ] ?. description ?? "" ,
163+ academicLevel : entry [ 1 ] ?. academic_level ?? "" ,
164+ period : entry [ 1 ] ?. period ?? "" ,
165+ credits : entry [ 1 ] ?. credits ?? 0 ,
166+ //lectureCount:entry[1].courseLectureCount,
167+ //prerequisites:entry.coursePrerequisites
168+ } ;
169+ model . addCourse ( course ) ;
170+ } ) ;
171+ }
64172
65- export async function fetchCoursesSnapshot ( ) {
66- const myRef = ref ( db , `courses` ) ;
67- const snapshot = await get ( myRef ) ;
68- if ( ! snapshot . exists ( ) ) return { } ; // Return empty object instead of array
69173
70- return snapshot . val ( ) ; // Return the object directly
174+ export async function addReviewForCourse ( courseCode , review ) {
175+ try {
176+ const reviewsRef = ref ( db , `reviews/${ courseCode } ` ) ;
177+ const newReviewRef = push ( reviewsRef ) ;
178+ await set ( newReviewRef , review ) ;
179+ } catch ( error ) {
180+ console . error ( "Error when adding a course to firebase:" , error ) ;
181+ }
71182}
72183
73- export async function saveJSONCoursesToFirebase ( model , data ) {
74- if ( ! data || ! model ) {
75- console . log ( "no model or data" )
76- return ;
77- }
78- const entries = Object . entries ( data ) ;
79- entries . forEach ( entry => {
80- const course = {
81- code : entry [ 1 ] . code ,
82- name : entry [ 1 ] ?. name ?? "" ,
83- location : entry [ 1 ] ?. location ?? "" ,
84- department : entry [ 1 ] ?. department ?? "" ,
85- language : entry [ 1 ] ?. language ?? "" ,
86- description : entry [ 1 ] ?. description ?? "" ,
87- academicLevel : entry [ 1 ] ?. academic_level ?? "" ,
88- period : entry [ 1 ] ?. period ?? "" ,
89- credits : entry [ 1 ] ?. credits ?? 0 ,
90- //lectureCount:entry[1].courseLectureCount,
91- //prerequisites:entry.coursePrerequisites
92- }
93- model . addCourse ( course ) ;
94-
95- } ) ;
184+
185+ export async function getReviewsForCourse ( courseCode ) {
186+ const reviewsRef = ref ( db , `reviews/${ courseCode } ` ) ;
187+ const snapshot = await get ( reviewsRef ) ;
188+ if ( ! snapshot . exists ( ) ) return [ ] ;
189+
190+ const reviews = [ ] ;
191+ snapshot . forEach ( childSnapshot => {
192+ reviews . push ( {
193+ id : childSnapshot . key ,
194+ ...childSnapshot . val ( )
195+ } ) ;
196+ } ) ;
197+ return reviews ;
96198}
97199
200+
201+
202+
203+
0 commit comments