training-management-system/main.go

555 lines
15 KiB
Go

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)
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
}
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)
}
}