auth-server / app /api /routes /projects_router.py
kamau1's picture
Upload 12 files
98f6c3a verified
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
@router.post("/", response_model=ProjectResponse)
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)
)
@router.get("/", response_model=List[ProjectResponse])
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)
)
@router.get("/{project_id}", response_model=ProjectResponse)
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)
)
@router.patch("/{project_id}", response_model=ProjectResponse)
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)
)
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
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)
)