Add File
This commit is contained in:
336
src/landppt/services/db_project_manager.py
Normal file
336
src/landppt/services/db_project_manager.py
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
"""
|
||||||
|
Database-aware Project and TODO Board Management Service
|
||||||
|
Replaces the in-memory ProjectManager with persistent database storage
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from ..api.models import (
|
||||||
|
PPTProject, TodoBoard, TodoStage, ProjectListResponse,
|
||||||
|
PPTGenerationRequest, PPTOutline, EnhancedPPTOutline
|
||||||
|
)
|
||||||
|
from ..database.service import DatabaseService
|
||||||
|
from ..database.database import get_async_db
|
||||||
|
|
||||||
|
# Configure logger for this module
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseProjectManager:
|
||||||
|
"""Database-aware project manager with persistent storage"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_db_service(self) -> DatabaseService:
|
||||||
|
"""Get database service instance with a new session"""
|
||||||
|
from ..database.database import AsyncSessionLocal
|
||||||
|
session = AsyncSessionLocal()
|
||||||
|
return DatabaseService(session)
|
||||||
|
|
||||||
|
async def create_project(self, request: PPTGenerationRequest) -> PPTProject:
|
||||||
|
"""Create a new PPT project with TODO board"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
project = await db_service.create_project(request)
|
||||||
|
logger.info(f"Created project {project.project_id}: {project.title}")
|
||||||
|
return project
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_todo_board_after_requirements(self, project_id: str, confirmed_requirements: Dict[str, Any]) -> bool:
|
||||||
|
"""Update TODO board after requirements confirmation"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
# Update project with confirmed requirements
|
||||||
|
project = await db_service.get_project(project_id)
|
||||||
|
if not project:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Save confirmed requirements
|
||||||
|
await db_service.project_repo.update(project_id, {
|
||||||
|
"confirmed_requirements": confirmed_requirements
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update requirements confirmation stage to completed
|
||||||
|
await db_service.update_stage_status(
|
||||||
|
project_id,
|
||||||
|
"requirements_confirmation",
|
||||||
|
"completed",
|
||||||
|
100.0,
|
||||||
|
confirmed_requirements
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Updated TODO board for project {project_id} after requirements confirmation")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating TODO board: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_todo_board_with_confirmed_requirements(self, project_id: str, confirmed_requirements: Dict[str, Any]) -> bool:
|
||||||
|
"""Compatibility method for EnhancedPPTService"""
|
||||||
|
return await self.update_todo_board_after_requirements(project_id, confirmed_requirements)
|
||||||
|
|
||||||
|
async def get_project(self, project_id: str) -> Optional[PPTProject]:
|
||||||
|
"""Get project by ID"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
return await db_service.get_project(project_id)
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def list_projects(self, page: int = 1, page_size: int = 10,
|
||||||
|
status: Optional[str] = None) -> ProjectListResponse:
|
||||||
|
"""List projects with pagination"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
return await db_service.list_projects(page, page_size, status)
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_project_status(self, project_id: str, status: str) -> bool:
|
||||||
|
"""Update project status"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.update_project_status(project_id, status)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Updated project {project_id} status to {status}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def get_todo_board(self, project_id: str) -> Optional[TodoBoard]:
|
||||||
|
"""Get TODO board for project"""
|
||||||
|
project = await self.get_project(project_id)
|
||||||
|
return project.todo_board if project else None
|
||||||
|
|
||||||
|
async def update_stage_status(self, project_id: str, stage_id: str,
|
||||||
|
status: str, progress: float = None,
|
||||||
|
result: Dict[str, Any] = None) -> bool:
|
||||||
|
"""Update stage status in TODO board"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.update_stage_status(project_id, stage_id, status, progress, result)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Updated stage {stage_id} to {status}, progress: {progress}%")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def save_project_outline(self, project_id: str, outline: Dict[str, Any]) -> bool:
|
||||||
|
"""Save project outline"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.save_project_outline(project_id, outline)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Saved outline for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def save_project_slides(self, project_id: str, slides_html: str,
|
||||||
|
slides_data: List[Dict[str, Any]] = None) -> bool:
|
||||||
|
"""Save project slides using optimized batch update"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.save_project_slides(project_id, slides_html, slides_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Saved slides for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def batch_save_slides(self, project_id: str, slides_data: List[Dict[str, Any]]) -> bool:
|
||||||
|
"""批量保存幻灯片 - 高效版本"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
# 准备幻灯片数据
|
||||||
|
slides_records = []
|
||||||
|
for i, slide_data in enumerate(slides_data):
|
||||||
|
slide_record = {
|
||||||
|
"project_id": project_id,
|
||||||
|
"slide_index": i,
|
||||||
|
"slide_id": slide_data.get("slide_id", f"slide_{i}"),
|
||||||
|
"title": slide_data.get("title", f"Slide {i+1}"),
|
||||||
|
"content_type": slide_data.get("content_type", "content"),
|
||||||
|
"html_content": slide_data.get("html_content", ""),
|
||||||
|
"slide_metadata": slide_data.get("metadata", {}),
|
||||||
|
"is_user_edited": slide_data.get("is_user_edited", False)
|
||||||
|
}
|
||||||
|
slides_records.append(slide_record)
|
||||||
|
|
||||||
|
# 使用批量upsert
|
||||||
|
success = await db_service.slide_repo.batch_upsert_slides(project_id, slides_records)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Batch saved {len(slides_data)} slides for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def replace_all_project_slides(self, project_id: str, slides_html: str,
|
||||||
|
slides_data: List[Dict[str, Any]] = None) -> bool:
|
||||||
|
"""完全替换项目的所有幻灯片 - 用于重新生成PPT等场景"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.replace_all_project_slides(project_id, slides_html, slides_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Replaced all slides for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def cleanup_excess_slides(self, project_id: str, current_slide_count: int) -> int:
|
||||||
|
"""清理多余的幻灯片"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
deleted_count = await db_service.cleanup_excess_slides(project_id, current_slide_count)
|
||||||
|
logger.info(f"Cleaned up {deleted_count} excess slides for project {project_id}")
|
||||||
|
return deleted_count
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def save_single_slide(self, project_id: str, slide_index: int, slide_data: Dict[str, Any]) -> bool:
|
||||||
|
"""Save a single slide to database immediately"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.save_single_slide(project_id, slide_index, slide_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Saved slide {slide_index + 1} for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_project_data(self, project_id: str, update_data: Dict[str, Any]) -> bool:
|
||||||
|
"""Update project data without affecting individual slides"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.update_project(project_id, update_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Updated project data for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_project(self, project_id: str, update_data: Dict[str, Any]) -> bool:
|
||||||
|
"""Alias for update_project_data for backward compatibility"""
|
||||||
|
return await self.update_project_data(project_id, update_data)
|
||||||
|
|
||||||
|
async def save_project_version(self, project_id: str, version_data: Dict[str, Any]) -> bool:
|
||||||
|
"""Save a version of the project"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.save_project_version(project_id, version_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Saved new version for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def get_project_versions(self, project_id: str) -> List[Dict[str, Any]]:
|
||||||
|
"""Get all versions of a project"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
project = await db_service.get_project(project_id)
|
||||||
|
|
||||||
|
if not project:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return project.versions
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def save_confirmed_requirements(self, project_id: str, requirements: Dict[str, Any]) -> bool:
|
||||||
|
"""Save confirmed requirements for a project"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.project_repo.update(project_id, {
|
||||||
|
"confirmed_requirements": requirements
|
||||||
|
})
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Saved confirmed requirements for project {project_id}")
|
||||||
|
|
||||||
|
return success is not None
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def get_confirmed_requirements(self, project_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Get confirmed requirements for a project"""
|
||||||
|
project = await self.get_project(project_id)
|
||||||
|
return project.confirmed_requirements if project else None
|
||||||
|
|
||||||
|
async def delete_project(self, project_id: str) -> bool:
|
||||||
|
"""Delete a project"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.project_repo.delete(project_id)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Deleted project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def update_project_metadata(self, project_id: str, metadata: Dict[str, Any]) -> bool:
|
||||||
|
"""Update project metadata"""
|
||||||
|
db_service = await self._get_db_service()
|
||||||
|
try:
|
||||||
|
success = await db_service.project_repo.update(project_id, {"project_metadata": metadata})
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"Updated metadata for project {project_id}")
|
||||||
|
|
||||||
|
return success
|
||||||
|
finally:
|
||||||
|
await db_service.session.close()
|
||||||
|
|
||||||
|
async def archive_project(self, project_id: str) -> bool:
|
||||||
|
"""Archive a project"""
|
||||||
|
return await self.update_project_status(project_id, "archived")
|
||||||
|
|
||||||
|
async def complete_project(self, project_id: str) -> bool:
|
||||||
|
"""Mark project as completed"""
|
||||||
|
return await self.update_project_status(project_id, "completed")
|
||||||
|
|
||||||
|
async def start_stage(self, project_id: str, stage_id: str) -> bool:
|
||||||
|
"""Start a specific stage"""
|
||||||
|
return await self.update_stage_status(project_id, stage_id, "running", 0.0)
|
||||||
|
|
||||||
|
async def complete_stage(self, project_id: str, stage_id: str, result: Dict[str, Any] = None) -> bool:
|
||||||
|
"""Complete a specific stage"""
|
||||||
|
return await self.update_stage_status(project_id, stage_id, "completed", 100.0, result)
|
||||||
|
|
||||||
|
async def fail_stage(self, project_id: str, stage_id: str, error_message: str) -> bool:
|
||||||
|
"""Mark a stage as failed"""
|
||||||
|
result = {"error": error_message, "timestamp": time.time()}
|
||||||
|
return await self.update_stage_status(project_id, stage_id, "failed", 0.0, result)
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""Close database connections - no longer needed as we use per-request sessions"""
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user