This commit is contained in:
xds
2026-02-15 12:42:15 +03:00
parent 97483b7030
commit 5e7dc19bf3
9 changed files with 162 additions and 21 deletions

View File

@@ -1,24 +1,28 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query, Body
from api.dependency import get_idea_service, get_current_user, get_project_id, get_generation_service from api.dependency import get_idea_service, get_project_id, get_generation_service
from api.endpoints.auth import get_current_user
from api.service.idea_service import IdeaService from api.service.idea_service import IdeaService
from api.service.generation_service import GenerationService from api.service.generation_service import GenerationService
from models.Idea import Idea from models.Idea import Idea
from api.models.GenerationRequest import GenerationResponse from api.models.GenerationRequest import GenerationResponse, GenerationsResponse
from api.models.IdeaRequest import IdeaCreateRequest, IdeaUpdateRequest
router = APIRouter(prefix="/ideas", tags=["ideas"]) router = APIRouter(prefix="/api/ideas", tags=["ideas"])
@router.post("", response_model=Idea) @router.post("", response_model=Idea)
async def create_idea( async def create_idea(
name: str, request: IdeaCreateRequest,
project_id: str = Depends(get_project_id), project_id: str = Depends(get_project_id),
current_user: dict = Depends(get_current_user), current_user: dict = Depends(get_current_user),
idea_service: IdeaService = Depends(get_idea_service) idea_service: IdeaService = Depends(get_idea_service)
): ):
if not project_id: if not project_id and not request.project_id:
raise HTTPException(status_code=400, detail="Project ID header is required") raise HTTPException(status_code=400, detail="Project ID header is required")
return await idea_service.create_idea(name, project_id, str(current_user["_id"])) pid = project_id or request.project_id
return await idea_service.create_idea(request.name, request.description, pid, str(current_user["_id"]))
@router.get("", response_model=List[Idea]) @router.get("", response_model=List[Idea])
async def get_ideas( async def get_ideas(
@@ -41,6 +45,17 @@ async def get_idea(
raise HTTPException(status_code=404, detail="Idea not found") raise HTTPException(status_code=404, detail="Idea not found")
return idea return idea
@router.put("/{idea_id}", response_model=Idea)
async def update_idea(
idea_id: str,
request: IdeaUpdateRequest,
idea_service: IdeaService = Depends(get_idea_service)
):
idea = await idea_service.update_idea(idea_id, request.name, request.description)
if not idea:
raise HTTPException(status_code=404, detail="Idea not found")
return idea
@router.delete("/{idea_id}") @router.delete("/{idea_id}")
async def delete_idea( async def delete_idea(
idea_id: str, idea_id: str,
@@ -51,11 +66,42 @@ async def delete_idea(
raise HTTPException(status_code=404, detail="Idea not found or could not be deleted") raise HTTPException(status_code=404, detail="Idea not found or could not be deleted")
return {"status": "success"} return {"status": "success"}
@router.get("/{idea_id}/generations", response_model=List[GenerationResponse]) @router.get("/{idea_id}/generations", response_model=GenerationsResponse)
async def get_idea_generations( async def get_idea_generations(
idea_id: str, idea_id: str,
limit: int = 50, limit: int = 50,
offset: int = 0, offset: int = 0,
generation_service: GenerationService = Depends(get_generation_service) generation_service: GenerationService = Depends(get_generation_service)
): ):
# Depending on how generation service implements filtering by idea_id.
# We might need to update generation_service to support getting by idea_id directly
# or ensure generic get_generations supports it.
# Looking at generation_router.py, get_generations doesn't have idea_id arg?
# Let's check generation_service.get_generations signature again.
# It has: (character_id, limit, offset, user_id, project_id). NO IDEA_ID.
# I need to update GenerationService.get_generations too!
# For now, let's assume I will update it.
return await generation_service.get_generations(idea_id=idea_id, limit=limit, offset=offset) return await generation_service.get_generations(idea_id=idea_id, limit=limit, offset=offset)
@router.post("/{idea_id}/generations/{generation_id}")
async def add_generation_to_idea(
idea_id: str,
generation_id: str,
idea_service: IdeaService = Depends(get_idea_service)
):
success = await idea_service.add_generation_to_idea(idea_id, generation_id)
if not success:
raise HTTPException(status_code=404, detail="Idea or Generation not found")
return {"status": "success"}
@router.delete("/{idea_id}/generations/{generation_id}")
async def remove_generation_from_idea(
idea_id: str,
generation_id: str,
idea_service: IdeaService = Depends(get_idea_service)
):
success = await idea_service.remove_generation_from_idea(idea_id, generation_id)
if not success:
raise HTTPException(status_code=404, detail="Idea or Generation not found")
return {"status": "success"}

11
api/models/IdeaRequest.py Normal file
View File

@@ -0,0 +1,11 @@
from typing import Optional
from pydantic import BaseModel
class IdeaCreateRequest(BaseModel):
name: str
description: Optional[str] = None
project_id: Optional[str] = None # Optional in body if passed via header/dependency
class IdeaUpdateRequest(BaseModel):
name: Optional[str] = None
description: Optional[str] = None

View File

@@ -97,10 +97,9 @@ class GenerationService:
return await asyncio.to_thread(self.gemini.generate_text, prompt=technical_prompt, images_list=images) return await asyncio.to_thread(self.gemini.generate_text, prompt=technical_prompt, images_list=images)
async def get_generations(self, character_id: Optional[str] = None, limit: int = 10, offset: int = 0, user_id: Optional[str] = None, project_id: Optional[str] = None) -> List[ async def get_generations(self, character_id: Optional[str] = None, limit: int = 10, offset: int = 0, user_id: Optional[str] = None, project_id: Optional[str] = None, idea_id: Optional[str] = None) -> GenerationsResponse:
Generation]: generations = await self.dao.generations.get_generations(character_id = character_id,limit=limit, offset=offset, created_by=user_id, project_id=project_id, idea_id=idea_id)
generations = await self.dao.generations.get_generations(character_id = character_id,limit=limit, offset=offset, created_by=user_id, project_id=project_id) total_count = await self.dao.generations.count_generations(character_id = character_id, created_by=user_id, project_id=project_id, idea_id=idea_id)
total_count = await self.dao.generations.count_generations(character_id = character_id, created_by=user_id, project_id=project_id)
generations = [GenerationResponse(**gen.model_dump()) for gen in generations] generations = [GenerationResponse(**gen.model_dump()) for gen in generations]
return GenerationsResponse(generations=generations, total_count=total_count) return GenerationsResponse(generations=generations, total_count=total_count)

View File

@@ -1,4 +1,5 @@
from typing import List, Optional from typing import List, Optional
from datetime import datetime
from repos.dao import DAO from repos.dao import DAO
from models.Idea import Idea from models.Idea import Idea
@@ -6,8 +7,8 @@ class IdeaService:
def __init__(self, dao: DAO): def __init__(self, dao: DAO):
self.dao = dao self.dao = dao
async def create_idea(self, name: str, project_id: str, user_id: str) -> Idea: async def create_idea(self, name: str, description: Optional[str], project_id: str, user_id: str) -> Idea:
idea = Idea(name=name, project_id=project_id, created_by=user_id) idea = Idea(name=name, description=description, project_id=project_id, created_by=user_id)
idea_id = await self.dao.ideas.create_idea(idea) idea_id = await self.dao.ideas.create_idea(idea)
idea.id = idea_id idea.id = idea_id
return idea return idea
@@ -18,5 +19,57 @@ class IdeaService:
async def get_idea(self, idea_id: str) -> Optional[Idea]: async def get_idea(self, idea_id: str) -> Optional[Idea]:
return await self.dao.ideas.get_idea(idea_id) return await self.dao.ideas.get_idea(idea_id)
async def update_idea(self, idea_id: str, name: Optional[str] = None, description: Optional[str] = None) -> Optional[Idea]:
idea = await self.dao.ideas.get_idea(idea_id)
if not idea:
return None
if name is not None:
idea.name = name
if description is not None:
idea.description = description
idea.updated_at = datetime.now()
await self.dao.ideas.update_idea(idea)
return idea
async def delete_idea(self, idea_id: str) -> bool: async def delete_idea(self, idea_id: str) -> bool:
return await self.dao.ideas.delete_idea(idea_id) return await self.dao.ideas.delete_idea(idea_id)
async def add_generation_to_idea(self, idea_id: str, generation_id: str) -> bool:
# Verify idea exists
idea = await self.dao.ideas.get_idea(idea_id)
if not idea:
return False
# Get generation
gen = await self.dao.generations.get_generation(generation_id)
if not gen:
return False
# Link
gen.idea_id = idea_id
gen.updated_at = datetime.now()
await self.dao.generations.update_generation(gen)
return True
async def remove_generation_from_idea(self, idea_id: str, generation_id: str) -> bool:
# Verify idea exists (optional, but good for validation)
idea = await self.dao.ideas.get_idea(idea_id)
if not idea:
return False
# Get generation
gen = await self.dao.generations.get_generation(generation_id)
if not gen:
return False
# Unlink only if currently linked to this idea
if gen.idea_id == idea_id:
gen.idea_id = None
gen.updated_at = datetime.now()
await self.dao.generations.update_generation(gen)
return True
return False

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
class Idea(BaseModel): class Idea(BaseModel):
id: Optional[str] = None id: Optional[str] = None
name: str = "New Idea" name: str = "New Idea"
description: Optional[str] = None
project_id: str project_id: str
created_by: str # User ID created_by: str # User ID
is_deleted: bool = False is_deleted: bool = False

View File

@@ -37,3 +37,17 @@ class IdeaRepo:
{"$set": {"is_deleted": True}} {"$set": {"is_deleted": True}}
) )
return res.modified_count > 0 return res.modified_count > 0
async def update_idea(self, idea: Idea) -> bool:
if not idea.id or not ObjectId.is_valid(idea.id):
return False
idea_dict = idea.model_dump()
if "id" in idea_dict:
del idea_dict["id"]
res = await self.collection.update_one(
{"_id": ObjectId(idea.id)},
{"$set": idea_dict}
)
return res.modified_count > 0

View File

@@ -27,14 +27,23 @@ async def test_idea_flow():
print("Creating idea...") print("Creating idea...")
user_id = "test_user_123" user_id = "test_user_123"
project_id = "test_project_abc" project_id = "test_project_abc"
idea = await service.create_idea("My Test Idea", project_id, user_id) idea = await service.create_idea("My Test Idea", "Initial Description", project_id, user_id)
print(f"Idea created: {idea.id} - {idea.name}") print(f"Idea created: {idea.id} - {idea.name}")
# 2. Add Generation linked to Idea # 2. Update Idea
print("Updating idea...")
updated_idea = await service.update_idea(idea.id, description="Updated description")
print(f"Idea updated: {updated_idea.description}")
if updated_idea.description == "Updated description":
print("✅ Idea update successful")
else:
print("❌ Idea update FAILED")
# 3. Add Generation linked to Idea
print("Creating generation linked to idea...") print("Creating generation linked to idea...")
gen = Generation( gen = Generation(
prompt="idea generation 1", prompt="idea generation 1",
idea_id=idea.id, # idea_id=idea.id, <-- Intentionally NOT linking initially to test linking method
project_id=project_id, project_id=project_id,
created_by=user_id, created_by=user_id,
aspect_ratio=AspectRatios.NINESIXTEEN, aspect_ratio=AspectRatios.NINESIXTEEN,
@@ -42,15 +51,23 @@ async def test_idea_flow():
assets_list=[] assets_list=[]
) )
gen_id = await dao.generations.create_generation(gen) gen_id = await dao.generations.create_generation(gen)
print(f"Created linked generation: {gen_id}") print(f"Created generation: {gen_id}")
# Link generation to idea
print("Linking generation to idea...")
success = await service.add_generation_to_idea(idea.id, gen_id)
if success:
print("✅ Linking successful")
else:
print("❌ Linking FAILED")
# Debug: Check if generation was saved with idea_id # Debug: Check if generation was saved with idea_id
saved_gen = await dao.generations.collection.find_one({"_id": ObjectId(gen_id)}) saved_gen = await dao.generations.collection.find_one({"_id": ObjectId(gen_id)})
print(f"DEBUG: Saved Generation in DB idea_id: {saved_gen.get('idea_id')}") print(f"DEBUG: Saved Generation in DB idea_id: {saved_gen.get('idea_id')}")
# 3. Fetch Generations for Idea (Verify filtering and ordering) # 4. Fetch Generations for Idea (Verify filtering and ordering)
print("Fetching generations for idea...") print("Fetching generations for idea...")
gens = await dao.generations.get_generations(idea_id=idea.id) gens = await service.dao.generations.get_generations(idea_id=idea.id) # using repo directly as service might return wrapper
print(f"Found {len(gens)} generations in idea") print(f"Found {len(gens)} generations in idea")
if len(gens) == 1 and gens[0].id == gen_id: if len(gens) == 1 and gens[0].id == gen_id:
@@ -58,7 +75,7 @@ async def test_idea_flow():
else: else:
print("❌ Generation retrieval FAILED") print("❌ Generation retrieval FAILED")
# 4. Fetch Ideas for Project # 5. Fetch Ideas for Project
ideas = await service.get_ideas(project_id) ideas = await service.get_ideas(project_id)
print(f"Found {len(ideas)} ideas for project") print(f"Found {len(ideas)} ideas for project")