# Copyright (c) 2025 Patrick Motsch # All rights reserved. from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request, Query from typing import List, Dict, Any, Optional from fastapi import status import logging import json # Import auth module from modules.auth import limiter, getCurrentUser # Import interfaces import modules.interfaces.interfaceDbManagement as interfaceDbManagement from modules.datamodels.datamodelUtils import Prompt from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict from modules.shared.i18nRegistry import apiRouteContext routeApiMsg = apiRouteContext("routeDataPrompts") # Configure logger logger = logging.getLogger(__name__) # Create router for prompt endpoints router = APIRouter( prefix="/api/prompts", tags=["Manage Prompts"], responses={404: {"description": "Not found"}} ) @router.get("") @limiter.limit("30/minute") def get_prompts( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), mode: Optional[str] = Query(None, description="'filterValues' for distinct column values, 'ids' for all filtered IDs"), column: Optional[str] = Query(None, description="Column key (required when mode=filterValues)"), currentUser: User = Depends(getCurrentUser) ): """ Get prompts with optional pagination, sorting, and filtering. Modes: - None: paginated list (default) - filterValues: distinct values for a column (cross-filtered) - ids: all IDs matching current filters """ from modules.routes.routeHelpers import ( handleFilterValuesInMemory, handleIdsInMemory, enrichRowsWithFkLabels, handleGroupingInRequest, applyGroupScopeFilter, ) from modules.interfaces.interfaceDbApp import getInterface as getAppInterface CONTEXT_KEY = "prompts" # Parse pagination params early — needed for grouping in all modes paginationParams = None if pagination: try: paginationDict = json.loads(pagination) if paginationDict: paginationDict = normalize_pagination_dict(paginationDict) paginationParams = PaginationParams(**paginationDict) except (json.JSONDecodeError, ValueError) as e: raise HTTPException(status_code=400, detail=f"Invalid pagination parameter: {str(e)}") appInterface = getAppInterface(currentUser) groupCtx = handleGroupingInRequest(paginationParams, appInterface, CONTEXT_KEY) def _promptsToEnrichedDicts(promptItems): dicts = [r.model_dump() if hasattr(r, 'model_dump') else (dict(r) if not isinstance(r, dict) else r) for r in promptItems] enrichRowsWithFkLabels(dicts, Prompt) return dicts managementInterface = interfaceDbManagement.getInterface(currentUser) if mode == "filterValues": if not column: raise HTTPException(status_code=400, detail="column parameter required for mode=filterValues") result = managementInterface.getAllPrompts(pagination=None) items = _promptsToEnrichedDicts(result) items = applyGroupScopeFilter(items, groupCtx.itemIds) return handleFilterValuesInMemory(items, column, pagination) if mode == "ids": result = managementInterface.getAllPrompts(pagination=None) items = _promptsToEnrichedDicts(result) items = applyGroupScopeFilter(items, groupCtx.itemIds) return handleIdsInMemory(items, pagination) result = managementInterface.getAllPrompts(pagination=paginationParams) if paginationParams: items = applyGroupScopeFilter(_promptsToEnrichedDicts(result.items), groupCtx.itemIds) return { "items": items, "pagination": PaginationMetadata( currentPage=paginationParams.page, pageSize=paginationParams.pageSize, totalItems=result.totalItems, totalPages=result.totalPages, sort=paginationParams.sort, filters=paginationParams.filters ).model_dump(), "groupTree": groupCtx.groupTree, } else: items = applyGroupScopeFilter(_promptsToEnrichedDicts(result), groupCtx.itemIds) return { "items": items, "pagination": None, "groupTree": groupCtx.groupTree, } @router.post("", response_model=Prompt) @limiter.limit("10/minute") def create_prompt( request: Request, prompt: Prompt, currentUser: User = Depends(getCurrentUser) ) -> Prompt: """Create a new prompt""" managementInterface = interfaceDbManagement.getInterface(currentUser) # Create prompt newPrompt = managementInterface.createPrompt(prompt) return Prompt(**newPrompt) @router.get("/{promptId}", response_model=Prompt) @limiter.limit("30/minute") def get_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt"), currentUser: User = Depends(getCurrentUser) ) -> Prompt: """Get a specific prompt""" managementInterface = interfaceDbManagement.getInterface(currentUser) # Get prompt prompt = managementInterface.getPrompt(promptId) if not prompt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Prompt with ID {promptId} not found" ) return prompt @router.put("/{promptId}", response_model=Prompt) @limiter.limit("10/minute") def update_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt to update"), promptData: Dict[str, Any] = Body(...), currentUser: User = Depends(getCurrentUser) ) -> Prompt: """Update an existing prompt (supports partial updates for inline editing)""" managementInterface = interfaceDbManagement.getInterface(currentUser) # Check if the prompt exists existingPrompt = managementInterface.getPrompt(promptId) if not existingPrompt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Prompt with ID {promptId} not found" ) # Remove id from update data if present update_data = {k: v for k, v in promptData.items() if k != "id"} # Update prompt (ownership check happens in interface) try: updatedPrompt = managementInterface.updatePrompt(promptId, update_data) except PermissionError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) if not updatedPrompt: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=routeApiMsg("Error updating the prompt") ) return Prompt(**updatedPrompt) @router.delete("/{promptId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") def delete_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt to delete"), currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: """Delete a prompt""" managementInterface = interfaceDbManagement.getInterface(currentUser) # Check if the prompt exists existingPrompt = managementInterface.getPrompt(promptId) if not existingPrompt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Prompt with ID {promptId} not found" ) try: success = managementInterface.deletePrompt(promptId) except PermissionError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) if not success: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=routeApiMsg("Error deleting the prompt") ) return {"message": f"Prompt with ID {promptId} successfully deleted"}