import json import shutil from datetime import datetime from pathlib import Path from config.settings import settings from models.bible import StoryBible from models.project import Project from models.user import User from services.auth_service import auth_service class ProjectService: def __init__(self): self.data_dir = Path(settings.DATA_DIR) self.projects_dir = self.data_dir / "projects" self.projects_dir.mkdir(parents=True, exist_ok=True) self.projects_file = self.data_dir / "projects.json" self.projects = self._load_projects() # Миграция: перенос старых проектов в структуру с пользователями self._migrate_old_data() def _load_projects(self) -> list[Project]: """Загрузить список проектов""" if self.projects_file.exists(): with open(self.projects_file, "r", encoding="utf-8") as f: data = json.load(f) return [Project(**p) for p in data] return [] def _save_projects(self): """Сохранить список проектов""" with open(self.projects_file, "w", encoding="utf-8") as f: json.dump( [p.model_dump() for p in self.projects], f, ensure_ascii=False, indent=2, ) def _migrate_old_data(self): """Миграция старых проектов в структуру data/users/{user_id}/projects/""" old_projects_dir = self.data_dir / "projects" users_dir = self.data_dir / "users" users_file = self.data_dir / "users.json" default_user_id = "default-user-0000-0000-0000-000000000000" # Если есть старые проекты, но нет пользователей — создаём дефолтного и перемещаем if old_projects_dir.exists() and not users_dir.exists(): print("Migrating old projects to a default user...") default_user = User( id=default_user_id, username="admin", email="admin@local", hashed_password=auth_service.get_password_hash("admin"), ) auth_service.users.append(default_user) auth_service._save_users() # Перемещаем папку проектов new_user_dir = users_dir / default_user.id new_user_dir.mkdir(parents=True, exist_ok=True) old_projects_dir.rename(new_user_dir / "projects") # Обновляем user_id у существующих проектов for i, project in enumerate(self.projects): self.projects[i].user_id = default_user.id self._save_projects() print("Migration complete. Default user: admin / admin") # Если проекты уже перемещены, но users.json отсутствует — восстанавливаем if users_dir.exists() and not users_file.exists(): has_projects_without_user = any( p.user_id == "" or p.user_id == default_user_id for p in self.projects ) if has_projects_without_user or any(users_dir.iterdir()): print("Restoring default user (users.json was missing)...") default_user = User( id=default_user_id, username="admin", email="admin@local", hashed_password=auth_service.get_password_hash("admin"), ) auth_service.users.append(default_user) auth_service._save_users() # Обновляем user_id у проектов без привязки for i, project in enumerate(self.projects): if not project.user_id: self.projects[i].user_id = default_user.id self._save_projects() print("Default user restored: admin / admin") # Если папка projects уже переехала, обновляем ссылку if users_dir.exists(): new_projects_dir = self.data_dir / "users" # Проверяем что old_projects_dir больше не существует (уже перемещён) if not old_projects_dir.exists(): self.projects_dir = new_projects_dir def get_project_dir(self, project_id: str, user_id: str) -> Path: """Получить директорию проекта с учётом пользователя""" # Определяем базу: если есть users/{user_id}, используем новую структуру user_projects_base = self.data_dir / "users" / user_id / "projects" if user_projects_base.exists(): project_dir = user_projects_base / project_id else: # Fallback для обратной совместимости project_dir = self.data_dir / "projects" / project_id if not project_dir.exists(): raise ValueError(f"Project directory not found: {project_id}") return project_dir def get_all_projects(self, user_id: str | None = None) -> list[Project]: """Получить все проекты (опционально фильтруя по пользователю)""" if user_id: return [p for p in self.projects if p.user_id == user_id] return self.projects def get_project(self, project_id: str) -> Project | None: """Получить проект по ID""" for project in self.projects: if project.id == project_id: return project return None def create_project( self, title: str, user_id: str, description: str = "" ) -> Project: """Создать новый проект""" project = Project(title=title, user_id=user_id, description=description) self.projects.append(project) self._save_projects() # Создаем директорию проекта в папке пользователя project_dir = self.data_dir / "users" / user_id / "projects" / project.id project_dir.mkdir(parents=True, exist_ok=True) # Инициализируем пустые файлы (project_dir / "documents.json").write_text("[]", encoding="utf-8") # Создаем пустую Story Bible empty_bible = StoryBible() (project_dir / "story_bible.json").write_text( json.dumps(empty_bible.model_dump(), ensure_ascii=False, indent=2), encoding="utf-8", ) return project def update_project( self, project_id: str, title: str | None = None, description: str | None = None, ) -> Project | None: """Обновить проект""" for i, project in enumerate(self.projects): if project.id == project_id: if title is not None: self.projects[i].title = title if description is not None: self.projects[i].description = description self.projects[i].updated_at = datetime.now().isoformat() self._save_projects() return self.projects[i] return None def delete_project(self, project_id: str, user_id: str) -> bool: """Удалить проект и все его данные""" for i, project in enumerate(self.projects): if project.id == project_id: # Проверяем принадлежность if project.user_id != user_id: raise ValueError("Project does not belong to this user") self.projects.pop(i) self._save_projects() # Удаляем директорию проекта project_dir = ( self.data_dir / "users" / user_id / "projects" / project_id ) if project_dir.exists(): shutil.rmtree(project_dir) return True return False # Singleton instance project_service = ProjectService()