Files
writer-ide/backend/services/project_service.py
T
admin 46af290db3
Build and Push Docker Images / build-and-push (push) Failing after 3m33s
initial commit
2026-11-07 16:39:43 +03:00

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()