diff --git a/.gitignore b/.gitignore index 82f5e3c..b4f8834 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ edusync-7bd5e-firebase-adminsdk-x49uh-af084a6314.json edusync-test-firebase-adminsdk-hk5kl-9af0162b09.json .env -*.json +config.json +edusync-426009-343696fa49b1.json *.exe \ No newline at end of file diff --git a/EduSync.exe b/EduSync.exe index bf099b4..b876bef 100644 Binary files a/EduSync.exe and b/EduSync.exe differ diff --git a/adminHandler.go b/adminHandler.go index 1f18bb7..bb8d5ae 100644 --- a/adminHandler.go +++ b/adminHandler.go @@ -31,6 +31,24 @@ 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") + + 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") @@ -428,7 +446,7 @@ func AdminHandler(router *mux.Router) { }).Methods("GET", "PUT") // Create a new announcement - router.HandleFunc("/admin/announcement/", func(res http.ResponseWriter, req *http.Request) { + router.HandleFunc("/admin/announcement", func(res http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodPost: var announcement Announcement @@ -454,4 +472,33 @@ func AdminHandler(router *mux.Router) { // 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 { + http.Error(res, "Unauthorized", http.StatusUnauthorized) + return + } + switch req.Method { + case http.MethodGet: + admin, err := readAdmin(currentUser.GoogleID, req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(admin) + 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 := updateAdmin(currentUser.GoogleID, updates, req); err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.WriteHeader(http.StatusNoContent) + } + }).Methods("GET", "PUT") } diff --git a/authHandler.go b/authHandler.go index 48a08f5..9e004de 100644 --- a/authHandler.go +++ b/authHandler.go @@ -15,8 +15,8 @@ import ( ) func AuthHandler(router *mux.Router, config *Config) { - maxAge := 86400 * 30 // 30 days - isProd := true // Set to true when serving over https + maxAge := 3600 // 1 hour + isProd := true // Set to true when serving over https store = sessions.NewCookieStore( []byte(config.AuthKey), @@ -26,16 +26,23 @@ func AuthHandler(router *mux.Router, config *Config) { store.Options.Path = "/" store.Options.HttpOnly = true // HttpOnly should always be enabled store.Options.Secure = isProd - store.Options = &sessions.Options{ - Path: "/", - MaxAge: 3600, // 1 hour - HttpOnly: true, - Secure: true, // This should be true if your application is served over HTTPS - } gothic.Store = store goth.UseProviders(google.New(config.GoogleClientID, config.GoogleClientSecret, "https://localhost:8080/auth/google/callback", "email", "profile", "https://www.googleapis.com/auth/drive.file")) + router.HandleFunc("/login", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/login.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, false) + }).Methods("GET") + + router.HandleFunc("/auth/{provider}", func(res http.ResponseWriter, req *http.Request) { + gothic.BeginAuthHandler(res, req) + }).Methods("GET") + router.HandleFunc("/auth/{provider}/callback", func(res http.ResponseWriter, req *http.Request) { user, err := gothic.CompleteUserAuth(res, req) if err != nil { @@ -74,17 +81,22 @@ func AuthHandler(router *mux.Router, config *Config) { } }).Methods("GET") - router.HandleFunc("/auth/{provider}", func(res http.ResponseWriter, req *http.Request) { - gothic.BeginAuthHandler(res, req) - }).Methods("GET") - - router.HandleFunc("/login", func(res http.ResponseWriter, req *http.Request) { - t, err := template.ParseFiles("templates/login.html") - if err != nil { - http.Error(res, err.Error(), http.StatusInternalServerError) - return - } - t.Execute(res, false) + router.HandleFunc("/logout", func(res http.ResponseWriter, req *http.Request) { + // Clear the session or cookie + http.SetCookie(res, &http.Cookie{ + Name: "session_token", + Value: "", + Path: "/", + MaxAge: -1, // This will delete the cookie + }) + + // Set headers to prevent caching + res.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") + res.Header().Set("Cache-Control", "post-check=0, pre-check=0") + res.Header().Set("Pragma", "no-cache") + + // Redirect to the login page or home page + http.Redirect(res, req, "/", http.StatusFound) // 302 Found }).Methods("GET") } diff --git a/config.go b/config.go index 71c42ff..d31c118 100644 --- a/config.go +++ b/config.go @@ -10,7 +10,6 @@ import ( type Config struct { GoogleClientID string `json:"google_client_id"` GoogleClientSecret string `json:"google_client_secret"` - SessionSecret string `json:"session_secret"` AuthKey string `json:"auth_key"` EncryptKey string `json:"encrypt_key"` } diff --git a/database.go b/database.go index 2abf6b0..beda59b 100644 --- a/database.go +++ b/database.go @@ -30,7 +30,7 @@ func SessionCookie() (string, error) { return sessionCookieStore, nil } -var sessionCookieStore, err = SessionCookie() +var sessionCookieStore, _ = SessionCookie() var store = sessions.NewCookieStore([]byte(sessionCookieStore)) func initDB(app *firebase.App) error { @@ -43,6 +43,48 @@ func initDB(app *firebase.App) error { return nil } +func getUserRole(email string) (User, string, error) { + ctx := context.Background() + var user User + var userRole string + + // Check if firebaseClient is initialized + if firebaseClient == nil { + log.Println("Firebase client is not initialized") + return user, userRole, fmt.Errorf("firebase client is not initialized") + } + + // Map categories to Firebase references + categoryRefs := map[string]string{ + "Student": "/students", + "Parent": "/parents", + "Instructor": "/instructors", + "Admin": "/admins", + } + + // Iterate through each category and check if the email exists + for category, ref := range categoryRefs { + categoryRef := firebaseClient.NewRef(ref) + dataSnapshot, err := categoryRef.OrderByChild("email").EqualTo(email).GetOrdered(ctx) + if err != nil { + log.Printf("Error fetching data from %s: %v", category, err) + continue + } + + // Check if dataSnapshot has any children + if len(dataSnapshot) > 0 { + userRole = category + // Assuming dataSnapshot[0] is the first match and it contains the user data + if err := dataSnapshot[0].Unmarshal(&user); err != nil { + log.Printf("Error unmarshalling data for %s: %v", category, err) + continue + } + break + } + } + return user, userRole, nil +} + // Utility function to get current user func GetCurrentUser(req *http.Request) (User, error) { session, err := store.Get(req, "auth-session") @@ -64,6 +106,295 @@ func GetCurrentUser(req *http.Request) (User, error) { return user, nil } +// Utility function to get current instructor +func GetCurrentInstructor(req *http.Request) (Instructor, error) { + user, err := GetCurrentUser(req) + if err != nil { + return Instructor{}, err + } + + if user.Role != "Instructor" { + return Instructor{}, fmt.Errorf("current user is not an instructor") + } + + // Query Firebase to find the instructor object with the same email as the user + ref := firebaseClient.NewRef("instructors") + var instructorsMap map[string]Instructor + if err := ref.Get(context.TODO(), &instructorsMap); err != nil { + return Instructor{}, fmt.Errorf("error reading instructors: %v", err) + } + + // Find the instructor with the same email as the user + var instructor Instructor + found := false + for _, i := range instructorsMap { + if i.Email == user.Email { + instructor = i + found = true + break + } + } + if !found { + return Instructor{}, fmt.Errorf("instructor not found for the current user") + } + return instructor, nil +} + +// Handler to get classes for the current instructor +func GetInstructorClasses(res http.ResponseWriter, req *http.Request) { + instructor, err := GetCurrentInstructor(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + // Query Firebase to get classes for the instructor + ref := firebaseClient.NewRef("classes") + var classesMap map[string]Class + if err := ref.Get(context.TODO(), &classesMap); err != nil { + http.Error(res, fmt.Sprintf("error reading classes: %v", err), http.StatusInternalServerError) + return + } + + var cp, dn, ie, py, sc, te bool + cp, dn, ie, py, sc, te = false, false, false, false, false, false + + // Filter classes by instructor's email + var instructorClasses [][]string + for _, class := range classesMap { + if class.Instructor == instructor.Email && class.Name == "CP" && !cp { + instructorClasses = append(instructorClasses, []string{"Coding Pioneers", class.FolderID}) + cp = true + } else if class.Instructor == instructor.Email && class.Name == "DN" && !dn { + instructorClasses = append(instructorClasses, []string{"Digital Navigators", class.FolderID}) + dn = true + } else if class.Instructor == instructor.Email && class.Name == "IE" && !ie { + instructorClasses = append(instructorClasses, []string{"Innovation Engineers", class.FolderID}) + ie = true + } else if class.Instructor == instructor.Email && class.Name == "PY" && !py { + instructorClasses = append(instructorClasses, []string{"Python", class.FolderID}) + py = true + } else if class.Instructor == instructor.Email && class.Name == "SC" && !sc { + instructorClasses = append(instructorClasses, []string{"Scratch", class.FolderID}) + sc = true + } else if class.Instructor == instructor.Email && class.Name == "TE" && !te { + instructorClasses = append(instructorClasses, []string{"Tech Explorers", class.FolderID}) + te = true + } + } + + // Return the instructor's classes as JSON + res.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(instructorClasses); err != nil { + http.Error(res, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError) + } +} + +// Handler to get classes for the current instructor +func GetInstructorClassIds(res http.ResponseWriter, req *http.Request) { + instructor, err := GetCurrentInstructor(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + // Query Firebase to get classes for the instructor + ref := firebaseClient.NewRef("classes") + var classesMap map[string]Class + if err := ref.Get(context.TODO(), &classesMap); err != nil { + http.Error(res, fmt.Sprintf("error reading classes: %v", err), http.StatusInternalServerError) + return + } + // Filter classes by instructor's email + var instructorClasses []string + for _, class := range classesMap { + if class.Instructor == instructor.Email { + instructorClasses = append(instructorClasses, class.ClassID) + } + } + + // Return the instructor's classes as JSON + res.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(instructorClasses); err != nil { + http.Error(res, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError) + } +} + +// Function to get students by ClassID and their corresponding Parent FolderIDs +func GetStudentsAndFoldersByClassID(classID string, res http.ResponseWriter, req *http.Request) { + // Query Firebase to get students + ref := firebaseClient.NewRef("students") + var studentsMap map[string]Student + if err := ref.Get(context.TODO(), &studentsMap); err != nil { + http.Error(res, fmt.Sprintf("error reading students: %v", err), http.StatusInternalServerError) + return + } + + // Query Firebase to get parents + refParents := firebaseClient.NewRef("parents") + var parentsMap map[string]Parent + if err := refParents.Get(context.TODO(), &parentsMap); err != nil { + http.Error(res, fmt.Sprintf("error reading parents: %v", err), http.StatusInternalServerError) + return + } + + // Prepare the result + var result []map[string]string + for _, student := range studentsMap { + if student.ClassID == classID { + parent, ok := parentsMap[student.ParentID] + if !ok { + http.Error(res, fmt.Sprintf("parent not found for student %s", student.Name), http.StatusNotFound) + return + } + result = append(result, map[string]string{ + "name": student.Name, + "folderID": parent.FolderID, + }) + } + } + + // Return the result as JSON + res.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(result); err != nil { + http.Error(res, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError) + } +} + +// Utility function to get current student +func GetCurrentStudent(req *http.Request) (Student, error) { + user, err := GetCurrentUser(req) + if err != nil { + return Student{}, err + } + + if user.Role != "Student" { + return Student{}, fmt.Errorf("current user is not a student") + } + + // Query Firebase to find the student object with the same email as the user + ref := firebaseClient.NewRef("students") + var studentsMap map[string]Student + if err := ref.Get(context.TODO(), &studentsMap); err != nil { + return Student{}, fmt.Errorf("error reading students: %v", err) + } + + // Find the student with the same email as the user + var student Student + found := false + for _, s := range studentsMap { + if s.Email == user.Email { + student = s + found = true + break + } + } + if !found { + return Student{}, fmt.Errorf("student not found for the current user") + } + return student, nil +} + +// Function to get the folder ID of a student's class +func GetStudentFolder(req *http.Request) (string, error) { + student, err := GetCurrentStudent(req) + if err != nil { + return "", err + } + + classID := student.ClassID + if classID == "" { + return "", fmt.Errorf("student is not enrolled in any class") + } + + // Fetch class information based on ClassID + class, err := GetClassByID(classID) + if err != nil { + return "", err + } + return class.FolderID, nil +} + +// Function to fetch a class by its ID from Firebase +func GetClassByID(classID string) (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) + } + + return class, nil +} + +// Utility function to get current parent +func GetCurrentParent(req *http.Request) (Parent, error) { + user, err := GetCurrentUser(req) + if err != nil { + return Parent{}, err + } + + if user.Role != "Parent" { + return Parent{}, fmt.Errorf("current user is not a parent") + } + + // Query Firebase to find the parent object with the same email as the user + ref := firebaseClient.NewRef("parents") + var parentsMap map[string]Parent + if err := ref.Get(context.TODO(), &parentsMap); err != nil { + return Parent{}, fmt.Errorf("error reading parents: %v", err) + } + + // Find the parent with the same email as the user + var parent Parent + found := false + for _, p := range parentsMap { + if p.Email == user.Email { + parent = p + found = true + break + } + } + if !found { + return Parent{}, fmt.Errorf("parent not found for the current user") + } + return parent, nil +} + +// Utility function to get current admin +func GetCurrentAdmin(req *http.Request) (Admin, error) { + user, err := GetCurrentUser(req) + if err != nil { + return Admin{}, err + } + + if user.Role != "Admin" { + return Admin{}, fmt.Errorf("current user is not an admin") + } + + // Query Firebase to find the admin object with the same email as the user + ref := firebaseClient.NewRef("admins") + var adminsMap map[string]Admin + if err := ref.Get(context.TODO(), &adminsMap); err != nil { + return Admin{}, fmt.Errorf("error reading admins: %v", err) + } + + // Find the admin with the same email as the user + var admin Admin + found := false + for _, a := range adminsMap { + if a.Email == user.Email { + admin = a + found = true + break + } + } + if !found { + return Admin{}, fmt.Errorf("admin not found for the current user") + } + return admin, nil +} + // Utility functions to check roles func isAdmin(user User) bool { return user.Role == "Admin" @@ -128,48 +459,6 @@ func isParentChildInClass(user User, students []Student, class Class) bool { return false } -func getUserRole(email string) (User, string, error) { - ctx := context.Background() - var user User - var userRole string - - // Check if firebaseClient is initialized - if firebaseClient == nil { - log.Println("Firebase client is not initialized") - return user, userRole, fmt.Errorf("firebase client is not initialized") - } - - // Map categories to Firebase references - categoryRefs := map[string]string{ - "Student": "/students", - "Parent": "/parents", - "Instructor": "/instructors", - "Admin": "/admins", - } - - // Iterate through each category and check if the email exists - for category, ref := range categoryRefs { - categoryRef := firebaseClient.NewRef(ref) - dataSnapshot, err := categoryRef.OrderByChild("email").EqualTo(email).GetOrdered(ctx) - if err != nil { - log.Printf("Error fetching data from %s: %v", category, err) - continue - } - - // Check if dataSnapshot has any children - if len(dataSnapshot) > 0 { - userRole = category - // Assuming dataSnapshot[0] is the first match and it contains the user data - if err := dataSnapshot[0].Unmarshal(&user); err != nil { - log.Printf("Error unmarshalling data for %s: %v", category, err) - continue - } - break - } - } - return user, userRole, nil -} - // CRUD operations with role checks // Student CRUD @@ -241,7 +530,9 @@ func searchStudents(name, class string) ([]Student, error) { } var filteredStudents []Student for _, student := range students { - if name == "" || strings.Contains(strings.ToLower(student.Name), strings.ToLower(name)) { + // Check both name and class + if (name == "" || strings.Contains(strings.ToLower(student.Name), strings.ToLower(name))) && + (class == "" || strings.Contains(strings.ToLower(student.ClassID), strings.ToLower(class))) { filteredStudents = append(filteredStudents, student) } } diff --git a/database_model.go b/database_model.go index 99dd27c..fa47f7a 100644 --- a/database_model.go +++ b/database_model.go @@ -24,8 +24,6 @@ type Student struct { LessonCredits float32 `json:"lesson_credits"` ClassID string `json:"class_id"` ParentID string `json:"parent_id"` - // Instructor string `json:"instructor"` - // ParentName string `json:"parent_name"` } // Instructor struct for storing instructor information @@ -44,10 +42,12 @@ type Admin struct { // Parent struct for storing parent information type Parent struct { User + FolderID string `json:"folder_id"` } type Class struct { ClassID string `json:"class_id"` + FolderID string `json:"folder_id"` Name string `json:"class_name"` Instructor string `json:"instructor"` Duration float64 `json:"duration"` diff --git a/database_test.go b/database_test.go index 0329564..b5a90a0 100644 --- a/database_test.go +++ b/database_test.go @@ -77,12 +77,15 @@ var ( Email: "janedoe_parent@nk.com", Role: "Parent", }, + FolderID: "1F38DgyV0V5q2CWzoO3xkHTC4FY6cNrig", } classes = []Class{ { - ClassID: "te-6-10", - Name: "Test Class", + ClassID: "te-6-10", + Name: "Test Class", + Instructor: "jeyvianang112462@gmail.com", + FolderID: "1zrj8iUefB9D0u1RXT8Pi0-At8aQf9n2m", }, } diff --git a/drive.go b/drive.go new file mode 100644 index 0000000..6ebbd41 --- /dev/null +++ b/drive.go @@ -0,0 +1,173 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "github.com/gorilla/mux" + "golang.org/x/oauth2/google" + "google.golang.org/api/drive/v3" + "google.golang.org/api/option" +) + +const ServiceAccountCredentials = "edusync-426009-343696fa49b1.json" + +func DriveHandler(router *mux.Router) { + router.HandleFunc("/api/files", func(res http.ResponseWriter, req *http.Request) { + folderID := req.URL.Query().Get("folder_id") + srv, err := createDriveService() + if err != nil { + http.Error(res, "Unable to create Drive service", http.StatusInternalServerError) + return + } + + files, err := listFiles(srv, folderID) + if err != nil { + http.Error(res, "Unable to retrieve files", http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(files) + }).Methods("GET") + + router.HandleFunc("/api/upload", func(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(res, "Invalid request method", http.StatusMethodNotAllowed) + return + } + + folderID := req.FormValue("folderId") + if folderID == "" { + http.Error(res, "folderId is required", http.StatusBadRequest) + return + } + + fileName := req.FormValue("fileName") + if fileName == "" { + http.Error(res, "fileName is required", http.StatusBadRequest) + return + } + + file, _, err := req.FormFile("file") + if err != nil { + http.Error(res, err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + srv, err := createDriveService() + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + uploadedFile, err := uploadFileToDrive(srv, folderID, file, fileName) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(uploadedFile) + }).Methods("POST") + + router.HandleFunc("/api/media-upload", func(res http.ResponseWriter, req *http.Request) { + // Initialize Google Drive client + srv, err := createDriveService() + if err != nil { + http.Error(res, "Failed to initialize Drive client", http.StatusInternalServerError) + return + } + + err = req.ParseMultipartForm(10 << 20) // 10 MB limit + if err != nil { + http.Error(res, "Failed to parse form", http.StatusBadRequest) + return + } + + files := req.MultipartForm.File["files"] + folderIds := req.MultipartForm.Value["folderIds"] + studentNames := req.MultipartForm.Value["studentNames"] + + if len(files) != len(folderIds) || len(folderIds) != len(studentNames) { + http.Error(res, "Mismatch between files, folder IDs, and student names", http.StatusBadRequest) + return + } + + var results []string + + for i, fileHeader := range files { + file, err := fileHeader.Open() + if err != nil { + http.Error(res, fmt.Sprintf("Failed to open file: %v", err), http.StatusInternalServerError) + return + } + defer file.Close() + + folderID := folderIds[i] + studentName := studentNames[i] + + _, err = uploadFileToDrive(srv, folderID, file, fileHeader.Filename) + if err != nil { + results = append(results, fmt.Sprintf("Failed to upload %s for %s: %v", fileHeader.Filename, studentName, err)) + } else { + results = append(results, fmt.Sprintf("Successfully uploaded %s for %s", fileHeader.Filename, studentName)) + } + } + + res.Header().Set("Content-Type", "application/json") + json.NewEncoder(res).Encode(results) + }).Methods("POST") +} + +func createDriveService() (*drive.Service, error) { + ctx := context.Background() + + b, err := os.ReadFile(ServiceAccountCredentials) + if err != nil { + return nil, fmt.Errorf("unable to read client secret file: %v", err) + } + + config, err := google.JWTConfigFromJSON(b, drive.DriveFileScope) + if err != nil { + return nil, fmt.Errorf("unable to parse client secret file to config: %v", err) + } + + client := config.Client(ctx) + + srv, err := drive.NewService(ctx, option.WithHTTPClient(client)) + if err != nil { + return nil, fmt.Errorf("unable to retrieve Drive client: %v", err) + } + + return srv, nil +} + +func listFiles(service *drive.Service, folderID string) ([]*drive.File, error) { + query := fmt.Sprintf("'%s' in parents", folderID) + fileList, err := service.Files.List().Q(query).PageSize(10).Fields("nextPageToken, files(id, name, mimeType)").Do() + if err != nil { + return nil, err + } + return fileList.Files, nil +} + +func uploadFileToDrive(srv *drive.Service, folderID string, file io.Reader, fileName string) (*drive.File, error) { + fileMetadata := &drive.File{ + Name: fileName, + Parents: []string{folderID}, + } + + // Create the file in Google Drive + res, err := srv.Files.Create(fileMetadata).Media(file).Do() + if err != nil { + return nil, fmt.Errorf("cannot create file: %v", err) + } + + return res, nil +} diff --git a/firebase.go b/firebase.go index 5cdbe7d..409c9f1 100644 --- a/firebase.go +++ b/firebase.go @@ -6,7 +6,7 @@ import ( "log" "os" - "github.com/joho/godotenv" + //"github.com/joho/godotenv" firebase "firebase.google.com/go" "google.golang.org/api/option" @@ -14,17 +14,17 @@ import ( // Use godot package to load/read the .env file and // return the value of the key (for local env) -func goDotEnvVariable(key string) string { +// func goDotEnvVariable(key string) string { - // load .env file - err := godotenv.Load(".env") +// // load .env file +// err := godotenv.Load(".env") - if err != nil { - log.Fatalf("Error loading .env file") - } +// if err != nil { +// log.Fatalf("Error loading .env file") +// } - return os.Getenv(key) -} +// return os.Getenv(key) +// } // InitializeFirebase initializes the Firebase app and sets the global firebaseClient variable func initializeFirebase() error { diff --git a/go.mod b/go.mod index 949ebac..20ad418 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module EduSync +module edusync go 1.22.3 @@ -7,28 +7,28 @@ require ( 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 - google.golang.org/api v0.188.0 + golang.org/x/oauth2 v0.21.0 + google.golang.org/api v0.189.0 ) require ( cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.7.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.4.0 // indirect - cloud.google.com/go/firestore v1.15.0 // indirect + cloud.google.com/go/auth v0.7.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/firestore v1.16.0 // indirect cloud.google.com/go/iam v1.1.10 // indirect cloud.google.com/go/longrunning v0.5.9 // indirect - cloud.google.com/go/storage v1.41.0 // indirect + cloud.google.com/go/storage v1.43.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect 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/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect @@ -38,15 +38,14 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index b6c6a64..2eab768 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts= -cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c= -cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= -cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= -cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= +cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI= cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k= cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= -cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= -cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -31,8 +31,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -73,16 +73,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 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= @@ -170,10 +168,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= -google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -181,12 +177,12 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b h1:dSTjko30weBaMj3eERKc0ZVXW4GudCswM3m+P++ukU0= -google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade h1:WxZOF2yayUHpHSbUE6NMzumUzBxYc3YGwo0YHnbzsJY= +google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/instructorHandler.go b/instructorHandler.go index 8c8a89a..729b256 100644 --- a/instructorHandler.go +++ b/instructorHandler.go @@ -16,4 +16,69 @@ func InstructorHandler(router *mux.Router) { } t.Execute(res, nil) }).Methods("GET") + + router.HandleFunc("/instructor/learning-materials/classes", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/instructor/lm-classes.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + router.HandleFunc("/instructor/classes/get-classes", func(res http.ResponseWriter, req *http.Request) { + GetInstructorClasses(res, req) + }).Methods("GET") + + router.HandleFunc("/instructor/learning-materials/upload", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/instructor/lm-upload.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + router.HandleFunc("/instructor/media/classes", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/instructor/media-classes.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + router.HandleFunc("/instructor/media/get-classes", func(res http.ResponseWriter, req *http.Request) { + GetInstructorClassIds(res, req) + }).Methods("GET") + + router.HandleFunc("/instructor/api/media", func(res http.ResponseWriter, req *http.Request) { + classId := req.URL.Query().Get("class_id") + GetStudentsAndFoldersByClassID(classId, res, req) + }).Methods("GET") + + router.HandleFunc("/instructor/media/upload", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/instructor/media-upload.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, nil) + }).Methods("GET") + + router.HandleFunc("/instructor/profile", func(res http.ResponseWriter, req *http.Request) { + instructor, err := GetCurrentInstructor(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + // Render the profile page + t, err := template.ParseFiles("templates/instructor/profile.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, instructor) + }).Methods("GET") } diff --git a/main.go b/main.go index 3de124c..7f234b8 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ func main() { // Set up authentication routes AuthHandler(router, config) MainHandler(router) + DriveHandler(router) log.Println("listening on localhost:8080") err = http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", router) diff --git a/mainHandler.go b/mainHandler.go index da44a74..78ed36e 100644 --- a/mainHandler.go +++ b/mainHandler.go @@ -1,5 +1,7 @@ +// This file contains the main handler for the web application. package main +//The import section declares packages that this source file depends on. import ( "html/template" "net/http" @@ -7,6 +9,7 @@ import ( "github.com/gorilla/mux" ) +// MainHandler handles the main page of the web application. func MainHandler(router *mux.Router) { router.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("templates/index.html") @@ -25,4 +28,40 @@ func MainHandler(router *mux.Router) { } t.Execute(res, false) }).Methods("GET") + + router.HandleFunc("/student-role", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/student.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, false) + }).Methods("GET") + + router.HandleFunc("/admin-role", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/admin.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, false) + }).Methods("GET") + + router.HandleFunc("/parent-role", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/parent.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, false) + }).Methods("GET") + + router.HandleFunc("/instructor-role", func(res http.ResponseWriter, req *http.Request) { + t, err := template.ParseFiles("templates/instructor.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, false) + }).Methods("GET") } diff --git a/parentHandler.go b/parentHandler.go index 8c97886..3f2ce0f 100644 --- a/parentHandler.go +++ b/parentHandler.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "html/template" "net/http" @@ -16,4 +17,34 @@ func ParentHandler(router *mux.Router) { } t.Execute(res, nil) }).Methods("GET") + + router.HandleFunc("/parent/profile", func(res http.ResponseWriter, req *http.Request) { + parent, err := GetCurrentParent(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + // Render the profile page + t, err := template.ParseFiles("templates/parent/profile.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, parent) + }).Methods("GET") + + router.HandleFunc("/parent/get-folder-id", func(res http.ResponseWriter, req *http.Request) { + parent, err := GetCurrentParent(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + response := map[string]string{"folder_id": parent.FolderID} + res.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(response); err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + } + }).Methods("GET") } diff --git a/studentHandler.go b/studentHandler.go index 1276a1c..f859343 100644 --- a/studentHandler.go +++ b/studentHandler.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "html/template" "net/http" @@ -16,4 +17,33 @@ func StudentHandler(router *mux.Router) { } t.Execute(res, nil) }).Methods("GET") + + router.HandleFunc("/student/get-folder-id", func(res http.ResponseWriter, req *http.Request) { + folderID, err := GetStudentFolder(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + response := map[string]string{"folder_id": folderID} + res.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(response); err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + } + }).Methods("GET") + + router.HandleFunc("/student/profile", func(res http.ResponseWriter, req *http.Request) { + student, err := GetCurrentStudent(req) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + + t, err := template.ParseFiles("templates/student/profile.html") + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + t.Execute(res, student) + }).Methods("GET") } diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..c1ec815 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,213 @@ + + + + EduSync - Admins + + + + + + + + + +
+ + +
+ + + +
+ +

Admin Role

+

Admins can use EduSync to edit details for students, parents, and instructors, and create announcements for parents. This platform enables admins to manage all the data and communication in an organized manner.

+ + +
+ EduSync: Connect, Learn, Succeed +
+ + +
+

If you have any queries, please email us at nkedusync@gmail.com. +
+
+ To sign up, please find more information at the NK Robotics Website.

+
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/templates/admin/create_announcement.html b/templates/admin/create_announcement.html index 4fbe75c..986949b 100644 --- a/templates/admin/create_announcement.html +++ b/templates/admin/create_announcement.html @@ -61,6 +61,23 @@ } }; +
@@ -78,6 +95,8 @@
  • Parents
  • Instructors
  • Announcements
  • +
  • Profile
  • +
  • Logout
  • @@ -90,10 +109,11 @@
    +


    -

    Send Announcement

    +

    Send Announcement



    diff --git a/templates/admin/create_instructor.html b/templates/admin/create_instructor.html index 7f3c997..a0d03b6 100644 --- a/templates/admin/create_instructor.html +++ b/templates/admin/create_instructor.html @@ -44,6 +44,23 @@ } } +
    @@ -61,15 +78,18 @@
  • Students
  • Parents
  • Announcements
  • +
  • Profile
  • +
  • Logout
  • +


    -

    Create New Instructor

    +

    Create New Instructor



    diff --git a/templates/admin/create_parent.html b/templates/admin/create_parent.html index 59135a9..98c1781 100644 --- a/templates/admin/create_parent.html +++ b/templates/admin/create_parent.html @@ -35,6 +35,23 @@ } } +
    @@ -52,15 +69,20 @@
  • Students
  • Parents
  • Announcements
  • +
  • Profile
  • +
  • Logout
  • +
    +
    +
    -

    Create New Parent

    +

    Create New Parent



    diff --git a/templates/admin/create_student.html b/templates/admin/create_student.html index 20a01d2..337e099 100644 --- a/templates/admin/create_student.html +++ b/templates/admin/create_student.html @@ -46,6 +46,23 @@ } } +
    @@ -63,6 +80,8 @@
  • Parents
  • Instructors
  • Announcements
  • +
  • Profile
  • +
  • Logout
  • @@ -75,10 +94,14 @@
    +
    +
    +

    Create New Student

    +

    diff --git a/templates/admin/edit_announcement.html b/templates/admin/edit_announcement.html index 4b543fa..bd1e73d 100644 --- a/templates/admin/edit_announcement.html +++ b/templates/admin/edit_announcement.html @@ -60,6 +60,23 @@ window.history.back(); } +
    @@ -77,6 +94,8 @@
  • Parents
  • Instructors
  • Announcements
  • +
  • Profile
  • +
  • Logout
  • @@ -89,10 +108,13 @@
    +
    - +
    +

    Edit Announcement Details

    +
    diff --git a/templates/admin/edit_instructor.html b/templates/admin/edit_instructor.html index 46a16f1..666867e 100644 --- a/templates/admin/edit_instructor.html +++ b/templates/admin/edit_instructor.html @@ -1,12 +1,12 @@ - Edit Instructor Details - - - + Edit Instructor Details + + + + -
    -

    EduSync provides administrative tools and functionalities to streamline educational management tasks.

    -

    Visit EduSync Website

    -

    Contact Us

    -

    Singapore, Singapore

    +

    Visit EduSync Website

    +

    Contact Us

    +

    Singapore, Singapore

    @@ -103,11 +111,11 @@

    Quick Contact

    diff --git a/templates/admin/profile.html b/templates/admin/profile.html new file mode 100644 index 0000000..6141549 --- /dev/null +++ b/templates/admin/profile.html @@ -0,0 +1,191 @@ + + + + Edit Admin Profile + + + + + + + + + + +
    + +
    + + +
    +
    +
    +

    Edit Admin Profile

    +
    + +
     
    +
    +
    +   +    + +
    +
    +
    +   +    + +
    +
    +
     
    +
    +
     
    +
    +
    + + + +
    + +
    +
    + + + + + + + + + diff --git a/templates/admin/search_announcement.html b/templates/admin/search_announcement.html index 16faf70..6c9e1d9 100644 --- a/templates/admin/search_announcement.html +++ b/templates/admin/search_announcement.html @@ -74,6 +74,23 @@ await fetchAnnouncements(`/admin/api/search_announcement`); } +
    @@ -106,8 +123,11 @@
    +
    +

    Search Announcements

    +
    diff --git a/templates/admin/search_instructor.html b/templates/admin/search_instructor.html index e6cd837..ea5f645 100644 --- a/templates/admin/search_instructor.html +++ b/templates/admin/search_instructor.html @@ -54,14 +54,32 @@ resultsDiv.appendChild(instructorDiv); }); } else { - resultsDiv.innerHTML = 'No instructors found.'; + resultsDiv.innerHTML = 'No instructors found.'; } } + + async function resetFilters() { + document.getElementById('nameFilter').value = ''; + await fetchInstructors(`/admin/api/search_instructor`); + } window.onload = async function() { await fetchInstructors(`/admin/api/search_instructor`); } +
    @@ -69,7 +87,9 @@
    - Logo Image + + Logo Image +