feat: implementacao inicial da arquitetura de rankings distribuidos
This commit is contained in:
92
app.py
Normal file
92
app.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import requests
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simulando Dados Locais para Consoles
|
||||||
|
LOCAL_NODES = {
|
||||||
|
"playstation": [
|
||||||
|
{"rank": 1, "name": "Spider-Man 2", "image": "https://image.api.playstation.com/vulcan/ap/rnd/202306/1219/1c7b75d8ed9271516546560d219ad0b22ee0a263b4537bd8.png", "score": "90"},
|
||||||
|
{"rank": 2, "name": "God of War Ragnarök", "image": "https://image.api.playstation.com/vulcan/ap/rnd/202207/1210/4xJ8XB3bi888QTLZYdl7Oi0s.png", "score": "94"}
|
||||||
|
],
|
||||||
|
"xbox": [
|
||||||
|
{"rank": 1, "name": "Halo Infinite", "image": "https://store-images.s-microsoft.com/image/apps.6040.13727851868390641.c9cc5f66-aff8-406c-af6b-440838730be0.2b6bcbc4-0d19-482f-870d-fcae0cebe2c7", "score": "87"},
|
||||||
|
{"rank": 2, "name": "Forza Horizon 5", "image": "https://store-images.s-microsoft.com/image/apps.4606.13886538057288673.eb91334c-2830-46eb-8e54-5264b7d142d7.604ff58f-b98a-40a2-ad3b-638e4a904000", "score": "92"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/ranking/{platform}")
|
||||||
|
def get_ranking(platform: str):
|
||||||
|
p = platform.lower()
|
||||||
|
if p in LOCAL_NODES: return LOCAL_NODES[p]
|
||||||
|
|
||||||
|
# Busca dados ao vivo para PC
|
||||||
|
store_id = "1" if p == "steam" else "25"
|
||||||
|
try:
|
||||||
|
r = requests.get(f"https://www.cheapshark.com/api/1.0/deals?storeID={store_id}&sortBy=Metacritic&pageSize=8").json()
|
||||||
|
return [{"rank": i+1, "name": g['title'], "image": g['thumb'], "score": g['metacriticScore']} for i, g in enumerate(r) if g['metacriticScore'] != "0"]
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@app.get("/compare")
|
||||||
|
def get_compare():
|
||||||
|
comparison = []
|
||||||
|
|
||||||
|
# 1. Pega o Líder PlayStation
|
||||||
|
if "playstation" in LOCAL_NODES:
|
||||||
|
comparison.append({
|
||||||
|
"platform": "PlayStation",
|
||||||
|
"name": LOCAL_NODES["playstation"][0]["name"],
|
||||||
|
"image": LOCAL_NODES["playstation"][0]["image"],
|
||||||
|
"score": LOCAL_NODES["playstation"][0]["score"]
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2. Pega o Líder Xbox
|
||||||
|
if "xbox" in LOCAL_NODES:
|
||||||
|
comparison.append({
|
||||||
|
"platform": "Xbox Network",
|
||||||
|
"name": LOCAL_NODES["xbox"][0]["name"],
|
||||||
|
"image": LOCAL_NODES["xbox"][0]["image"],
|
||||||
|
"score": LOCAL_NODES["xbox"][0]["score"]
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3. Pega o Líder Steam
|
||||||
|
try:
|
||||||
|
r_steam = requests.get("https://www.cheapshark.com/api/1.0/deals?storeID=1&sortBy=Metacritic&pageSize=1").json()
|
||||||
|
if r_steam:
|
||||||
|
comparison.append({
|
||||||
|
"platform": "Steam",
|
||||||
|
"name": r_steam[0]['title'],
|
||||||
|
"image": r_steam[0]['thumb'],
|
||||||
|
"score": r_steam[0]['metacriticScore']
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 4. Pega o Líder Epic Games
|
||||||
|
try:
|
||||||
|
r_epic = requests.get("https://www.cheapshark.com/api/1.0/deals?storeID=25&sortBy=Metacritic&pageSize=1").json()
|
||||||
|
if r_epic:
|
||||||
|
comparison.append({
|
||||||
|
"platform": "Epic Games",
|
||||||
|
"name": r_epic[0]['title'],
|
||||||
|
"image": r_epic[0]['thumb'],
|
||||||
|
"score": r_epic[0]['metacriticScore']
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return comparison
|
||||||
|
|
||||||
|
# Esse é o "motor" que provavelmente tinha sido apagado!
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
118
index.html
Normal file
118
index.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ranking por Plataforma</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-steam: #171a21;
|
||||||
|
--panel: #1b2838;
|
||||||
|
--blue-bright: #66c0f4;
|
||||||
|
--text-gray: #c7d5e0;
|
||||||
|
--green-meta: #a3ff00;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-steam); color: var(--text-gray);
|
||||||
|
font-family: 'Segoe UI', Arial, sans-serif; padding: 40px; margin: 0;
|
||||||
|
}
|
||||||
|
h1 { text-transform: uppercase; letter-spacing: 2px; color: #fff; margin-bottom: 30px; border-bottom: 1px solid #2a475e; padding-bottom: 10px; }
|
||||||
|
|
||||||
|
.tabs { display: flex; gap: 5px; margin-bottom: 25px; }
|
||||||
|
.tab-btn {
|
||||||
|
background: var(--panel); border: none; color: var(--text-gray); padding: 12px 25px;
|
||||||
|
cursor: pointer; font-weight: bold; text-transform: uppercase; transition: 0.3s;
|
||||||
|
}
|
||||||
|
.tab-btn:hover, .tab-btn.active { background: var(--blue-bright); color: #000; }
|
||||||
|
|
||||||
|
.container { background: rgba(0,0,0,0.3); padding: 20px; border-radius: 4px; }
|
||||||
|
|
||||||
|
/* Estilo da Lista Comum */
|
||||||
|
.game-row {
|
||||||
|
display: grid; grid-template-columns: 50px 120px 1fr 120px; align-items: center;
|
||||||
|
background: var(--panel); margin-bottom: 8px; padding: 15px; border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
.game-row:hover { border-left-color: var(--blue-bright); background: #212e3d; }
|
||||||
|
.rank-id { color: var(--blue-bright); font-weight: bold; font-size: 1.2em; }
|
||||||
|
.game-img { width: 110px; height: 62px; object-fit: cover; border-radius: 2px; }
|
||||||
|
.game-name { padding-left: 20px; font-weight: bold; color: #fff; font-size: 1.1em; }
|
||||||
|
|
||||||
|
/* Estilo da Nota */
|
||||||
|
.score-box { text-align: right; }
|
||||||
|
.score-label { display: block; font-size: 0.65em; text-transform: uppercase; color: #8b949e; }
|
||||||
|
.score-val { color: var(--green-meta); font-family: monospace; font-size: 1.5em; font-weight: bold; }
|
||||||
|
|
||||||
|
/* Grade de Comparação Profissional */
|
||||||
|
.compare-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; }
|
||||||
|
.compare-card {
|
||||||
|
background: var(--panel); border: 1px solid #2a475e; padding: 0;
|
||||||
|
border-radius: 4px; overflow: hidden; display: flex; flex-direction: column;
|
||||||
|
}
|
||||||
|
.card-header { background: #2a475e; padding: 10px; font-size: 0.75em; font-weight: bold; text-transform: uppercase; color: var(--blue-bright); text-align: center; }
|
||||||
|
.card-img { width: 100%; height: 130px; object-fit: cover; }
|
||||||
|
.card-body { padding: 15px; text-align: center; flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; }
|
||||||
|
.card-name { color: #fff; font-weight: bold; font-size: 1.1em; margin-bottom: 15px; display: block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Ranking por Plataforma</h1>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<button class="tab-btn active" onclick="loadData('steam', this)">Steam</button>
|
||||||
|
<button class="tab-btn" onclick="loadData('epic', this)">Epic Games</button>
|
||||||
|
<button class="tab-btn" onclick="loadData('playstation', this)">PlayStation</button>
|
||||||
|
<button class="tab-btn" onclick="loadData('xbox', this)">Xbox</button>
|
||||||
|
<button class="tab-btn" onclick="loadData('compare', this)">Análise Comparativa</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" id="main-view"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadData(platform, btn) {
|
||||||
|
const view = document.getElementById('main-view');
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
view.innerHTML = "<p style='color: #8b949e;'>Carregando...</p>";
|
||||||
|
|
||||||
|
const route = platform === 'compare' ? '/compare' : `/ranking/${platform}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:8000${route}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (platform === 'compare') {
|
||||||
|
view.innerHTML = `<div class="compare-grid">` + data.map(game => `
|
||||||
|
<div class="compare-card">
|
||||||
|
<div class="card-header">${game.platform}</div>
|
||||||
|
<img src="${game.image}" class="card-img">
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-name">${game.name}</span>
|
||||||
|
<div class="score-box" style="text-align: center;">
|
||||||
|
<span class="score-label">Nota Metacritic</span>
|
||||||
|
<span class="score-val">${game.score}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') + `</div>`;
|
||||||
|
} else {
|
||||||
|
view.innerHTML = data.map(game => `
|
||||||
|
<div class="game-row">
|
||||||
|
<div class="rank-id">#${game.rank}</div>
|
||||||
|
<img src="${game.image}" class="game-img">
|
||||||
|
<div class="game-name">${game.name}</div>
|
||||||
|
<div class="score-box">
|
||||||
|
<span class="score-label">Nota Metacritic</span>
|
||||||
|
<span class="score-val">${game.score}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
view.innerHTML = "<p style='color: #ff4a4a;'>Erro de conexão com o servidor.</p>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.onload = () => loadData('steam', document.querySelector('.tab-btn'));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
requests
|
||||||
|
pandas
|
||||||
Reference in New Issue
Block a user