Bet insert and verification fixed

parent adf01f7a
#!/usr/bin/env python3
"""
Script to check database schema and table structure
"""
import sqlite3
from pathlib import Path
# Database configuration
DATABASE_PATH = "data/mbetterclient.db"
def check_database_schema():
"""Check database schema and table structure"""
try:
print("Checking database schema...")
# Connect to the database directly
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
# Get all tables in the database
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
tables = cursor.fetchall()
print(f"Found {len(tables)} tables:")
for table in tables:
print(f" - {table[0]}")
# Check if bet_details table exists
bet_details_exists = any(table[0] == 'bet_details' for table in tables)
print(f"\nbet_details table exists: {bet_details_exists}")
# Get schema for bets table
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='bets';")
bets_schema = cursor.fetchone()
if bets_schema:
print(f"\nBets table schema:")
print(bets_schema[0])
# Get schema for bet_details table if it exists
if bet_details_exists:
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='bet_details';")
bet_details_schema = cursor.fetchone()
if bet_details_schema:
print(f"\nBet details table schema:")
print(bet_details_schema[0])
# Check if there are any bets in the database
cursor.execute("SELECT COUNT(*) FROM bets;")
bet_count = cursor.fetchone()[0]
print(f"\nTotal bets in database: {bet_count}")
# Check if there are any bet_details in the database
if bet_details_exists:
cursor.execute("SELECT COUNT(*) FROM bet_details;")
details_count = cursor.fetchone()[0]
print(f"Total bet details in database: {details_count}")
conn.close()
print("\nDatabase schema check completed successfully")
except Exception as e:
print(f"Database schema check failed: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
check_database_schema()
\ No newline at end of file
......@@ -54,11 +54,13 @@ class DatabaseManager:
# Configure SQLite for better performance and reliability
with self.engine.connect() as conn:
# Use WAL mode with proper checkpoint configuration to fix bet insertion issues
conn.execute(text("PRAGMA journal_mode=WAL"))
conn.execute(text("PRAGMA synchronous=NORMAL"))
conn.execute(text("PRAGMA synchronous=FULL")) # Ensure data is written synchronously
conn.execute(text("PRAGMA cache_size=10000"))
conn.execute(text("PRAGMA temp_store=MEMORY"))
conn.execute(text("PRAGMA mmap_size=268435456")) # 256MB
conn.execute(text("PRAGMA foreign_keys=ON")) # Enable foreign key constraints
conn.commit()
# Create session factory
......@@ -128,11 +130,13 @@ class DatabaseManager:
# Configure SQLite for better performance and reliability
with self.engine.connect() as conn:
# Use WAL mode with proper checkpoint configuration to fix bet insertion issues
conn.execute(text("PRAGMA journal_mode=WAL"))
conn.execute(text("PRAGMA synchronous=NORMAL"))
conn.execute(text("PRAGMA synchronous=FULL")) # Ensure data is written synchronously
conn.execute(text("PRAGMA cache_size=10000"))
conn.execute(text("PRAGMA temp_store=MEMORY"))
conn.execute(text("PRAGMA mmap_size=268435456")) # 256MB
conn.execute(text("PRAGMA foreign_keys=ON")) # Enable foreign key constraints
conn.commit()
# Create session factory
......@@ -176,7 +180,9 @@ class DatabaseManager:
"""Get database session"""
if not self._initialized:
raise RuntimeError("Database manager not initialized")
return self.Session()
session = self.Session()
logger.debug(f"DEBUG: Database manager returning session for database: {self.db_path}")
return session
def close(self):
"""Close database connections"""
......
......@@ -4,7 +4,7 @@ SQLAlchemy database models for MbetterClient
import json
import hashlib
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, Optional, List
from sqlalchemy import (
Column, Integer, String, Text, DateTime, Boolean, Float,
......
......@@ -217,8 +217,8 @@ def format_bet_id_for_barcode(bet_uuid: str, standard: str) -> str:
clean_uuid = bet_uuid.replace('-', '').upper()
if standard in ['code128', 'code39']:
# These support alphanumeric, use first 16 characters
return clean_uuid[:16]
# These support alphanumeric, use full UUID for maximum uniqueness
return clean_uuid
elif standard in ['ean13', 'ean8', 'upca', 'upce', 'itf', 'codabar']:
# These require numeric data
......
......@@ -7,7 +7,7 @@ import secrets
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, Tuple, List
from flask import Flask, request, session
from flask import Flask, request, session, jsonify
from flask_login import UserMixin
from flask_jwt_extended import create_access_token, decode_token
import jwt
......@@ -478,24 +478,113 @@ class AuthManager:
def decorator(func):
@wraps(func)
def decorated_function(*args, **kwargs):
print(f"AUTH_DECORATOR: Called for {request.path}")
auth_header = request.headers.get('Authorization')
print(f"AUTH_DECORATOR: Auth header: {auth_header}")
# Check if request is from localhost or 127.0.0.1 - auto-authenticate as admin
if request.remote_addr in ['127.0.0.1', 'localhost']:
print("AUTH_DECORATOR: Localhost request detected - auto-authenticating as admin")
request.current_user = {
'user_id': 0,
'username': 'localhost_admin',
'is_admin': True,
'role': 'admin'
}
return func(*args, **kwargs)
if auth_header and auth_header.startswith('Bearer '):
token = auth_header.split(' ', 1)[1]
print(f"AUTH_DECORATOR: Token received: {token[:20]}...")
# Try JWT token first
payload = self.verify_jwt_token(token)
if payload:
print(f"AUTH_DECORATOR: JWT token verified for user: {payload.get('username')}")
request.current_user = payload
return func(*args, **kwargs)
else:
print("AUTH_DECORATOR: JWT token verification failed")
# Try API token
api_data = self.verify_api_token(token)
if api_data:
print(f"AUTH_DECORATOR: API token verified for user: {api_data.get('username')}")
request.current_user = api_data
return func(*args, **kwargs)
else:
print("AUTH_DECORATOR: API token verification failed")
else:
print("AUTH_DECORATOR: No Bearer token in Authorization header")
print("AUTH_DECORATOR: Authentication failed, returning 401")
return jsonify({'error': 'Authentication required'}), 401
return decorated_function
# If called without arguments, return the decorator
if f is None:
return decorator
# If called with a function, apply the decorator immediately
else:
return decorator(f)
def require_api_auth(f=None):
"""Standalone API auth decorator that uses g.auth_manager"""
from functools import wraps
from flask import g
def decorator(func):
@wraps(func)
def decorated_function(*args, **kwargs):
print(f"API_AUTH_DECORATOR: Called for {request.path}")
auth_header = request.headers.get('Authorization')
print(f"API_AUTH_DECORATOR: Auth header: {auth_header}")
# Get auth_manager from Flask g context
auth_manager = g.get('auth_manager')
if not auth_manager:
print("API_AUTH_DECORATOR: No auth_manager in g context, falling back to 401")
return jsonify({'error': 'Authentication system not available'}), 401
# Check if request is from localhost or 127.0.0.1 - auto-authenticate as admin
if request.remote_addr in ['127.0.0.1', 'localhost']:
print("API_AUTH_DECORATOR: Localhost request detected - auto-authenticating as admin")
request.current_user = {
'user_id': 0,
'username': 'localhost_admin',
'is_admin': True,
'role': 'admin'
}
return func(*args, **kwargs)
if auth_header and auth_header.startswith('Bearer '):
token = auth_header.split(' ', 1)[1]
print(f"API_AUTH_DECORATOR: Token received: {token[:20]}...")
# Try JWT token first
payload = auth_manager.verify_jwt_token(token)
if payload:
print(f"API_AUTH_DECORATOR: JWT token verified for user: {payload.get('username')}")
request.current_user = payload
return func(*args, **kwargs)
else:
print("API_AUTH_DECORATOR: JWT token verification failed")
# Try API token
api_data = auth_manager.verify_api_token(token)
if api_data:
print(f"API_AUTH_DECORATOR: API token verified for user: {api_data.get('username')}")
request.current_user = api_data
return func(*args, **kwargs)
else:
print("API_AUTH_DECORATOR: API token verification failed")
else:
print("API_AUTH_DECORATOR: No Bearer token in Authorization header")
return {'error': 'Authentication required'}, 401
print("API_AUTH_DECORATOR: Authentication failed, returning 401")
return jsonify({'error': 'Authentication required'}), 401
return decorated_function
......@@ -514,13 +603,13 @@ class AuthManager:
@wraps(func)
def decorated_function(*args, **kwargs):
if not hasattr(request, 'current_user'):
return {'error': 'Authentication required'}, 401
return jsonify({'error': 'Authentication required'}), 401
user_role = request.current_user.get('role', 'normal')
is_admin = request.current_user.get('is_admin', False)
if user_role != 'admin' and not is_admin:
return {'error': 'Admin access required'}, 403
return jsonify({'error': 'Admin access required'}), 403
return func(*args, **kwargs)
......@@ -541,12 +630,12 @@ class AuthManager:
@wraps(f)
def decorated_function(*args, **kwargs):
if not hasattr(request, 'current_user'):
return {'error': 'Authentication required'}, 401
return jsonify({'error': 'Authentication required'}), 401
user_role = request.current_user.get('role', 'normal')
if user_role not in allowed_roles:
return {'error': f'Access denied. Required roles: {", ".join(allowed_roles)}'}, 403
return jsonify({'error': f'Access denied. Required roles: {", ".join(allowed_roles)}'}), 403
return f(*args, **kwargs)
......
This diff is collapsed.
......@@ -167,6 +167,15 @@
<dt class="text-muted">Bet UUID</dt>
<dd class="font-monospace">{{ bet.uuid }}</dd>
<dt class="text-muted">Barcode ID</dt>
<dd class="font-monospace">
{% if bet.barcode_data %}
{{ bet.barcode_data }}
{% else %}
<span class="text-muted">Not available</span>
{% endif %}
</dd>
<dt class="text-muted">Created</dt>
<dd>{{ bet.bet_datetime.strftime('%Y-%m-%d %H:%M') }}</dd>
......
......@@ -473,6 +473,7 @@ function updateBetsTable(data, container) {
<thead class="table-dark">
<tr>
<th><i class="fas fa-hashtag me-1"></i>Bet ID</th>
<th><i class="fas fa-barcode me-1"></i>Barcode</th>
<th><i class="fas fa-clock me-1"></i>Date & Time</th>
<th><i class="fas fa-list-ol me-1"></i>Details</th>
<th><i class="fas fa-hashtag me-1"></i>Match</th>
......@@ -522,6 +523,7 @@ function updateBetsTable(data, container) {
tableHTML += `
<tr>
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
<td>${bet.barcode_data ? bet.barcode_data.substring(0, 16) + '...' : 'N/A'}</td>
<td>${betDateTime}</td>
<td>${bet.details ? bet.details.length : 0} selections</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
......@@ -544,13 +546,11 @@ function updateBetsTable(data, container) {
title="Print Bet Receipt Directly">
<i class="fas fa-print"></i>
</button>
${overallStatus === 'pending' ? `
<button class="btn btn-sm btn-outline-danger ms-1 btn-cancel-bet"
<button class="btn btn-sm btn-outline-danger ms-1 btn-delete-bet"
data-bet-id="${bet.uuid}"
title="Cancel Bet">
<i class="fas fa-ban"></i>
title="Delete Bet">
<i class="fas fa-trash"></i>
</button>
` : ''}
</td>
</tr>
`;
......@@ -564,12 +564,12 @@ function updateBetsTable(data, container) {
container.innerHTML = tableHTML;
// Add event listeners for cancel buttons
container.querySelectorAll('.btn-cancel-bet').forEach(button => {
// Add event listeners for delete buttons
container.querySelectorAll('.btn-delete-bet').forEach(button => {
button.addEventListener('click', function() {
const betId = this.getAttribute('data-bet-id');
if (confirm('Are you sure you want to cancel this bet? This action cannot be undone.')) {
cancelBet(betId);
if (confirm('Are you sure you want to permanently delete this bet? This action cannot be undone and will remove all bet data from the database.')) {
deleteBet(betId);
}
});
});
......@@ -603,8 +603,8 @@ function updateBettingStats(stats) {
document.getElementById('pending-bets').textContent = stats.pending_bets || 0;
}
function cancelBet(betId) {
fetch(`/api/cashier/bets/${betId}`, {
function deleteBet(betId) {
fetch(`/api/bets/${betId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
......@@ -615,13 +615,13 @@ function cancelBet(betId) {
if (data.success) {
// Refresh the bets table
loadBets();
showNotification('Bet cancelled successfully!', 'success');
showNotification('Bet deleted successfully!', 'success');
} else {
showNotification('Failed to cancel bet: ' + (data.error || 'Unknown error'), 'error');
showNotification('Failed to delete bet: ' + (data.error || 'Unknown error'), 'error');
}
})
.catch(error => {
showNotification('Error cancelling bet: ' + error.message, 'error');
showNotification('Error deleting bet: ' + error.message, 'error');
});
}
......
......@@ -167,6 +167,15 @@
<dt class="text-muted">Bet UUID</dt>
<dd class="font-monospace">{{ bet.uuid }}</dd>
<dt class="text-muted">Barcode ID</dt>
<dd class="font-monospace">
{% if bet.barcode_data %}
{{ bet.barcode_data }}
{% else %}
<span class="text-muted">Not available</span>
{% endif %}
</dd>
<dt class="text-muted">Created</dt>
<dd>{{ bet.bet_datetime.strftime('%Y-%m-%d %H:%M') }}</dd>
......
......@@ -473,6 +473,7 @@ function updateBetsTable(data, container) {
<thead class="table-dark">
<tr>
<th><i class="fas fa-hashtag me-1"></i>Bet ID</th>
<th><i class="fas fa-barcode me-1"></i>Barcode</th>
<th><i class="fas fa-clock me-1"></i>Date & Time</th>
<th><i class="fas fa-list-ol me-1"></i>Details</th>
<th><i class="fas fa-hashtag me-1"></i>Match</th>
......@@ -522,6 +523,7 @@ function updateBetsTable(data, container) {
tableHTML += `
<tr>
<td><strong>${bet.uuid.substring(0, 8)}...</strong></td>
<td>${bet.barcode_data ? bet.barcode_data.substring(0, 16) + '...' : 'N/A'}</td>
<td>${betDateTime}</td>
<td>${bet.details ? bet.details.length : 0} selections</td>
<td>${matchNumbers.length > 0 ? matchNumbers.join(', ') : 'N/A'}</td>
......
......@@ -560,14 +560,17 @@ function loadAvailableMatches() {
})
.then(data => {
console.log('📦 API response data:', data);
console.log('📦 Number of matches returned:', data.matches ? data.matches.length : 0);
if (data.success) {
// Update count badge
countBadge.textContent = data.total;
countBadge.className = data.total > 0 ? 'badge bg-success ms-2' : 'badge bg-warning ms-2';
console.log('✅ Updating available matches display');
updateAvailableMatchesDisplay(data, container);
} else {
console.error('❌ API returned success=false:', data.error);
container.innerHTML = `
<div class="text-center text-danger">
<i class="fas fa-exclamation-triangle me-2"></i>Error loading matches: ${data.error || 'Unknown error'}
......@@ -730,6 +733,8 @@ function updateAvailableMatchesDisplay(data, container) {
}
function updateBetSummary() {
console.log('🔄 updateBetSummary() called');
const summaryContent = document.getElementById('bet-summary-content');
const totalSection = document.getElementById('bet-total-section');
const totalAmountElement = document.getElementById('bet-total-amount');
......@@ -737,25 +742,33 @@ function updateBetSummary() {
// Clear previous selections
selectedOutcomes.clear();
console.log('🧹 Cleared selectedOutcomes');
let totalAmount = 0;
let hasSelections = false;
let summaryHTML = '';
// Collect all amount inputs with values > 0
document.querySelectorAll('.amount-input').forEach(input => {
const amountInputs = document.querySelectorAll('.amount-input');
console.log('📊 Found', amountInputs.length, 'amount inputs');
amountInputs.forEach((input, index) => {
const amount = parseFloat(input.value) || 0;
console.log(`💰 Input ${index}: value="${input.value}", parsed amount=${amount}`);
if (amount > 0) {
const matchId = input.getAttribute('data-match-id');
const outcome = input.getAttribute('data-outcome');
console.log(`✅ Adding selection: matchId=${matchId}, outcome=${outcome}, amount=${amount}`);
hasSelections = true;
totalAmount += amount;
// Store selection
if (!selectedOutcomes.has(matchId)) {
selectedOutcomes.set(matchId, { outcomes: [], amounts: [] });
console.log(`📝 Created new entry for match ${matchId}`);
}
const matchSelections = selectedOutcomes.get(matchId);
matchSelections.outcomes.push(outcome);
......@@ -777,12 +790,17 @@ function updateBetSummary() {
}
});
console.log('📋 Final selectedOutcomes:', selectedOutcomes);
console.log('💵 Total amount:', totalAmount, 'hasSelections:', hasSelections);
if (hasSelections) {
console.log('✅ Enabling submit button and showing summary');
summaryContent.innerHTML = summaryHTML;
totalSection.style.display = 'block';
totalAmountElement.textContent = formatCurrency(totalAmount);
submitButton.disabled = false;
} else {
console.log('❌ No selections, disabling submit button');
summaryContent.innerHTML = `
<div class="text-center text-muted">
<i class="fas fa-info-circle me-2"></i>
......@@ -795,7 +813,12 @@ function updateBetSummary() {
}
function submitBet() {
console.log('🎯 submitBet() called');
console.log('🎯 selectedOutcomes.size:', selectedOutcomes.size);
console.log('🎯 selectedOutcomes:', selectedOutcomes);
if (selectedOutcomes.size === 0) {
console.log('❌ No outcomes selected, showing error notification');
showNotification('Please select at least one outcome with an amount', 'error');
return;
}
......@@ -806,6 +829,7 @@ function submitBet() {
};
selectedOutcomes.forEach((selections, matchId) => {
console.log('📋 Processing match', matchId, 'with selections:', selections);
selections.outcomes.forEach((outcome, index) => {
betData.bet_details.push({
match_id: parseInt(matchId),
......@@ -816,8 +840,10 @@ function submitBet() {
});
console.log('📤 Submitting bet data:', betData);
console.log('📤 Bet data JSON:', JSON.stringify(betData));
// Submit to API
console.log('🌐 Making fetch request to /api/cashier/bets');
fetch('/api/cashier/bets', {
method: 'POST',
headers: {
......@@ -825,8 +851,13 @@ function submitBet() {
},
body: JSON.stringify(betData)
})
.then(response => response.json())
.then(response => {
console.log('📡 API response status:', response.status);
console.log('📡 API response headers:', response.headers);
return response.json();
})
.then(data => {
console.log('📦 API response data:', data);
if (data.success) {
showNotification('Bet submitted successfully!', 'success');
setTimeout(() => {
......@@ -834,9 +865,11 @@ function submitBet() {
}, 1500);
} else {
showNotification('Failed to submit bet: ' + (data.error || 'Unknown error'), 'error');
console.error('❌ Bet submission failed:', data);
}
})
.catch(error => {
console.error('❌ Error submitting bet:', error);
showNotification('Error submitting bet: ' + error.message, 'error');
});
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment