++
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
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")
|
||||
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):
|
||||
@@ -30,6 +40,62 @@ def _localize(value, locale: str):
|
||||
|
||||
|
||||
@router.get("/presets")
|
||||
async def get_presets(request: Request):
|
||||
locale = locale_from_request(request)
|
||||
return {"presets": _localize(PRESETS, locale)}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user