package main import ( "database/sql" "embed" "fmt" "html/template" "log" "net/http" "time" "github.com/gorilla/sessions" "golang.org/x/crypto/bcrypt" _ "github.com/mattn/go-sqlite3" ) //go:embed templates/* var templatesFS embed.FS //go:embed schema.sql var schemaSQL string // Store for sessions (adjust the key to something secure) var store = sessions.NewCookieStore([]byte("your-secret-key")) type Trainer struct { ID int Name string Email string Areas []TrainingArea } type Training struct { ID int Title string TrainingType string Mode string Start time.Time End time.Time Status string Trainers []Trainer Area TrainingArea } type TrainingArea struct { ID int Name string Trainers []Trainer } func (ta TrainingArea) IsTrainerAssigned(trainerID int) bool { if ta.Trainers == nil { return false } for _, trainer := range ta.Trainers { if trainer.ID == trainerID { return true } } return false } var db *sql.DB func initDB() { var err error db, err = sql.Open("sqlite3", "training.db") if err != nil { log.Fatalf("Error opening database: %v", err) } _, err = db.Exec(schemaSQL) if err != nil { log.Fatalf("Error initializing database schema: %v", err) } log.Println("Database initialized successfully") } var tmpl *template.Template func initTemplates() { var err error tmpl, err = template.ParseFS(templatesFS, "templates/*.html") if err != nil { log.Fatalf("Error parsing templates: %v", err) } log.Println("Templates loaded successfully") } func homeHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling home page request") err := tmpl.ExecuteTemplate(w, "home.html", nil) if err != nil { log.Printf("Error in templating page home.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func trainingAreaListHandler(w http.ResponseWriter, r *http.Request) { log.Println("Fetching training areas and trainers") areas, err := fetchTrainingAreasWithTrainers() if err != nil { log.Printf("Error fetching training areas: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } trainers, err := fetchTrainers() if err != nil { log.Printf("Error fetching trainers: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } data := struct { Areas []TrainingArea Trainers []Trainer }{ Areas: areas, Trainers: trainers, } err = tmpl.ExecuteTemplate(w, "training_area_list.html", data) if err != nil { log.Printf("Error in templating page training_area_list.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) return } log.Println("Training areas and trainers displayed successfully") } func fetchTrainingAreasWithTrainers() ([]TrainingArea, error) { log.Println("Executing query to fetch training areas with trainers") rows, err := db.Query(` SELECT ta.id, ta.name, t.id, t.name, t.email FROM training_areas ta LEFT JOIN trainer_training_areas tta ON ta.id = tta.training_area_id LEFT JOIN trainers t ON tta.trainer_id = t.id ORDER BY ta.id, t.name`) if err != nil { log.Printf("Database error while fetching training areas: %v", err) return nil, err } defer rows.Close() areaMap := make(map[int]*TrainingArea) for rows.Next() { var areaID int var areaName string var trainerID sql.NullInt64 var trainerName sql.NullString var trainerEmail sql.NullString err := rows.Scan(&areaID, &areaName, &trainerID, &trainerName, &trainerEmail) if err != nil { log.Printf("Error scanning row for training area: %v", err) return nil, err } _, exists := areaMap[areaID] if !exists { area := &TrainingArea{ID: areaID, Name: areaName} areaMap[areaID] = area } if trainerID.Valid { areaMap[areaID].Trainers = append(areaMap[areaID].Trainers, Trainer{ ID: int(trainerID.Int64), Name: trainerName.String, Email: trainerEmail.String, }) } } var areas []TrainingArea for _, area := range areaMap { areas = append(areas, *area) } log.Printf("Found %d training areas", len(areas)) return areas, nil } func assignTrainerToAreaHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling trainer assignment to training area") if r.Method == http.MethodPost { trainerID := r.FormValue("trainer_id") areaID := r.FormValue("training_area_id") if trainerID == "" || areaID == "" { log.Println("Missing trainer or training area ID") http.Error(w, "Trainer and Training Area are required", http.StatusBadRequest) return } _, err := db.Exec("INSERT OR IGNORE INTO trainer_training_areas (trainer_id, training_area_id) VALUES (?, ?)", trainerID, areaID) if err != nil { log.Printf("Error assigning trainer to area: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } log.Println("Trainer assigned to area successfully") http.Redirect(w, r, "/training-areas", http.StatusSeeOther) } } func addTrainingAreaHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling request to add a new training area") if r.Method == http.MethodPost { name := r.FormValue("name") if name == "" { log.Println("Training area name is required") http.Error(w, "Name is required", http.StatusBadRequest) return } _, err := db.Exec("INSERT INTO training_areas (name) VALUES (?)", name) if err != nil { log.Printf("Error adding training area: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } log.Println("New training area added successfully") http.Redirect(w, r, "/training-areas", http.StatusSeeOther) return } err := tmpl.ExecuteTemplate(w, "add_training_area.html", nil) if err != nil { log.Printf("Error in templating page add_training_area.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func fetchTrainings() ([]Training, error) { log.Println("Fetching trainings from the database") rows, err := db.Query("SELECT id, title, training_type, mode, start, end, status FROM trainings") if err != nil { log.Printf("Error fetching trainings: %v", err) return nil, err } defer rows.Close() var trainings []Training for rows.Next() { var t Training var start, end string err := rows.Scan(&t.ID, &t.Title, &t.TrainingType, &t.Mode, &start, &end, &t.Status) if err != nil { log.Printf("Error scanning row for training: %v", err) return nil, err } t.Start, _ = time.Parse("2006-01-02 15:04:05", start) t.End, _ = time.Parse("2006-01-02 15:04:05", end) trainings = append(trainings, t) } log.Printf("Found %d trainings", len(trainings)) return trainings, nil } func trainingListHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling training list request") trainings, err := fetchTrainings() if err != nil { log.Printf("Error fetching trainings: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } err = tmpl.ExecuteTemplate(w, "training_list.html", trainings) if err != nil { log.Printf("Error in templating page training_list.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func fetchTrainers() ([]Trainer, error) { log.Println("Fetching trainers from the database") rows, err := db.Query("SELECT id, name, email FROM trainers") if err != nil { log.Printf("Error fetching trainers: %v", err) return nil, err } defer rows.Close() var trainers []Trainer for rows.Next() { var t Trainer err := rows.Scan(&t.ID, &t.Name, &t.Email) if err != nil { log.Printf("Error scanning row for trainer: %v", err) return nil, err } trainers = append(trainers, t) } log.Printf("Found %d trainers", len(trainers)) return trainers, nil } func trainerListHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling trainer list request") trainers, err := fetchTrainers() if err != nil { log.Printf("Error fetching trainers: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } err = tmpl.ExecuteTemplate(w, "trainer_list.html", trainers) if err != nil { log.Printf("Error in templating page trainer_list.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func addTrainerHandler(w http.ResponseWriter, r *http.Request) { log.Println("Handling request to add a new trainer") if r.Method == http.MethodPost { name := r.FormValue("name") email := r.FormValue("email") if name == "" || email == "" { log.Println("Trainer name and email are required") http.Error(w, "Name and Email are required", http.StatusBadRequest) return } _, err := db.Exec("INSERT INTO trainers (name, email) VALUES (?, ?)", name, email) if err != nil { log.Printf("Error adding trainer: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } log.Println("New trainer added successfully") http.Redirect(w, r, "/trainers", http.StatusSeeOther) return } err := tmpl.ExecuteTemplate(w, "add_trainer.html", nil) if err != nil { log.Printf("Error in templating page add_trainer.html: %v", err) http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func hashPassword(password string) (string, error) { log.Println("Hashing password") bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(bytes), err } func setupAdminHandler(w http.ResponseWriter, r *http.Request) { // Check if an admin exists var count int err := db.QueryRow("SELECT COUNT(*) FROM admins").Scan(&count) if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) return } // If an admin exists, redirect to login if count > 0 { http.Redirect(w, r, "/login", http.StatusSeeOther) return } // If it's a POST request, create the admin if r.Method == http.MethodPost { name := r.FormValue("name") email := r.FormValue("email") password := r.FormValue("password") if email == "" || password == "" || name == "" { http.Error(w, "Name, email and password are required", http.StatusBadRequest) return } // Hash the password hashedPassword, err := hashPassword(password) if err != nil { http.Error(w, "Error hashing password", http.StatusInternalServerError) return } // Insert the admin into the database _, err = db.Exec("INSERT INTO admins (name, email, password) VALUES (?, ?, ?)", name, email, hashedPassword) if err != nil { http.Error(w, "Error saving admin", http.StatusInternalServerError) return } // Redirect to login after setup http.Redirect(w, r, "/login", http.StatusSeeOther) return } // Show the admin setup page err = tmpl.ExecuteTemplate(w, "setup_admin.html", nil) if err != nil { http.Error(w, "Template rendering error", http.StatusInternalServerError) } } func redirectIfNoAdmin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var count int err := db.QueryRow("SELECT COUNT(*) FROM admins").Scan(&count) if err != nil || count == 0 { http.Redirect(w, r, "/setup-admin", http.StatusSeeOther) return } session, err := store.Get(r, "user-session") if err != nil || session.Values["username"] == nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } next.ServeHTTP(w, r) }) } func checkPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // / loginHandler handles the user login func loginHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // Show the login page log.Println("Rendering login page.") err := tmpl.ExecuteTemplate(w, "login.html", nil) if err != nil { log.Println("Error rendering login template:", err) http.Error(w, "Template rendering error", http.StatusInternalServerError) } return } if r.Method == http.MethodPost { username := r.FormValue("username") password := r.FormValue("password") log.Printf("Received login attempt for username: %s", username) // Query the admin table for the password hash var storedHash string err := db.QueryRow("SELECT password FROM admins WHERE email = $1", username).Scan(&storedHash) if err != nil { if err == sql.ErrNoRows { // User doesn't exist log.Println("Invalid username or password: no matching user found.") http.Error(w, "Invalid username or password", http.StatusUnauthorized) } else { // Database error log.Println("Error querying database:", err) http.Error(w, "Database error", http.StatusInternalServerError) } return } // Check if the provided password matches the stored hash if !checkPasswordHash(password, storedHash) { log.Println("Invalid username or password: password mismatch.") http.Error(w, "Invalid username or password", http.StatusUnauthorized) return } // Password is correct; set up a session session, err := store.Get(r, "user-session") if err != nil { log.Println("Error creating session:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } // Store username in the session session.Values["username"] = username err = session.Save(r, w) if err != nil { log.Println("Error saving session:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } log.Printf("User '%s' successfully logged in.", username) // Redirect to the home page (or dashboard) http.Redirect(w, r, "/", http.StatusSeeOther) } } // logoutHandler logs the user out by destroying their session func logoutHandler(w http.ResponseWriter, r *http.Request) { log.Println("Logging out user.") session, err := store.Get(r, "user-session") if err != nil { log.Println("Error fetching session:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } // Clear the session session.Options.MaxAge = -1 err = session.Save(r, w) if err != nil { log.Println("Error saving session:", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } log.Println("User successfully logged out.") // Redirect to the login page or home page http.Redirect(w, r, "/login", http.StatusSeeOther) } func initServerHandlers() { http.Handle("/", redirectIfNoAdmin(http.HandlerFunc(homeHandler))) http.Handle("/trainings", redirectIfNoAdmin(http.HandlerFunc(trainingListHandler))) http.Handle("/trainers", redirectIfNoAdmin(http.HandlerFunc(trainerListHandler))) http.Handle("/trainers/add", redirectIfNoAdmin(http.HandlerFunc(addTrainerHandler))) http.Handle("/training-areas", redirectIfNoAdmin(http.HandlerFunc(trainingAreaListHandler))) http.Handle("/training-areas/add", redirectIfNoAdmin(http.HandlerFunc(addTrainingAreaHandler))) http.Handle("/training-areas/assign-trainer", redirectIfNoAdmin(http.HandlerFunc(assignTrainerToAreaHandler))) http.HandleFunc("/setup-admin", setupAdminHandler) http.HandleFunc("/login", loginHandler) http.HandleFunc("/logout", logoutHandler) } func main() { initDB() initTemplates() initServerHandlers() log.Println("Starting server on :8080") err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatalf("Error starting server: %v", err) } }