From e8a61464b822bca3e8c920da44f0c7ec3c5ff015 Mon Sep 17 00:00:00 2001 From: Jose1277 Date: Thu, 27 Jun 2024 00:32:24 -0300 Subject: [PATCH] =?UTF-8?q?Novas=20apis=20e=20integra=C3=A7=C3=A3o=20clien?= =?UTF-8?q?t=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/app/{ => auth}/register/layout.tsx | 0 .../client/app/{ => auth}/register/page.tsx | 0 projeto/client/app/home/page.tsx | 149 ++++++++++++------ projeto/client/app/models/match.tsx | 22 ++- .../components/auth/home/card-times.tsx | 37 ----- projeto/client/components/auth/home/grid.tsx | 25 --- projeto/client/components/home/card-times.tsx | 47 ++++++ projeto/client/components/home/grid.tsx | 32 ++++ .../server/src/repositories/api.repository.ts | 87 +++++++++- .../src/repositories/scraping.repository.ts | 18 +++ projeto/server/src/routes/api.route.ts | 37 +++++ projeto/server/src/routes/auth.route.ts | 12 ++ projeto/server/src/routes/scraping.route.ts | 6 +- projeto/server/src/routing.ts | 2 + projeto/server/src/schemas/api.schema.ts | 7 + 15 files changed, 356 insertions(+), 125 deletions(-) rename projeto/client/app/{ => auth}/register/layout.tsx (100%) rename projeto/client/app/{ => auth}/register/page.tsx (100%) delete mode 100644 projeto/client/components/auth/home/card-times.tsx delete mode 100644 projeto/client/components/auth/home/grid.tsx create mode 100644 projeto/client/components/home/card-times.tsx create mode 100644 projeto/client/components/home/grid.tsx create mode 100644 projeto/server/src/routes/auth.route.ts diff --git a/projeto/client/app/register/layout.tsx b/projeto/client/app/auth/register/layout.tsx similarity index 100% rename from projeto/client/app/register/layout.tsx rename to projeto/client/app/auth/register/layout.tsx diff --git a/projeto/client/app/register/page.tsx b/projeto/client/app/auth/register/page.tsx similarity index 100% rename from projeto/client/app/register/page.tsx rename to projeto/client/app/auth/register/page.tsx diff --git a/projeto/client/app/home/page.tsx b/projeto/client/app/home/page.tsx index 1975a3e..7e23d2f 100644 --- a/projeto/client/app/home/page.tsx +++ b/projeto/client/app/home/page.tsx @@ -1,16 +1,15 @@ "use client"; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { cn } from '@/lib/utils'; import { Match } from '@/app/models/match'; import { Bebas_Neue } from 'next/font/google'; -import { GridTimes } from '@/components/auth/home/grid'; -import { CardTimes } from '@/components/auth/home/card-times'; - +import { GridTimes } from '@/components/home/grid'; +import { CardTimes } from '@/components/home/card-times'; const font = Bebas_Neue({ - subsets: ['latin'], - weight: ['400'], + subsets: ['latin'], + weight: ['400'], }); const h1ClassName = "text-4xl font-semibold text-black drop-shadow-md text-center mt-8 mb-8"; @@ -18,56 +17,110 @@ const h2ClassName = "text-3xl font-semibold text-black drop-shadow-md"; const divCardsClassName = "w-full pt-4 pb-4"; // Partidas exemplos -const lib = new Match("Libertadores", "24/06/2024 13:30", "Time A", "Time B", ["Globo", "SporTV"]); -const br = new Match("Brasileirão", "24/06/2024 13:30", "Time A", "Time B", ["Globo", "SporTV"]); +const lib = new Match("Libertadores", "Sem Jogos", "", "", [""]); +const br = new Match("Brasileirão", "Sem Jogos", "", "", [""]); export default function HomePage() { - return ( -
-
- -
-

Rodada X

- -
+ const [favorito, setFavorito] = useState(); + useEffect(() => { + fetch("http://localhost:8000/auth/v1/favorito") + .then(response => response.json()) + .then(data => { + setFavorito(data.favorito); + }) + .catch(error => { + console.error('Erro ao buscar os dados:', error); + }); + }, []); -
-

Time Usuário

- -
-
-

Brasileirão

+ const [rodada, setRodada] = useState(0); + useEffect(() => { + fetch("http://localhost:8000/api/v1/rodada") + .then(response => response.json()) + .then(data => { + setRodada(data); + }) + .catch(error => { + console.error('Erro ao buscar os dados:', error); + }); + }, []); -
- -
+ const [jogoBr, setJogoBr] = useState(br); + useEffect(() => { + fetch(`http://localhost:8000/api/v1/games/brasileirao/${favorito}`) + .then(response => response.json()) + .then(data => { + const jogoBrData = data[0]; + if (jogoBrData.campeonato && jogoBrData.data_hora && jogoBrData.time_1 && jogoBrData.time_2 && jogoBrData.channels) { + const JogoBR = new Match(jogoBrData.campeonato, jogoBrData.data_hora, jogoBrData.time_1, jogoBrData.time_2, jogoBrData.channels); + console.log(JogoBR); + setJogoBr(JogoBR); + } else { + console.error('Dados inválidos recebidos da API', jogoBrData); + } + }) + .catch(error => { + console.error('Erro ao buscar o jogo do Brasileirão:', error); + }); + }, [favorito]); -
- -
-

Libertadores

+ const [jogoLib, setJogoLib] = useState(lib); + useEffect(() => { + fetch(`http://localhost:8000/api/v1/games/liberta/${favorito}`) + .then(response => response.json()) + .then(data => { + const jogoLibData = data[0]; + if (jogoLibData.campeonato && jogoLibData.data_hora && jogoLibData.time_1 && jogoLibData.time_2 && jogoLibData.channels) { + const JogoLib = new Match(jogoLibData.campeonato, jogoLibData.data_hora, jogoLibData.time_1, jogoLibData.time_2, jogoLibData.channels); + console.log(JogoLib); + setJogoLib(JogoLib); + } else { + console.error('Dados inválidos recebidos da API', data); + } + }) + .catch(error => { + console.error('Erro ao buscar o jogo da Libertadores:', error); + }); + }, [favorito]); + + return ( +
+
-
- +
+

Rodada {rodada}

+
-
- -
-

Canais de Transmissão

-
-
+
+

{favorito}

-
+
+
+

Brasileirão

+ +
+ +
+
-
-
+
+

Libertadores

- ); -}; +
+ +
+
+ +
+

Canais de Transmissão

+
+ +
+ +
+ +
+
+ ); +} diff --git a/projeto/client/app/models/match.tsx b/projeto/client/app/models/match.tsx index 39d6ac1..ad4a016 100644 --- a/projeto/client/app/models/match.tsx +++ b/projeto/client/app/models/match.tsx @@ -1,16 +1,16 @@ export class Match { campeonato: string; - data_hora: Date; + data_hora: Date | null; time1: string; time2: string; channels: string[]; constructor( - campeonato: string, - data_hora: string, - time1: string, - time2: string, - channels: string[] + campeonato: string, + data_hora: string, + time1: string, + time2: string, + channels: string[] ) { this.campeonato = campeonato; this.data_hora = this.parseDate(data_hora); @@ -19,7 +19,11 @@ export class Match { this.channels = channels; } - private parseDate(dateString: string): Date { + private parseDate(dateString: string): Date | null { + if (!dateString || dateString.toLowerCase() === "sem jogos") { + console.warn('Date string is undefined, empty or "Sem Jogos"'); + return null; + } const [datePart, timePart] = dateString.split(' '); const [day, month, year] = datePart.split('/').map(Number); const [hours, minutes] = timePart.split(':').map(Number); @@ -27,6 +31,9 @@ export class Match { } public formatDate(): string { + if (this.data_hora === null) { + return "Sem Jogos"; + } const day = String(this.data_hora.getDate()).padStart(2, '0'); const month = String(this.data_hora.getMonth() + 1).padStart(2, '0'); const year = this.data_hora.getFullYear(); @@ -35,4 +42,3 @@ export class Match { return `${day}/${month}/${year} - ${hours}:${minutes}`; } } - \ No newline at end of file diff --git a/projeto/client/components/auth/home/card-times.tsx b/projeto/client/components/auth/home/card-times.tsx deleted file mode 100644 index 3334370..0000000 --- a/projeto/client/components/auth/home/card-times.tsx +++ /dev/null @@ -1,37 +0,0 @@ -"use client"; - -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card" - -import { Match } from "@/app/models/match"; - -interface CardTimesProps { - match: Match; -}; - -export const CardTimes = ({ match }: CardTimesProps) => { - return ( - - - {match.formatDate()} - - - -

{match.time1} 🏐 X 🏐 {match.time2}

-
- - - {match.channels.map((channel, index) => ( - - {channel} - - ))} - -
- ); -}; diff --git a/projeto/client/components/auth/home/grid.tsx b/projeto/client/components/auth/home/grid.tsx deleted file mode 100644 index be289c3..0000000 --- a/projeto/client/components/auth/home/grid.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import { Match } from "@/app/models/match"; -import { CardTimes } from "@/components/auth/home/card-times"; - -const matches: Match[] = [ - new Match("Brasileirão", "24/06/2024 13:30", "Time A", "Time B", ["Globo", "SporTV"]), - new Match("Brasileirão", "25/06/2024 16:00", "Time C", "Time D", ["ESPN", "Fox Sports"]), - new Match("Brasileirão", "26/06/2024 18:30", "Time E", "Time F", ["TNT Sports", "Premiere"]), - new Match("Brasileirão", "27/06/2024 20:45", "Time G", "Time H", ["Globo", "SporTV"]), - new Match("Brasileirão", "28/06/2024 21:00", "Time I", "Time J", ["ESPN", "Fox Sports"]), - new Match("Brasileirão", "29/06/2024 14:00", "Time K", "Time L", ["TNT Sports", "Premiere"]), - new Match("Brasileirão", "30/06/2024 15:30", "Time M", "Time N", ["Globo", "SporTV"]), - new Match("Brasileirão", "01/07/2024 19:00", "Time O", "Time P", ["ESPN", "Fox Sports"]) -]; - -export const GridTimes = () => { - return ( -
- {matches.map((match, index) => ( - - ))} -
- ); -}; diff --git a/projeto/client/components/home/card-times.tsx b/projeto/client/components/home/card-times.tsx new file mode 100644 index 0000000..a675b30 --- /dev/null +++ b/projeto/client/components/home/card-times.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" + +import { Match } from "@/app/models/match"; + +interface CardTimesProps { + match: Match; +}; + +export const CardTimes = ({ match }: CardTimesProps) => { + if (match.data_hora === null) { + return ( + + +

Não há jogos

+
+
+ ); + } + + return ( + + + {match.formatDate()} + + + +

{match.time1} X {match.time2}

+
+ + + {match.channels.map((channel, index) => ( + + {channel} + + ))} + +
+ ); +}; diff --git a/projeto/client/components/home/grid.tsx b/projeto/client/components/home/grid.tsx new file mode 100644 index 0000000..777000d --- /dev/null +++ b/projeto/client/components/home/grid.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { Match } from "@/app/models/match"; +import { CardTimes } from "@/components/home/card-times"; +import {useEffect, useState} from "react"; + +export const GridTimes = () => { + const [matches, setMatches] = useState([]); + + useEffect(() => { + const fetchMatches = async () => { + try { + const response = await fetch("http://localhost:8000/api/v4/games/8"); + const data = await response.json(); + const fetchedMatches = data.map((item: any) => new Match(item.campeonato, item.data_hora, item.time_1, item.time_2, item.channels)); + setMatches(fetchedMatches); + } catch (error) { + console.error('Erro ao buscar os dados:', error); + } + }; + + fetchMatches(); + }, []); + + return ( +
+ {matches.map((match, index) => ( + + ))} +
+ ); +}; diff --git a/projeto/server/src/repositories/api.repository.ts b/projeto/server/src/repositories/api.repository.ts index 2660ddd..5795f34 100644 --- a/projeto/server/src/repositories/api.repository.ts +++ b/projeto/server/src/repositories/api.repository.ts @@ -1,6 +1,11 @@ -import { mysqlConn } from '../base/mysql'; +import {mysqlConn} from '../base/mysql'; import ScrapingRepository from '../repositories/scraping.repository'; -import {GameSchema, InsertGameSchema, InsertGame, GameChannel, GameChannelSchema} from '../schemas/api.schema'; +import { + GameChannel, + GameChannelSchema, + InsertGame, + InsertGameSchema, +} from '../schemas/api.schema'; function toMySQLDateTimeString(date: Date): string { return date.toISOString().slice(0, 19).replace('T', ' '); @@ -61,6 +66,7 @@ class GameRepository { const channels = game.channels.length > 2 ? game.channels.slice(0, 2) : game.channels; const insertGame: InsertGame = { + rodada: game.rodada, campeonato: game.campeonato, hora: horaMySQL, time1: game.time1, @@ -79,8 +85,8 @@ class GameRepository { if (existingGame.length === 0) { const [result]: any = await mysqlConn.execute( - "INSERT INTO JOGO (TIME_1, TIME_2, DATA_HORA, CAMPEONATO) VALUES (?, ?, ?, ?)", - [game.time1, game.time2, game.hora, game.campeonato] + "INSERT INTO JOGO (TIME_1, TIME_2, DATA_HORA, CAMPEONATO, RODADA) VALUES (?, ?, ?, ?, ?)", + [game.time1, game.time2, game.hora, game.campeonato, game.rodada] ); const jogoId = result.insertId; @@ -188,6 +194,79 @@ class GameRepository { return [] } } + + async getRodada(): Promise { + try { + const [response]: [any[], any[]] = await mysqlConn.execute("SELECT RODADA FROM JOGO"); + const rodada: number = response[0].RODADA; + return rodada; + } catch (error) { + console.error(error); + return 0; + } + } + + async getGamesByQuantidade(quantidade: string): Promise { + try { + const [games]: any = await mysqlConn.execute( + "SELECT J.CAMPEONATO, J.DATA_HORA, J.TIME_1, J.TIME_2, GROUP_CONCAT(C.NOME_CANAL) AS CHANNELS " + + "FROM JOGO J " + + "JOIN PASSA_EM PE ON J.IDJOGO = PE.ID_JOGO " + + "JOIN CANAL C ON PE.ID_CANAL = C.IDCANAL " + + "WHERE J.DATA_HORA >= NOW() " + + "GROUP BY J.CAMPEONATO, J.DATA_HORA, J.IDJOGO " + + "LIMIT ?", [quantidade] + ); + + return retornaJogos(games); + } catch (error) { + console.error(error); + return []; + } + } + + async getGamesBrasileiraoByFavorito(favorito: string): Promise { + try { + const [games]: any = await mysqlConn.execute( + "SELECT J.CAMPEONATO, J.DATA_HORA, J.TIME_1, J.TIME_2, GROUP_CONCAT(C.NOME_CANAL) AS CHANNELS " + + "FROM JOGO J " + + "JOIN PASSA_EM PE ON J.IDJOGO = PE.ID_JOGO " + + "JOIN CANAL C ON PE.ID_CANAL = C.IDCANAL " + + "WHERE J.DATA_HORA >= NOW() " + + "AND (J.TIME_1 = ? OR J.TIME_2 = ?) " + + "AND J.CAMPEONATO = 'Brasileirão' " + + "GROUP BY J.CAMPEONATO, J.DATA_HORA, J.IDJOGO "+ + "LIMIT 1", [favorito, favorito] + ); + + return retornaJogos(games); + } catch (error) { + console.error(error); + return []; + } + } + + async getGamesLibertaByFavorito(favorito: string): Promise { + try { + const [games]: any = await mysqlConn.execute( + "SELECT J.CAMPEONATO, J.DATA_HORA, J.TIME_1, J.TIME_2, GROUP_CONCAT(C.NOME_CANAL) AS CHANNELS " + + "FROM JOGO J " + + "JOIN PASSA_EM PE ON J.IDJOGO = PE.ID_JOGO " + + "JOIN CANAL C ON PE.ID_CANAL = C.IDCANAL " + + "WHERE J.DATA_HORA >= NOW() " + + "AND (J.TIME_1 = ? OR J.TIME_2 = ?) " + + "AND J.CAMPEONATO = 'Libertadores' " + + "GROUP BY J.CAMPEONATO, J.DATA_HORA, J.IDJOGO " + + "LIMIT 1", [favorito, favorito] + ); + + return retornaJogos(games); + } catch (error) { + console.error(error); + return []; + } + } + } export default new GameRepository(); diff --git a/projeto/server/src/repositories/scraping.repository.ts b/projeto/server/src/repositories/scraping.repository.ts index 8e99a63..05745e4 100644 --- a/projeto/server/src/repositories/scraping.repository.ts +++ b/projeto/server/src/repositories/scraping.repository.ts @@ -89,6 +89,7 @@ class ScrapingRepository { datetime.setMilliseconds(0); games.push({ + rodada: 0, campeonato: "Brasileirão", hora: datetime, time1: time1, @@ -112,6 +113,8 @@ class ScrapingRepository { const response = await fetchWithRetry(url2, 3, 2000); // 3 retries com 2 segundos de atraso const html = response.data; const $ = cheerio.load(html); + let rodadaMatch: string | string[] | null; + let rodada: number; const months: { [key: string]: string } = { janeiro: "01", @@ -129,6 +132,18 @@ class ScrapingRepository { }; const games: Game[] = []; + $('h2').each((index, element) => { + const hTexto = $(element).text().trim(); + console.log(`Processing header: ${hTexto}`); + const rodadaRegex = /\d{1,2}ª\s+rodada/u; + rodadaMatch = rodadaRegex.exec(hTexto); + console.log(`Extracted round: ${rodadaMatch}`); + if (rodadaMatch != null) { + rodada = parseInt((rodadaMatch[0].split('ª')[0])); + console.log(`Extracted round: ${rodada}`); + } + }); + $('p').each((index, element) => { const pTexto = $(element).text().trim(); console.log(`Processing paragraph: ${pTexto}`); @@ -138,6 +153,7 @@ class ScrapingRepository { const dia = dataMatch ? dataMatch[1] : null; const mes = dataMatch ? months[dataMatch[2]] : null; + console.log(`Extracted date - Day: ${dia}, Month: ${mes}`); if (dataMatch) { @@ -177,6 +193,7 @@ class ScrapingRepository { console.log(`Game datetime: ${datetime}`); games.push({ + rodada: rodada, campeonato: "Brasileirão", hora: datetime, time1: time1, @@ -259,6 +276,7 @@ class ScrapingRepository { console.log(`Game datetime: ${datetime}`); games.push({ + rodada: 0, campeonato: "Libertadores", hora: datetime, time1: time1, diff --git a/projeto/server/src/routes/api.route.ts b/projeto/server/src/routes/api.route.ts index 2db32a6..671f74e 100644 --- a/projeto/server/src/routes/api.route.ts +++ b/projeto/server/src/routes/api.route.ts @@ -48,4 +48,41 @@ router.get('/v3/games/:campeonato', async (req, res) => { } }); +router.get('/v4/games/:quantidade', async (req, res) => { + try { + const games = await GameRepository.getGamesByQuantidade((req.params.quantidade)); + res.send(games); + } catch (error) { + res.status(500).send(error); + } +}); + +router.get('/v1/rodada' , async (req, res) => { + try { + const rodada = await GameRepository.getRodada(); + const rodadaJson = JSON.stringify(rodada); + res.send(rodadaJson); + } catch (error) { + res.status(500).send(error); + } +}); + +router.get('/v1/games/brasileirao/:favorito', async (req, res) => { + try { + const games = await GameRepository.getGamesBrasileiraoByFavorito(req.params.favorito); + res.send(games); + } catch (error) { + res.status(500).send(error) + } +}); + +router.get('/v1/games/liberta/:favorito', async (req, res) => { + try { + const games = await GameRepository.getGamesLibertaByFavorito(req.params.favorito); + res.send(games); + } catch (error) { + res.status(500).send(error) + } +}); + export default router; diff --git a/projeto/server/src/routes/auth.route.ts b/projeto/server/src/routes/auth.route.ts new file mode 100644 index 0000000..a5c1d8d --- /dev/null +++ b/projeto/server/src/routes/auth.route.ts @@ -0,0 +1,12 @@ +import { Router } from 'express'; + +const router = Router(); + +router.get('/v1/favorito', async (req, res) => { + const favoritoJson = { + "favorito": "Palmeiras" + } + res.send(favoritoJson); +}); + +export default router; \ No newline at end of file diff --git a/projeto/server/src/routes/scraping.route.ts b/projeto/server/src/routes/scraping.route.ts index 354ffb0..bb144f8 100644 --- a/projeto/server/src/routes/scraping.route.ts +++ b/projeto/server/src/routes/scraping.route.ts @@ -3,7 +3,7 @@ import ScrapingRepository from '../repositories/scraping.repository'; const router = Router(); -router.get("test/v1/scraping", async (req, res) => { +router.get("/v1/scraping", async (req, res) => { try{ let response = await ScrapingRepository.scrapingUrl1(); res.send(response); @@ -13,7 +13,7 @@ router.get("test/v1/scraping", async (req, res) => { } }); -router.get("test/v2/scraping", async (req, res) => { +router.get("/v2/scraping", async (req, res) => { try{ let response = await ScrapingRepository.scrapingUrl2(); res.send(response); @@ -23,7 +23,7 @@ router.get("test/v2/scraping", async (req, res) => { } }); -router.get("test/v3/scraping", async (req, res) => { +router.get("/v3/scraping", async (req, res) => { try{ let response = await ScrapingRepository.scrapingUrlLiberta(); res.send(response); diff --git a/projeto/server/src/routing.ts b/projeto/server/src/routing.ts index 61359b7..d3ebdd7 100644 --- a/projeto/server/src/routing.ts +++ b/projeto/server/src/routing.ts @@ -1,9 +1,11 @@ import {type Express} from 'express'; import apiRouter from './routes/api.route'; import scrapingRouter from './routes/scraping.route'; +import authRouter from './routes/auth.route'; export default function routing(app: Express): void{ app.use('/scraping', scrapingRouter); app.use('/api', apiRouter); + app.use('/auth', authRouter); } diff --git a/projeto/server/src/schemas/api.schema.ts b/projeto/server/src/schemas/api.schema.ts index 5d17aa5..7552cd0 100644 --- a/projeto/server/src/schemas/api.schema.ts +++ b/projeto/server/src/schemas/api.schema.ts @@ -1,6 +1,7 @@ import {z} from 'zod'; export const GameSchema = z.object({ + rodada: z.number(), campeonato: z.string(), hora: z.string().regex(/^\d{2}h\d{2}$/, "Invalid time format").transform((val) => { const [hours, minutes] = val.split('h').map(Number); @@ -17,6 +18,7 @@ export const GameSchema = z.object({ }); export const InsertGameSchema = z.object({ + rodada: z.number(), campeonato: z.string(), hora: z.string(), time1: z.string(), @@ -35,4 +37,9 @@ export const GameChannelSchema = z.object({ channels: z.array(z.string()) }); +export const rodadaSchema = z.object({ + rodada: z.number() +}); +export type Rodada = z.infer; + export type GameChannel = z.infer; \ No newline at end of file