diff --git a/handlers/account.go b/handlers/account.go index e314e8c..7f33341 100644 --- a/handlers/account.go +++ b/handlers/account.go @@ -1,7 +1,6 @@ package handlers import ( - "html/template" "log" "net/http" "synlotto-website/helpers" @@ -18,12 +17,7 @@ func Login(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusSeeOther) return } - - tmpl := template.Must(template.New("login.html").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/topbar.html", - "templates/account/login.html", - )) + tmpl := helpers.LoadTemplateFiles("login.html", "templates/account/login.html") context := helpers.TemplateContext(w, r, models.TemplateData{}) context["csrfField"] = csrf.TemplateField(r) @@ -101,10 +95,7 @@ func Logout(w http.ResponseWriter, r *http.Request) { func Signup(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - tmpl := template.Must(template.ParseFiles( - "templates/layout.html", - "templates/account/signup.html", - )) + tmpl := helpers.LoadTemplateFiles("signup.html", "templates/account/signup.html") tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{ "csrfField": csrf.TemplateField(r), diff --git a/handlers/admin/audit.go b/handlers/admin/audit.go index 3c84ffc..03c2273 100644 --- a/handlers/admin/audit.go +++ b/handlers/admin/audit.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net/http" "synlotto-website/helpers" @@ -46,10 +45,8 @@ func AdminAccessLogHandler(db *sql.DB) http.HandlerFunc { } context["AuditLogs"] = logs - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/logs/access_log.html", - )) + tmpl := helpers.LoadTemplateFiles("access_log.html", "templates/admin/logs/access_log.html") + _ = tmpl.ExecuteTemplate(w, "layout", context) }) } @@ -84,10 +81,7 @@ func AuditLogHandler(db *sql.DB) http.HandlerFunc { context["AuditLogs"] = logs - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/logs/audit.html", - )) + tmpl := helpers.LoadTemplateFiles("audit.html", "templates/admin/logs/audit.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/handlers/admin/dashboard.go b/handlers/admin/dashboard.go index 1333a2b..960881a 100644 --- a/handlers/admin/dashboard.go +++ b/handlers/admin/dashboard.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net/http" @@ -55,10 +54,7 @@ func AdminDashboardHandler(db *sql.DB) http.HandlerFunc { } context["MatchLogs"] = logs - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/dashboard.html", - )) + tmpl := helpers.LoadTemplateFiles("dashboard.html", "templates/admin/dashboard.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/handlers/admin/draws.go b/handlers/admin/draws.go index 49eb8bb..ad569b2 100644 --- a/handlers/admin/draws.go +++ b/handlers/admin/draws.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net/http" @@ -31,10 +30,8 @@ func NewDrawHandler(db *sql.DB) http.HandlerFunc { return } - tmpl := template.Must(template.New("new_draw").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/draws/new_draw.html", - )) + tmpl := helpers.LoadTemplateFiles("new_draw", "templates/admin/draws/new_draw.html") + tmpl.ExecuteTemplate(w, "layout", context) }) } @@ -103,10 +100,8 @@ func ListDrawsHandler(db *sql.DB) http.HandlerFunc { context["Draws"] = draws - tmpl := template.Must(template.New("draw_list").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/draws/list.html", - )) + tmpl := helpers.LoadTemplateFiles("list.html", "templates/admin/draws/list.html") + tmpl.ExecuteTemplate(w, "layout", context) }) } diff --git a/handlers/admin/manualtriggers.go b/handlers/admin/manualtriggers.go index 52209a7..6e9de20 100644 --- a/handlers/admin/manualtriggers.go +++ b/handlers/admin/manualtriggers.go @@ -3,7 +3,6 @@ package handlers import ( "database/sql" "fmt" - "html/template" "log" "net/http" "net/url" @@ -18,7 +17,6 @@ func AdminTriggersHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { context := helpers.TemplateContext(w, r, models.TemplateData{}) - // Inject flash message if available if flash := r.URL.Query().Get("flash"); flash != "" { context["Flash"] = flash } @@ -69,16 +67,11 @@ func AdminTriggersHandler(db *sql.DB) http.HandlerFunc { flashMsg = "⚠️ Unknown action." } - // Redirect back with flash message http.Redirect(w, r, "/admin/triggers?flash="+url.QueryEscape(flashMsg), http.StatusSeeOther) return } - // Render the admin trigger page - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/triggers.html", - )) + tmpl := helpers.LoadTemplateFiles("triggers.html", "templates/admin/triggers.html") err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/handlers/admin/prizes.go b/handlers/admin/prizes.go index 31163c5..59abe7f 100644 --- a/handlers/admin/prizes.go +++ b/handlers/admin/prizes.go @@ -3,7 +3,6 @@ package handlers import ( "database/sql" "fmt" - "html/template" "net/http" "strconv" "synlotto-website/helpers" @@ -13,10 +12,8 @@ import ( func AddPrizesHandler(db *sql.DB) http.HandlerFunc { return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/draws/prizes/add_prizes.html", - )) + tmpl := helpers.LoadTemplateFiles("add_prizes.html", "templates/admin/draws/prizes/add_prizes.html") + tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r, models.TemplateData{})) return } @@ -47,10 +44,8 @@ func AddPrizesHandler(db *sql.DB) http.HandlerFunc { func ModifyPrizesHandler(db *sql.DB) http.HandlerFunc { return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/admin/draws/prizes/modify_prizes.html", - )) + tmpl := helpers.LoadTemplateFiles("modify_prizes.html", "templates/admin/draws/prizes/modify_prizes.html") + tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r, models.TemplateData{})) return } diff --git a/handlers/draw_handler.go b/handlers/draw_handler.go index 47a837f..da04b6d 100644 --- a/handlers/draw_handler.go +++ b/handlers/draw_handler.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net/http" @@ -18,11 +17,7 @@ func NewDraw(db *sql.DB) http.HandlerFunc { context["Page"] = "new_draw" context["Data"] = nil - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/topbar.html", - "templates/new_draw.html", - )) + tmpl := helpers.LoadTemplateFiles("new_draw.html", "templates/new_draw.html") // ToDo: may need removing or moving add draw should be admin functionality and only when manually required. Potential live drawing of numbers in the future. err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { @@ -48,7 +43,6 @@ func Submit(w http.ResponseWriter, r *http.Request) { Thunderball: helpers.Atoi(r.FormValue("thunderball")), } - // For now you're appending to memory - can replace with DB insert later Draws = append(Draws, draw) log.Printf("📅 %s | 🛠 %s | 🎱 %d | 🔢 %d,%d,%d,%d,%d | ⚡ %d\n", diff --git a/handlers/home.go b/handlers/home.go index fa6f20a..61effee 100644 --- a/handlers/home.go +++ b/handlers/home.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net/http" "synlotto-website/helpers" @@ -13,11 +12,7 @@ func Home(db *sql.DB) http.HandlerFunc { data := BuildTemplateData(db, w, r) context := helpers.TemplateContext(w, r, data) - tmpl := template.Must(template.New("index.html").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/topbar.html", - "templates/index.html", - )) + tmpl := helpers.LoadTemplateFiles("index.html", "templates/index.html") err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/handlers/notifications.go b/handlers/notifications.go index 37a356c..e37d8b0 100644 --- a/handlers/notifications.go +++ b/handlers/notifications.go @@ -5,7 +5,6 @@ import ( "log" "net/http" "strconv" - "text/template" "synlotto-website/helpers" "synlotto-website/storage" @@ -16,13 +15,7 @@ func NotificationsHandler(db *sql.DB) http.HandlerFunc { data := BuildTemplateData(db, w, r) context := helpers.TemplateContext(w, r, data) - tmpl := template.Must(template.New("notifications.html"). - Funcs(helpers.TemplateFuncs()). - ParseFiles( - "templates/layout.html", - "templates/topbar.html", - "templates/account/notifications.html", - )) + tmpl := helpers.LoadTemplateFiles("notifications.html", "templates/account/notifications.html") err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { @@ -63,13 +56,7 @@ func MarkNotificationReadHandler(db *sql.DB) http.HandlerFunc { context := helpers.TemplateContext(w, r, data) context["Notification"] = notification - tmpl := template.Must(template.New("read.html"). - Funcs(helpers.TemplateFuncs()). - ParseFiles( - "templates/layout.html", - "templates/topbar.html", - "templates/account/notifications/read.html", - )) + tmpl := helpers.LoadTemplateFiles("read.html", "templates/account/notifications/read.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/handlers/results.go b/handlers/results.go index 18e565a..fbc5101 100644 --- a/handlers/results.go +++ b/handlers/results.go @@ -2,7 +2,6 @@ package handlers import ( "database/sql" - "html/template" "log" "net" "net/http" @@ -110,10 +109,7 @@ func ResultsThunderball(db *sql.DB) http.HandlerFunc { noResultsMsg = "No results found for \"" + query + "\"" } - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/results/thunderball.html", - )) + tmpl := helpers.LoadTemplateFiles("thunderball.html", "templates/results/thunderball.html") err = tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{ "Results": results, diff --git a/handlers/ticket_handler.go b/handlers/ticket_handler.go index f06b3ea..4ad0268 100644 --- a/handlers/ticket_handler.go +++ b/handlers/ticket_handler.go @@ -3,7 +3,6 @@ package handlers import ( "database/sql" "fmt" - "html/template" "io" "log" "net/http" @@ -44,10 +43,7 @@ func AddTicket(db *sql.DB) http.HandlerFunc { context["csrfField"] = csrf.TemplateField(r) context["DrawDates"] = drawDates - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/account/tickets/add_ticket.html", - )) + tmpl := helpers.LoadTemplateFiles("add_ticket.html", "templates/account/tickets/add_ticket.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { @@ -362,10 +358,7 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc { context := helpers.TemplateContext(w, r, models.TemplateData{}) context["Tickets"] = tickets - tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/account/tickets/my_tickets.html", - )) + tmpl := helpers.LoadTemplateFiles("my_tickets.html", "templates/account/tickets/my_tickets.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { diff --git a/helpers/pages.go b/helpers/pages.go index eb8ffc0..e4074bf 100644 --- a/helpers/pages.go +++ b/helpers/pages.go @@ -2,18 +2,23 @@ package helpers // ToDo should be a handler? import ( - "html/template" + "fmt" + "log" "net/http" "synlotto-website/models" ) -func Render403(w http.ResponseWriter, r *http.Request) { +func RenderError(w http.ResponseWriter, r *http.Request, statusCode int) { context := TemplateContext(w, r, models.TemplateData{}) - tmpl := template.Must(template.New("").Funcs(TemplateFuncs()).ParseFiles( - "templates/layout.html", - "templates/error/403.html", - )) - tmpl.ExecuteTemplate(w, "layout", context) + page := fmt.Sprintf("templates/error/%d.html", statusCode) + tmpl := LoadTemplateFiles(fmt.Sprintf("%d.html", statusCode), page) + + w.WriteHeader(statusCode) + err := tmpl.ExecuteTemplate(w, "layout", context) + if err != nil { + log.Printf("❌ Failed to render error page for %d: %v", statusCode, err) + http.Error(w, http.StatusText(statusCode), statusCode) + } } diff --git a/helpers/template.go b/helpers/template.go index afbe856..e935d16 100644 --- a/helpers/template.go +++ b/helpers/template.go @@ -32,7 +32,6 @@ func TemplateContext(w http.ResponseWriter, r *http.Request, data models.Templat } } -// TemplateFuncs provides helper functions to be used in templates. func TemplateFuncs() template.FuncMap { return template.FuncMap{ "plus1": func(i int) int { return i + 1 }, @@ -63,7 +62,16 @@ func TemplateFuncs() template.FuncMap { } } -// SetFlash sets a flash message in session. +func LoadTemplateFiles(name string, files ...string) *template.Template { + shared := []string{ + "templates/layout.html", + "templates/topbar.html", + } + all := append(shared, files...) + + return template.Must(template.New(name).Funcs(TemplateFuncs()).ParseFiles(all...)) +} + func SetFlash(w http.ResponseWriter, r *http.Request, message string) { session, _ := GetSession(w, r) session.Values["flash"] = message diff --git a/middleware/admin.go b/middleware/admin.go index 0e76150..ab08b9b 100644 --- a/middleware/admin.go +++ b/middleware/admin.go @@ -7,12 +7,12 @@ import ( "synlotto-website/helpers" ) -// Wraps an existing handler but checks is_admin before executing func AdminOnly(db *sql.DB, next http.HandlerFunc) http.HandlerFunc { return Auth(true)(func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok || !helpers.IsAdmin(db, userID) { - http.Redirect(w, r, "/", http.StatusSeeOther) + log.Printf("⛔️ Unauthorized admin attempt: user_id=%v, IP=%s, Path=%s", userID, r.RemoteAddr, r.URL.Path) + helpers.RenderError(w, r, http.StatusForbidden) return } @@ -20,7 +20,6 @@ func AdminOnly(db *sql.DB, next http.HandlerFunc) http.HandlerFunc { ua := r.UserAgent() path := r.URL.Path - // Store log entry in DB _, err := db.Exec(` INSERT INTO admin_access_log (user_id, path, ip, user_agent) VALUES (?, ?, ?, ?)`, @@ -30,9 +29,10 @@ func AdminOnly(db *sql.DB, next http.HandlerFunc) http.HandlerFunc { log.Printf("⚠️ Failed to log admin access: %v", err) } - // Optional: still log to console log.Printf("🛡️ Admin access: user_id=%d IP=%s Path=%s", userID, ip, path) next(w, r) }) } + +// ToDo need to look into audit/access log tables and consolidate diff --git a/templates/error/429.html b/templates/error/429.html new file mode 100644 index 0000000..fe0361b --- /dev/null +++ b/templates/error/429.html @@ -0,0 +1,7 @@ +{{ define "content" }} +
Whoa there! You're making requests too quickly. Please slow down and try again in a moment.
+ Back to home +Something went wrong on our end. We're working to fix it.
+ +Please try again later or contact support if the issue persists.
+ + Return to Homepage +