feat: local browser saves with opt-in cloud sync behind saves.cloud permission
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4418da47eb
commit
47fc121e7d
8 files changed
+377
-31
No files matched your search
@@ -9,6 +9,7 @@ PERMISSIONS: dict[str, str] = {
|
||||
"versions.manage_any": "Управление чужими версиями",
|
||||
"translations.edit": "Перевод игр в своих версиях",
|
||||
"versions.publish": "Публикация версий и переводов, выбор главной версии",
|
||||
"saves.cloud": "Облачные сохранения (синхронизация между устройствами)",
|
||||
"users.manage": "Управление пользователями",
|
||||
"roles.manage": "Управление ролями",
|
||||
}
|
||||
@@ -18,6 +19,6 @@ ALL = sorted(PERMISSIONS)
|
||||
SYSTEM_ROLES: dict[str, list[str]] = {
|
||||
"admin": ALL,
|
||||
"moderator": ["games.play", "games.upload", "games.edit", "versions.fork",
|
||||
"translations.edit", "versions.publish"],
|
||||
"player": ["games.play"],
|
||||
"translations.edit", "versions.publish", "saves.cloud"],
|
||||
"player": ["games.play", "saves.cloud"],
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Cloud saves became an explicit permission; keep them for existing system roles."""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "0004"
|
||||
down_revision = "0003"
|
||||
branch_labels = depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.get_bind().execute(sa.text(
|
||||
"UPDATE roles SET permissions = array_append(permissions, 'saves.cloud') "
|
||||
"WHERE is_system AND name IN ('moderator', 'player') AND NOT ('saves.cloud' = ANY(permissions))"))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.get_bind().execute(sa.text(
|
||||
"UPDATE roles SET permissions = array_remove(permissions, 'saves.cloud')"))
|
||||
Reference in new issue
Block a user