Add tickets to db.

This commit is contained in:
2025-03-26 13:26:43 +00:00
parent f001cfe35e
commit 66abdbdd4d
5 changed files with 231 additions and 42 deletions

View File

@@ -92,42 +92,71 @@ func AddTicket(db *sql.DB) http.HandlerFunc {
} }
} }
ballCount := 6 var ballCount, bonusCount int
bonusCount := 2 switch game {
case "Thunderball":
ballCount, bonusCount = 5, 1
case "Lotto":
ballCount, bonusCount = 6, 0
case "EuroMillions":
ballCount, bonusCount = 5, 2
case "SetForLife":
ballCount, bonusCount = 5, 1
default:
http.Error(w, "Unsupported game type", http.StatusBadRequest)
return
}
balls := make([][]int, ballCount) balls := make([][]int, ballCount)
bonuses := make([][]int, bonusCount) bonuses := make([][]int, bonusCount)
for i := 1; i <= ballCount; i++ { for i := 1; i <= ballCount; i++ {
balls[i-1] = helpers.ParseIntSlice(r.Form["ball"+strconv.Itoa(i)]) field := fmt.Sprintf("ball%d[]", i)
log.Printf("🔢 ball%d: %v", i, balls[i-1]) balls[i-1] = helpers.ParseIntSlice(r.Form[field])
log.Printf("🔢 %s: %v", field, balls[i-1])
} }
for i := 1; i <= bonusCount; i++ { for i := 1; i <= bonusCount; i++ {
bonuses[i-1] = helpers.ParseIntSlice(r.Form["bonus"+strconv.Itoa(i)]) field := fmt.Sprintf("bonus%d[]", i)
log.Printf("🎯 bonus%d: %v", i, bonuses[i-1]) bonuses[i-1] = helpers.ParseIntSlice(r.Form[field])
log.Printf("🎯 %s: %v", field, bonuses[i-1])
} }
lineCount := len(balls[0]) lineCount := 0
if len(balls) > 0 {
lineCount = len(balls[0])
}
log.Println("🧾 Total lines to insert:", lineCount) log.Println("🧾 Total lines to insert:", lineCount)
for i := 0; i < lineCount; i++ { for i := 0; i < lineCount; i++ {
var b [6]int b := make([]int, 6)
var bo [2]int bo := make([]int, 2)
valid := true
for j := 0; j < ballCount; j++ { for j := 0; j < ballCount; j++ {
if j < len(balls) && i < len(balls[j]) { if j < len(balls) && i < len(balls[j]) {
b[j] = balls[j][i] b[j] = balls[j][i]
if b[j] == 0 {
valid = false
}
} }
} }
for j := 0; j < bonusCount; j++ { for j := 0; j < bonusCount; j++ {
if j < len(bonuses) && i < len(bonuses[j]) { if j < len(bonuses) && i < len(bonuses[j]) {
bo[j] = bonuses[j][i] bo[j] = bonuses[j][i]
if bo[j] == 0 {
valid = false
}
} }
} }
if !valid {
log.Printf("⚠️ Skipping invalid line %d (incomplete values)", i+1)
continue
}
_, err := db.Exec(` _, err := db.Exec(`
INSERT INTO my_tickets ( INSERT INTO my_tickets (
user_id, game_type, draw_date, userId, game_type, draw_date,
ball1, ball2, ball3, ball4, ball5, ball6, ball1, ball2, ball3, ball4, ball5, ball6,
bonus1, bonus2, bonus1, bonus2,
purchase_method, purchase_date, image_path purchase_method, purchase_date, image_path
@@ -237,23 +266,26 @@ func SubmitTicket(db *sql.DB) http.HandlerFunc {
}) })
} }
func ListTickets(db *sql.DB) http.HandlerFunc { func GetMyTickets(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
log.Println("📋 Tickets page hit") userID, ok := helpers.GetCurrentUserID(r)
if !ok {
tmpl := template.Must(template.ParseFiles( http.Redirect(w, r, "/login", http.StatusSeeOther)
"templates/layout.html", return
"templates/tickets.html", }
))
rows, err := db.Query(` rows, err := db.Query(`
SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, bonus1, bonus2, duplicate SELECT id, game_type, draw_date,
ball1, ball2, ball3, ball4, ball5, ball6,
bonus1, bonus2,
purchase_method, purchase_date, image_path
FROM my_tickets FROM my_tickets
ORDER BY draw_date DESC WHERE user_id = ?
`) ORDER BY draw_date DESC, created_at DESC
`, userID)
if err != nil { if err != nil {
log.Println("❌ Failed to query tickets:", err) log.Println("❌ Failed to load user tickets:", err)
http.Error(w, "Could not load tickets", http.StatusInternalServerError) http.Error(w, "Error loading tickets", http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
@@ -263,23 +295,22 @@ func ListTickets(db *sql.DB) http.HandlerFunc {
var t models.MyTicket var t models.MyTicket
err := rows.Scan( err := rows.Scan(
&t.Id, &t.GameType, &t.DrawDate, &t.Id, &t.GameType, &t.DrawDate,
&t.Ball1, &t.Ball2, &t.Ball3, &t.Ball4, &t.Ball5, &t.Ball1, &t.Ball2, &t.Ball3, &t.Ball4, &t.Ball5, &t.Ball6,
&t.Bonus1, &t.Bonus2, &t.Duplicate, &t.Bonus1, &t.Bonus2,
&t.PurchaseMethod, &t.PurchaseDate, &t.ImagePath,
) )
if err != nil { if err == nil {
log.Println("❌ Row scan error:", err) tickets = append(tickets, t)
continue
} }
tickets = append(tickets, t)
} }
err = tmpl.ExecuteTemplate(w, "layout", map[string]any{ context := helpers.TemplateContext(w, r)
"Page": "tickets", context["Tickets"] = tickets
"Data": tickets,
}) tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles(
if err != nil { "templates/layout.html",
log.Println("❌ Template rendering error:", err) "templates/account/tickets/my_tickets.html",
http.Error(w, "Could not render page", http.StatusInternalServerError) ))
} tmpl.ExecuteTemplate(w, "layout", context)
} })
} }

View File

@@ -35,7 +35,8 @@ func main() {
mux.HandleFunc("/login", middleware.Auth(false)(handlers.Login)) mux.HandleFunc("/login", middleware.Auth(false)(handlers.Login))
mux.HandleFunc("/logout", handlers.Logout) mux.HandleFunc("/logout", handlers.Logout)
mux.HandleFunc("/signup", middleware.Auth(false)(handlers.Signup)) mux.HandleFunc("/signup", middleware.Auth(false)(handlers.Signup))
mux.HandleFunc("/account/tickets/add_ticket", middleware.Auth(true)(handlers.AddTicket(db))) mux.HandleFunc("/account/tickets/add_ticket", handlers.AddTicket(db))
mux.HandleFunc("/account/tickets/my_tickets", handlers.GetMyTickets(db))
log.Println("🌐 Running on http://localhost:8080") log.Println("🌐 Running on http://localhost:8080")
http.ListenAndServe(":8080", helpers.RateLimit(csrfMiddleware(mux))) http.ListenAndServe(":8080", helpers.RateLimit(csrfMiddleware(mux)))

View File

@@ -55,7 +55,7 @@ func GetUserByID(id int) *User {
} }
func LogLoginAttempt(username string, success bool) { func LogLoginAttempt(username string, success bool) {
_, err := db.Exec("INSERT INTO login_audit (username, success, timestamp) VALUES (?, ?, ?)", _, err := db.Exec("INSERT INTO auditlog (username, success, timestamp) VALUES (?, ?, ?)",
username, boolToInt(success), time.Now().Format(time.RFC3339)) username, boolToInt(success), time.Now().Format(time.RFC3339))
if err != nil { if err != nil {
log.Println("❌ Failed to log login:", err) log.Println("❌ Failed to log login:", err)

View File

@@ -42,11 +42,13 @@ func InitDB(filepath string) *sql.DB {
ball3 INTEGER, ball3 INTEGER,
ball4 INTEGER, ball4 INTEGER,
ball5 INTEGER, ball5 INTEGER,
ball6 INTEGER,
bonus1 INTEGER, bonus1 INTEGER,
bonus2 INTEGER, bonus2 INTEGER,
duplicate BOOLEAN DEFAULT 0, duplicate BOOLEAN DEFAULT 0,
purchased_date TEXT, purchase_date TEXT,
purchased_location TEXT, purchase_method TEXT,
image_path TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (userId) REFERENCES users(id) FOREIGN KEY (userId) REFERENCES users(id)
);` );`

View File

@@ -0,0 +1,155 @@
{{ define "content" }}
<a href="/">← Back</a>
<h2>Log My Ticket</h2>
<form method="POST" action="/account/tickets/add_ticket" enctype="multipart/form-data" id="ticketForm">
{{ .csrfField }}
<div class="form-section">
<label>Game:
<select name="game_type" id="gameType" required>
<option value="">-- Select Game --</option>
<option value="Thunderball" selected>Thunderball</option>
<option value="Lotto">Lotto</option>
<option value="EuroMillions">EuroMillions</option>
<option value="SetForLife">Set For Life</option>
</select>
</label>
</div>
<div class="form-section">
<label>Draw Date:
<select name="draw_date" required>
{{ range .DrawDates }}
<option value="{{ . }}">{{ . }}</option>
{{ else }}
<option disabled>No draws available</option>
{{ end }}
</select>
</label>
</div>
<div class="form-section">
<label>Purchase Method:
<select name="purchase_method" required>
<option value="In-store">In-store</option>
<option value="National-lottery.com">National-lottery.com</option>
<option value="SynLotto">SynLotto</option>
<option value="Other Online">Other Online</option>
</select>
</label>
</div>
<div class="form-section">
<label for="purchase_date">Purchase Date:</label>
<input type="date" name="purchase_date" id="purchase_date" required>
<label for="purchase_time">(Optional) Time:</label>
<input type="time" name="purchase_time" id="purchase_time">
</div>
<div id="ticketLinesContainer">
<!-- JS will insert ticket lines here -->
</div>
<div class="form-section">
<label for="ticket_image">Upload Ticket Image (optional):</label>
<input type="file" name="ticket_image" id="ticket_image" accept="image/*">
</div>
<button type="button" onclick="addLine()">+ Add Line</button><br><br>
<button type="submit">Save Ticket(s)</button>
</form>
<script>
const gameConfig = {
Thunderball: { balls: 5, bonus: 1, ballRange: [1, 39], bonusRange: [1, 14] },
Lotto: { balls: 6, bonus: 0, ballRange: [1, 59], bonusRange: [] },
EuroMillions: { balls: 5, bonus: 2, ballRange: [1, 50], bonusRange: [1, 12] },
SetForLife: { balls: 5, bonus: 1, ballRange: [1, 47], bonusRange: [1, 10] }
};
const container = document.getElementById("ticketLinesContainer");
let lineCount = 0;
document.getElementById('gameType').addEventListener('change', () => {
container.innerHTML = '';
lineCount = 0;
addLine();
});
function addLine() {
const game = document.getElementById("gameType").value;
if (!game) {
alert("Please select a game first.");
return;
}
const config = gameConfig[game];
const lineId = `line-${lineCount++}`;
const div = document.createElement('div');
div.className = "ticket-line";
div.style.marginBottom = "10px";
div.innerHTML = `
<strong>Line ${lineCount}</strong><br>
${Array.from({ length: 6 }, (_, i) => `
<input type="number" name="ball${i+1}[]" ${i < config.balls ? '' : 'disabled'} required min="${config.ballRange[0]}" max="${config.ballRange[1]}" placeholder="Ball ${i+1}">
`).join('')}
${Array.from({ length: 2 }, (_, i) => `
<input type="number" name="bonus${i+1}[]" ${i < config.bonus ? '' : 'disabled'} ${i < config.bonus ? 'required' : ''} min="${config.bonusRange[0] || 0}" max="${config.bonusRange[1] || 0}" placeholder="Bonus ${i+1}">
`).join('')}
`;
container.appendChild(div);
console.log(`🆕 Line ${lineCount} added for ${game}`);
}
const form = document.getElementById('ticketForm');
form.addEventListener('submit', function (e) {
const lines = document.querySelectorAll(".ticket-line");
if (lines.length === 0) {
e.preventDefault();
alert("Please select a game and add at least one line.");
return;
}
for (const line of lines) {
const mainBalls = Array.from(line.querySelectorAll('input[name^="ball"]'))
.filter(i => !i.disabled)
.map(i => parseInt(i.value, 10));
const bonusBalls = Array.from(line.querySelectorAll('input[name^="bonus"]'))
.filter(i => !i.disabled)
.map(i => parseInt(i.value, 10));
const mainSet = new Set(mainBalls);
const bonusSet = new Set(bonusBalls);
if (mainBalls.includes(NaN) || bonusBalls.includes(NaN)) {
alert("All fields must be filled with valid numbers.");
e.preventDefault();
return;
}
if (mainSet.size !== mainBalls.length) {
alert("Duplicate main numbers detected.");
e.preventDefault();
return;
}
if (bonusSet.size !== bonusBalls.length) {
alert("Duplicate bonus numbers detected.");
e.preventDefault();
return;
}
}
});
window.addEventListener('DOMContentLoaded', () => {
const gameSelect = document.getElementById('gameType');
if (gameSelect.value) {
addLine();
}
});
</script>
{{ end }}