diff --git a/.github/workflows/example.yml b/.github/workflows/example.yml index 9fceff9..76c8744 100644 --- a/.github/workflows/example.yml +++ b/.github/workflows/example.yml @@ -7,6 +7,7 @@ on: env: DATABASE_URL: ${{secrets.DATABASE_URL}} + COOKIESTORE: ${{secrets.COOKIESTORE}} # FIREBASE_ADMIN_PASSPHRASE: ${{secrets.FIREBASE_ADMIN_PASSPHRASE}} jobs: @@ -30,15 +31,9 @@ jobs: #go mod init calc # for go mod. If we use go build, it will do the same for the 3 lines. go mod tidy go get github.com/franela/goblin + go get golang.org/x/tools/cmd/godoc@latest ls - - # - name: Setup Firebase JSON file - # run: echo "{${{ secrets.FIREBASE_JSON }}}" > edusync-7bd5e-firebase-adminsdk-x49uh-af084a6314.json - - # - name: Build and run JSON validator - # run: | - # go run json_validator.go edusync-7bd5e-firebase-adminsdk-x49uh-af084a6314.json - + - name: Decrypt Firebase JSON run: | chmod +x decrypt_secret.sh diff --git a/EduSync.exe b/EduSync.exe index c713d85..b876bef 100644 Binary files a/EduSync.exe and b/EduSync.exe differ diff --git a/adminHandler.go b/adminHandler.go index 2be35a7..bb8d5ae 100644 --- a/adminHandler.go +++ b/adminHandler.go @@ -2,12 +2,25 @@ package main import ( "encoding/json" + "fmt" "html/template" "net/http" + "time" + "github.com/google/uuid" "github.com/gorilla/mux" ) +// AdminHandler handles all admin-related routes. +// +// This handler is responsible for setting up routes for admin-related tasks, +// such as searching for students, parents, instructors, and announcements, +// as well as editing and updating their information. +// +// Example usage: +// router := mux.NewRouter() +// AdminHandler(router) + func AdminHandler(router *mux.Router) { router.HandleFunc("/admin", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("templates/admin/index.html") @@ -27,6 +40,16 @@ func AdminHandler(router *mux.Router) { t.Execute(res, nil) }).Methods("GET") + router.HandleFunc("/admin/profile", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/profile.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + //searve the search student page router.HandleFunc("/admin/search_student", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("templates/admin/search_student.html") if err != nil { @@ -36,6 +59,17 @@ func AdminHandler(router *mux.Router) { t.Execute(res, nil) }).Methods("GET") + // Serve the create student page + router.HandleFunc("/admin/create_student", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/create_student.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + // Search for students by name and class router.HandleFunc("/admin/api/search_student", func(res http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get("name") class := req.URL.Query().Get("class") @@ -48,17 +82,16 @@ func AdminHandler(router *mux.Router) { json.NewEncoder(res).Encode(students) }).Methods("GET") + // Example usage: + // GET /admin/api/search_student?name=John&class=10th + // Response: JSON list of students matching the search criteria + + // Edit student information router.HandleFunc("/admin/student/{googleID}/edit", func(res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) - if err != nil { - http.Error(res, err.Error(), http.StatusInternalServerError) - return - } - - student, err := readStudent(currentUser, googleID) + student, err := readStudent(googleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -72,19 +105,18 @@ func AdminHandler(router *mux.Router) { t.Execute(res, student) }).Methods("GET") + // Example usage: + // GET /admin/student/1234567890/edit + // Response: HTML form to edit student information + + // Update student information router.HandleFunc("/admin/student/{googleID}", func(res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) - if err != nil { - http.Error(res, err.Error(), http.StatusInternalServerError) - return - } - switch req.Method { case http.MethodGet: - student, err := readStudent(currentUser, googleID) + student, err := readStudent(googleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -97,7 +129,7 @@ func AdminHandler(router *mux.Router) { http.Error(res, err.Error(), http.StatusBadRequest) return } - if err := updateStudent(currentUser, googleID, updates); err != nil { + if err := updateStudent(googleID, updates, req); err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return } @@ -105,6 +137,36 @@ func AdminHandler(router *mux.Router) { } }).Methods("GET", "PUT") + // Example usage: + // PUT /admin/student/1234567890 + // Request Body: JSON object with updated student information + // Response: HTTP Status No Content (204) + + // Create a new student + router.HandleFunc("/admin/student/", func(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPost: + var student Student // Replace 'Student' with your actual student struct type + if err := json.NewDecoder(req.Body).Decode(&student); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Invalid request payload: %v"}`, err), http.StatusBadRequest) + return + } + student.GoogleID = uuid.New().String() + student.Role = "Student" + student.CreatedAt = time.Now() + student.UpdatedAt = time.Now() + if err := createStudent(student, req); err != nil { // Implement createStudent to handle the logic + http.Error(res, fmt.Sprintf(`{"error": "Failed to create student: %v"}`, err), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusCreated) + json.NewEncoder(res).Encode(student) + default: + http.Error(res, `{"error": "Invalid request method"}`, http.StatusMethodNotAllowed) + } + }).Methods("POST") + + // Serve the search parent page router.HandleFunc("/admin/search_parent", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("templates/admin/search_parent.html") if err != nil { @@ -114,6 +176,16 @@ func AdminHandler(router *mux.Router) { t.Execute(res, nil) }).Methods("GET") + // Serve the create parent page + router.HandleFunc("/admin/create_parent", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/create_parent.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + router.HandleFunc("/admin/api/search_parent", func(res http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get("name") parents, err := searchParents(name) @@ -129,13 +201,7 @@ func AdminHandler(router *mux.Router) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) - if err != nil { - http.Error(res, "Unauthorized", http.StatusUnauthorized) - return - } - - parent, err := readParent(currentUser, googleID) + parent, err := readParent(googleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -153,15 +219,9 @@ func AdminHandler(router *mux.Router) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) - if err != nil { - http.Error(res, "Unauthorized", http.StatusUnauthorized) - return - } - switch req.Method { case http.MethodGet: - parent, err := readParent(currentUser, googleID) + parent, err := readParent(googleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -174,7 +234,7 @@ func AdminHandler(router *mux.Router) { http.Error(res, err.Error(), http.StatusBadRequest) return } - if err := updateParent(currentUser, googleID, updates); err != nil { + if err := updateParent(googleID, updates, req); err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return } @@ -182,6 +242,31 @@ func AdminHandler(router *mux.Router) { } }).Methods("GET", "PUT") + // Create a new parent + router.HandleFunc("/admin/parent/", func(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPost: + var parent Parent + if err := json.NewDecoder(req.Body).Decode(&parent); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Invalid request payload: %v"}`, err), http.StatusBadRequest) + return + } + parent.GoogleID = uuid.New().String() + parent.Role = "Parent" + parent.CreatedAt = time.Now() + parent.UpdatedAt = time.Now() + if err := createParent(parent, req); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Failed to create parent: %v"}`, err), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusCreated) + json.NewEncoder(res).Encode(parent) + default: + http.Error(res, `{"error": "Invalid request method"}`, http.StatusMethodNotAllowed) + } + }).Methods("POST") + + // Serve the search instructor page router.HandleFunc("/admin/search_instructor", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("templates/admin/search_instructor.html") if err != nil { @@ -191,6 +276,17 @@ func AdminHandler(router *mux.Router) { t.Execute(res, nil) }).Methods("GET") + // Serve the create instructor page + router.HandleFunc("/admin/create_instructor", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/create_instructor.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + // Serve the search instructor API endpoint router.HandleFunc("/admin/api/search_instructor", func(res http.ResponseWriter, req *http.Request) { name := req.URL.Query().Get("name") instructors, err := searchInstructors(name) @@ -206,13 +302,7 @@ func AdminHandler(router *mux.Router) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) - if err != nil { - http.Error(res, "Unauthorized", http.StatusUnauthorized) - return - } - - instructor, err := readInstructor(currentUser, googleID) + instructor, err := readInstructor(googleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -230,28 +320,124 @@ func AdminHandler(router *mux.Router) { vars := mux.Vars(req) googleID := vars["googleID"] - currentUser, err := GetCurrentUser(req) + switch req.Method { + case http.MethodGet: + instructor, err := readInstructor(googleID, req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(instructor) + case http.MethodPut: + var updates map[string]interface{} + if err := json.NewDecoder(req.Body).Decode(&updates); err != nil { + http.Error(res, err.Error(), http.StatusBadRequest) + return + } + if err := updateInstructor(googleID, updates, req); err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusNoContent) + } + }).Methods("GET", "PUT") + + // Create a new instructor + router.HandleFunc("/admin/instructor/", func(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPost: + var instructor Instructor + if err := json.NewDecoder(req.Body).Decode(&instructor); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Invalid request payload: %v"}`, err), http.StatusBadRequest) + return + } + instructor.GoogleID = uuid.New().String() + instructor.Role = "Instructor" + instructor.CreatedAt = time.Now() + instructor.UpdatedAt = time.Now() + if err := createInstructor(instructor, req); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Failed to create instructor: %v"}`, err), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusCreated) + json.NewEncoder(res).Encode(instructor) + default: + http.Error(res, `{"error": "Invalid request method"}`, http.StatusMethodNotAllowed) + } + }).Methods("POST") + + //Serve the search announcement page + router.HandleFunc("/admin/search_announcement", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/search_announcement.html") if err != nil { - http.Error(res, "Unauthorized", http.StatusUnauthorized) + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + // Serve the create announcement page + router.HandleFunc("/admin/create_announcement", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin/create_announcement.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + // Search for an announcement + router.HandleFunc("/admin/api/search_announcement", func(res http.ResponseWriter, req *http.Request) { + subject := req.URL.Query().Get("subject") + // content := req.URL.Query().Get("content") + announcements, err := searchAnnouncements(subject) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(announcements) + }).Methods("GET") + + router.HandleFunc("/admin/announcement/{announcementID}/edit", func(res http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + announcementID := vars["announcementID"] + + announcement, err := readAnnouncement(announcementID, req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + t, err := template.ParseFiles("templates/admin/edit_announcement.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) return } + t.Execute(res, announcement) + }).Methods("GET") + + router.HandleFunc("/admin/announcement/{announcementID}", func(res http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + announcementID := vars["announcementID"] switch req.Method { case http.MethodGet: - instructor, err := readInstructor(currentUser, googleID) + announcement, err := readAnnouncement(announcementID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return } res.Header().Set("Content-Type", "application/json") - json.NewEncoder(res).Encode(instructor) + json.NewEncoder(res).Encode(announcement) case http.MethodPut: var updates map[string]interface{} if err := json.NewDecoder(req.Body).Decode(&updates); err != nil { http.Error(res, err.Error(), http.StatusBadRequest) return } - if err := updateInstructor(currentUser, googleID, updates); err != nil { + if err := updateAnnouncement(announcementID, updates, req); err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return } @@ -259,6 +445,34 @@ func AdminHandler(router *mux.Router) { } }).Methods("GET", "PUT") + // Create a new announcement + router.HandleFunc("/admin/announcement", func(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPost: + var announcement Announcement + if err := json.NewDecoder(req.Body).Decode(&announcement); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Invalid request payload: %v"}`, err), http.StatusBadRequest) + return + } + announcement.AnnouncementID = uuid.New().String() + announcement.CreatedAt = time.Now() + announcement.UpdatedAt = time.Now() + if err := createAnnouncement(announcement, req); err != nil { + http.Error(res, fmt.Sprintf(`{"error": "Failed to create announcement: %v"}`, err), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusCreated) + json.NewEncoder(res).Encode(announcement) + default: + http.Error(res, `{"error": "Invalid request method"}`, http.StatusMethodNotAllowed) + } + }).Methods("POST") + + // Example usage: + // POST /admin/announcement + // Request Body: JSON object with announcement details + // Response: HTTP Status Created (201) + router.HandleFunc("/admin/api/profile", func(res http.ResponseWriter, req *http.Request) { currentUser, err := GetCurrentUser(req) if err != nil { @@ -267,7 +481,7 @@ func AdminHandler(router *mux.Router) { } switch req.Method { case http.MethodGet: - admin, err := readAdmin(currentUser, currentUser.GoogleID) + admin, err := readAdmin(currentUser.GoogleID, req) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return @@ -280,7 +494,7 @@ func AdminHandler(router *mux.Router) { http.Error(res, err.Error(), http.StatusBadRequest) return } - if err := updateAdmin(currentUser, currentUser.GoogleID, updates); err != nil { + if err := updateAdmin(currentUser.GoogleID, updates, req); err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return } diff --git a/database.go b/database.go index 32a937e..beda59b 100644 --- a/database.go +++ b/database.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + "os" "strings" firebase "firebase.google.com/go" @@ -14,7 +15,23 @@ import ( ) var firebaseClient *db.Client -var store = sessions.NewCookieStore([]byte("1L6x-SPtG8-EqJUkR7htTJx-5K4rt-ZTKeh-rxPw-AM=")) + +func SessionCookie() (string, error) { + // sessionCookieStore := goDotEnvVariable("SESSION_COOKIE_STORE") + // if sessionCookieStore == "" { + // return sessionCookieStore, fmt.Errorf("SESSION_COOKIE_STORE is not set in the environment variables") + // } + + sessionCookieStore, found := os.LookupEnv("COOKIESTORE") + if !found { + log.Fatalf("COOKIESTORE is not set in the environment variables") + } + + return sessionCookieStore, nil +} + +var sessionCookieStore, _ = SessionCookie() +var store = sessions.NewCookieStore([]byte(sessionCookieStore)) func initDB(app *firebase.App) error { // Initialize Firebase client @@ -401,9 +418,9 @@ func isSelf(user User, googleID string) bool { } // Check if instructor can access student (student's class' instructor = instructor name) -func canInstructorAccessStudent(currentUser User, student Student, classes []Class) bool { +func canInstructorAccessStudent(user User, student Student, classes []Class) bool { for _, class := range classes { - if class.Instructor == currentUser.GoogleID && class.ClassID == student.ClassID { + if class.Instructor == user.GoogleID && class.ClassID == student.ClassID { return true } } @@ -411,21 +428,21 @@ func canInstructorAccessStudent(currentUser User, student Student, classes []Cla } // Check if parent can access child (student's parent's id = parent id) -func canParentAccessChild(currentUser User, student Student) bool { +func canParentAccessChild(user User, student Student) bool { // Implement logic to check if parent can access the child - return currentUser.GoogleID == student.ParentID + return user.GoogleID == student.ParentID } // Check if student can access parent (student's parent's name = parent name) -func canChildAccessParent(currentUser User, parent Parent) bool { +func canChildAccessParent(user User, parent Parent) bool { // Implement logic to check if parent can access the child - return currentUser.GoogleID == parent.GoogleID + return user.GoogleID == parent.GoogleID } // Check if student is in the class -func isStudentInClass(currentUser User, students []Student, class Class) bool { +func isStudentInClass(user User, students []Student, class Class) bool { for _, student := range students { - if student.GoogleID == currentUser.GoogleID && student.ClassID == class.ClassID { + if student.GoogleID == user.GoogleID && student.ClassID == class.ClassID { return true } } @@ -433,9 +450,9 @@ func isStudentInClass(currentUser User, students []Student, class Class) bool { } // Check if the parent's child is in the class -func isParentChildInClass(currentUser User, students []Student, class Class) bool { +func isParentChildInClass(user User, students []Student, class Class) bool { for _, student := range students { - if student.ParentID == currentUser.GoogleID && student.ClassID == class.ClassID { + if student.ParentID == user.GoogleID && student.ClassID == class.ClassID { return true } } @@ -445,7 +462,11 @@ func isParentChildInClass(currentUser User, students []Student, class Class) boo // CRUD operations with role checks // Student CRUD -func createStudent(currentUser User, student Student) error { +func createStudent(student Student, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not an admin, return error when attempting to create student if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create students") @@ -457,14 +478,19 @@ func createStudent(currentUser User, student Student) error { return ref.Set(context.TODO(), student) } -func readStudent(currentUser User, studentGoogleID string) (Student, error) { +func readStudent(studentGoogleID string, req *http.Request) (Student, error) { ref := firebaseClient.NewRef("students/" + studentGoogleID) var student Student if err := ref.Get(context.TODO(), &student); err != nil { return Student{}, fmt.Errorf("error reading student: %v", err) } - classes, err := readAllClasses(currentUser) + currentUser, err := GetCurrentUser(req) + if err != nil { + return Student{}, fmt.Errorf("error getting current user") + } + + classes, err := readAllClasses(req) if err != nil { return Student{}, fmt.Errorf("error reading classes: %v", err) } @@ -513,22 +539,26 @@ func searchStudents(name, class string) ([]Student, error) { return filteredStudents, nil } -func updateStudent(currentUser User, studentGoogleID string, updates map[string]interface{}) error { - // Fetch the student information using the provided GoogleID - student, err := readStudent(currentUser, studentGoogleID) +func updateStudent(studentGoogleID string, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } + // Fetch the student information + student, err := readStudent(studentGoogleID, req) if err != nil { return fmt.Errorf("error fetching student: %v", err) } // Fetch all classes - classes, err := readAllClasses(currentUser) + classes, err := readAllClasses(req) if err != nil { return fmt.Errorf("error reading classes: %v", err) } // If user is not admin, instructor, or parent, return error when attempting to update student if !isAdmin(currentUser) && //not admin - !(isSelf(currentUser, student.GoogleID) && isStudent(currentUser)) && //not student and reading self + !(isSelf(currentUser, studentGoogleID) && isStudent(currentUser)) && //not student and reading self !(currentUser.Role == "Instructor" && canInstructorAccessStudent(currentUser, student, classes)) && //instructor can access only their students' info !(currentUser.Role == "Parent" && canParentAccessChild(currentUser, student)) { // parent can access only their child's info { return fmt.Errorf("unauthorized access: you can only update your own details") @@ -541,7 +571,11 @@ func updateStudent(currentUser User, studentGoogleID string, updates map[string] return nil } -func deleteStudent(currentUser User, student Student) error { +func deleteStudent(student Student, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete student if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete students") @@ -554,7 +588,11 @@ func deleteStudent(currentUser User, student Student) error { } // Instructor CRUD -func createInstructor(currentUser User, instructor Instructor) error { +func createInstructor(instructor Instructor, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to create instructor if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create instructors") @@ -566,13 +604,18 @@ func createInstructor(currentUser User, instructor Instructor) error { return ref.Set(context.TODO(), instructor) } -func readInstructor(currentUser User, instructorGoogleID string) (Instructor, error) { +func readInstructor(instructorGoogleID string, req *http.Request) (Instructor, error) { ref := firebaseClient.NewRef("instructors/" + instructorGoogleID) var instructor Instructor if err := ref.Get(context.TODO(), &instructor); err != nil { return Instructor{}, fmt.Errorf("error reading instructor: %v", err) } + currentUser, err := GetCurrentUser(req) + if err != nil { + return Instructor{}, fmt.Errorf("error getting current user: %v", err) + } + // If user is not admin or (self & instructor), return error when attempting to read instructor if !isAdmin(currentUser) && //not admin !(isSelf(currentUser, instructor.GoogleID) && isInstructor(currentUser)) { @@ -613,9 +656,13 @@ func searchInstructors(name string) ([]Instructor, error) { return filteredInstructors, nil } -func updateInstructor(currentUser User, instructorGoogleID string, updates map[string]interface{}) error { +func updateInstructor(instructorGoogleID string, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // Fetch the instructor information using the provided GoogleID - instructor, err := readInstructor(currentUser, instructorGoogleID) + instructor, err := readInstructor(instructorGoogleID, req) if err != nil { return fmt.Errorf("error fetching instructor: %v", err) } @@ -632,7 +679,11 @@ func updateInstructor(currentUser User, instructorGoogleID string, updates map[s return ref.Update(context.TODO(), updates) } -func deleteInstructor(currentUser User, instructor Instructor) error { +func deleteInstructor(instructor Instructor, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete instructor if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete instructors") @@ -645,7 +696,11 @@ func deleteInstructor(currentUser User, instructor Instructor) error { } // Admin CRUD -func createAdmin(currentUser User, admin Admin) error { +func createAdmin(admin Admin, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to create admin if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create admins") @@ -657,13 +712,18 @@ func createAdmin(currentUser User, admin Admin) error { return ref.Set(context.TODO(), admin) } -func readAdmin(currentUser User, adminGoogleID string) (Admin, error) { +func readAdmin(adminGoogleID string, req *http.Request) (Admin, error) { ref := firebaseClient.NewRef("admins/" + adminGoogleID) var admin Admin if err := ref.Get(context.TODO(), &admin); err != nil { return Admin{}, fmt.Errorf("error reading admin: %v", err) } + currentUser, err := GetCurrentUser(req) + if err != nil { + return Admin{}, fmt.Errorf("error getting current user: %v", err) + } + // If user is not admin, return error when attempting to read admin if !isAdmin(currentUser) { return Admin{}, fmt.Errorf("unauthorized access: you can only read your own details") @@ -671,9 +731,13 @@ func readAdmin(currentUser User, adminGoogleID string) (Admin, error) { return admin, nil } -func updateAdmin(currentUser User, adminGoogleID string, updates map[string]interface{}) error { +func updateAdmin(adminGoogleID string, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // Fetch the admin information using the provided GoogleID - admin, err := readAdmin(currentUser, adminGoogleID) + admin, err := readAdmin(adminGoogleID, req) if err != nil { return fmt.Errorf("error fetching admin: %v", err) } @@ -689,7 +753,11 @@ func updateAdmin(currentUser User, adminGoogleID string, updates map[string]inte return ref.Update(context.TODO(), updates) } -func deleteAdmin(currentUser User, admin Admin) error { +func deleteAdmin(admin Admin, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete admin if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete admins") @@ -702,7 +770,11 @@ func deleteAdmin(currentUser User, admin Admin) error { } // Parent CRUD -func createParent(currentUser User, parent Parent) error { +func createParent(parent Parent, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to create parent if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create parents") @@ -714,13 +786,18 @@ func createParent(currentUser User, parent Parent) error { return ref.Set(context.TODO(), parent) } -func readParent(currentUser User, parentGoogleID string) (Parent, error) { +func readParent(parentGoogleID string, req *http.Request) (Parent, error) { ref := firebaseClient.NewRef("parents/" + parentGoogleID) var parent Parent if err := ref.Get(context.TODO(), &parent); err != nil { return Parent{}, fmt.Errorf("error reading parent: %v", err) } + currentUser, err := GetCurrentUser(req) + if err != nil { + return Parent{}, fmt.Errorf("error getting current user: %v", err) + } + // If user is not admin or (self and parent), return error when attempting to update parent if !isAdmin(currentUser) && //not admin !(isSelf(currentUser, parent.GoogleID) && isParent(currentUser)) && //not parent and reading self @@ -761,9 +838,13 @@ func searchParents(name string) ([]Parent, error) { return filteredParents, nil } -func updateParent(currentUser User, parentGoogleID string, updates map[string]interface{}) error { +func updateParent(parentGoogleID string, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // Fetch the parent information using the provided GoogleID - parent, err := readParent(currentUser, parentGoogleID) + parent, err := readParent(parentGoogleID, req) if err != nil { return fmt.Errorf("error fetching parent: %v", err) } @@ -779,7 +860,11 @@ func updateParent(currentUser User, parentGoogleID string, updates map[string]in return ref.Update(context.TODO(), updates) } -func deleteParent(currentUser User, parent Parent) error { +func deleteParent(parent Parent, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete parent if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete parents") @@ -792,7 +877,11 @@ func deleteParent(currentUser User, parent Parent) error { } // class CRUD -func createClass(currentUser User, class Class) error { +func createClass(class Class, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to create class if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create classes") @@ -804,13 +893,18 @@ func createClass(currentUser User, class Class) error { return ref.Set(context.TODO(), class) } -func readClass(currentUser User, students []Student, classID string) (Class, error) { +func readClass(students []Student, classID string, req *http.Request) (Class, error) { ref := firebaseClient.NewRef("classes/" + classID) var class Class if err := ref.Get(context.TODO(), &class); err != nil { return Class{}, fmt.Errorf("error reading class: %v", err) } + currentUser, err := GetCurrentUser(req) + if err != nil { + return Class{}, fmt.Errorf("error getting current user: %v", err) + } + // If user is not admin or (self and class), return error when attempting to read class if !isAdmin(currentUser) && //not admin !isInstructor(currentUser) && //not instructor @@ -821,7 +915,7 @@ func readClass(currentUser User, students []Student, classID string) (Class, err return class, nil } -func readAllClasses(currentUser User) ([]Class, error) { +func readAllClasses(req *http.Request) ([]Class, error) { ref := firebaseClient.NewRef("classes") var classesMap map[string]Class @@ -830,6 +924,10 @@ func readAllClasses(currentUser User) ([]Class, error) { } var classes []Class + currentUser, err := GetCurrentUser(req) + if err != nil { + return []Class{}, fmt.Errorf("error getting current user: %v", err) + } for _, class := range classesMap { // If user is not authorized to read the class, skip it if !isAdmin(currentUser) && // not admin @@ -842,7 +940,11 @@ func readAllClasses(currentUser User) ([]Class, error) { return classes, nil } -func updateClass(currentUser User, class Class, updates map[string]interface{}) error { +func updateClass(class Class, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin or (self and class), return error when attempting to update class if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: you can only update your own details") @@ -854,7 +956,11 @@ func updateClass(currentUser User, class Class, updates map[string]interface{}) return ref.Update(context.TODO(), updates) } -func deleteClass(currentUser User, class Class) error { +func deleteClass(class Class, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete class if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete classes") @@ -867,51 +973,106 @@ func deleteClass(currentUser User, class Class) error { } // Announcements CRUD -func createAnnouncement(currentUser User, announcement Announcement) error { +func createAnnouncement(announcement Announcement, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to create announcement if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can create announcements") } - ref := firebaseClient.NewRef("admins/" + announcement.AnnouncementID) + ref := firebaseClient.NewRef("announcements/" + announcement.AnnouncementID) if err := ref.Set(context.TODO(), announcement); err != nil { return fmt.Errorf("error creating admin: %v", err) } return ref.Set(context.TODO(), announcement) } -func readAnnouncement(currentUser User, announcement Announcement) (Announcement, error) { - // If user is not admin, return error when attempting to read admin +func readAnnouncement(announcementID string, req *http.Request) (Announcement, error) { + ref := firebaseClient.NewRef("announcements/" + announcementID) + var announcement Announcement + if err := ref.Get(context.TODO(), &announcement); err != nil { + return Announcement{}, fmt.Errorf("error reading admin: %v", err) + } + + currentUser, err := GetCurrentUser(req) + if err != nil { + return Announcement{}, fmt.Errorf("error getting current user: %v", err) + } + + // If user is not NK user, return error when attempting to read admin if !isAdmin(currentUser) && !isInstructor(currentUser) && !isParent(currentUser) && !isStudent(currentUser) { return Announcement{}, fmt.Errorf("unauthorized access: you are not allowed to read this announcement") } - ref := firebaseClient.NewRef("announcements/" + announcement.AnnouncementID) - if err := ref.Get(context.TODO(), &announcement); err != nil { - return Announcement{}, fmt.Errorf("error reading admin: %v", err) - } return announcement, nil } -func updateAnnouncement(currentUser User, announcement Announcement, updates map[string]interface{}) error { +func readAnnouncements() ([]Announcement, error) { + var announcementsMap map[string]Announcement + ref := firebaseClient.NewRef("announcements") + if err := ref.Get(context.TODO(), &announcementsMap); err != nil { + return nil, fmt.Errorf("error reading announcements: %v", err) + } + // Convert map to slice + announcements := make([]Announcement, 0, len(announcementsMap)) + for _, student := range announcementsMap { + announcements = append(announcements, student) + } + return announcements, nil +} + +func searchAnnouncements(subject string) ([]Announcement, error) { + // Read all announcements from the data source + announcements, err := readAnnouncements() + if err != nil { + return nil, err + } + // If the search subject is empty, return all announcements + if subject == "" { + return announcements, nil + } + // Filter announcements based on whether the subject contains the search term + var filteredAnnouncements []Announcement + lowerSubject := strings.ToLower(subject) + for _, announcement := range announcements { + if strings.Contains(strings.ToLower(announcement.Subject), lowerSubject) { + filteredAnnouncements = append(filteredAnnouncements, announcement) + } + } + return filteredAnnouncements, nil +} + +func updateAnnouncement(announcementID string, updates map[string]interface{}, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to update announcement if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can update this announcement") } - ref := firebaseClient.NewRef("announcements/" + announcement.AnnouncementID) + ref := firebaseClient.NewRef("announcements/" + announcementID) if err := ref.Update(context.TODO(), updates); err != nil { return fmt.Errorf("error updating announcement: %v", err) } - return ref.Update(context.TODO(), updates) + // return ref.Update(context.TODO(), updates) + return nil } -func deleteAnnouncement(currentUser User, announcement Announcement) error { +func deleteAnnouncement(announcementID string, req *http.Request) error { + currentUser, err := GetCurrentUser(req) + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } // If user is not admin, return error when attempting to delete announcement if !isAdmin(currentUser) { return fmt.Errorf("unauthorized access: only admins can delete announcements") } - ref := firebaseClient.NewRef("announcements/" + announcement.AnnouncementID) + ref := firebaseClient.NewRef("announcements/" + announcementID) if err := ref.Delete(context.TODO()); err != nil { return fmt.Errorf("error deleting announcement: %v", err) } diff --git a/database_model.go b/database_model.go index 219acfa..fa47f7a 100644 --- a/database_model.go +++ b/database_model.go @@ -5,7 +5,7 @@ import ( // "github.com/google/uuid" ) -// User struct to represent the base user with common fields +// User represent the base user with common fields type User struct { // ID uuid.UUID `json:"id"` GoogleID string `json:"google_id"` @@ -17,7 +17,7 @@ type User struct { UpdatedAt time.Time `json:"updated_at"` } -// Student struct for storing student information +// Student represents struct for storing student information type Student struct { User Age int `json:"age"` @@ -64,7 +64,6 @@ type Announcement struct { } // NewStudent creates a new Student instance -// func NewStudent(googleID, name, email, contactNumber, class, instructor, parentName, role string, age int, lessonCredits float32) Student { func NewStudent(googleID, name, email, contactNumber, classID, parentID, role string, age int, lessonCredits float32) Student { return Student{ User: User{ diff --git a/database_test.go b/database_test.go index 4b6075a..b5a90a0 100644 --- a/database_test.go +++ b/database_test.go @@ -1,83 +1,121 @@ package main import ( + "log" + "net/http" + "net/http/httptest" "reflect" "testing" + + "encoding/gob" + "encoding/json" ) -var currentUser = User{GoogleID: "admin-user", Role: "Admin"} +func init() { + // Register the User type with gob + gob.Register(User{}) + gob.Register(Announcement{}) +} + +var ( + // testStore = sessions.NewCookieStore([]byte("a-very-secret-key")) + + currentUser = User{ + GoogleID: "admin-user", + Name: "Admin User", + Email: "jiaweicodetest@gmail.com", + ContactNumber: "12345678", + Role: "Admin", + } -// Create a new student -var students = []Student{ - { + // Sample data for testing + students = []Student{ + { + User: User{ + GoogleID: "test-student", + Name: "John Doe", + Email: "jeyvianangjieen@gmail.com", + ContactNumber: "91234567", + Role: "Student", + }, + Age: 12, + LessonCredits: 10.0, + ClassID: "TE-6-10", + ParentID: "test-parent", + }, + } + + instructor = Instructor{ User: User{ - GoogleID: "test-student-3", - Name: "Joann", - Email: "jeyvian@gmail.com", + GoogleID: "test-instructor", + Name: "John Doe", + Email: "jeyvianang112462@gmail.com", ContactNumber: "91234567", Role: "Student", }, - Age: 12, - LessonCredits: 10.0, - ClassID: "dn-6-11", - }, -} + BasePay: 15, + NumberOfStudents: 24, + } -// Create a new instructor -var instructor = Instructor{ - User: User{ - GoogleID: "test-instructor", - Name: "Awesomeness", - Email: "jeyvianang112462@gmail.com", - ContactNumber: "99999999", - Role: "Instructor", - }, - BasePay: 15, - NumberOfStudents: 24, -} + admin = Admin{ + User: User{ + GoogleID: "test-admin", + Name: "Awesomeness", + ContactNumber: "99999999", + Email: "awesome_admin@nk.com", + Role: "Admin", + }, + BasePay: 15, + Incentive: 24, + } -// Create a new admin -var admin = Admin{ - User: User{ - GoogleID: "test-admin", - Name: "Awesomeness", - ContactNumber: "99999999", - Email: "jeyvianang112462@gmail.com", - Role: "Admin", - }, - BasePay: 15, - Incentive: 24, -} + parent = Parent{ + User: User{ + GoogleID: "test-parent", + Name: "Awesomeness", + ContactNumber: "99999999", + Email: "janedoe_parent@nk.com", + Role: "Parent", + }, + FolderID: "1F38DgyV0V5q2CWzoO3xkHTC4FY6cNrig", + } -// Create a new parent -var parent = Parent{ - User: User{ - GoogleID: "test-parent-1", - Name: "OHNO", - ContactNumber: "99999999", - Email: "jeyvianangjie@gmail.com", - Role: "Parent", - }, - FolderID: "1F38DgyV0V5q2CWzoO3xkHTC4FY6cNrig", -} + classes = []Class{ + { + ClassID: "te-6-10", + Name: "Test Class", + Instructor: "jeyvianang112462@gmail.com", + FolderID: "1zrj8iUefB9D0u1RXT8Pi0-At8aQf9n2m", + }, + } -// Create a dummy class for testing -var classes = []Class{ - { - ClassID: "dn-6-11", - Name: "DN", - Instructor: "jeyvianang112462@gmail.com", - FolderID: "1zrj8iUefB9D0u1RXT8Pi0-At8aQf9n2m", - }, -} + announcement = Announcement{ + AnnouncementID: "test-announcement", + Subject: "Test Announcement", + Content: "This is a test announcement.", + } +) + +func mockRequest() *http.Request { + req, _ := http.NewRequest("GET", "/test", nil) + recorder := httptest.NewRecorder() + session, _ := store.Get(req, "auth-session") -var announcement = Announcement{ - Subject: "Test Announcement", - Content: "This is a test announcement.", + userData, err := json.Marshal(currentUser) + if err != nil { + log.Println(err) + } + + session.Values["user"] = userData + err = session.Save(req, recorder) + if err != nil { + log.Println(err) + } + req.Header.Set("Cookie", recorder.Header().Get("Set-Cookie")) + return req } func TestInitializeFirebase(t *testing.T) { - // Test case 1: FirebaseClient is set correctly err := initializeFirebase() if err != nil { t.Fatalf("Error initializing Firebase: %v", err) @@ -85,20 +123,21 @@ func TestInitializeFirebase(t *testing.T) { if firebaseClient == nil { t.Fatal("FirebaseClient is not set") } - - // Run tests - // os.Exit(t.Run()) } // Testing for student CRUD operations func TestCreateStudent(t *testing.T) { - err := createStudent(currentUser, students[0]) + err := initializeFirebase() + if err != nil { + t.Fatalf("Error initializing Firebase: %v", err) + } + err = createStudent(students[0], mockRequest()) if err != nil { t.Fatalf("Error creating student: %v", err) } - readStudent, err := readStudent(currentUser, students[0].GoogleID) + readStudent, err := readStudent(students[0].GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading student: %v", err) } @@ -109,7 +148,7 @@ func TestCreateStudent(t *testing.T) { } func TestReadStudent(t *testing.T) { - readStudent, err := readStudent(currentUser, students[0].GoogleID) + readStudent, err := readStudent(students[0].GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading student: %v", err) } @@ -122,49 +161,44 @@ func TestReadStudent(t *testing.T) { func TestUpdateStudent(t *testing.T) { // Update the student's email updates := map[string]interface{}{ - "name": "Updated Student", + "name": "John Doe", + "contact_number": "99999999", } - err := updateStudent(currentUser, students[0].GoogleID, updates) + err := updateStudent(students[0].GoogleID, updates, mockRequest()) if err != nil { t.Fatalf("Error updating student: %v", err) } // Read the updated student - readStudent, err := readStudent(currentUser, students[0].GoogleID) + readStudent, err := readStudent(students[0].GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading student after updating: %v", err) } // Assert that the updated student's email is correct - if readStudent.Name != updates["name"] { + if students[0].Name != updates["name"] { t.Errorf("Updated student's name is incorrect. Expected: %v, Got: %v", updates["name"], readStudent.Name) } } func TestDeleteStudent(t *testing.T) { // Delete the student - err := deleteStudent(currentUser, students[0]) + err := deleteStudent(students[0], mockRequest()) if err != nil { t.Fatalf("Error deleting student: %v", err) } - - // Try to read the deleted student - // _, err = readStudent(googleID) - // if err == nil { - // t.Error("Deleted student still exists") - // } } // Testing for instructor CRUD operations func TestCreateInstructor(t *testing.T) { - err := createInstructor(currentUser, instructor) + err := createInstructor(instructor, mockRequest()) if err != nil { t.Fatalf("Error creating instructor: %v", err) } // Read the created instructor - readInstructor, err := readInstructor(currentUser, instructor.GoogleID) + readInstructor, err := readInstructor(instructor.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading instructor: %v", err) } @@ -177,7 +211,7 @@ func TestCreateInstructor(t *testing.T) { func TestReadInstructor(t *testing.T) { - instructor, err := readInstructor(currentUser, instructor.GoogleID) + instructor, err := readInstructor(instructor.GoogleID, mockRequest()) if err != nil { t.Fatalf("Failed to read instructor: %v", err) } @@ -193,13 +227,13 @@ func TestUpdateInstructor(t *testing.T) { "email": "amazing_instructor@nk.com", } - err := updateInstructor(currentUser, instructor.GoogleID, updates) + err := updateInstructor(instructor.GoogleID, updates, mockRequest()) if err != nil { t.Fatalf("Error updating instructor: %v", err) } // Read the updated instructor - readInstructor, err := readInstructor(currentUser, instructor.GoogleID) + readInstructor, err := readInstructor(instructor.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading instructor: %v", err) } @@ -212,27 +246,21 @@ func TestUpdateInstructor(t *testing.T) { func TestDeleteInstructor(t *testing.T) { // Delete the instructor - err := deleteInstructor(currentUser, instructor) + err := deleteInstructor(instructor, mockRequest()) if err != nil { t.Fatalf("Error deleting instructor: %v", err) } - - // Try to read the deleted instructor - // _, err = readInstructor(googleID) - // if err == nil { - // t.Error("Deleted instructor still exists") - // } } // Testing for admin CRUD operations func TestCreateAdmin(t *testing.T) { - err := createAdmin(currentUser, admin) + err := createAdmin(admin, mockRequest()) if err != nil { t.Fatalf("Error creating admin: %v", err) } // Read the created admin - readAdmin, err := readAdmin(currentUser, admin.GoogleID) + readAdmin, err := readAdmin(admin.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading admin: %v", err) } @@ -244,7 +272,7 @@ func TestCreateAdmin(t *testing.T) { } func TestReadAdmin(t *testing.T) { - admin, err := readAdmin(currentUser, admin.GoogleID) + admin, err := readAdmin(admin.GoogleID, mockRequest()) if err != nil { t.Fatalf("Failed to read instructor: %v", err) } @@ -260,13 +288,13 @@ func TestUpdateAdmin(t *testing.T) { "email": "amazing_admin@nk.com", } - err := updateAdmin(currentUser, admin.GoogleID, updates) + err := updateAdmin(admin.GoogleID, updates, mockRequest()) if err != nil { t.Fatalf("Error updating admin: %v", err) } // Read the updated admin - readAdmin, err := readAdmin(currentUser, admin.GoogleID) + readAdmin, err := readAdmin(admin.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading admin: %v", err) } @@ -279,27 +307,21 @@ func TestUpdateAdmin(t *testing.T) { func TestDeleteAdmin(t *testing.T) { // Delete the admin - err := deleteAdmin(currentUser, admin) + err := deleteAdmin(admin, mockRequest()) if err != nil { t.Fatalf("Error deleting admin: %v", err) } - - // Try to read the deleted admin - // _, err = readAdmin(googleID) - // if err == nil { - // t.Error("Deleted admin still exists") - // } } // Testing for parent CRUD operations func TestCreateParent(t *testing.T) { - err := createParent(currentUser, parent) + err := createParent(parent, mockRequest()) if err != nil { t.Fatalf("Error creating parent: %v", err) } // Read the created parent - readParent, err := readParent(currentUser, parent.GoogleID) + readParent, err := readParent(parent.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading parent: %v", err) } @@ -311,7 +333,7 @@ func TestCreateParent(t *testing.T) { } func TestReadParent(t *testing.T) { - parent, err := readParent(currentUser, parent.GoogleID) + parent, err := readParent(parent.GoogleID, mockRequest()) if err != nil { t.Fatalf("Failed to read parent: %v", err) } @@ -327,13 +349,13 @@ func TestUpdateParent(t *testing.T) { "email": "jane_doe_parent@nk.com", } - err := updateParent(currentUser, parent.GoogleID, updates) + err := updateParent(parent.GoogleID, updates, mockRequest()) if err != nil { t.Fatalf("Error updating parent: %v", err) } // Read the updated parent - readParent, err := readParent(currentUser, parent.GoogleID) + readParent, err := readParent(parent.GoogleID, mockRequest()) if err != nil { t.Fatalf("Error reading parent: %v", err) } @@ -346,28 +368,21 @@ func TestUpdateParent(t *testing.T) { func TestDeleteParent(t *testing.T) { // Delete the parent - err := deleteParent(currentUser, parent) + err := deleteParent(parent, mockRequest()) if err != nil { t.Fatalf("Error deleting parent: %v", err) } - - // Try to read the deleted parent - // _, err = readParent(googleID) - // if err == nil { - // t.Error("Deleted parent still exists") - // } } // Testing for class CRUD operations - func TestCreateClass(t *testing.T) { - err := createClass(currentUser, classes[0]) + err := createClass(classes[0], mockRequest()) if err != nil { t.Fatalf("Error creating class: %v", err) } // Read the created class - readClass, err := readClass(currentUser, students, classes[0].ClassID) + readClass, err := readClass(students, classes[0].ClassID, mockRequest()) if err != nil { t.Fatalf("Error reading class: %v", err) } @@ -379,7 +394,7 @@ func TestCreateClass(t *testing.T) { } func TestReadClass(t *testing.T) { - class, err := readClass(currentUser, students, classes[0].ClassID) + class, err := readClass(students, classes[0].ClassID, mockRequest()) if err != nil { t.Fatalf("Failed to read class: %v", err) } @@ -395,13 +410,13 @@ func TestUpdateClass(t *testing.T) { "class_name": "DN", } - err := updateClass(currentUser, classes[0], updates) + err := updateClass(classes[0], updates, mockRequest()) if err != nil { t.Fatalf("Error updating class: %v", err) } // Read the updated class - readClass, err := readClass(currentUser, students, classes[0].ClassID) + readClass, err := readClass(students, classes[0].ClassID, mockRequest()) if err != nil { t.Fatalf("Error reading class: %v", err) } @@ -414,38 +429,32 @@ func TestUpdateClass(t *testing.T) { func TestDeleteClass(t *testing.T) { // Delete the class - err := deleteClass(currentUser, classes[0]) + err := deleteClass(classes[0], mockRequest()) if err != nil { t.Fatalf("Error deleting class: %v", err) } - - // Try to read the deleted class - // _, err = readClass(currentUser, students, classes[0]) - // if err == nil { - // t.Error("Deleted class still exists") - // } } func TestCreateAnnouncement(t *testing.T) { - err := createAnnouncement(currentUser, announcement) + err := createAnnouncement(announcement, mockRequest()) if err != nil { t.Fatalf("Error creating announcement: %v", err) } // Read the announcement - readAnnouncement, err := readAnnouncement(currentUser, announcement) + readAnnouncement, err := readAnnouncement(announcement.AnnouncementID, mockRequest()) if err != nil { t.Fatalf("Error reading announcement: %v", err) } // Assert that the created and read announcement are equal if !reflect.DeepEqual(announcement, readAnnouncement) { - t.Error("Created and read announcements are not equal") + t.Errorf("Created and read announcements are not equal. Expected: %+v, Got: %+v", announcement, readAnnouncement) } } func TestReadAnnouncement(t *testing.T) { - announcement, err := readAnnouncement(currentUser, announcement) + announcement, err := readAnnouncement(announcement.AnnouncementID, mockRequest()) if err != nil { t.Fatalf("Failed to read announcement: %v", err) } @@ -461,13 +470,13 @@ func TestUpdateAnnouncement(t *testing.T) { "content": "This is an updated announcement.", } - err := updateAnnouncement(currentUser, announcement, updates) + err := updateAnnouncement(announcement.AnnouncementID, updates, mockRequest()) if err != nil { t.Fatalf("Error updating announcement: %v", err) } // Read the updated announcement - readAnnouncement, err := readAnnouncement(currentUser, announcement) + readAnnouncement, err := readAnnouncement(announcement.AnnouncementID, mockRequest()) if err != nil { t.Fatalf("Error reading announcement: %v", err) } @@ -480,14 +489,8 @@ func TestUpdateAnnouncement(t *testing.T) { func TestDeleteAnnouncement(t *testing.T) { // Delete the announcement - err := deleteAnnouncement(currentUser, announcement) + err := deleteAnnouncement(announcement.AnnouncementID, mockRequest()) if err != nil { t.Fatalf("Error deleting announcement: %v", err) } - - // Try to read the deleted announcement - // _, err = readAnnouncement(currentUser, announcement) - // if err == nil { - // t.Error("Deleted announcement still exists") - // } } diff --git a/go.mod b/go.mod index f2f1757..20ad418 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.22.3 require ( firebase.google.com/go v3.13.0+incompatible + github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/sessions v1.3.0 - github.com/joho/godotenv v1.5.1 github.com/markbates/goth v1.80.0 golang.org/x/oauth2 v0.21.0 google.golang.org/api v0.189.0 @@ -27,7 +27,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/go.sum b/go.sum index 1d21121..2eab768 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,6 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg= github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8= github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/json_validator.go b/json_validator.go deleted file mode 100644 index 7e00390..0000000 --- a/json_validator.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "encoding/json" - // "fmt" - "io" - - // "log" - "os" -) - -// func validateJSON(jsonFilePath string) { -// // Open the JSON file -// jsonFile, err := os.Open(jsonFilePath) -// if err != nil { -// log.Fatalf("Error opening JSON file: %v", err) -// } -// defer jsonFile.Close() - -// // Read the JSON file -// jsonBytes, err := io.ReadAll(jsonFile) -// if err != nil { -// log.Fatalf("Error reading JSON file: %v", err) -// } - -// // Validate JSON format -// var jsonData map[string]interface{} -// if err := json.Unmarshal(jsonBytes, &jsonData); err != nil { -// log.Fatalf("Error unmarshalling JSON: %v", err) -// } - -// fmt.Println("JSON is valid and correctly formatted.") -// } - -func validateJSON(jsonFilePath string) error { - // Open the JSON file - jsonFile, err := os.Open(jsonFilePath) - if err != nil { - return err - } - defer jsonFile.Close() - - // Read the JSON file - jsonData, err := io.ReadAll(jsonFile) - if err != nil { - return err - } - - // Validate JSON format - var parsedData interface{} - if err := json.Unmarshal(jsonData, &parsedData); err != nil { - return err - } - - // Additional validation logic can be added here - - return nil -} diff --git a/main.go b/main.go index b3e36a9..7f234b8 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( ) func init() { - // database() initializeFirebase() err := initializeFirebase() if err != nil { diff --git a/templates/admin/create_announcement.html b/templates/admin/create_announcement.html new file mode 100644 index 0000000..986949b --- /dev/null +++ b/templates/admin/create_announcement.html @@ -0,0 +1,137 @@ + + + + Send Announcement + + + + + + + + + + +
+ +


+ + +
+

Send Announcement


+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/templates/admin/create_instructor.html b/templates/admin/create_instructor.html new file mode 100644 index 0000000..a0d03b6 --- /dev/null +++ b/templates/admin/create_instructor.html @@ -0,0 +1,119 @@ + + + + EduSync + + + + + + + + + + +
+ +


+ +
+

Create New Instructor


+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + diff --git a/templates/admin/create_parent.html b/templates/admin/create_parent.html new file mode 100644 index 0000000..98c1781 --- /dev/null +++ b/templates/admin/create_parent.html @@ -0,0 +1,108 @@ + + + + EduSync + + + + + + + + + + +
+ +
+
+
+ +
+

Create New Parent


+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + diff --git a/templates/admin/create_student.html b/templates/admin/create_student.html new file mode 100644 index 0000000..337e099 --- /dev/null +++ b/templates/admin/create_student.html @@ -0,0 +1,135 @@ + + + + EduSync + + + + + + + + + + +
+ +
+ + +
+
+
+

Create New Student

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + diff --git a/templates/admin/edit_announcement.html b/templates/admin/edit_announcement.html new file mode 100644 index 0000000..bd1e73d --- /dev/null +++ b/templates/admin/edit_announcement.html @@ -0,0 +1,140 @@ + + + + EduSync + + + + + + + + + + +
+ +
+ +
+
+
+

Edit Announcement Details

+
+ +
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/templates/admin/edit_instructor.html b/templates/admin/edit_instructor.html index bbdb2a3..666867e 100644 --- a/templates/admin/edit_instructor.html +++ b/templates/admin/edit_instructor.html @@ -128,23 +128,26 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • - -
    - -
    -
    - -
    - - - - + + +
    + +
    +
    + +
    + + + +
    -

    + +

    Edit Instructor Details


    diff --git a/templates/admin/edit_parent.html b/templates/admin/edit_parent.html index b7d6770..a8fdda1 100644 --- a/templates/admin/edit_parent.html +++ b/templates/admin/edit_parent.html @@ -135,23 +135,24 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • - - -
    - -
    -
    - -
    -
    - - - + + +
    + +
    +
    + +
    + + + +
    - -
    + +

    Edit Parent Details

    @@ -164,7 +165,8 @@

    Edit Parent Details

         -

    + +
         diff --git a/templates/admin/edit_student.html b/templates/admin/edit_student.html index c99c030..b92d861 100644 --- a/templates/admin/edit_student.html +++ b/templates/admin/edit_student.html @@ -155,6 +155,7 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • diff --git a/templates/admin/index.html b/templates/admin/index.html index 3097f91..b5129be 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -31,6 +31,7 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • diff --git a/templates/admin/search_announcement.html b/templates/admin/search_announcement.html new file mode 100644 index 0000000..6c9e1d9 --- /dev/null +++ b/templates/admin/search_announcement.html @@ -0,0 +1,149 @@ + + + + EduSync + + + + + + + + + + +
    + +
    + +
    +
    +
    +

    Search Announcements

    +
    +
    + + + + +
    +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/templates/admin/search_instructor.html b/templates/admin/search_instructor.html index 40686f9..ea5f645 100644 --- a/templates/admin/search_instructor.html +++ b/templates/admin/search_instructor.html @@ -99,6 +99,7 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • @@ -127,6 +128,8 @@

    Search Instructors

      +   +

    diff --git a/templates/admin/search_parent.html b/templates/admin/search_parent.html index 114675f..7854ec9 100644 --- a/templates/admin/search_parent.html +++ b/templates/admin/search_parent.html @@ -99,6 +99,7 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • @@ -127,6 +128,8 @@

    Search Parents

      +   +
    diff --git a/templates/admin/search_student.html b/templates/admin/search_student.html index 0f60eb3..01f8063 100644 --- a/templates/admin/search_student.html +++ b/templates/admin/search_student.html @@ -95,20 +95,21 @@
  • Students
  • Parents
  • Instructors
  • +
  • Announcements
  • Profile
  • Logout
  • - - -
    - -
    -
    - -
    - - - - + + +
    + +
    +
    + +
    + + + +

    @@ -126,6 +127,8 @@

    Update Student Details

      +   +