Spaces:
Running
Running
from fastapi import APIRouter, HTTPException, Depends, status | |
from typing import List, Optional | |
from pydantic import BaseModel | |
import json | |
import time | |
import logging | |
from .auth_router import get_current_user | |
from app.utils import db_http | |
# Configure logging | |
logger = logging.getLogger("auth-server") | |
router = APIRouter() | |
# Models | |
class ProjectBase(BaseModel): | |
title: str | |
description: Optional[str] = None | |
geojson: Optional[str] = None | |
class ProjectCreate(ProjectBase): | |
pass | |
class ProjectUpdate(ProjectBase): | |
title: Optional[str] = None | |
class ProjectResponse(ProjectBase): | |
id: int | |
owner_id: int | |
storage_bucket: str | |
created_at: str | |
updated_at: str | |
# Routes | |
async def create_project( | |
project: ProjectCreate, | |
current_user = Depends(get_current_user) | |
): | |
operation_id = f"create_project_{int(time.time())}" | |
logger.info(f"[{operation_id}] Creating new project: {project.title}") | |
try: | |
# Get user ID based on the type of current_user | |
if isinstance(current_user, dict): | |
user_id = current_user.get("id") | |
else: | |
user_id = current_user[0] | |
logger.info(f"[{operation_id}] User ID: {user_id}") | |
# Validate GeoJSON if provided | |
if project.geojson: | |
try: | |
geojson_data = json.loads(project.geojson) | |
# Basic validation | |
if not isinstance(geojson_data, dict) or "type" not in geojson_data: | |
logger.warning(f"[{operation_id}] Invalid GeoJSON format") | |
raise ValueError("Invalid GeoJSON format") | |
except json.JSONDecodeError: | |
logger.warning(f"[{operation_id}] Invalid GeoJSON format (JSON decode error)") | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Invalid GeoJSON format" | |
) | |
logger.info(f"[{operation_id}] GeoJSON validation passed") | |
# Prepare project data | |
project_data = { | |
"owner_id": user_id, | |
"title": project.title, | |
"description": project.description, | |
"geojson": project.geojson, | |
"storage_bucket": "default" # Default storage bucket | |
} | |
# Insert the new project using HTTP API | |
logger.info(f"[{operation_id}] Inserting new project") | |
project_id = db_http.insert_record("projects", project_data, operation_id=operation_id) | |
if not project_id: | |
logger.error(f"[{operation_id}] Failed to insert project") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Failed to create project" | |
) | |
logger.info(f"[{operation_id}] Project inserted with ID: {project_id}") | |
# Get the newly created project | |
new_project = db_http.get_record_by_id("projects", project_id, operation_id=operation_id) | |
if not new_project: | |
logger.warning(f"[{operation_id}] Project not found after insert, trying by owner and title") | |
# Try to get by owner and title as fallback | |
projects = db_http.select_records( | |
"projects", | |
condition="owner_id = ? AND title = ?", | |
condition_params=[ | |
{"type": "integer", "value": str(user_id)}, | |
{"type": "text", "value": project.title} | |
], | |
order_by="id DESC", | |
limit=1, | |
operation_id=operation_id | |
) | |
if projects: | |
new_project = projects[0] | |
project_id = new_project.get("id") | |
logger.info(f"[{operation_id}] Found project by owner and title with ID: {project_id}") | |
else: | |
logger.error(f"[{operation_id}] Project not found after insert") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Project was created but could not be retrieved" | |
) | |
logger.info(f"[{operation_id}] Project created successfully with ID: {project_id}") | |
return { | |
"id": new_project.get("id"), | |
"owner_id": new_project.get("owner_id"), | |
"title": new_project.get("title"), | |
"description": new_project.get("description"), | |
"geojson": new_project.get("geojson"), | |
"storage_bucket": new_project.get("storage_bucket", "default"), | |
"created_at": new_project.get("created_at", ""), | |
"updated_at": new_project.get("updated_at", "") | |
} | |
except HTTPException: | |
raise | |
except Exception as e: | |
logger.error(f"[{operation_id}] Error creating project: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=str(e) | |
) | |
async def get_projects( | |
current_user = Depends(get_current_user), | |
skip: int = 0, | |
limit: int = 100 | |
): | |
operation_id = f"get_projects_{int(time.time())}" | |
logger.info(f"[{operation_id}] Getting projects (skip={skip}, limit={limit})") | |
try: | |
# Get user ID based on the type of current_user | |
if isinstance(current_user, dict): | |
user_id = current_user.get("id") | |
else: | |
user_id = current_user[0] | |
logger.info(f"[{operation_id}] User ID: {user_id}") | |
# Get projects using HTTP API | |
projects = db_http.select_records( | |
"projects", | |
condition="owner_id = ?", | |
condition_params=[{"type": "integer", "value": str(user_id)}], | |
order_by="updated_at DESC", | |
limit=limit, | |
offset=skip, | |
operation_id=operation_id | |
) | |
logger.info(f"[{operation_id}] Found {len(projects)} projects") | |
# Projects are already in dictionary format from the HTTP API | |
# Just need to ensure all required fields are present | |
result = [] | |
for project in projects: | |
result.append({ | |
"id": project.get("id"), | |
"owner_id": project.get("owner_id"), | |
"title": project.get("title"), | |
"description": project.get("description"), | |
"geojson": project.get("geojson"), | |
"storage_bucket": project.get("storage_bucket", "default"), | |
"created_at": project.get("created_at", ""), | |
"updated_at": project.get("updated_at", "") | |
}) | |
return result | |
except Exception as e: | |
logger.error(f"[{operation_id}] Error getting projects: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=str(e) | |
) | |
async def get_project( | |
project_id: int, | |
current_user = Depends(get_current_user) | |
): | |
operation_id = f"get_project_{int(time.time())}" | |
logger.info(f"[{operation_id}] Getting project with ID: {project_id}") | |
try: | |
# Get user ID based on the type of current_user | |
if isinstance(current_user, dict): | |
user_id = current_user.get("id") | |
else: | |
user_id = current_user[0] | |
logger.info(f"[{operation_id}] User ID: {user_id}") | |
# Get project using HTTP API | |
projects = db_http.select_records( | |
"projects", | |
condition="id = ? AND owner_id = ?", | |
condition_params=[ | |
{"type": "integer", "value": str(project_id)}, | |
{"type": "integer", "value": str(user_id)} | |
], | |
limit=1, | |
operation_id=operation_id | |
) | |
if not projects: | |
logger.warning(f"[{operation_id}] Project not found with ID: {project_id}") | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail="Project not found" | |
) | |
project = projects[0] | |
logger.info(f"[{operation_id}] Found project: {project.get('title')}") | |
return { | |
"id": project.get("id"), | |
"owner_id": project.get("owner_id"), | |
"title": project.get("title"), | |
"description": project.get("description"), | |
"geojson": project.get("geojson"), | |
"storage_bucket": project.get("storage_bucket", "default"), | |
"created_at": project.get("created_at", ""), | |
"updated_at": project.get("updated_at", "") | |
} | |
except HTTPException: | |
raise | |
except Exception as e: | |
logger.error(f"[{operation_id}] Error getting project: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=str(e) | |
) | |
async def update_project( | |
project_id: int, | |
project_update: ProjectUpdate, | |
current_user = Depends(get_current_user) | |
): | |
operation_id = f"update_project_{int(time.time())}" | |
logger.info(f"[{operation_id}] Updating project with ID: {project_id}") | |
try: | |
# Get user ID based on the type of current_user | |
if isinstance(current_user, dict): | |
user_id = current_user.get("id") | |
else: | |
user_id = current_user[0] | |
logger.info(f"[{operation_id}] User ID: {user_id}") | |
# Check if project exists and belongs to user | |
projects = db_http.select_records( | |
"projects", | |
condition="id = ? AND owner_id = ?", | |
condition_params=[ | |
{"type": "integer", "value": str(project_id)}, | |
{"type": "integer", "value": str(user_id)} | |
], | |
limit=1, | |
operation_id=operation_id | |
) | |
if not projects: | |
logger.warning(f"[{operation_id}] Project not found with ID: {project_id}") | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail="Project not found" | |
) | |
existing_project = projects[0] | |
logger.info(f"[{operation_id}] Found project: {existing_project.get('title')}") | |
# Prepare update data | |
update_data = {} | |
if project_update.title is not None: | |
update_data["title"] = project_update.title | |
if project_update.description is not None: | |
update_data["description"] = project_update.description | |
if project_update.geojson is not None: | |
# Validate GeoJSON | |
try: | |
geojson_data = json.loads(project_update.geojson) | |
if not isinstance(geojson_data, dict) or "type" not in geojson_data: | |
logger.warning(f"[{operation_id}] Invalid GeoJSON format") | |
raise ValueError("Invalid GeoJSON format") | |
except json.JSONDecodeError: | |
logger.warning(f"[{operation_id}] Invalid GeoJSON format (JSON decode error)") | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Invalid GeoJSON format" | |
) | |
update_data["geojson"] = project_update.geojson | |
# Add updated_at field | |
update_data["updated_at"] = time.strftime('%Y-%m-%d %H:%M:%S') | |
# If no fields to update, return the existing project | |
if len(update_data) <= 1: # Only updated_at | |
logger.info(f"[{operation_id}] No fields to update") | |
return { | |
"id": existing_project.get("id"), | |
"owner_id": existing_project.get("owner_id"), | |
"title": existing_project.get("title"), | |
"description": existing_project.get("description"), | |
"geojson": existing_project.get("geojson"), | |
"storage_bucket": existing_project.get("storage_bucket", "default"), | |
"created_at": existing_project.get("created_at", ""), | |
"updated_at": existing_project.get("updated_at", "") | |
} | |
# Update the project using HTTP API | |
logger.info(f"[{operation_id}] Updating project with data: {update_data}") | |
success = db_http.update_record( | |
"projects", | |
update_data, | |
"id = ? AND owner_id = ?", | |
[ | |
{"type": "integer", "value": str(project_id)}, | |
{"type": "integer", "value": str(user_id)} | |
], | |
operation_id=operation_id | |
) | |
if not success: | |
logger.error(f"[{operation_id}] Failed to update project") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Failed to update project" | |
) | |
logger.info(f"[{operation_id}] Project updated successfully") | |
# Get the updated project | |
updated_project = db_http.get_record_by_id("projects", project_id, operation_id=operation_id) | |
if not updated_project: | |
logger.warning(f"[{operation_id}] Updated project not found") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Project was updated but could not be retrieved" | |
) | |
return { | |
"id": updated_project.get("id"), | |
"owner_id": updated_project.get("owner_id"), | |
"title": updated_project.get("title"), | |
"description": updated_project.get("description"), | |
"geojson": updated_project.get("geojson"), | |
"storage_bucket": updated_project.get("storage_bucket", "default"), | |
"created_at": updated_project.get("created_at", ""), | |
"updated_at": updated_project.get("updated_at", "") | |
} | |
except HTTPException: | |
raise | |
except Exception as e: | |
logger.error(f"[{operation_id}] Error updating project: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=str(e) | |
) | |
async def delete_project( | |
project_id: int, | |
current_user = Depends(get_current_user) | |
): | |
operation_id = f"delete_project_{int(time.time())}" | |
logger.info(f"[{operation_id}] Deleting project with ID: {project_id}") | |
try: | |
# Get user ID based on the type of current_user | |
if isinstance(current_user, dict): | |
user_id = current_user.get("id") | |
else: | |
user_id = current_user[0] | |
logger.info(f"[{operation_id}] User ID: {user_id}") | |
# Check if project exists and belongs to user | |
projects = db_http.select_records( | |
"projects", | |
condition="id = ? AND owner_id = ?", | |
condition_params=[ | |
{"type": "integer", "value": str(project_id)}, | |
{"type": "integer", "value": str(user_id)} | |
], | |
limit=1, | |
operation_id=operation_id | |
) | |
if not projects: | |
logger.warning(f"[{operation_id}] Project not found with ID: {project_id}") | |
raise HTTPException( | |
status_code=status.HTTP_404_NOT_FOUND, | |
detail="Project not found" | |
) | |
logger.info(f"[{operation_id}] Found project to delete: {projects[0].get('title')}") | |
# Delete the project using HTTP API | |
success = db_http.delete_record( | |
"projects", | |
"id = ?", | |
[{"type": "integer", "value": str(project_id)}], | |
operation_id=operation_id | |
) | |
if not success: | |
logger.error(f"[{operation_id}] Failed to delete project") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Failed to delete project" | |
) | |
logger.info(f"[{operation_id}] Project deleted successfully") | |
return None | |
except HTTPException: | |
raise | |
except Exception as e: | |
logger.error(f"[{operation_id}] Error deleting project: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=str(e) | |
) | |