Refactor and remove sqlite and replace with MySQL

This commit is contained in:
2025-10-23 18:43:31 +01:00
parent d53e27eea8
commit 21ebc9c34b
139 changed files with 1013 additions and 529 deletions

View File

@@ -0,0 +1,49 @@
package middleware
import (
"net/http"
"time"
httpHelpers "synlotto-website/helpers/http"
"synlotto-website/constants"
)
func Auth(required bool) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := httpHelpers.GetSession(w, r)
_, ok := session.Values["user_id"].(int)
if required && !ok {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
if ok {
last, hasLast := session.Values["last_activity"].(time.Time)
if hasLast && time.Since(last) > constants.SessionDuration {
session.Options.MaxAge = -1
session.Save(r, w)
newSession, _ := httpHelpers.GetSession(w, r)
newSession.Values["flash"] = "Your session has timed out."
newSession.Save(r, w)
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
session.Values["last_activity"] = time.Now()
session.Save(r, w)
}
next(w, r)
}
}
}
func Protected(h http.HandlerFunc) http.HandlerFunc {
return Auth(true)(SessionTimeout(h))
}

View File

@@ -0,0 +1,23 @@
package middleware
import "net/http"
func EnforceHTTPS(next http.Handler, enabled bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if enabled && r.Header.Get("X-Forwarded-Proto") != "https" && r.TLS == nil {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
return
}
next.ServeHTTP(w, r)
})
}
func SecureHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' https://cdn.jsdelivr.net; script-src 'self' https://cdn.jsdelivr.net; font-src 'self' https://cdn.jsdelivr.net")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,35 @@
package middleware
import (
"net"
"net/http"
"sync"
"golang.org/x/time/rate"
)
var visitors = make(map[string]*rate.Limiter)
var mu sync.Mutex
func GetVisitorLimiter(ip string) *rate.Limiter {
mu.Lock()
defer mu.Unlock()
limiter, exists := visitors[ip]
if !exists {
limiter = rate.NewLimiter(3, 5)
visitors[ip] = limiter
}
return limiter
}
func RateLimit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
if !GetVisitorLimiter(ip).Allow() {
http.Error(w, "Too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,22 @@
package middleware
import (
"log"
"net/http"
"runtime/debug"
templateHelpers "synlotto-website/helpers/template"
)
func Recover(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("🔥 Recovered from panic: %v\n%s", rec, debug.Stack())
templateHelpers.RenderError(w, r, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,40 @@
package middleware
import (
"log"
"net/http"
"time"
session "synlotto-website/handlers/session"
"synlotto-website/constants"
)
func SessionTimeout(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sess, err := session.GetSession(w, r)
if err != nil {
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
last, ok := sess.Values["last_activity"].(time.Time)
if !ok || time.Since(last) > constants.SessionDuration {
sess.Options.MaxAge = -1
_ = sess.Save(r, w)
newSession, _ := session.GetSession(w, r)
newSession.Values["flash"] = "Your session has timed out."
_ = newSession.Save(r, w)
log.Printf("Session timeout triggered")
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
return
}
sess.Values["last_activity"] = time.Now().UTC()
_ = sess.Save(r, w)
next(w, r)
}
}

View File

@@ -0,0 +1,28 @@
package routes
import (
"database/sql"
"net/http"
accountHandlers "synlotto-website/handlers/account"
lotteryDrawHandlers "synlotto-website/handlers/lottery/tickets"
"synlotto-website/handlers"
"synlotto-website/middleware"
)
func SetupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/account/login", accountHandlers.Login(db))
mux.HandleFunc("/account/logout", middleware.Protected(accountHandlers.Logout))
mux.HandleFunc("/account/signup", accountHandlers.Signup)
mux.HandleFunc("/account/tickets/add_ticket", lotteryDrawHandlers.AddTicket(db))
mux.HandleFunc("/account/tickets/my_tickets", lotteryDrawHandlers.GetMyTickets(db))
mux.HandleFunc("/account/messages", middleware.Protected(handlers.MessagesInboxHandler(db)))
mux.HandleFunc("/account/messages/read", middleware.Protected(handlers.ReadMessageHandler(db)))
mux.HandleFunc("/account/messages/archive", middleware.Protected(handlers.ArchiveMessageHandler(db)))
mux.HandleFunc("/account/messages/archived", middleware.Protected(handlers.ArchivedMessagesHandler(db)))
mux.HandleFunc("/account/messages/restore", middleware.Protected(handlers.RestoreMessageHandler(db)))
mux.HandleFunc("/account/messages/send", middleware.Protected(handlers.SendMessageHandler(db)))
mux.HandleFunc("/account/notifications", middleware.Protected(handlers.NotificationsHandler(db)))
mux.HandleFunc("/account/notifications/read", middleware.Protected(handlers.MarkNotificationReadHandler(db)))
}

View File

@@ -0,0 +1,27 @@
package routes
import (
"database/sql"
"net/http"
admin "synlotto-website/handlers/admin"
"synlotto-website/middleware"
)
func SetupAdminRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/admin/access", middleware.Protected(admin.AdminAccessLogHandler(db)))
mux.HandleFunc("/admin/audit", middleware.Protected(admin.AuditLogHandler(db)))
mux.HandleFunc("/admin/dashboard", middleware.Protected(admin.AdminDashboardHandler(db)))
mux.HandleFunc("/admin/triggers", middleware.Protected(admin.AdminTriggersHandler(db)))
// Draw management
mux.HandleFunc("/admin/draws", middleware.Protected(admin.ListDrawsHandler(db)))
// mux.HandleFunc("/admin/draws/new", middleware.AdminOnly(db, admin.RenderNewDrawForm(db)))
// mux.HandleFunc("/admin/draws/submit", middleware.AdminOnly(db, admin.CreateDrawHandler(db)))
mux.HandleFunc("/admin/draws/modify", middleware.Protected(admin.ModifyDrawHandler(db)))
mux.HandleFunc("/admin/draws/delete", middleware.Protected(admin.DeleteDrawHandler(db)))
// Prize management
mux.HandleFunc("/admin/draws/prizes/add", middleware.Protected(admin.AddPrizesHandler(db)))
mux.HandleFunc("/admin/draws/prizes/modify", middleware.Protected(admin.ModifyPrizesHandler(db)))
}

View File

@@ -0,0 +1,12 @@
package routes
import (
"database/sql"
"net/http"
"synlotto-website/handlers"
)
func SetupResultRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/results/thunderball", handlers.ResultsThunderball(db))
}

View File

@@ -0,0 +1,13 @@
package routes
import (
"database/sql"
"net/http"
handlers "synlotto-website/handlers/statistics"
"synlotto-website/middleware"
)
func SetupStatisticsRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/statistics/thunderball", middleware.Auth(true)(handlers.StatisticsThunderball(db)))
}

View File

@@ -0,0 +1,25 @@
package routes
import (
"database/sql"
"net/http"
lotterySyndicateHandlers "synlotto-website/handlers/lottery/syndicate"
"synlotto-website/middleware"
)
func SetupSyndicateRoutes(mux *http.ServeMux, db *sql.DB) {
mux.HandleFunc("/syndicate", middleware.Auth(true)(lotterySyndicateHandlers.ListSyndicatesHandler(db)))
mux.HandleFunc("/syndicate/create", middleware.Auth(true)(lotterySyndicateHandlers.CreateSyndicateHandler(db)))
mux.HandleFunc("/syndicate/view", middleware.Auth(true)(lotterySyndicateHandlers.ViewSyndicateHandler(db)))
mux.HandleFunc("/syndicate/tickets", middleware.Auth(true)(lotterySyndicateHandlers.SyndicateTicketsHandler(db)))
mux.HandleFunc("/syndicate/tickets/new", middleware.Auth(true)(lotterySyndicateHandlers.SyndicateLogTicketHandler(db)))
mux.HandleFunc("/syndicate/invites", middleware.Auth(true)(lotterySyndicateHandlers.ViewInvitesHandler(db)))
mux.HandleFunc("/syndicate/invites/accept", middleware.Auth(true)(lotterySyndicateHandlers.AcceptInviteHandler(db)))
mux.HandleFunc("/syndicate/invites/decline", middleware.Auth(true)(lotterySyndicateHandlers.DeclineInviteHandler(db)))
mux.HandleFunc("/syndicate/invite/token", middleware.Auth(true)(lotterySyndicateHandlers.GenerateInviteLinkHandler(db)))
mux.HandleFunc("/syndicate/invite/tokens", middleware.Auth(true)(lotterySyndicateHandlers.ManageInviteTokensHandler(db)))
mux.HandleFunc("/syndicate/join", middleware.Auth(true)(lotterySyndicateHandlers.JoinSyndicateWithTokenHandler(db)))
}