Stack of changes to get gin, scs, nosurf running.

This commit is contained in:
2025-10-28 11:56:42 +00:00
parent 07117ba35e
commit 86be6479f1
65 changed files with 1890 additions and 1503 deletions

View File

@@ -1,3 +1,4 @@
// internal/handlers/lottery/tickets/ticket_handler.go
package handlers
import (
@@ -10,21 +11,23 @@ import (
"strconv"
"time"
httpHelpers "synlotto-website/internal/helpers/http"
templateHandlers "synlotto-website/internal/handlers/template"
securityHelpers "synlotto-website/internal/helpers/security"
templateHelpers "synlotto-website/internal/helpers/template"
draws "synlotto-website/internal/services/draws"
"synlotto-website/internal/helpers"
"synlotto-website/internal/models"
"synlotto-website/internal/platform/bootstrap"
"github.com/justinas/nosurf"
)
func AddTicket(db *sql.DB) http.HandlerFunc {
return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
// AddTicket renders the add-ticket form (GET) and handles multi-line ticket submission (POST).
func AddTicket(app *bootstrap.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
rows, err := db.Query(`
rows, err := app.DB.Query(`
SELECT DISTINCT draw_date
FROM results_thunderball
ORDER BY draw_date DESC
@@ -44,29 +47,27 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
}
}
data := models.TemplateData{}
// Use shared template data builder (expects *bootstrap.App)
data := templateHandlers.BuildTemplateData(app, w, r)
context := templateHelpers.TemplateContext(w, r, data)
context["CSRFToken"] = nosurf.Token(r)
context["DrawDates"] = drawDates
tmpl := templateHelpers.LoadTemplateFiles("add_ticket.html", "templates/account/tickets/add_ticket.html")
err = tmpl.ExecuteTemplate(w, "layout", context)
if err != nil {
tmpl := templateHelpers.LoadTemplateFiles("add_ticket.html", "web/templates/account/tickets/add_ticket.html")
if err := tmpl.ExecuteTemplate(w, "layout", context); err != nil {
log.Println("❌ Template render error:", err)
http.Error(w, "Error rendering form", http.StatusInternalServerError)
}
return
}
err := r.ParseMultipartForm(10 << 20)
if err != nil {
if err := r.ParseMultipartForm(10 << 20); err != nil {
http.Error(w, "Invalid form", http.StatusBadRequest)
log.Println("❌ Failed to parse form:", err)
return
}
userID, ok := securityHelpers.GetCurrentUserID(r)
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
if !ok {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
@@ -77,7 +78,6 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
purchaseMethod := r.FormValue("purchase_method")
purchaseDate := r.FormValue("purchase_date")
purchaseTime := r.FormValue("purchase_time")
if purchaseTime != "" {
purchaseDate += "T" + purchaseTime
}
@@ -90,7 +90,7 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
out, err := os.Create(filename)
if err == nil {
defer out.Close()
io.Copy(out, file)
_, _ = io.Copy(out, file)
imagePath = filename
}
}
@@ -157,7 +157,7 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
continue
}
_, err := db.Exec(`
if _, err := app.DB.Exec(`
INSERT INTO my_tickets (
userId, game_type, draw_date,
ball1, ball2, ball3, ball4, ball5, ball6,
@@ -169,27 +169,26 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
b[0], b[1], b[2], b[3], b[4], b[5],
bo[0], bo[1],
purchaseMethod, purchaseDate, imagePath,
)
if err != nil {
); err != nil {
log.Println("❌ Failed to insert ticket line:", err)
} else {
log.Printf("✅ Ticket line %d saved", i+1) // ToDo create audit
log.Printf("✅ Ticket line %d saved", i+1)
}
}
http.Redirect(w, r, "/tickets", http.StatusSeeOther)
})
}
}
func SubmitTicket(db *sql.DB) http.HandlerFunc {
return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(10 << 20)
if err != nil {
// SubmitTicket handles alternate multipart ticket submission (POST-only).
func SubmitTicket(app *bootstrap.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(10 << 20); err != nil {
http.Error(w, "Invalid form", http.StatusBadRequest)
return
}
userID, ok := securityHelpers.GetCurrentUserID(r)
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
if !ok {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
@@ -200,7 +199,6 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
purchaseMethod := r.FormValue("purchase_method")
purchaseDate := r.FormValue("purchase_date")
purchaseTime := r.FormValue("purchase_time")
if purchaseTime != "" {
purchaseDate += "T" + purchaseTime
}
@@ -213,13 +211,13 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
out, err := os.Create(filename)
if err == nil {
defer out.Close()
io.Copy(out, file)
_, _ = io.Copy(out, file)
imagePath = filename
}
}
ballCount := 6
bonusCount := 2
const ballCount = 6
const bonusCount = 2
balls := make([][]int, ballCount)
bonuses := make([][]int, bonusCount)
@@ -247,7 +245,7 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
}
}
_, err := db.Exec(`
if _, err := app.DB.Exec(`
INSERT INTO my_tickets (
user_id, game_type, draw_date,
ball1, ball2, ball3, ball4, ball5, ball6,
@@ -259,30 +257,30 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
b[0], b[1], b[2], b[3], b[4], b[5],
bo[0], bo[1],
purchaseMethod, purchaseDate, imagePath,
)
if err != nil {
); err != nil {
log.Println("❌ Insert failed:", err)
}
}
http.Redirect(w, r, "/tickets", http.StatusSeeOther)
})
}
}
func GetMyTickets(db *sql.DB) http.HandlerFunc {
return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
data := models.TemplateData{}
var tickets []models.Ticket
// GetMyTickets lists the current user's tickets.
func GetMyTickets(app *bootstrap.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Use shared template data builder (ensures user/flash/notifications present)
data := templateHandlers.BuildTemplateData(app, w, r)
context := templateHelpers.TemplateContext(w, r, data)
context["Tickets"] = tickets
userID, ok := securityHelpers.GetCurrentUserID(r)
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
if !ok {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
rows, err := db.Query(`
var tickets []models.Ticket
rows, err := app.DB.Query(`
SELECT id, game_type, draw_date,
ball1, ball2, ball3, ball4, ball5, ball6,
bonus1, bonus2,
@@ -308,19 +306,18 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc {
var prizeLabel sql.NullString
var prizeAmount sql.NullFloat64
err := rows.Scan(
if err := rows.Scan(
&t.Id, &t.GameType, &t.DrawDate,
&b1, &b2, &b3, &b4, &b5, &b6,
&bo1, &bo2,
&t.PurchaseMethod, &t.PurchaseDate, &t.ImagePath, &t.Duplicate,
&matchedMain, &matchedBonus, &prizeTier, &isWinner, &prizeLabel, &prizeAmount,
)
if err != nil {
); err != nil {
log.Println("⚠️ Failed to scan ticket row:", err)
continue
}
// Build primary number + bonus fields
// Normalize fields
t.Ball1 = int(b1.Int64)
t.Ball2 = int(b2.Int64)
t.Ball3 = int(b3.Int64)
@@ -348,28 +345,55 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc {
if prizeAmount.Valid {
t.PrizeAmount = prizeAmount.Float64
}
// Build balls slices (for template use)
// Derived fields for templates
t.Balls = helpers.BuildBallsSlice(t)
t.BonusBalls = helpers.BuildBonusSlice(t)
// 🎯 Get the actual draw info (used to show which numbers matched)
draw := draws.GetDrawResultForTicket(db, t.GameType, t.DrawDate)
// Fetch matching draw info
draw := draws.GetDrawResultForTicket(app.DB, t.GameType, t.DrawDate)
t.MatchedDraw = draw
// ✅ DEBUG
log.Printf("✅ Ticket #%d", t.Id)
log.Printf("Balls: %v", t.Balls)
log.Printf("DrawResult: %+v", draw)
tickets = append(tickets, t)
}
tmpl := templateHelpers.LoadTemplateFiles("my_tickets.html", "templates/account/tickets/my_tickets.html")
context["Tickets"] = tickets
err = tmpl.ExecuteTemplate(w, "layout", context)
if err != nil {
tmpl := templateHelpers.LoadTemplateFiles("my_tickets.html", "web/templates/account/tickets/my_tickets.html")
if err := tmpl.ExecuteTemplate(w, "layout", context); err != nil {
log.Println("❌ Template error:", err)
http.Error(w, "Error rendering page", http.StatusInternalServerError)
}
})
}
}
// ToDo
// http: superfluous response.WriteHeader call (from SCS)
//This happens when headers are written twice in a request. With SCS, it sets cookies in WriteHeader. If something else already wrote the headers (or wrote them again), you see this warning.
//Common culprits & fixes:
//Use Gins redirect instead of the stdlib one:
// Replace:
//http.Redirect(w, r, "/account/login", http.StatusSeeOther)
// With:
//c.Redirect(http.StatusSeeOther, "/account/login")
//c.Abort() // stop further handlers writing
//Do this everywhere you redirect (signup, login, logout).
//Dont call two status-writes. For template GETs, this is fine:
//c.Status(http.StatusOK)
//_ = tmpl.ExecuteTemplate(c.Writer, "layout", ctx) // writes body once
//Just make sure you never write another header after that.
//Keep your wrapping order as you have it (its correct):
//Gin → SCS.LoadAndSave → NoSurf → http.Server
//If you still get the warning after switching to c.Redirect + c.Abort(), tell me which handler its coming from and Ill point to the exact double-write.