package main import ( "database/sql" "embed" "fmt" "html/template" "net/http" "time" "golang.org/x/crypto/bcrypt" _ "github.com/mattn/go-sqlite3" ) //go:embed templates/* var templatesFS embed.FS //go:embed schema.sql var schemaSQL string 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 { panic(err) } _, err = db.Exec(schemaSQL) if err != nil { panic(err) } } var tmpl *template.Template func initTemplates() { var err error tmpl, err = template.ParseFS(templatesFS, "templates/*.html") if err != nil { panic(fmt.Sprintf("Error parsing templates: %v", err)) } } func homeHandler(w http.ResponseWriter, r *http.Request) { err := tmpl.ExecuteTemplate(w, "home.html", nil) if err != nil { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func trainingAreaListHandler(w http.ResponseWriter, r *http.Request) { areas, err := fetchTrainingAreasWithTrainers() if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) return } trainers, err := fetchTrainers() if err != nil { 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 { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) return } } func fetchTrainingAreasWithTrainers() ([]TrainingArea, error) { 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 { 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 { 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) } return areas, nil } func assignTrainerToAreaHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { trainerID := r.FormValue("trainer_id") areaID := r.FormValue("training_area_id") if trainerID == "" || areaID == "" { 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 { http.Error(w, "Database error", http.StatusInternalServerError) return } http.Redirect(w, r, "/training-areas", http.StatusSeeOther) } } func addTrainingAreaHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { name := r.FormValue("name") if name == "" { http.Error(w, "Name is required", http.StatusBadRequest) return } _, err := db.Exec("INSERT INTO training_areas (name) VALUES (?)", name) if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) return } http.Redirect(w, r, "/training-areas", http.StatusSeeOther) return } err := tmpl.ExecuteTemplate(w, "add_training_area.html", nil) if err != nil { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func fetchTrainings() ([]Training, error) { rows, err := db.Query("SELECT id, title, training_type, mode, start, end, status FROM trainings") if err != nil { 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 { 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) } return trainings, nil } func trainingListHandler(w http.ResponseWriter, r *http.Request) { trainings, err := fetchTrainings() if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) return } err = tmpl.ExecuteTemplate(w, "training_list.html", trainings) if err != nil { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func fetchTrainers() ([]Trainer, error) { rows, err := db.Query("SELECT id, name, email FROM trainers") if err != nil { 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 { return nil, err } trainers = append(trainers, t) } return trainers, nil } func trainerListHandler(w http.ResponseWriter, r *http.Request) { trainers, err := fetchTrainers() if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) return } err = tmpl.ExecuteTemplate(w, "trainer_list.html", trainers) if err != nil { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func addTrainerHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { name := r.FormValue("name") email := r.FormValue("email") if name == "" || email == "" { 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 { http.Error(w, "Database error", http.StatusInternalServerError) return } http.Redirect(w, r, "/trainers", http.StatusSeeOther) return } err := tmpl.ExecuteTemplate(w, "add_trainer.html", nil) if err != nil { http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError) } } func hashPassword(password string) (string, error) { 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 } next.ServeHTTP(w, r) }) } func main() { initDB() initTemplates() 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) fmt.Println("Server running on http://localhost:8080") http.ListenAndServe(":8080", nil) }