package handlers import ( "database/sql" "log" "net" "net/http" "regexp" "sort" "strconv" templateHelpers "synlotto-website/helpers/template" "synlotto-website/helpers" "synlotto-website/middleware" "synlotto-website/models" ) func ResultsThunderball(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ip, _, _ := net.SplitHostPort(r.RemoteAddr) limiter := middleware.GetVisitorLimiter(ip) if !limiter.Allow() { http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) return } const pageSize = 20 page := 1 offset := 0 query := r.URL.Query().Get("q") pageStr := r.URL.Query().Get("page") yearFilter := r.URL.Query().Get("year") machineFilter := r.URL.Query().Get("machine") ballSetFilter := r.URL.Query().Get("ballset") if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { page = p offset = (page - 1) * pageSize } isValidDate := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString isValidNumber := regexp.MustCompile(`^\d+$`).MatchString doSearch := isValidDate(query) || isValidNumber(query) whereClause := "WHERE 1=1" args := []interface{}{} if doSearch { whereClause += " AND (draw_date = ? OR id = ?)" args = append(args, query, query) } if yearFilter != "" { whereClause += " AND strftime('%Y', draw_date) = ?" args = append(args, yearFilter) } if machineFilter != "" { whereClause += " AND machine = ?" args = append(args, machineFilter) } if ballSetFilter != "" { whereClause += " AND ballset = ?" args = append(args, ballSetFilter) } totalPages, totalResults := templateHelpers.GetTotalPages(db, "results_thunderball", whereClause, args, pageSize) if page < 1 || page > totalPages { http.NotFound(w, r) return } querySQL := ` SELECT id, draw_date, machine, ballset, ball1, ball2, ball3, ball4, ball5, thunderball FROM results_thunderball ` + whereClause + ` ORDER BY id DESC LIMIT ? OFFSET ?` argsWithLimit := append(args, pageSize, offset) rows, err := db.Query(querySQL, argsWithLimit...) if err != nil { http.Error(w, "Database error", http.StatusInternalServerError) log.Println("❌ DB error:", err) return } defer rows.Close() var results []models.ThunderballResult for rows.Next() { var res models.ThunderballResult if err := rows.Scan( &res.Id, &res.DrawDate, &res.Machine, &res.BallSet, &res.Ball1, &res.Ball2, &res.Ball3, &res.Ball4, &res.Ball5, &res.Thunderball, ); err != nil { log.Println("❌ Row scan error:", err) continue } res.SortedBalls = []int{res.Ball1, res.Ball2, res.Ball3, res.Ball4, res.Ball5} sort.Ints(res.SortedBalls) results = append(results, res) } years, _ := helpers.GetDistinctValues(db, "strftime('%Y', draw_date)") machines, _ := helpers.GetDistinctValues(db, "machine") ballsets, _ := helpers.GetDistinctValues(db, "ballset") var noResultsMsg string if query != "" && !doSearch { noResultsMsg = "Invalid search. Please enter a draw number or date (yyyy-mm-dd)." } else if len(results) == 0 && query != "" { noResultsMsg = "No results found for \"" + query + "\"" } tmpl := templateHelpers.LoadTemplateFiles("thunderball.html", "templates/results/thunderball.html") err = tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{ "Results": results, "Page": page, "TotalPages": totalPages, "TotalResults": totalResults, "Query": query, "NoResultsMsg": noResultsMsg, "Years": years, "Machines": machines, "BallSets": ballsets, "YearFilter": yearFilter, "MachineFilter": machineFilter, "BallSetFilter": ballSetFilter, }) if err != nil { log.Println("❌ Template error:", err) http.Error(w, "Error rendering results", http.StatusInternalServerError) } } }