This commit is contained in:
commit
46af290db3
100 files changed
+16663
No files matched your search
@@ -0,0 +1,196 @@
|
||||
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()
|
||||
Reference in new issue
Block a user