feat: add proper HTTP status codes to all API error responses

- 217 error returns across 18 route files + api.py now use JSONResponse
  with appropriate HTTP status codes instead of returning HTTP 200
- Status code distribution: 500 (121), 400 (39), 503 (28), 404 (24), 409 (3), 502 (2)
- Fixed language.py tuple-return bug (was serializing as JSON array)
- Fixed bare except clauses in bipolar_mode.py and voice.py
- Body-level error schemas preserved (status/error + success/error patterns)
  so web UI continues working without changes
- chat.py (SSE) unchanged: errors sent within stream protocol
- All 170 tests pass
This commit is contained in:
2026-04-15 15:43:18 +03:00
parent 33b2033cc3
commit edc9f27925
19 changed files with 243 additions and 227 deletions

View File

@@ -5,6 +5,7 @@ import os
import json
from typing import List
from fastapi import APIRouter, UploadFile, File, Form
from fastapi.responses import JSONResponse
import discord
import globals
from routes.models import CustomPromptRequest
@@ -25,7 +26,7 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest):
user_id_int = int(user_id)
user = globals.client.get_user(user_id_int)
if not user:
return {"status": "error", "message": f"User {user_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"})
# Use the LLM query function for DM context
from utils.llm import query_llama
@@ -47,9 +48,9 @@ async def send_custom_prompt_dm(user_id: str, req: CustomPromptRequest):
return {"status": "ok", "message": f"Custom DM prompt queued for user {user_id}"}
except ValueError:
return {"status": "error", "message": "Invalid user ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"})
except Exception as e:
return {"status": "error", "message": f"Error: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
@router.post("/dm/{user_id}/manual")
@@ -65,7 +66,7 @@ async def send_manual_message_dm(
user_id_int = int(user_id)
user = globals.client.get_user(user_id_int)
if not user:
return {"status": "error", "message": f"User {user_id} not found"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"User {user_id} not found"})
# Read file content immediately before the request closes
file_data = []
@@ -78,7 +79,7 @@ async def send_manual_message_dm(
})
except Exception as e:
logger.error(f"Failed to read file {file.filename}: {e}")
return {"status": "error", "message": f"Failed to read file {file.filename}: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to read file {file.filename}: {e}"})
async def send_dm_message_and_files():
try:
@@ -120,9 +121,9 @@ async def send_manual_message_dm(
return {"status": "ok", "message": f"Manual DM message queued for user {user_id}"}
except ValueError:
return {"status": "error", "message": "Invalid user ID format"}
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid user ID format"})
except Exception as e:
return {"status": "error", "message": f"Error: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Error: {e}"})
# ========== DM Logging Endpoints ==========
@@ -134,7 +135,7 @@ def get_dm_users():
users = dm_logger.get_all_dm_users()
return {"status": "ok", "users": users}
except Exception as e:
return {"status": "error", "message": f"Failed to get DM users: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get DM users: {e}"})
@router.get("/dms/users/{user_id}")
@@ -146,9 +147,9 @@ def get_dm_user_conversation(user_id: str):
summary = dm_logger.get_user_conversation_summary(user_id_int)
return {"status": "ok", "summary": summary}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to get user conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user conversation: {e}"})
@router.get("/dms/users/{user_id}/conversations")
@@ -180,10 +181,10 @@ def get_dm_conversations(user_id: str, limit: int = 50):
return {"status": "ok", "conversations": conversations}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to get conversations for user {user_id}: {e}")
return {"status": "error", "message": f"Failed to get conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get conversations: {e}"})
@router.get("/dms/users/{user_id}/search")
@@ -195,9 +196,9 @@ def search_dm_conversations(user_id: str, query: str, limit: int = 10):
results = dm_logger.search_user_conversations(user_id_int, query, limit)
return {"status": "ok", "results": results}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to search conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to search conversations: {e}"})
@router.get("/dms/users/{user_id}/export")
@@ -209,9 +210,9 @@ def export_dm_conversation(user_id: str, format: str = "json"):
export_path = dm_logger.export_user_conversation(user_id_int, format)
return {"status": "ok", "export_path": export_path, "format": format}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to export conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to export conversation: {e}"})
@router.delete("/dms/users/{user_id}")
@@ -225,11 +226,11 @@ def delete_dm_user_logs(user_id: str):
os.remove(log_file)
return {"status": "ok", "message": f"Deleted DM logs for user {user_id}"}
else:
return {"status": "error", "message": f"No DM logs found for user {user_id}"}
return JSONResponse(status_code=404, content={"status": "error", "message": f"No DM logs found for user {user_id}"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
return {"status": "error", "message": f"Failed to delete DM logs: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete DM logs: {e}"})
# ========== User Blocking & DM Management ==========
@@ -242,7 +243,7 @@ def get_blocked_users():
return {"status": "ok", "blocked_users": blocked_users}
except Exception as e:
logger.error(f"Failed to get blocked users: {e}")
return {"status": "error", "message": f"Failed to get blocked users: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get blocked users: {e}"})
@router.post("/dms/users/{user_id}/block")
@@ -261,13 +262,13 @@ def block_user(user_id: str):
logger.info(f"User {user_id} ({username}) blocked")
return {"status": "ok", "message": f"User {username} has been blocked"}
else:
return {"status": "error", "message": f"User {username} is already blocked"}
return JSONResponse(status_code=409, content={"status": "error", "message": f"User {username} is already blocked"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to block user {user_id}: {e}")
return {"status": "error", "message": f"Failed to block user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to block user: {e}"})
@router.post("/dms/users/{user_id}/unblock")
@@ -281,13 +282,13 @@ def unblock_user(user_id: str):
logger.info(f"User {user_id} unblocked")
return {"status": "ok", "message": f"User has been unblocked"}
else:
return {"status": "error", "message": f"User is not blocked"}
return JSONResponse(status_code=409, content={"status": "error", "message": f"User is not blocked"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to unblock user {user_id}: {e}")
return {"status": "error", "message": f"Failed to unblock user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to unblock user: {e}"})
@router.post("/dms/users/{user_id}/conversations/{conversation_id}/delete")
@@ -308,10 +309,10 @@ def delete_conversation(user_id: str, conversation_id: str):
return {"status": "ok", "message": "Message deletion queued (will delete from both Discord and logs)"}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to queue conversation deletion {conversation_id}: {e}")
return {"status": "error", "message": f"Failed to delete conversation: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversation: {e}"})
@router.post("/dms/users/{user_id}/conversations/delete-all")
@@ -331,10 +332,10 @@ def delete_all_conversations(user_id: str):
return {"status": "ok", "message": "Bulk deletion queued (will delete all Miku messages from Discord and clear logs)"}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to queue bulk conversation deletion for user {user_id}: {e}")
return {"status": "error", "message": f"Failed to delete conversations: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete conversations: {e}"})
@router.post("/dms/users/{user_id}/delete-completely")
@@ -348,13 +349,13 @@ def delete_user_completely(user_id: str):
logger.info(f"Completely deleted user {user_id}")
return {"status": "ok", "message": "User data deleted completely"}
else:
return {"status": "error", "message": "No user data found"}
return JSONResponse(status_code=404, content={"status": "error", "message": "No user data found"})
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to completely delete user {user_id}: {e}")
return {"status": "error", "message": f"Failed to delete user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to delete user: {e}"})
# ========== DM Interaction Analysis Endpoints ==========
@@ -366,7 +367,7 @@ def run_dm_analysis():
from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None:
return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}
return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."})
# Schedule analysis in Discord's event loop
async def run_analysis():
@@ -377,7 +378,7 @@ def run_dm_analysis():
return {"status": "ok", "message": "DM analysis started"}
except Exception as e:
logger.error(f"Failed to run DM analysis: {e}")
return {"status": "error", "message": f"Failed to run DM analysis: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to run DM analysis: {e}"})
@router.post("/dms/users/{user_id}/analyze")
@@ -387,7 +388,7 @@ def analyze_user_interaction(user_id: str):
from utils.dm_interaction_analyzer import dm_analyzer
if dm_analyzer is None:
return {"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."}
return JSONResponse(status_code=503, content={"status": "error", "message": "DM Analyzer not initialized. Set OWNER_USER_ID environment variable."})
user_id_int = int(user_id)
@@ -401,10 +402,10 @@ def analyze_user_interaction(user_id: str):
return {"status": "ok", "message": f"Analysis started for user {user_id}", "reported": True}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to analyze user {user_id}: {e}")
return {"status": "error", "message": f"Failed to analyze user: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to analyze user: {e}"})
@router.get("/dms/analysis/reports")
@@ -432,7 +433,7 @@ def get_analysis_reports(limit: int = 20):
return {"status": "ok", "reports": reports}
except Exception as e:
logger.error(f"Failed to get reports: {e}")
return {"status": "error", "message": f"Failed to get reports: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get reports: {e}"})
@router.get("/dms/analysis/reports/{user_id}")
@@ -461,7 +462,7 @@ def get_user_reports(user_id: str, limit: int = 10):
return {"status": "ok", "reports": reports}
except ValueError:
return {"status": "error", "message": f"Invalid user ID format: {user_id}"}
return JSONResponse(status_code=400, content={"status": "error", "message": f"Invalid user ID format: {user_id}"})
except Exception as e:
logger.error(f"Failed to get user reports: {e}")
return {"status": "error", "message": f"Failed to get user reports: {e}"}
return JSONResponse(status_code=500, content={"status": "error", "message": f"Failed to get user reports: {e}"})