PhotoBench-Protected / src /storage.py
SorrowTea's picture
Initial PhotoBench-Protected Leaderboard
01f4cb5
import json
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
STORAGE_DIR = Path("/data")
SUBMISSIONS_DIR = STORAGE_DIR / "submissions"
LEADERBOARD_FILE = STORAGE_DIR / "leaderboard.jsonl"
RATE_LIMIT_FILE = STORAGE_DIR / "rate_limits.json"
# Seed data bundled with the app (used on first boot)
SEED_LEADERBOARD = Path(__file__).parent.parent / "assets" / "leaderboard.jsonl"
def ensure_dirs():
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
SUBMISSIONS_DIR.mkdir(parents=True, exist_ok=True)
def _seed_leaderboard():
"""Copy bundled leaderboard data to /data on first boot."""
if LEADERBOARD_FILE.exists():
return
if SEED_LEADERBOARD.exists():
import shutil
shutil.copy(SEED_LEADERBOARD, LEADERBOARD_FILE)
print(f"[SEED] Copied leaderboard data from {SEED_LEADERBOARD} to {LEADERBOARD_FILE}")
def save_submission(submission_id: str, payload: dict) -> str:
"""Save raw submission JSON to local storage."""
ensure_dirs()
file_path = SUBMISSIONS_DIR / f"{submission_id}.json"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
return str(file_path)
def list_submissions() -> list[dict]:
"""List all submission metadata."""
ensure_dirs()
results = []
for f in sorted(SUBMISSIONS_DIR.glob("*.json")):
try:
with open(f, "r", encoding="utf-8") as fp:
data = json.load(fp)
meta = data.get("meta", {})
meta["file"] = str(f.name)
results.append(meta)
except Exception:
continue
return results
def load_leaderboard() -> list[dict]:
"""Load current leaderboard data from local storage."""
_seed_leaderboard()
if not LEADERBOARD_FILE.exists():
return []
entries = []
with open(LEADERBOARD_FILE, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
entries.append(json.loads(line))
return entries
def save_leaderboard(entries: list[dict]) -> None:
"""Overwrite leaderboard file with the current entries."""
ensure_dirs()
with open(LEADERBOARD_FILE, "w", encoding="utf-8") as f:
for entry in entries:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
# ---- Rate limiting ----
def check_rate_limit(email: str, cooldown_minutes: int = 60) -> tuple[bool, str]:
"""Check if email is allowed to submit. Returns (allowed, message)."""
ensure_dirs()
limits = {}
if RATE_LIMIT_FILE.exists():
with open(RATE_LIMIT_FILE, "r", encoding="utf-8") as f:
limits = json.load(f)
last_str = limits.get(email)
if last_str:
last_time = datetime.fromisoformat(last_str)
next_allowed = last_time + timedelta(minutes=cooldown_minutes)
if datetime.utcnow() < next_allowed:
remaining = int((next_allowed - datetime.utcnow()).total_seconds() / 60)
return False, f"This email has already submitted within the last hour. Please wait {remaining} minutes."
return True, ""
def record_submission_time(email: str) -> None:
"""Record the current submission time for an email."""
ensure_dirs()
limits = {}
if RATE_LIMIT_FILE.exists():
with open(RATE_LIMIT_FILE, "r", encoding="utf-8") as f:
limits = json.load(f)
limits[email] = datetime.utcnow().isoformat()
with open(RATE_LIMIT_FILE, "w", encoding="utf-8") as f:
json.dump(limits, f, ensure_ascii=False, indent=2)