feat: local browser saves with opt-in cloud sync behind saves.cloud permission
CI / frontend (push) Successful in 21s
CI / backend (push) Successful in 47s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 12:44:42 +03:00
1 parent 4418da47eb
commit 47fc121e7d
8 files changed
+377 -31

No files matched your search

+7 -5
View File
@@ -268,7 +268,9 @@ async def delete_version(version_id: int, user: CurrentUser, db: DB) -> None:
remove_upload(version_id)
# --- saves -----------------------------------------------------------------
# --- cloud saves -----------------------------------------------------------
# Saves live in the browser by default; these endpoints are the opt-in cloud copy.
CloudSaves = Annotated[User, Depends(require("games.play", "saves.cloud"))]
def _check_slot(slot: str) -> str:
if not slot or len(slot) > 32 or not all(c.isalnum() or c in "_-" for c in slot):
@@ -277,7 +279,7 @@ def _check_slot(slot: str) -> str:
@router.get("/versions/{version_id}/saves", response_model=list[SaveOut])
async def list_saves(version_id: int, user: Player, db: DB) -> list[Save]:
async def list_saves(version_id: int, user: CloudSaves, db: DB) -> list[Save]:
await get_version(db, user, version_id)
return list((await db.scalars(
select(Save).where(Save.user_id == user.id, Save.version_id == version_id)
@@ -291,7 +293,7 @@ async def _find_save(db: DB, user: User, version_id: int, slot: str) -> Save | N
@router.get("/versions/{version_id}/saves/{slot}", response_model=SaveDetailOut)
async def get_save(version_id: int, slot: str, user: Player, db: DB) -> Save:
async def get_save(version_id: int, slot: str, user: CloudSaves, db: DB) -> Save:
save = await _find_save(db, user, version_id, slot)
if not save:
raise HTTPException(status.HTTP_404_NOT_FOUND, "Сохранение не найдено")
@@ -299,7 +301,7 @@ async def get_save(version_id: int, slot: str, user: Player, db: DB) -> Save:
@router.put("/versions/{version_id}/saves/{slot}", response_model=SaveOut)
async def put_save(version_id: int, slot: str, body: SaveIn, user: Player, db: DB) -> Save:
async def put_save(version_id: int, slot: str, body: SaveIn, user: CloudSaves, db: DB) -> Save:
await get_version(db, user, version_id)
save = await _find_save(db, user, version_id, slot)
if save is None:
@@ -315,7 +317,7 @@ async def put_save(version_id: int, slot: str, body: SaveIn, user: Player, db: D
@router.delete("/versions/{version_id}/saves/{slot}", status_code=204)
async def delete_save(version_id: int, slot: str, user: Player, db: DB) -> None:
async def delete_save(version_id: int, slot: str, user: CloudSaves, db: DB) -> None:
save = await _find_save(db, user, version_id, slot)
if save:
await db.delete(save)