389 lines
9.5 KiB
Go
389 lines
9.5 KiB
Go
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)
|
|
}
|