Skip to content
Merged

V2 #3

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions backend/app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from fastapi import APIRouter

from app.controllers.image import ImageController
from app.controllers.messages import MessagesController
from app.controllers.image_pair import ImagePairController
from app.controllers.project import ProjectController
from app.services.image import ImageService
from app.services.messages import MessagesService
from app.services.image_pair import ImagePairService
from app.services.project import ProjectService

log = logging.getLogger(__name__)

Expand All @@ -20,18 +22,33 @@ async def status():
return {"status": "ok"}


### Messages
### Projects


def get_messages_controller_router():
service = MessagesService()
return MessagesController(service=service).router
def get_project_controller_router():
service = ProjectService()
return ProjectController(service=service).router


router.include_router(
get_messages_controller_router(),
tags=["messages"],
prefix="/api/messages",
get_project_controller_router(),
tags=["projects"],
prefix="/api/projects",
)


### Image Pairs


def get_image_pair_controller_router():
service = ImagePairService()
return ImagePairController(service=service).router


router.include_router(
get_image_pair_controller_router(),
tags=["image-pairs"],
prefix="/api/image-pairs",
)


Expand Down
55 changes: 55 additions & 0 deletions backend/app/controllers/image_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import logging

from fastapi import APIRouter, Header, HTTPException

from app.models.image_pair import ImagePairListResponse
from app.services.image_pair import ImagePairService
from app.utils.database import db_client

log = logging.getLogger(__name__)


class ImagePairController:
def __init__(self, service: ImagePairService):
self.router = APIRouter()
self.service = service
self.setup_routes()

def setup_routes(self):
router = self.router

@router.get(
"/{project_id}",
response_model=ImagePairListResponse,
)
async def get_image_pairs(
project_id: str,
authorization: str = Header(None),
) -> ImagePairListResponse:
"""
Fetch all image pairs for a given project ID.
"""
log.info(f"Fetching image pairs for project_id: {project_id}")
try:
# Extract token from authorization header
token = authorization.replace("Bearer ", "") if authorization else ""

# Get database client
supabase_client = await db_client(token=token)

# Fetch image pairs
image_pairs = await self.service.get_image_pairs_by_project_id(
supabase_client=supabase_client, project_id=project_id
)

log.info(f"Successfully retrieved {len(image_pairs)} image pairs")
return ImagePairListResponse(image_pairs=image_pairs)

except RuntimeError as e:
log.error(f"Service error: {e}")
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
log.error(f"Unexpected error: {e}")
raise HTTPException(
status_code=500, detail="An unexpected error occurred"
)
28 changes: 0 additions & 28 deletions backend/app/controllers/messages.py

This file was deleted.

55 changes: 55 additions & 0 deletions backend/app/controllers/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import logging

from fastapi import APIRouter, Header, HTTPException

from app.models.project import ProjectListResponse
from app.services.project import ProjectService
from app.utils.database import db_client

log = logging.getLogger(__name__)


class ProjectController:
def __init__(self, service: ProjectService):
self.router = APIRouter()
self.service = service
self.setup_routes()

def setup_routes(self):
router = self.router

@router.get(
"/{user_id}",
response_model=ProjectListResponse,
)
async def get_projects(
user_id: str,
authorization: str = Header(None),
) -> ProjectListResponse:
"""
Fetch all projects for a given user ID.
"""
log.info(f"Fetching projects for user_id: {user_id}")
try:
# Extract token from authorization header
token = authorization.replace("Bearer ", "") if authorization else ""

# Get database client
supabase_client = await db_client(token=token)

# Fetch projects
projects = await self.service.get_projects_by_user_id(
supabase_client=supabase_client, user_id=user_id
)

log.info(f"Successfully retrieved {len(projects)} projects")
return ProjectListResponse(projects=projects)

except RuntimeError as e:
log.error(f"Service error: {e}")
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
log.error(f"Unexpected error: {e}")
raise HTTPException(
status_code=500, detail="An unexpected error occurred"
)
49 changes: 49 additions & 0 deletions backend/app/models/image_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


class ImagePair(BaseModel):
id: str = Field(description="The unique identifier for the image pair.")
project_id: str = Field(description="The project ID this image pair belongs to.")
input_url: str = Field(description="URL to the input image.")
input_mime_type: Optional[str] = Field(
default=None, description="MIME type of the input image."
)
input_width: Optional[int] = Field(
default=None, description="Width of the input image in pixels."
)
input_height: Optional[int] = Field(
default=None, description="Height of the input image in pixels."
)
output_url: Optional[str] = Field(
default=None, description="URL to the output image."
)
output_mime_type: Optional[str] = Field(
default=None, description="MIME type of the output image."
)
output_width: Optional[int] = Field(
default=None, description="Width of the output image in pixels."
)
output_height: Optional[int] = Field(
default=None, description="Height of the output image in pixels."
)
prompt_text: Optional[str] = Field(
default=None, description="The prompt text used to generate the output image."
)
metadata: Optional[Dict[str, Any]] = Field(
default=None, description="Additional metadata for the image pair."
)
created_at: datetime = Field(
description="The timestamp when the image pair was created."
)
updated_at: datetime = Field(
description="The timestamp when the image pair was last updated."
)


class ImagePairListResponse(BaseModel):
image_pairs: List[ImagePair] = Field(
description="List of image pairs for the project."
)
21 changes: 0 additions & 21 deletions backend/app/models/messages.py

This file was deleted.

26 changes: 26 additions & 0 deletions backend/app/models/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


class Project(BaseModel):
id: str = Field(description="The unique identifier for the project.")
user_id: str = Field(description="The user ID who owns the project.")
name: Optional[str] = Field(default=None, description="The name of the project.")
description: Optional[str] = Field(
default=None, description="The description of the project."
)
snapshot: Optional[Dict[str, Any]] = Field(
default=None, description="The snapshot data for the project."
)
created_at: datetime = Field(
description="The timestamp when the project was created."
)
updated_at: datetime = Field(
description="The timestamp when the project was last updated."
)


class ProjectListResponse(BaseModel):
projects: List[Project] = Field(description="List of projects for the user.")
51 changes: 51 additions & 0 deletions backend/app/services/image_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import logging
from typing import List

from supabase._async.client import AsyncClient as Client

from app.models.image_pair import ImagePair

log = logging.getLogger(__name__)


class ImagePairService:
async def get_image_pairs_by_project_id(
self, supabase_client: Client, project_id: str
) -> List[ImagePair]:
"""
Fetch all image pairs for a given project ID.

Args:
supabase_client: The Supabase client instance
project_id: The project ID to fetch image pairs for

Returns:
List of ImagePair objects for the project
"""
log.info(f"Fetching image pairs for project_id: {project_id}")

try:
# Query the image_pairs table (uses the project_id index)
response = (
await supabase_client.table("image_pairs")
.select("*")
.eq("project_id", project_id)
.order("created_at", desc=True)
.execute()
)

if not response.data:
log.info(f"No image pairs found for project_id: {project_id}")
return []

# Convert to ImagePair models
image_pairs = [ImagePair(**pair_data) for pair_data in response.data]
log.info(
f"Found {len(image_pairs)} image pairs for project_id: {project_id}"
)

return image_pairs

except Exception as e:
log.error(f"Error fetching image pairs for project_id {project_id}: {e}")
raise RuntimeError(f"Failed to fetch image pairs: {e}")
10 changes: 0 additions & 10 deletions backend/app/services/messages.py

This file was deleted.

Loading
Loading