feat: foo
This commit is contained in:
1 parent
9d741b8516
commit
3413c4f94a
5 files changed
+81
-87
No files matched your search
@@ -1,8 +1,15 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Phenix.Models;
|
||||||
using Phenix.Services;
|
using Phenix.Services;
|
||||||
|
|
||||||
namespace Phenix.Controllers;
|
namespace Phenix.Controllers;
|
||||||
|
|
||||||
|
public record CreateGameRequest(
|
||||||
|
string Date, string Time, string Home, string Away,
|
||||||
|
int? HomeScore = null, int? AwayScore = null,
|
||||||
|
bool Practice = false, bool Rain = false
|
||||||
|
);
|
||||||
|
|
||||||
[Route("/api")]
|
[Route("/api")]
|
||||||
public class ApiController : Controller
|
public class ApiController : Controller
|
||||||
{
|
{
|
||||||
@@ -42,4 +49,26 @@ public class ApiController : Controller
|
|||||||
var years = games.Select(g => g.Year).Distinct().OrderByDescending(y => y);
|
var years = games.Select(g => g.Year).Distinct().OrderByDescending(y => y);
|
||||||
return Json(years);
|
return Json(years);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("games")]
|
||||||
|
public async Task<IActionResult> CreateGame([FromBody] CreateGameRequest req)
|
||||||
|
{
|
||||||
|
var (game, error) = await _games.CreateNewGame(req.Date, req.Time, req.Home, req.Away);
|
||||||
|
if (game is null)
|
||||||
|
return error switch
|
||||||
|
{
|
||||||
|
"game already exists at this date/time" => Conflict(new { error }),
|
||||||
|
_ => BadRequest(new { error }),
|
||||||
|
};
|
||||||
|
|
||||||
|
await _games.UpdateGame(game.Id, req.HomeScore, req.AwayScore, req.Rain, req.Practice);
|
||||||
|
return Created($"/api/games/{game.Id}", game);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("games")]
|
||||||
|
public async Task<IActionResult> ClearGames()
|
||||||
|
{
|
||||||
|
await _games.ClearAllGames();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,5 @@ public interface IGameService
|
|||||||
Task<bool> UpdateGame(string id, int? homeScore, int? awayScore, bool? rain, bool? practice);
|
Task<bool> UpdateGame(string id, int? homeScore, int? awayScore, bool? rain, bool? practice);
|
||||||
Task<bool> DeleteGameById(string id);
|
Task<bool> DeleteGameById(string id);
|
||||||
Task<bool> SwitchVisitors(string id);
|
Task<bool> SwitchVisitors(string id);
|
||||||
|
Task ClearAllGames();
|
||||||
}
|
}
|
||||||
@@ -145,6 +145,15 @@ public class PostgresGameService : IGameService
|
|||||||
return await cmd.ExecuteNonQueryAsync() > 0;
|
return await cmd.ExecuteNonQueryAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ClearAllGames()
|
||||||
|
{
|
||||||
|
await using var conn = new NpgsqlConnection(_connectionString);
|
||||||
|
await conn.OpenAsync();
|
||||||
|
var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = "DELETE FROM games";
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> SwitchVisitors(string id)
|
public async Task<bool> SwitchVisitors(string id)
|
||||||
{
|
{
|
||||||
var game = await GetGameById(id);
|
var game = await GetGameById(id);
|
||||||
|
|||||||
@@ -145,6 +145,15 @@ public class SqliteGameService : IGameService
|
|||||||
return await cmd.ExecuteNonQueryAsync() > 0;
|
return await cmd.ExecuteNonQueryAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ClearAllGames()
|
||||||
|
{
|
||||||
|
await using var conn = new SqliteConnection(_connectionString);
|
||||||
|
await conn.OpenAsync();
|
||||||
|
var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = "DELETE FROM games";
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> SwitchVisitors(string id)
|
public async Task<bool> SwitchVisitors(string id)
|
||||||
{
|
{
|
||||||
var game = await GetGameById(id);
|
var game = await GetGameById(id);
|
||||||
|
|||||||
+33
-87
@@ -1,33 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def find_project_root():
|
|
||||||
direnv_file = os.environ.get("DIRENV_FILE")
|
|
||||||
if direnv_file:
|
|
||||||
return Path(direnv_file).parent.resolve()
|
|
||||||
|
|
||||||
direnv_dir = os.environ.get("DIRENV_DIR")
|
|
||||||
if direnv_dir:
|
|
||||||
return Path(direnv_dir).resolve()
|
|
||||||
|
|
||||||
cwd = Path.cwd().resolve()
|
|
||||||
for parent in [cwd] + list(cwd.parents):
|
|
||||||
if (parent / "flake.nix").exists():
|
|
||||||
return parent
|
|
||||||
|
|
||||||
raise RuntimeError(
|
|
||||||
"Cannot find project root. Set DIRENV_FILE or DIRENV_DIR, "
|
|
||||||
"or run from within the project tree."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_json(api_root, path):
|
def fetch_json(api_root, path):
|
||||||
url = f"{api_root}{path}"
|
url = f"{api_root}{path}"
|
||||||
resp = requests.get(url, timeout=30)
|
resp = requests.get(url, timeout=30)
|
||||||
@@ -37,17 +15,17 @@ def fetch_json(api_root, path):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Populate a local SQLite DB from production data"
|
description="Populate a local instance from production data via REST"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--url",
|
"--source",
|
||||||
default=os.environ.get("PHENIX_URL", "https://ballephenix.com"),
|
default=os.environ.get("PHENIX_SOURCE_URL", "https://ballephenix.com"),
|
||||||
help="Source API root (default: https://ballephenix.com)",
|
help="Source API root (default: https://ballephenix.com)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--db",
|
"--target",
|
||||||
default=None,
|
default=os.environ.get("PHENIX_TARGET_URL", "http://localhost:5000"),
|
||||||
help="Path to SQLite DB (default: <project-root>/phenix.db)",
|
help="Target API root (default: http://localhost:5000)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--force",
|
"--force",
|
||||||
@@ -56,41 +34,23 @@ def main():
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
api_root = args.url.rstrip("/")
|
source_root = args.source.rstrip("/")
|
||||||
|
target_root = args.target.rstrip("/")
|
||||||
|
|
||||||
project_root = find_project_root()
|
years = fetch_json(source_root, "/api/available-years.json")
|
||||||
db_path = Path(args.db) if args.db else project_root / "phenix.db"
|
|
||||||
|
|
||||||
years = fetch_json(api_root, "/api/available-years.json")
|
|
||||||
print(f"Years on source: {len(years)} ({min(years)}–{max(years)})")
|
print(f"Years on source: {len(years)} ({min(years)}–{max(years)})")
|
||||||
|
|
||||||
all_games = []
|
all_games = []
|
||||||
for year in years:
|
for year in years:
|
||||||
games = fetch_json(api_root, f"/api/results/{year}.json")
|
games = fetch_json(source_root, f"/api/results/{year}.json")
|
||||||
all_games.extend(games)
|
all_games.extend(games)
|
||||||
print(f"Total games fetched: {len(all_games)}")
|
print(f"Total games fetched: {len(all_games)}")
|
||||||
|
|
||||||
conn = sqlite3.connect(str(db_path))
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS games (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
date TEXT NOT NULL,
|
|
||||||
time TEXT NOT NULL,
|
|
||||||
home TEXT NOT NULL,
|
|
||||||
away TEXT NOT NULL,
|
|
||||||
year INTEGER NOT NULL,
|
|
||||||
home_score INTEGER,
|
|
||||||
away_score INTEGER,
|
|
||||||
practice INTEGER NOT NULL DEFAULT 0,
|
|
||||||
rain INTEGER NOT NULL DEFAULT 0,
|
|
||||||
UNIQUE(date, time)
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
if args.force:
|
if args.force:
|
||||||
cursor.execute("DELETE FROM games")
|
resp = requests.delete(f"{target_root}/api/games", timeout=30)
|
||||||
|
if resp.status_code not in (204, 200):
|
||||||
|
print(f"Failed to clear games: {resp.status_code} {resp.text}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
print("Cleared existing games")
|
print("Cleared existing games")
|
||||||
|
|
||||||
inserted = 0
|
inserted = 0
|
||||||
@@ -98,46 +58,32 @@ def main():
|
|||||||
errors = 0
|
errors = 0
|
||||||
|
|
||||||
for g in all_games:
|
for g in all_games:
|
||||||
try:
|
payload = {
|
||||||
home_score = int(g["homeScore"]) if g.get("homeScore") is not None else None
|
"date": g.get("date", ""),
|
||||||
away_score = int(g["awayScore"]) if g.get("awayScore") is not None else None
|
"time": g.get("time", ""),
|
||||||
except (ValueError, TypeError):
|
"home": g.get("home", ""),
|
||||||
home_score = None
|
"away": g.get("away", ""),
|
||||||
away_score = None
|
"homeScore": g.get("homeScore"),
|
||||||
|
"awayScore": g.get("awayScore"),
|
||||||
|
"practice": bool(g.get("practice")),
|
||||||
|
"rain": bool(g.get("rain")),
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute(
|
resp = requests.post(f"{target_root}/api/games", json=payload, timeout=30)
|
||||||
"""
|
if resp.status_code == 201:
|
||||||
INSERT OR IGNORE INTO games
|
|
||||||
(id, date, time, home, away, year, home_score, away_score, practice, rain)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
g.get("id", ""),
|
|
||||||
g["date"],
|
|
||||||
g.get("time", ""),
|
|
||||||
g.get("home", ""),
|
|
||||||
g.get("away", ""),
|
|
||||||
g.get("year", 0),
|
|
||||||
home_score,
|
|
||||||
away_score,
|
|
||||||
1 if g.get("practice") else 0,
|
|
||||||
1 if g.get("rain") else 0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if cursor.rowcount > 0:
|
|
||||||
inserted += 1
|
inserted += 1
|
||||||
else:
|
elif resp.status_code == 409:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
except sqlite3.Error as e:
|
else:
|
||||||
print(f"DB error on {g.get('date')} {g.get('time')}: {e}", file=sys.stderr)
|
print(f"Error on {g.get('date')} {g.get('time')}: {resp.status_code} {resp.text}", file=sys.stderr)
|
||||||
|
errors += 1
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"Request error on {g.get('date')} {g.get('time')}: {e}", file=sys.stderr)
|
||||||
errors += 1
|
errors += 1
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
print(f"Inserted: {inserted}, skipped: {skipped}, errors: {errors}")
|
print(f"Inserted: {inserted}, skipped: {skipped}, errors: {errors}")
|
||||||
print(f"Database: {db_path}")
|
print(f"Target: {target_root}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in new issue
Block a user