From 46dd7b1bb9caca3f0417ead48528d33338633cb2 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 1 Feb 2026 19:38:42 +0000 Subject: [PATCH] 78 --- .../20260201120000-public-read-permissions.js | 43 ++ .../20260201120001-more-sample-data.js | 98 +++++ backend/src/index.js | 4 +- backend/src/services/tickets.js | 20 +- frontend/src/pages/index.tsx | 372 ++++++++++++------ 5 files changed, 406 insertions(+), 131 deletions(-) create mode 100644 backend/src/db/migrations/20260201120000-public-read-permissions.js create mode 100644 backend/src/db/migrations/20260201120001-more-sample-data.js diff --git a/backend/src/db/migrations/20260201120000-public-read-permissions.js b/backend/src/db/migrations/20260201120000-public-read-permissions.js new file mode 100644 index 0000000..12c65cf --- /dev/null +++ b/backend/src/db/migrations/20260201120000-public-read-permissions.js @@ -0,0 +1,43 @@ + +module.exports = { + up: async (queryInterface, Sequelize) => { + const roles = await queryInterface.sequelize.query( + `SELECT id FROM roles WHERE name = 'Public' LIMIT 1;`, + { type: Sequelize.QueryTypes.SELECT } + ); + + if (roles.length > 0) { + const publicRoleId = roles[0].id; + + const permissions = await queryInterface.sequelize.query( + `SELECT id, name FROM permissions WHERE name IN ('READ_GAMES', 'READ_SELECTIONS', 'READ_MARKETS', 'READ_TEAMS', 'READ_LEAGUES');`, + { type: Sequelize.QueryTypes.SELECT } + ); + + const now = new Date(); + const rolePermissions = permissions.map(p => ({ + createdAt: now, + updatedAt: now, + roles_permissionsId: publicRoleId, + permissionId: p.id + })); + + // Use queryInterface.bulkInsert for the join table + // Note: We use queryInterface.sequelize.query because the table name might have double quotes and case sensitivity + for (const rp of rolePermissions) { + await queryInterface.sequelize.query( + `INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId") + VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING;`, + { + replacements: [rp.createdAt, rp.updatedAt, rp.roles_permissionsId, rp.permissionId], + type: Sequelize.QueryTypes.INSERT + } + ); + } + } + }, + + down: async (queryInterface, Sequelize) => { + // Optionally implement removal + } +}; diff --git a/backend/src/db/migrations/20260201120001-more-sample-data.js b/backend/src/db/migrations/20260201120001-more-sample-data.js new file mode 100644 index 0000000..3160198 --- /dev/null +++ b/backend/src/db/migrations/20260201120001-more-sample-data.js @@ -0,0 +1,98 @@ + +const { v4: uuid } = require('uuid'); + +module.exports = { + up: async (queryInterface, Sequelize) => { + const now = new Date(); + + // Get some leagues and teams + const leagues = await queryInterface.sequelize.query(`SELECT id FROM leagues LIMIT 5`, { type: Sequelize.QueryTypes.SELECT }); + const teams = await queryInterface.sequelize.query(`SELECT id FROM teams LIMIT 10`, { type: Sequelize.QueryTypes.SELECT }); + + if (leagues.length === 0 || teams.length < 2) return; + + const games = []; + for (let i = 0; i < 15; i++) { + const homeTeam = teams[Math.floor(Math.random() * teams.length)]; + let awayTeam = teams[Math.floor(Math.random() * teams.length)]; + while (awayTeam.id === homeTeam.id) { + awayTeam = teams[Math.floor(Math.random() * teams.length)]; + } + + const gameId = uuid(); + games.push({ + id: gameId, + title: `Game ${i + 4}`, + status: 'scheduled', + start_time: new Date(now.getTime() + (Math.random() * 7 * 24 * 60 * 60 * 1000)), + venue: 'Stadium ' + (i + 4), + leagueId: leagues[Math.floor(Math.random() * leagues.length)].id, + home_teamId: homeTeam.id, + away_teamId: awayTeam.id, + createdAt: now, + updatedAt: now + }); + } + + await queryInterface.bulkInsert('games', games); + + const markets = []; + const selections = []; + + for (const game of games) { + const marketId = uuid(); + markets.push({ + id: marketId, + name: 'Match Winner', + market_type: 'match_winner', + status: 'active', + min_selections: 1, + gameId: game.id, + createdAt: now, + updatedAt: now + }); + + // 1x2 odds + const odds1 = (1.5 + Math.random() * 2).toFixed(2); + const oddsX = (2.5 + Math.random() * 2).toFixed(2); + const odds2 = (2.0 + Math.random() * 3).toFixed(2); + + selections.push({ + id: uuid(), + name: 'Home Win', + odds: odds1, + probability: (1/odds1).toFixed(2), + result: 'pending', + marketId: marketId, + createdAt: now, + updatedAt: now + }); + selections.push({ + id: uuid(), + name: 'Draw', + odds: oddsX, + probability: (1/oddsX).toFixed(2), + result: 'pending', + marketId: marketId, + createdAt: now, + updatedAt: now + }); + selections.push({ + id: uuid(), + name: 'Away Win', + odds: odds2, + probability: (1/odds2).toFixed(2), + result: 'pending', + marketId: marketId, + createdAt: now, updatedAt: now + }); + } + + await queryInterface.bulkInsert('markets', markets); + await queryInterface.bulkInsert('selections', selections); + }, + + down: async (queryInterface, Sequelize) => { + // Optionally implement removal + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index 239b3b1..d6fc520 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -110,11 +110,11 @@ app.use('/api/teams', passport.authenticate('jwt', {session: false}), teamsRoute app.use('/api/leagues', passport.authenticate('jwt', {session: false}), leaguesRoutes); -app.use('/api/games', passport.authenticate('jwt', {session: false}), gamesRoutes); +app.use('/api/games', gamesRoutes); app.use('/api/markets', passport.authenticate('jwt', {session: false}), marketsRoutes); -app.use('/api/selections', passport.authenticate('jwt', {session: false}), selectionsRoutes); +app.use('/api/selections', selectionsRoutes); app.use('/api/tickets', ticketsRoutes); diff --git a/backend/src/services/tickets.js b/backend/src/services/tickets.js index 6cf322f..00e1ad8 100644 --- a/backend/src/services/tickets.js +++ b/backend/src/services/tickets.js @@ -7,10 +7,6 @@ const axios = require('axios'); const config = require('../config'); const stream = require('stream'); - - - - module.exports = class TicketsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); @@ -134,8 +130,8 @@ module.exports = class TicketsService { static async generateAutoTicket(data, currentUser) { const { desired_odds, game_count } = data; - const targetOdds = parseFloat(desired_odds) || 2.0; - const count = parseInt(game_count) || 3; + const targetOdds = parseFloat(desired_odds) || 2.5; + const count = parseInt(game_count) || 5; // 1. Get all active selections with their game info const selections = await db.selections.findAll({ @@ -150,13 +146,13 @@ module.exports = class TicketsService { }); if (selections.length === 0) { - throw new Error('No selections available in the database'); + return { error: 'No selections available in the database' }; } // Group selections by game to ensure we pick from different games const gameGroups = {}; selections.forEach(s => { - if (s.market && s.market.game) { + if (s.market && s.market.game && s.market.game.status === 'scheduled') { const gameId = s.market.game.id; if (!gameGroups[gameId]) gameGroups[gameId] = []; gameGroups[gameId].push(s); @@ -167,14 +163,14 @@ module.exports = class TicketsService { const actualCount = Math.min(count, gameIds.length); if (actualCount === 0) { - throw new Error('Not enough games available'); + return { error: 'Not enough scheduled games available' }; } // Try multiple times to find a good combination let bestTicket = null; let minDiff = Infinity; - for (let i = 0; i < 200; i++) { + for (let i = 0; i < 500; i++) { // Randomly pick actualCount games const shuffledGames = gameIds.sort(() => 0.5 - Math.random()); const selectedGameIds = shuffledGames.slice(0, actualCount); @@ -199,9 +195,9 @@ module.exports = class TicketsService { }; } - if (minDiff < 0.05) break; // Close enough + if (minDiff < (targetOdds * 0.02)) break; // Close enough (2% tolerance) } return bestTicket; } -}; \ No newline at end of file +}; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 34edf78..24855ac 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import { mdiFlash, mdiDiceMultiple, mdiTrophy, mdiRefresh, mdiCheckDecagram } from '@mdi/js'; +import { mdiFlash, mdiDiceMultiple, mdiTrophy, mdiRefresh, mdiCheckDecagram, mdiChartLine, mdiSoccer } from '@mdi/js'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; import LayoutGuest from '../layouts/Guest'; @@ -11,20 +11,40 @@ import SectionMain from '../components/SectionMain'; import { getPageTitle } from '../config'; import { useAppDispatch } from '../stores/hooks'; import { generateAutoTicket } from '../stores/tickets/ticketsSlice'; +import axios from 'axios'; export default function Home() { const dispatch = useAppDispatch(); - const [targetOdds, setTargetOdds] = useState('2.50'); - const [gameCount, setGameCount] = useState('5'); + const [targetOdds, setTargetOdds] = useState('5.00'); + const [gameCount, setGameCount] = useState('4'); + const [stake, setStake] = useState('10'); const [generatedTicket, setGeneratedTicket] = useState(null); const [loading, setLoading] = useState(false); + const [liveGames, setLiveGames] = useState([]); + + useEffect(() => { + const fetchGames = async () => { + try { + const response = await axios.get('games?limit=6'); + setLiveGames(response.data.rows || []); + } catch (error) { + console.error('Failed to fetch games:', error); + } + }; + fetchGames(); + }, []); const handleGenerate = async () => { setLoading(true); + setGeneratedTicket(null); try { const resultAction = await dispatch(generateAutoTicket({ desired_odds: targetOdds, game_count: gameCount })); if (generateAutoTicket.fulfilled.match(resultAction)) { - setGeneratedTicket(resultAction.payload); + if (resultAction.payload.error) { + alert(resultAction.payload.error); + } else { + setGeneratedTicket(resultAction.payload); + } } } catch (error) { console.error('Failed to generate ticket:', error); @@ -33,163 +53,236 @@ export default function Home() { } }; + const potentialReturn = generatedTicket ? (parseFloat(stake) * generatedTicket.totalOdds).toFixed(2) : '0.00'; + return ( -
+
- {getPageTitle('BetMagic - Auto Ticket Generator')} + {getPageTitle('BetMagic - AI Sports Betting Generator')} {/* Header */} -