Massive refactor!
This commit is contained in:
32
bootstrap/license.go
Normal file
32
bootstrap/license.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
internal "synlotto-website/internal/licensecheck"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
var globalChecker *internal.LicenseChecker
|
||||
|
||||
func InitLicenseChecker(config *models.Config) error {
|
||||
checker := &internal.LicenseChecker{
|
||||
LicenseAPIURL: config.License.APIURL,
|
||||
APIKey: config.License.APIKey,
|
||||
PollInterval: 10 * time.Minute,
|
||||
}
|
||||
|
||||
if err := checker.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checker.StartBackgroundCheck()
|
||||
globalChecker = checker
|
||||
log.Println("✅ License validation started.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLicenseChecker() *internal.LicenseChecker {
|
||||
return globalChecker
|
||||
}
|
||||
@@ -3,23 +3,27 @@ package handlers
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
"time"
|
||||
|
||||
securityHelpers "license-server/helpers/security"
|
||||
httpHelpers "synlotto-website/helpers/http"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
"synlotto-website/models"
|
||||
"synlotto-website/storage"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
)
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
session, _ := httpHelpers.GetSession(w, r)
|
||||
if _, ok := session.Values["user_id"].(int); ok {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
tmpl := helpers.LoadTemplateFiles("login.html", "templates/account/login.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("login.html", "templates/account/login.html")
|
||||
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context["csrfField"] = csrf.TemplateField(r)
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
@@ -34,12 +38,12 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
||||
password := r.FormValue("password")
|
||||
|
||||
user := models.GetUserByUsername(username)
|
||||
if user == nil || !helpers.CheckPasswordHash(user.PasswordHash, password) {
|
||||
if user == nil || !securityHelpers.CheckPasswordHash(user.PasswordHash, password) {
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
session, _ := httpHelpers.GetSession(w, r)
|
||||
|
||||
for k := range session.Values {
|
||||
delete(session.Values, k)
|
||||
@@ -65,18 +69,18 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil || !helpers.CheckPasswordHash(user.PasswordHash, password) {
|
||||
models.LogLoginAttempt(username, false)
|
||||
if user == nil || !securityHelpers.CheckPasswordHash(user.PasswordHash, password) {
|
||||
storage.LogLoginAttempt(username, false)
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
models.LogLoginAttempt(username, true)
|
||||
storage.LogLoginAttempt(username, true)
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
session, _ := httpHelpers.GetSession(w, r)
|
||||
|
||||
for k := range session.Values {
|
||||
delete(session.Values, k)
|
||||
@@ -95,7 +99,7 @@ func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func Signup(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
tmpl := helpers.LoadTemplateFiles("signup.html", "templates/account/signup.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("signup.html", "templates/account/signup.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||
"csrfField": csrf.TemplateField(r),
|
||||
@@ -106,7 +110,7 @@ func Signup(w http.ResponseWriter, r *http.Request) {
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
hashed, err := helpers.HashPassword(password)
|
||||
hashed, err := securityHelpers.HashPassword(password)
|
||||
if err != nil {
|
||||
http.Error(w, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"synlotto-website/helpers"
|
||||
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/middleware"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
@@ -19,7 +21,7 @@ type AdminLogEntry struct {
|
||||
|
||||
func AdminAccessLogHandler(db *sql.DB) http.HandlerFunc {
|
||||
return middleware.Auth(true)(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT accessed_at, user_id, path, ip, user_agent
|
||||
@@ -45,7 +47,7 @@ func AdminAccessLogHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
context["AuditLogs"] = logs
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("access_log.html", "templates/admin/logs/access_log.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("access_log.html", "templates/admin/logs/access_log.html")
|
||||
|
||||
_ = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
})
|
||||
@@ -53,7 +55,7 @@ func AdminAccessLogHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func AuditLogHandler(db *sql.DB) http.HandlerFunc {
|
||||
return middleware.Auth(true)(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT timestamp, user_id, action, ip, user_agent
|
||||
@@ -81,7 +83,7 @@ func AuditLogHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
context["AuditLogs"] = logs
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("audit.html", "templates/admin/logs/audit.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("audit.html", "templates/admin/logs/audit.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
"net/http"
|
||||
|
||||
helpers "synlotto-website/helpers"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func AdminDashboardHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
// userID, ok := helpers.GetCurrentUserID(r)
|
||||
// userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
// if !ok {
|
||||
// http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
// return
|
||||
@@ -19,7 +20,7 @@ func AdminDashboardHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
// TODO: check is_admin from users table here
|
||||
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
|
||||
// Total ticket stats
|
||||
var total, winners int
|
||||
@@ -54,7 +55,7 @@ func AdminDashboardHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
context["MatchLogs"] = logs
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("dashboard.html", "templates/admin/dashboard.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("dashboard.html", "templates/admin/dashboard.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"net/http"
|
||||
|
||||
helpers "synlotto-website/helpers"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func NewDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
game := r.FormValue("game_type")
|
||||
@@ -30,7 +32,7 @@ func NewDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("new_draw", "templates/admin/draws/new_draw.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("new_draw", "templates/admin/draws/new_draw.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
})
|
||||
@@ -72,7 +74,7 @@ func DeleteDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func ListDrawsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
draws := []models.DrawSummary{}
|
||||
|
||||
rows, err := db.Query(`
|
||||
@@ -100,7 +102,7 @@ func ListDrawsHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
context["Draws"] = draws
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("list.html", "templates/admin/draws/list.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("list.html", "templates/admin/draws/list.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
})
|
||||
|
||||
@@ -8,14 +8,15 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
services "synlotto-website/services/tickets"
|
||||
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func AdminTriggersHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
|
||||
if flash := r.URL.Query().Get("flash"); flash != "" {
|
||||
context["Flash"] = flash
|
||||
@@ -71,7 +72,7 @@ func AdminTriggersHandler(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("triggers.html", "templates/admin/triggers.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("triggers.html", "templates/admin/triggers.html")
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,16 +5,17 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"synlotto-website/helpers"
|
||||
helpers "synlotto-website/helpers"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func AddPrizesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
tmpl := helpers.LoadTemplateFiles("add_prizes.html", "templates/admin/draws/prizes/add_prizes.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("add_prizes.html", "templates/admin/draws/prizes/add_prizes.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r, models.TemplateData{}))
|
||||
tmpl.ExecuteTemplate(w, "layout", templateHelpers.TemplateContext(w, r, models.TemplateData{}))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -44,9 +45,9 @@ 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 := helpers.LoadTemplateFiles("modify_prizes.html", "templates/admin/draws/prizes/modify_prizes.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("modify_prizes.html", "templates/admin/draws/prizes/modify_prizes.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r, models.TemplateData{}))
|
||||
tmpl.ExecuteTemplate(w, "layout", templateHelpers.TemplateContext(w, r, models.TemplateData{}))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,17 @@ import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"synlotto-website/helpers"
|
||||
|
||||
templateHandlers "synlotto-website/handlers/template"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
)
|
||||
|
||||
func Home(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("index.html", "templates/index.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("index.html", "templates/index.html")
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
@@ -13,11 +15,11 @@ func NewDraw(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("➡️ New draw form opened")
|
||||
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context["Page"] = "new_draw"
|
||||
context["Data"] = nil
|
||||
|
||||
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.
|
||||
tmpl := templateHelpers.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 {
|
||||
@@ -5,6 +5,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
templateHandlers "synlotto-website/handlers/template"
|
||||
securityHelpers "synlotto-website/helpers/security"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
"synlotto-website/storage"
|
||||
@@ -14,18 +19,18 @@ func CreateSyndicateHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
tmpl := helpers.LoadTemplateFiles("create-syndicate.html", "templates/syndicate/create.html")
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
tmpl := templateHelpers.LoadTemplateFiles("create-syndicate.html", "templates/syndicate/create.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
|
||||
case http.MethodPost:
|
||||
name := r.FormValue("name")
|
||||
description := r.FormValue("description")
|
||||
|
||||
userId, ok := helpers.GetCurrentUserID(r)
|
||||
userId, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok || name == "" {
|
||||
helpers.SetFlash(w, r, "Invalid data submitted")
|
||||
templateHelpers.SetFlash(w, r, "Invalid data submitted")
|
||||
http.Redirect(w, r, "/syndicate/create", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
@@ -33,23 +38,23 @@ func CreateSyndicateHandler(db *sql.DB) http.HandlerFunc {
|
||||
_, err := storage.CreateSyndicate(db, userId, name, description)
|
||||
if err != nil {
|
||||
log.Printf("❌ CreateSyndicate failed: %v", err)
|
||||
helpers.SetFlash(w, r, "Failed to create syndicate")
|
||||
templateHelpers.SetFlash(w, r, "Failed to create syndicate")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Syndicate created successfully")
|
||||
templateHelpers.SetFlash(w, r, "Syndicate created successfully")
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
default:
|
||||
helpers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
templateHelpers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ListSyndicatesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403) // ToDo need to make this use the handler so i dont need to define errors.
|
||||
templateHelpers.RenderError(w, r, 403) // ToDo need to make this use the handler so i dont need to define errors.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,28 +73,28 @@ func ListSyndicatesHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["ManagedSyndicates"] = managed
|
||||
context["JoinedSyndicates"] = filteredJoined
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("syndicates.html", "templates/syndicate/index.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("syndicates.html", "templates/syndicate/index.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
|
||||
func ViewSyndicateHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
syndicate, err := storage.GetSyndicateByID(db, syndicateID)
|
||||
if err != nil || syndicate == nil {
|
||||
helpers.RenderError(w, r, 404)
|
||||
templateHelpers.RenderError(w, r, 404)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -97,45 +102,45 @@ func ViewSyndicateHandler(db *sql.DB) http.HandlerFunc {
|
||||
isMember := storage.IsSyndicateMember(db, syndicateID, userID)
|
||||
|
||||
if !isManager && !isMember {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
members := storage.GetSyndicateMembers(db, syndicateID)
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Syndicate"] = syndicate
|
||||
context["Members"] = members
|
||||
context["IsManager"] = isManager
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("syndicate-view.html", "templates/syndicate/view.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("syndicate-view.html", "templates/syndicate/view.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
|
||||
func SyndicateLogTicketHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateId := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
syndicate, err := storage.GetSyndicateByID(db, syndicateId)
|
||||
if err != nil || syndicate.OwnerID != userID {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Syndicate"] = syndicate
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("syndicate-log-ticket.html", "templates/syndicate/log_ticket.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("syndicate-log-ticket.html", "templates/syndicate/log_ticket.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
|
||||
case http.MethodPost:
|
||||
@@ -153,46 +158,46 @@ func SyndicateLogTicketHandler(db *sql.DB) http.HandlerFunc {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to add ticket.")
|
||||
templateHelpers.SetFlash(w, r, "Failed to add ticket.")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Ticket added for syndicate.")
|
||||
templateHelpers.SetFlash(w, r, "Ticket added for syndicate.")
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/syndicate/view?id=%d", syndicateId), http.StatusSeeOther)
|
||||
|
||||
default:
|
||||
helpers.RenderError(w, r, 405)
|
||||
templateHelpers.RenderError(w, r, 405)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SyndicateTicketsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
if syndicateID == 0 {
|
||||
helpers.RenderError(w, r, 400)
|
||||
templateHelpers.RenderError(w, r, 400)
|
||||
return
|
||||
}
|
||||
|
||||
if !storage.IsSyndicateMember(db, syndicateID, userID) {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
tickets := storage.GetSyndicateTickets(db, syndicateID)
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["SyndicateID"] = syndicateID
|
||||
context["Tickets"] = tickets
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("syndicate-tickets.html", "templates/syndicate/tickets.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("syndicate-tickets.html", "templates/syndicate/tickets.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
@@ -7,29 +7,33 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
templateHandlers "synlotto-website/handlers/template"
|
||||
securityHelpers "synlotto-website/helpers/security"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/storage"
|
||||
)
|
||||
|
||||
func SyndicateInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, http.StatusForbidden)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["SyndicateID"] = syndicateID
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("invite-syndicate.html", "templates/syndicate/invite.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-syndicate.html", "templates/syndicate/invite.html")
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
helpers.RenderError(w, r, 500)
|
||||
templateHelpers.RenderError(w, r, 500)
|
||||
}
|
||||
case http.MethodPost:
|
||||
syndicateID := helpers.Atoi(r.FormValue("syndicate_id"))
|
||||
@@ -37,32 +41,32 @@ func SyndicateInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
err := storage.InviteToSyndicate(db, userID, syndicateID, username)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to send invite: "+err.Error())
|
||||
templateHelpers.SetFlash(w, r, "Failed to send invite: "+err.Error())
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Invite sent successfully.")
|
||||
templateHelpers.SetFlash(w, r, "Invite sent successfully.")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
|
||||
default:
|
||||
helpers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
templateHelpers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ViewInvitesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
invites := storage.GetPendingInvites(db, userID)
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Invites"] = invites
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("invites.html", "templates/syndicate/invites.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invites.html", "templates/syndicate/invites.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
@@ -70,16 +74,16 @@ func ViewInvitesHandler(db *sql.DB) http.HandlerFunc {
|
||||
func AcceptInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
inviteID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
err := storage.AcceptInvite(db, inviteID, userID)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to accept invite")
|
||||
templateHelpers.SetFlash(w, r, "Failed to accept invite")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "You have joined the syndicate")
|
||||
templateHelpers.SetFlash(w, r, "You have joined the syndicate")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
}
|
||||
@@ -94,7 +98,7 @@ func DeclineInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
|
||||
func CreateInviteToken(db *sql.DB, syndicateID, invitedByID int, ttlHours int) (string, error) {
|
||||
token, err := helpers.GenerateSecureToken()
|
||||
token, err := securityHelpers.GenerateSecureToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -142,16 +146,16 @@ func AcceptInviteToken(db *sql.DB, token string, userID int) error {
|
||||
|
||||
func GenerateInviteLinkHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, http.StatusForbidden)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
token, err := CreateInviteToken(db, syndicateID, userID, 48) // token valid for 48 hours
|
||||
token, err := CreateInviteToken(db, syndicateID, userID, 48)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to generate invite link.")
|
||||
templateHelpers.SetFlash(w, r, "Failed to generate invite link.")
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
@@ -164,31 +168,31 @@ func GenerateInviteLinkHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
inviteLink := fmt.Sprintf("%s/syndicate/join?token=%s", origin, token)
|
||||
|
||||
helpers.SetFlash(w, r, "Invite link created: "+inviteLink)
|
||||
templateHelpers.SetFlash(w, r, "Invite link created: "+inviteLink)
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, http.StatusForbidden)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
helpers.SetFlash(w, r, "Invalid or missing invite token.")
|
||||
templateHelpers.SetFlash(w, r, "Invalid or missing invite token.")
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err := AcceptInviteToken(db, token, userID)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to join syndicate: "+err.Error())
|
||||
templateHelpers.SetFlash(w, r, "Failed to join syndicate: "+err.Error())
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "You have joined the syndicate!")
|
||||
templateHelpers.SetFlash(w, r, "You have joined the syndicate!")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
}
|
||||
@@ -196,27 +200,27 @@ func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func ManageInviteTokensHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
|
||||
if !storage.IsSyndicateManager(db, syndicateID, userID) {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
tokens := storage.GetInviteTokensForSyndicate(db, syndicateID)
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Tokens"] = tokens
|
||||
context["SyndicateID"] = syndicateID
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("invite-links.html", "templates/syndicate/invite_links.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-links.html", "templates/syndicate/invite_links.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,14 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
securityHelpers "synlotto-website/helpers/security"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
draws "synlotto-website/services/draws"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
draws "synlotto-website/services/draws"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
)
|
||||
@@ -39,11 +43,11 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context["csrfField"] = csrf.TemplateField(r)
|
||||
context["DrawDates"] = drawDates
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("add_ticket.html", "templates/account/tickets/add_ticket.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("add_ticket.html", "templates/account/tickets/add_ticket.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
@@ -60,7 +64,7 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
@@ -183,7 +187,7 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
@@ -265,7 +269,7 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func GetMyTickets(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
@@ -355,10 +359,10 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc {
|
||||
tickets = append(tickets, t)
|
||||
}
|
||||
|
||||
context := helpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context := templateHelpers.TemplateContext(w, r, models.TemplateData{})
|
||||
context["Tickets"] = tickets
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("my_tickets.html", "templates/account/tickets/my_tickets.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("my_tickets.html", "templates/account/tickets/my_tickets.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
@@ -4,20 +4,26 @@ import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
templateHandlers "synlotto-website/handlers/template"
|
||||
"synlotto-website/helpers"
|
||||
httpHelpers "synlotto-website/helpers/http"
|
||||
securityHelpers "synlotto-website/helpers/security"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/storage"
|
||||
)
|
||||
|
||||
func MessagesInboxHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
page := helpers.Atoi(r.URL.Query().Get("page"))
|
||||
page := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
@@ -31,18 +37,18 @@ func MessagesInboxHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
messages := storage.GetInboxMessages(db, userID, page, perPage)
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
|
||||
context["Messages"] = messages
|
||||
context["CurrentPage"] = page
|
||||
context["TotalPages"] = totalPages
|
||||
context["PageRange"] = helpers.PageRange(page, totalPages)
|
||||
context["PageRange"] = templateHelpers.PageRange(page, totalPages)
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("messages.html", "templates/account/messages/index.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("messages.html", "templates/account/messages/index.html")
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "layout", context); err != nil {
|
||||
helpers.RenderError(w, r, 500)
|
||||
templateHelpers.RenderError(w, r, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,10 +58,10 @@ func ReadMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
idStr := r.URL.Query().Get("id")
|
||||
messageID := helpers.Atoi(idStr)
|
||||
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
session, _ := httpHelpers.GetSession(w, r)
|
||||
userID, ok := session.Values["user_id"].(int)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,11 +73,11 @@ func ReadMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
_ = storage.MarkMessageAsRead(db, messageID, userID)
|
||||
}
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Message"] = message
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("read-message.html", "templates/account/messages/read.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("read-message.html", "templates/account/messages/read.html")
|
||||
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
@@ -80,17 +86,17 @@ func ReadMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
func ArchiveMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
err := storage.ArchiveMessage(db, userID, id)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to archive message.")
|
||||
templateHelpers.SetFlash(w, r, "Failed to archive message.")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Message archived.")
|
||||
templateHelpers.SetFlash(w, r, "Message archived.")
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/account/messages", http.StatusSeeOther)
|
||||
@@ -99,9 +105,9 @@ func ArchiveMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
func ArchivedMessagesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,13 +120,13 @@ func ArchivedMessagesHandler(db *sql.DB) http.HandlerFunc {
|
||||
messages := storage.GetArchivedMessages(db, userID, page, perPage)
|
||||
hasMore := len(messages) == perPage
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Messages"] = messages
|
||||
context["Page"] = page
|
||||
context["HasMore"] = hasMore
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("archived.html", "templates/account/messages/archived.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("archived.html", "templates/account/messages/archived.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
}
|
||||
}
|
||||
@@ -129,19 +135,17 @@ func SendMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
// Display the form
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
tmpl := helpers.LoadTemplateFiles("send-message.html", "templates/account/messages/send.html")
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
tmpl := templateHelpers.LoadTemplateFiles("send-message.html", "templates/account/messages/send.html")
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "layout", context); err != nil {
|
||||
helpers.RenderError(w, r, 500)
|
||||
templateHelpers.RenderError(w, r, 500)
|
||||
}
|
||||
case http.MethodPost:
|
||||
// Handle form submission
|
||||
senderID, ok := helpers.GetCurrentUserID(r)
|
||||
senderID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -150,13 +154,13 @@ func SendMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
body := r.FormValue("message")
|
||||
|
||||
if err := storage.SendMessage(db, senderID, recipientID, subject, body); err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to send message.")
|
||||
templateHelpers.SetFlash(w, r, "Failed to send message.")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Message sent.")
|
||||
templateHelpers.SetFlash(w, r, "Message sent.")
|
||||
}
|
||||
http.Redirect(w, r, "/account/messages", http.StatusSeeOther)
|
||||
default:
|
||||
helpers.RenderError(w, r, 405)
|
||||
templateHelpers.RenderError(w, r, 405)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,17 +168,17 @@ func SendMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
func RestoreMessageHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok {
|
||||
helpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
return
|
||||
}
|
||||
|
||||
err := storage.RestoreMessage(db, userID, id)
|
||||
if err != nil {
|
||||
helpers.SetFlash(w, r, "Failed to restore message.")
|
||||
templateHelpers.SetFlash(w, r, "Failed to restore message.")
|
||||
} else {
|
||||
helpers.SetFlash(w, r, "Message restored.")
|
||||
templateHelpers.SetFlash(w, r, "Message restored.")
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/account/messages/archived", http.StatusSeeOther)
|
||||
|
||||
@@ -6,16 +6,19 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
templateHandlers "synlotto-website/handlers/template"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/storage"
|
||||
)
|
||||
|
||||
func NotificationsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("index.html", "templates/account/notifications/index.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("index.html", "templates/account/notifications/index.html")
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
@@ -52,11 +55,11 @@ func MarkNotificationReadHandler(db *sql.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
data := BuildTemplateData(db, w, r)
|
||||
context := helpers.TemplateContext(w, r, data)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Notification"] = notification
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("read.html", "templates/account/notifications/read.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("read.html", "templates/account/notifications/read.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/middleware"
|
||||
"synlotto-website/models"
|
||||
@@ -111,7 +113,7 @@ func ResultsThunderball(db *sql.DB) http.HandlerFunc {
|
||||
noResultsMsg = "No results found for \"" + query + "\""
|
||||
}
|
||||
|
||||
tmpl := helpers.LoadTemplateFiles("thunderball.html", "templates/results/thunderball.html")
|
||||
tmpl := templateHelpers.LoadTemplateFiles("thunderball.html", "templates/results/thunderball.html")
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||
"Results": results,
|
||||
|
||||
@@ -2,14 +2,20 @@ package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"synlotto-website/helpers"
|
||||
|
||||
httpHelper "synlotto-website/helpers/http"
|
||||
|
||||
"synlotto-website/models"
|
||||
"synlotto-website/storage"
|
||||
)
|
||||
|
||||
func BuildTemplateData(db *sql.DB, w http.ResponseWriter, r *http.Request) models.TemplateData {
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
session, err := httpHelper.GetSession(w, r)
|
||||
if err != nil {
|
||||
log.Printf("Session error: %v", err)
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
var isAdmin bool
|
||||
@@ -18,19 +24,15 @@ func BuildTemplateData(db *sql.DB, w http.ResponseWriter, r *http.Request) model
|
||||
var messageCount int
|
||||
var messages []models.Message
|
||||
|
||||
switch v := session.Values["user_id"].(type) {
|
||||
case int:
|
||||
user = models.GetUserByID(v) // ToDo should be storage not models
|
||||
case int64:
|
||||
user = models.GetUserByID(int(v))
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
isAdmin = user.IsAdmin
|
||||
notificationCount = storage.GetNotificationCount(db, user.Id)
|
||||
notifications = storage.GetRecentNotifications(db, user.Id, 15)
|
||||
messageCount, _ = storage.GetMessageCount(db, user.Id)
|
||||
messages = storage.GetRecentMessages(db, user.Id, 15)
|
||||
if userId, ok := session.Values["user_id"].(int); ok {
|
||||
user = storage.GetUserByID(db, userId)
|
||||
if user != nil {
|
||||
isAdmin = user.IsAdmin
|
||||
notificationCount = storage.GetNotificationCount(db, user.Id)
|
||||
notifications = storage.GetRecentNotifications(db, user.Id, 15)
|
||||
messageCount, _ = storage.GetMessageCount(db, user.Id)
|
||||
messages = storage.GetRecentMessages(db, user.Id, 15)
|
||||
}
|
||||
}
|
||||
|
||||
return models.TemplateData{
|
||||
51
helpers/http/session.go
Normal file
51
helpers/http/session.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
session "synlotto-website/handlers/session"
|
||||
|
||||
"synlotto-website/constants"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
func GetSession(w http.ResponseWriter, r *http.Request) (*sessions.Session, error) {
|
||||
return session.GetSession(w, r)
|
||||
}
|
||||
|
||||
func IsSessionExpired(session *sessions.Session) bool {
|
||||
last, ok := session.Values["last_activity"].(time.Time)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return time.Since(last) > constants.SessionDuration
|
||||
}
|
||||
|
||||
func UpdateSessionActivity(session *sessions.Session, r *http.Request, w http.ResponseWriter) {
|
||||
session.Values["last_activity"] = time.Now().UTC()
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
session, _ := GetSession(w, r)
|
||||
|
||||
if IsSessionExpired(session) {
|
||||
session.Options.MaxAge = -1
|
||||
session.Save(r, w)
|
||||
|
||||
newSession, _ := GetSession(w, r)
|
||||
newSession.Values["flash"] = "Your session has timed out."
|
||||
newSession.Save(r, w)
|
||||
|
||||
http.Redirect(w, r, "/account/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
UpdateSessionActivity(session, r, w)
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package security
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package security
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
17
helpers/security/users.go
Normal file
17
helpers/security/users.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httpHelpers "synlotto-website/helpers/http"
|
||||
)
|
||||
|
||||
func GetCurrentUserID(r *http.Request) (int, bool) {
|
||||
session, err := httpHelpers.GetSession(nil, r)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
id, ok := session.Values["user_id"].(int)
|
||||
return id, ok
|
||||
}
|
||||
@@ -65,13 +65,3 @@ func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func GetCurrentUserID(r *http.Request) (int, bool) {
|
||||
session, err := GetSession(nil, r)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
id, ok := session.Values["user_id"].(int)
|
||||
return id, ok
|
||||
}
|
||||
|
||||
@@ -5,14 +5,21 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"synlotto-website/config"
|
||||
helpers "synlotto-website/helpers/http"
|
||||
"synlotto-website/models"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
)
|
||||
|
||||
func TemplateContext(w http.ResponseWriter, r *http.Request, data models.TemplateData) map[string]interface{} {
|
||||
session, _ := GetSession(w, r)
|
||||
cfg := config.Get()
|
||||
if cfg == nil {
|
||||
log.Println("⚠️ Config not initialized!")
|
||||
}
|
||||
session, _ := helpers.GetSession(w, r)
|
||||
|
||||
var flash string
|
||||
if f, ok := session.Values["flash"].(string); ok {
|
||||
@@ -30,6 +37,8 @@ func TemplateContext(w http.ResponseWriter, r *http.Request, data models.Templat
|
||||
"Notifications": data.Notifications,
|
||||
"MessageCount": data.MessageCount,
|
||||
"Messages": data.Messages,
|
||||
"SiteName": cfg.Site.SiteName,
|
||||
"YearStart": cfg.Site.CopyrightStart,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +67,8 @@ func TemplateFuncs() template.FuncMap {
|
||||
}
|
||||
return *p
|
||||
},
|
||||
"inSlice": InSlice,
|
||||
"lower": lower,
|
||||
"rangeClass": rangeClass,
|
||||
"inSlice": InSlice,
|
||||
"lower": lower,
|
||||
"truncate": func(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
@@ -68,22 +76,37 @@ func TemplateFuncs() template.FuncMap {
|
||||
return s[:max] + "..."
|
||||
},
|
||||
"PageRange": PageRange,
|
||||
"now": time.Now,
|
||||
"humanTime": func(v interface{}) string {
|
||||
switch t := v.(type) {
|
||||
case time.Time:
|
||||
return t.Local().Format("02 Jan 2006 15:04")
|
||||
case string:
|
||||
parsed, err := time.Parse(time.RFC3339, t)
|
||||
if err == nil {
|
||||
return parsed.Local().Format("02 Jan 2006 15:04")
|
||||
}
|
||||
return t
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LoadTemplateFiles(name string, files ...string) *template.Template {
|
||||
shared := []string{
|
||||
"templates/layout.html",
|
||||
"templates/topbar.html",
|
||||
"templates/main/layout.html",
|
||||
"templates/main/topbar.html",
|
||||
"templates/main/footer.html",
|
||||
}
|
||||
all := append(shared, files...)
|
||||
|
||||
log.Printf("📄 Loading templates: %v", all)
|
||||
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, _ := helpers.GetSession(w, r)
|
||||
session.Values["flash"] = message
|
||||
session.Save(r, w)
|
||||
}
|
||||
@@ -101,6 +124,15 @@ func lower(input string) string {
|
||||
return strings.ToLower(input)
|
||||
}
|
||||
|
||||
func PageRange(current, total int) []int {
|
||||
var pages []int
|
||||
for i := 1; i <= total; i++ {
|
||||
pages = append(pages, i)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
// ToDo: Should be ball range class, and should it even be here?
|
||||
func rangeClass(n int) string {
|
||||
switch {
|
||||
case n >= 1 && n <= 9:
|
||||
@@ -117,11 +149,3 @@ func rangeClass(n int) string {
|
||||
return "50-plus"
|
||||
}
|
||||
}
|
||||
|
||||
func PageRange(current, total int) []int {
|
||||
var pages []int
|
||||
for i := 1; i <= total; i++ {
|
||||
pages = append(pages, i)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
@@ -36,5 +37,3 @@ func RenderError(w http.ResponseWriter, r *http.Request, statusCode int) {
|
||||
|
||||
log.Println("✅ Successfully rendered error page") // ToDo: log these to database
|
||||
}
|
||||
|
||||
//ToDo Pages.go /template.go to be merged?
|
||||
25
internal/licensecheck/checker.go
Normal file
25
internal/licensecheck/checker.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LicenseChecker struct {
|
||||
LicenseAPIURL string
|
||||
APIKey string
|
||||
PollInterval time.Duration
|
||||
|
||||
mu sync.RWMutex
|
||||
lastGood time.Time
|
||||
valid bool
|
||||
}
|
||||
|
||||
func (lc *LicenseChecker) setValid(ok bool) {
|
||||
lc.mu.Lock()
|
||||
defer lc.mu.Unlock()
|
||||
lc.valid = ok
|
||||
if ok {
|
||||
lc.lastGood = time.Now()
|
||||
}
|
||||
}
|
||||
76
internal/licensecheck/validate.go
Normal file
76
internal/licensecheck/validate.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (lc *LicenseChecker) Validate() error {
|
||||
url := fmt.Sprintf("%s/license/lookup?key=%s&format=json", lc.LicenseAPIURL, lc.APIKey)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
lc.setValid(false)
|
||||
|
||||
return fmt.Errorf("license lookup failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
lc.setValid(false)
|
||||
|
||||
return fmt.Errorf("license lookup error: %s", resp.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
lc.setValid(false)
|
||||
|
||||
return fmt.Errorf("reading response failed: %w", err)
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Revoked bool `json:"revoked"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &data); err != nil {
|
||||
lc.setValid(false)
|
||||
|
||||
return fmt.Errorf("unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
if data.Revoked || time.Now().After(data.ExpiresAt) {
|
||||
lc.setValid(false)
|
||||
|
||||
return fmt.Errorf("license expired or revoked")
|
||||
}
|
||||
|
||||
lc.mu.Lock()
|
||||
lc.valid = true
|
||||
lc.lastGood = time.Now()
|
||||
lc.mu.Unlock()
|
||||
|
||||
log.Printf("✅ License validated. Expires: %s", data.ExpiresAt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lc *LicenseChecker) StartBackgroundCheck() {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(lc.PollInterval)
|
||||
err := lc.Validate()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ License check failed: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (lc *LicenseChecker) IsValid() bool {
|
||||
lc.mu.RLock()
|
||||
defer lc.mu.RUnlock()
|
||||
return lc.valid
|
||||
}
|
||||
4
main.go
4
main.go
@@ -34,6 +34,10 @@ func main() {
|
||||
logging.Error("❌ Failed to init session: %v", err)
|
||||
}
|
||||
|
||||
// if err := bootstrap.InitLicenseChecker(appState.Config); err != nil {
|
||||
// logging.Error("❌ Invalid license: %v", err)
|
||||
// }
|
||||
|
||||
err = bootstrap.InitCSRFProtection([]byte(appState.Config.CSRF.CSRFKey), appState.Config.HttpServer.ProductionMode)
|
||||
if err != nil {
|
||||
logging.Error("Failed to init CSRF: %v", err)
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"synlotto-website/helpers"
|
||||
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
)
|
||||
|
||||
func Recover(next http.Handler) http.Handler {
|
||||
@@ -13,7 +14,7 @@ func Recover(next http.Handler) http.Handler {
|
||||
if rec := recover(); rec != nil {
|
||||
log.Printf("🔥 Recovered from panic: %v\n%s", rec, debug.Stack())
|
||||
|
||||
helpers.RenderError(w, r, http.StatusInternalServerError)
|
||||
templateHelpers.RenderError(w, r, http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
@@ -11,6 +11,11 @@ type Config struct {
|
||||
CSRFKey string `json:"csrfKey"`
|
||||
} `json:"csrf"`
|
||||
|
||||
License struct {
|
||||
APIURL string `json:"apiUrl"`
|
||||
APIKey string `json:"apiKey"`
|
||||
} `json:"license"`
|
||||
|
||||
Session struct {
|
||||
AuthKeyPath string `json:"authKeyPath"`
|
||||
EncryptionKeyPath string `json:"encryptionKeyPath"`
|
||||
|
||||
@@ -59,35 +59,3 @@ func GetUserByUsername(username string) *User {
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func GetUserByID(id int) *User {
|
||||
row := db.QueryRow("SELECT id, username, password_hash, is_admin FROM users WHERE id = ?", id)
|
||||
|
||||
var user User
|
||||
err := row.Scan(&user.Id, &user.Username, &user.PasswordHash, &user.IsAdmin)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
log.Println("DB error:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func LogLoginAttempt(username string, success bool) {
|
||||
_, err := db.Exec("INSERT INTO auditlog (username, success, timestamp) VALUES (?, ?, ?)",
|
||||
username, boolToInt(success), time.Now().Format(time.RFC3339)) // tOdO: SHOULD BE USING UTC
|
||||
if err != nil {
|
||||
log.Println("❌ Failed to log login:", err)
|
||||
}
|
||||
} // ToDo this shouldn't be in models. Also why did i build a bool to int? just use the bool
|
||||
|
||||
func boolToInt(b bool) int {
|
||||
if b {
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
account "synlotto-website/handlers/account"
|
||||
|
||||
"synlotto-website/handlers"
|
||||
"synlotto-website/middleware"
|
||||
)
|
||||
|
||||
func SetupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||
mux.HandleFunc("/login", middleware.Protected(handlers.Login))
|
||||
mux.HandleFunc("/logout", handlers.Logout)
|
||||
mux.HandleFunc("/signup", middleware.Protected(handlers.Signup))
|
||||
mux.HandleFunc("/login", middleware.Protected(account.Login))
|
||||
mux.HandleFunc("/logout", account.Logout)
|
||||
mux.HandleFunc("/signup", middleware.Protected(account.Signup))
|
||||
mux.HandleFunc("/account/tickets/add_ticket", handlers.AddTicket(db))
|
||||
mux.HandleFunc("/account/tickets/my_tickets", handlers.GetMyTickets(db))
|
||||
mux.HandleFunc("/account/messages", middleware.Protected(handlers.MessagesInboxHandler(db)))
|
||||
|
||||
@@ -4,21 +4,22 @@ import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"synlotto-website/handlers"
|
||||
lotterySyndicateHandlers "synlotto-website/handlers/lottery/syndicate"
|
||||
|
||||
"synlotto-website/middleware"
|
||||
)
|
||||
|
||||
func SetupSyndicateRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||
mux.HandleFunc("/syndicate", middleware.Auth(true)(handlers.ListSyndicatesHandler(db)))
|
||||
mux.HandleFunc("/syndicate/create", middleware.Auth(true)(handlers.CreateSyndicateHandler(db)))
|
||||
mux.HandleFunc("/syndicate/view", middleware.Auth(true)(handlers.ViewSyndicateHandler(db)))
|
||||
mux.HandleFunc("/syndicate/tickets", middleware.Auth(true)(handlers.SyndicateTicketsHandler(db)))
|
||||
mux.HandleFunc("/syndicate/tickets/new", middleware.Auth(true)(handlers.SyndicateLogTicketHandler(db)))
|
||||
mux.HandleFunc("/syndicate/invites", middleware.Auth(true)(handlers.ViewInvitesHandler(db)))
|
||||
mux.HandleFunc("/syndicate/invites/accept", middleware.Auth(true)(handlers.AcceptInviteHandler(db)))
|
||||
mux.HandleFunc("/syndicate/invites/decline", middleware.Auth(true)(handlers.DeclineInviteHandler(db)))
|
||||
mux.HandleFunc("/syndicate/invite/token", middleware.Auth(true)(handlers.GenerateInviteLinkHandler(db)))
|
||||
mux.HandleFunc("/syndicate/invite/tokens", middleware.Auth(true)(handlers.ManageInviteTokensHandler(db)))
|
||||
mux.HandleFunc("/syndicate/join", middleware.Auth(true)(handlers.JoinSyndicateWithTokenHandler(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)))
|
||||
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"synlotto-website/handlers"
|
||||
|
||||
lotteryTicketHandlers "synlotto-website/handlers/lottery/tickets"
|
||||
thunderballrules "synlotto-website/rules"
|
||||
services "synlotto-website/services/draws"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/matcher"
|
||||
"synlotto-website/models"
|
||||
thunderballrules "synlotto-website/rules"
|
||||
services "synlotto-website/services/draws"
|
||||
)
|
||||
|
||||
func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, error) {
|
||||
@@ -27,7 +29,6 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Buffer results to avoid writing while iterating
|
||||
var pending []models.Ticket
|
||||
|
||||
for rows.Next() {
|
||||
@@ -64,7 +65,7 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er
|
||||
}
|
||||
|
||||
draw := services.GetDrawResultForTicket(db, t.GameType, t.DrawDate)
|
||||
result := handlers.MatchTicketToDraw(matchTicket, draw, thunderballrules.ThunderballPrizeRules)
|
||||
result := lotteryTicketHandlers.MatchTicketToDraw(matchTicket, draw, thunderballrules.ThunderballPrizeRules)
|
||||
|
||||
if result.MatchedDrawID == 0 {
|
||||
continue
|
||||
@@ -105,7 +106,6 @@ func UpdateMissingPrizes(db *sql.DB) error {
|
||||
|
||||
var tickets []TicketInfo
|
||||
|
||||
// Step 1: Load all relevant tickets
|
||||
rows, err := db.Query(`
|
||||
SELECT id, game_type, draw_date, matched_main, matched_bonus
|
||||
FROM my_tickets
|
||||
@@ -125,7 +125,6 @@ func UpdateMissingPrizes(db *sql.DB) error {
|
||||
tickets = append(tickets, t)
|
||||
}
|
||||
|
||||
// Step 2: Now that the reader is closed, perform updates
|
||||
for _, t := range tickets {
|
||||
if t.GameType != "Thunderball" {
|
||||
continue
|
||||
@@ -196,7 +195,7 @@ func RefreshTicketPrizes(db *sql.DB) error {
|
||||
}
|
||||
tickets = append(tickets, t)
|
||||
}
|
||||
rows.Close() // ✅ Release read lock before updating
|
||||
rows.Close()
|
||||
|
||||
for _, row := range tickets {
|
||||
matchTicket := models.MatchTicket{
|
||||
|
||||
@@ -5,16 +5,18 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"synlotto-website/helpers"
|
||||
securityHelpers "synlotto-website/helpers/security"
|
||||
templateHelpers "synlotto-website/helpers/template"
|
||||
|
||||
"synlotto-website/middleware"
|
||||
)
|
||||
|
||||
func AdminOnly(db *sql.DB, next http.HandlerFunc) http.HandlerFunc {
|
||||
return middleware.Auth(true)(func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := helpers.GetCurrentUserID(r)
|
||||
if !ok || !helpers.IsAdmin(db, userID) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
if !ok || !securityHelpers.IsAdmin(db, userID) {
|
||||
log.Printf("⛔️ Unauthorized admin attempt: user_id=%v, IP=%s, Path=%s", userID, r.RemoteAddr, r.URL.Path)
|
||||
helpers.RenderError(w, r, http.StatusForbidden)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,5 +38,3 @@ func AdminOnly(db *sql.DB, next http.HandlerFunc) http.HandlerFunc {
|
||||
next(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// ToDo need to look into audit/access log tables and consolidate
|
||||
|
||||
22
storage/audit.go
Normal file
22
storage/audit.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"synlotto-website/logging"
|
||||
)
|
||||
|
||||
func LogLoginAttempt(r *http.Request, username string, success bool) {
|
||||
ip := r.RemoteAddr
|
||||
userAgent := r.UserAgent()
|
||||
|
||||
_, err := db.Exec(
|
||||
`INSERT INTO audit_login (username, success, ip, user_agent, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
username, success, ip, userAgent, time.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
logging.Info("❌ Failed to log login:", err)
|
||||
}
|
||||
}
|
||||
@@ -98,16 +98,6 @@ func IsSyndicateMember(db *sql.DB, syndicateID, userID int) bool {
|
||||
return err == nil && count > 0
|
||||
}
|
||||
|
||||
func GetUserByUsername(db *sql.DB, username string) *models.User {
|
||||
row := db.QueryRow(`SELECT id, username, is_admin FROM users WHERE username = ?`, username) // ToDo: needs hash
|
||||
var u models.User
|
||||
err := row.Scan(&u.Id, &u.Username, &u.IsAdmin)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &u
|
||||
}
|
||||
|
||||
func AddMemberToSyndicate(db *sql.DB, syndicateID, userID int) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO syndicate_members (syndicate_id, user_id, joined_at)
|
||||
|
||||
34
storage/users.go
Normal file
34
storage/users.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"synlotto-website/logging"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func GetUserByID(db *sql.DB, id int) *models.User {
|
||||
row := db.QueryRow("SELECT id, username, password_hash, is_admin FROM users WHERE id = ?", id)
|
||||
|
||||
var user models.User
|
||||
err := row.Scan(&user.Id, &user.Username, &user.PasswordHash, &user.IsAdmin)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
logging.Error("DB error:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func GetUserByUsername(db *sql.DB, username string) *models.User {
|
||||
row := db.QueryRow(`SELECT id, username, password_hash, is_admin FROM users WHERE username = ?`, username)
|
||||
|
||||
var u models.User
|
||||
err := row.Scan(&u.Id, &u.Username, &u.PasswordHash, &u.IsAdmin)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &u
|
||||
}
|
||||
17
templates/main/footer.html
Normal file
17
templates/main/footer.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{ define "footer" }}
|
||||
<footer class="bg-light text-center text-muted py-3 mt-auto border-top">
|
||||
<small>
|
||||
© Copyright {{ .SiteName }}
|
||||
{{ $currentYear := now.Year }}
|
||||
{{ if eq .YearStart $currentYear }}
|
||||
{{ $currentYear }}
|
||||
{{ else }}
|
||||
{{ .YearStart }} - {{ $currentYear }}
|
||||
{{ end }}
|
||||
All rights reserved.
|
||||
| <a href="/legal/privacy">Privacy Policy</a> |
|
||||
<a href="/legal/terms">Terms & Conditions</a> |
|
||||
<a href="/contact">Contact Us</a>
|
||||
</small>
|
||||
</footer>
|
||||
{{ end }}
|
||||
@@ -1,9 +1,10 @@
|
||||
{{ define "layout" }}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SynLotto</title>
|
||||
<title>{{ .SiteName }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/site.css">
|
||||
@@ -12,9 +13,9 @@
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<!-- Topbar -->
|
||||
{{ template "topbar" . }}
|
||||
<!-- Main Layout -->
|
||||
<div class="container-fluid flex-grow-1">
|
||||
<div class="row">
|
||||
|
||||
<!-- Main layout using Flexbox -->
|
||||
<div class="d-flex flex-grow-1">
|
||||
<!-- Sidebar -->
|
||||
<nav class="col-md-2 d-none d-md-block bg-light sidebar pt-3">
|
||||
<div class="position-sticky">
|
||||
@@ -62,25 +63,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 pt-4">
|
||||
{{ if .Flash }}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ .Flash }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "content" . }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="col px-md-4 pt-4">
|
||||
{{ if .Flash }}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ .Flash }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ template "content" . }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-light text-center text-muted py-3 mt-auto border-top">
|
||||
<small>© xxx SynLotto. All rights reserved. | <a href="/privacy">Privacy Policy</a> | <a href="/privacy">Terms & Conditions</a> | <a href="/privacy">Contact Us </a></small>
|
||||
</footer>
|
||||
{{ template "footer" . }}
|
||||
|
||||
<!-- JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user