Add tickets to db.
This commit is contained in:
@@ -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)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -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)))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
);`
|
);`
|
||||||
|
|||||||
155
templates/account/tickets/add_ticket.html
Normal file
155
templates/account/tickets/add_ticket.html
Normal 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 }}
|
||||||
Reference in New Issue
Block a user