Files
mirosociety/backend/app/api/presets.py
T
2026-09-27 23:06:24 +08:00

102 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from pathlib import Path
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from app.presets import PRESETS
from app.services.i18n import DEFAULT_LOCALE, locale_from_request
router = APIRouter(prefix="/api")
CUSTOM_SIMULATION_DIR = Path(__file__).resolve().parents[3] / "custom_simulation"
class CustomPresetPayload(BaseModel):
title: str = Field(min_length=1, max_length=160)
content: str = Field(min_length=1)
def _localize(value, locale: str):
"""Recursively unwrap {en, zh} dicts to the requested locale.
Falls back to the default locale, then to the first available value.
Strings (and other scalars) are returned as-is.
"""
if isinstance(value, dict):
# Heuristic: dicts with only "en"/"zh" keys are translation maps.
keys = set(value.keys())
if keys <= {"en", "zh"}:
if locale in value:
return value[locale]
if DEFAULT_LOCALE in value:
return value[DEFAULT_LOCALE]
return next(iter(value.values()))
return {k: _localize(v, locale) for k, v in value.items()}
if isinstance(value, list):
return [_localize(v, locale) for v in value]
return value
@router.get("/presets")
async def get_presets(request: Request):
locale = locale_from_request(request)
return {"presets": _localize(PRESETS, locale)}
def _custom_presets():
CUSTOM_SIMULATION_DIR.mkdir(parents=True, exist_ok=True)
presets = []
for path in sorted(CUSTOM_SIMULATION_DIR.glob("*.txt"), key=lambda p: p.name.lower()):
try:
content = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
continue
presets.append({
"id": f"custom:{path.name}",
"name": path.stem,
"rules": content,
"custom": True,
"filename": path.name,
})
return presets
@router.get("/custom-presets")
async def get_custom_presets():
"""Load user-authored scenario files from the project custom_simulation folder."""
return {"presets": _custom_presets()}
@router.post("/custom-presets")
async def create_custom_preset(payload: CustomPresetPayload):
"""Save a custom scenario, suffixing duplicate titles with _1, _2, ... ."""
title = " ".join(payload.title.strip().split())
# Keep unicode letters (including Chinese) while preventing path traversal.
title = title.replace("/", "/").replace("\\", "\")
if not title or title in {".", ".."}:
raise HTTPException(status_code=400, detail="Title is required")
CUSTOM_SIMULATION_DIR.mkdir(parents=True, exist_ok=True)
candidate = CUSTOM_SIMULATION_DIR / f"{title}.txt"
index = 1
while candidate.exists():
candidate = CUSTOM_SIMULATION_DIR / f"{title}_{index}.txt"
index += 1
try:
candidate.write_text(payload.content, encoding="utf-8")
except OSError as exc:
raise HTTPException(status_code=500, detail=f"Unable to save preset: {exc}") from exc
return {
"preset": {
"id": f"custom:{candidate.name}",
"name": candidate.stem,
"rules": payload.content,
"custom": True,
"filename": candidate.name,
}
}