Перейти к содержанию

Draft Storage

Хранение черновиков и незавершённых проектов пользователей.

Возможности

  • Автосохранение черновиков
  • Версионирование
  • TTL для устаревших черновиков
  • Поиск и фильтрация
  • Export/Import

Использование

Базовое использование

from kit.services import DraftStorage

storage = DraftStorage(storage_path="./drafts")

# Сохранение черновика
await storage.save(
    user_id="user_123",
    project_id="project_1",
    data={
        "title": "Мой проект",
        "content": "...",
        "settings": {"style": "cinematic"}
    }
)

# Загрузка
draft = await storage.load("user_123", "project_1")
print(draft["title"])  # "Мой проект"

# Удаление
await storage.delete("user_123", "project_1")

Автосохранение

storage = DraftStorage(
    storage_path="./drafts",
    auto_save_interval=30  # Автосохранение каждые 30 секунд
)

# Регистрация проекта для автосохранения
storage.register_autosave("user_123", "project_1", get_data_func)

# Отмена автосохранения
storage.unregister_autosave("user_123", "project_1")

Версионирование

storage = DraftStorage(
    storage_path="./drafts",
    max_versions=10  # Хранить 10 последних версий
)

# Сохранение создаёт новую версию
await storage.save("user_123", "project_1", {"v": 1})
await storage.save("user_123", "project_1", {"v": 2})
await storage.save("user_123", "project_1", {"v": 3})

# Получение истории версий
versions = await storage.get_versions("user_123", "project_1")
# [{"version": 1, "timestamp": ...}, {"version": 2, ...}, ...]

# Загрузка конкретной версии
old_draft = await storage.load("user_123", "project_1", version=1)

Список черновиков пользователя

# Все черновики пользователя
drafts = await storage.list_user_drafts("user_123")

for draft in drafts:
    print(f"{draft['project_id']}: {draft['title']} (updated: {draft['updated_at']})")

# С фильтрацией
recent = await storage.list_user_drafts(
    "user_123",
    filter_fn=lambda d: d["updated_at"] > yesterday
)

TTL для черновиков

storage = DraftStorage(
    storage_path="./drafts",
    default_ttl=604800  # 7 дней
)

# Черновики автоматически удаляются через 7 дней
await storage.save("user_123", "temp_project", data)

# Кастомный TTL
await storage.save("user_123", "important", data, ttl=2592000)  # 30 дней

Export/Import

# Export всех черновиков пользователя
export_data = await storage.export_user_drafts("user_123")
with open("backup.json", "w") as f:
    json.dump(export_data, f)

# Import
with open("backup.json") as f:
    import_data = json.load(f)
await storage.import_user_drafts("user_123", import_data)

API Reference

DraftStorage

class DraftStorage:
    def __init__(
        self,
        storage_path: str = "./drafts",
        max_versions: int = 5,
        default_ttl: int = None,
        auto_save_interval: int = None
    )

    async def save(
        self,
        user_id: str,
        project_id: str,
        data: dict,
        ttl: int = None
    ) -> None

    async def load(
        self,
        user_id: str,
        project_id: str,
        version: int = None  # None = latest
    ) -> Optional[dict]

    async def delete(
        self,
        user_id: str,
        project_id: str
    ) -> bool

    async def exists(
        self,
        user_id: str,
        project_id: str
    ) -> bool

    async def list_user_drafts(
        self,
        user_id: str,
        filter_fn: Callable = None
    ) -> List[DraftInfo]

    async def get_versions(
        self,
        user_id: str,
        project_id: str
    ) -> List[VersionInfo]

    async def export_user_drafts(self, user_id: str) -> dict
    async def import_user_drafts(self, user_id: str, data: dict) -> int

    def register_autosave(
        self,
        user_id: str,
        project_id: str,
        get_data: Callable
    ) -> None
    def unregister_autosave(self, user_id: str, project_id: str) -> None

    async def cleanup_expired(self) -> int

DraftInfo

@dataclass
class DraftInfo:
    project_id: str
    title: str
    created_at: datetime
    updated_at: datetime
    version: int
    size_bytes: int

@dataclass
class VersionInfo:
    version: int
    timestamp: datetime
    size_bytes: int

Примеры из production

Autoshorts — черновики видео-проектов

class VideoProjectStorage:
    def __init__(self):
        self.storage = DraftStorage(
            storage_path="./video_drafts",
            max_versions=10,
            default_ttl=604800 * 2  # 2 недели
        )

    async def save_project(self, user_id: str, project: dict) -> str:
        """Сохранение проекта."""
        project_id = project.get("id") or str(uuid4())

        await self.storage.save(user_id, project_id, {
            "id": project_id,
            "title": project["title"],
            "script": project["script"],
            "settings": project["settings"],
            "assets": project.get("assets", []),
            "status": project.get("status", "draft")
        })

        return project_id

    async def get_user_projects(self, user_id: str) -> List[dict]:
        """Список проектов пользователя."""
        drafts = await self.storage.list_user_drafts(user_id)

        projects = []
        for draft in drafts:
            data = await self.storage.load(user_id, draft.project_id)
            projects.append({
                "id": draft.project_id,
                "title": data.get("title", "Untitled"),
                "status": data.get("status", "draft"),
                "updated_at": draft.updated_at
            })

        return sorted(projects, key=lambda x: x["updated_at"], reverse=True)

    async def restore_version(
        self,
        user_id: str,
        project_id: str,
        version: int
    ) -> dict:
        """Восстановление версии проекта."""
        old_data = await self.storage.load(user_id, project_id, version=version)

        if old_data:
            await self.storage.save(user_id, project_id, old_data)

        return old_data

Dashboard — черновики настроек

class SettingsDraftStorage:
    def __init__(self):
        self.storage = DraftStorage(
            storage_path="./settings_drafts",
            default_ttl=3600  # 1 час
        )

    async def save_draft(self, user_id: str, settings: dict):
        """Автосохранение настроек."""
        await self.storage.save(
            user_id,
            "settings_draft",
            {
                "settings": settings,
                "saved_at": datetime.now().isoformat()
            }
        )

    async def get_draft(self, user_id: str) -> Optional[dict]:
        """Получение черновика настроек."""
        draft = await self.storage.load(user_id, "settings_draft")
        return draft.get("settings") if draft else None

    async def discard_draft(self, user_id: str):
        """Отмена черновика."""
        await self.storage.delete(user_id, "settings_draft")

Real-time collaboration

class CollaborativeDraftStorage:
    def __init__(self):
        self.storage = DraftStorage(
            storage_path="./collab_drafts",
            max_versions=50,  # Больше версий для истории
            auto_save_interval=5  # Частое автосохранение
        )
        self.locks = {}  # project_id -> user_id

    async def acquire_lock(self, project_id: str, user_id: str) -> bool:
        """Блокировка проекта для редактирования."""
        if project_id in self.locks and self.locks[project_id] != user_id:
            return False
        self.locks[project_id] = user_id
        return True

    async def release_lock(self, project_id: str, user_id: str):
        """Снятие блокировки."""
        if self.locks.get(project_id) == user_id:
            del self.locks[project_id]

    async def save_with_conflict_check(
        self,
        user_id: str,
        project_id: str,
        data: dict,
        expected_version: int
    ) -> bool:
        """Сохранение с проверкой конфликтов."""
        versions = await self.storage.get_versions(user_id, project_id)
        current_version = versions[-1].version if versions else 0

        if current_version != expected_version:
            return False  # Конфликт версий

        await self.storage.save(user_id, project_id, data)
        return True