197 lines
8.2 KiB
Python
197 lines
8.2 KiB
Python
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()
|