Remove API versioning and implement robust error handling for all endpoints
This commit is contained in:
parent
522c749b23
commit
669d16ace5
37
README.md
37
README.md
@ -25,25 +25,14 @@ A RESTful API for managing tasks, built with FastAPI and SQLite.
|
||||
|
||||
- `GET /`: Get API information and available endpoints
|
||||
|
||||
### Task Management (Versioned API)
|
||||
### Task Management
|
||||
|
||||
- `GET /api/v1/tasks`: Get all tasks
|
||||
- `POST /api/v1/tasks`: Create a new task
|
||||
- `GET /api/v1/tasks/{task_id}`: Get a specific task
|
||||
- `PUT /api/v1/tasks/{task_id}`: Update a task
|
||||
- `DELETE /api/v1/tasks/{task_id}`: Delete a task
|
||||
- `POST /api/v1/tasks/{task_id}/complete`: Mark a task as completed
|
||||
|
||||
### Task Management (Legacy Unversioned API - Redirects to Versioned API)
|
||||
|
||||
The following endpoints are maintained for backward compatibility and will redirect to the versioned API:
|
||||
|
||||
- `GET /tasks`: Redirects to `/api/v1/tasks`
|
||||
- `POST /tasks`: Redirects to `/api/v1/tasks`
|
||||
- `GET /tasks/{task_id}`: Redirects to `/api/v1/tasks/{task_id}`
|
||||
- `PUT /tasks/{task_id}`: Redirects to `/api/v1/tasks/{task_id}`
|
||||
- `DELETE /tasks/{task_id}`: Redirects to `/api/v1/tasks/{task_id}`
|
||||
- `POST /tasks/{task_id}/complete`: Redirects to `/api/v1/tasks/{task_id}/complete`
|
||||
- `GET /tasks`: Get all tasks
|
||||
- `POST /tasks`: Create a new task
|
||||
- `GET /tasks/{task_id}`: Get a specific task
|
||||
- `PUT /tasks/{task_id}`: Update a task
|
||||
- `DELETE /tasks/{task_id}`: Delete a task
|
||||
- `POST /tasks/{task_id}/complete`: Mark a task as completed
|
||||
|
||||
### Health and Diagnostic Endpoints
|
||||
|
||||
@ -56,7 +45,7 @@ The following endpoints are maintained for backward compatibility and will redir
|
||||
|
||||
```bash
|
||||
curl -X 'POST' \
|
||||
'https://your-domain.com/api/v1/tasks/' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
@ -73,7 +62,7 @@ curl -X 'POST' \
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://your-domain.com/api/v1/tasks/' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
@ -81,7 +70,7 @@ curl -X 'GET' \
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://your-domain.com/api/v1/tasks/1' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
@ -89,7 +78,7 @@ curl -X 'GET' \
|
||||
|
||||
```bash
|
||||
curl -X 'PUT' \
|
||||
'https://your-domain.com/api/v1/tasks/1' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
@ -103,7 +92,7 @@ curl -X 'PUT' \
|
||||
|
||||
```bash
|
||||
curl -X 'DELETE' \
|
||||
'https://your-domain.com/api/v1/tasks/1' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
@ -111,7 +100,7 @@ curl -X 'DELETE' \
|
||||
|
||||
```bash
|
||||
curl -X 'POST' \
|
||||
'https://your-domain.com/api/v1/tasks/1/complete' \
|
||||
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1/complete' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
|
||||
|
@ -21,11 +21,70 @@ def read_tasks(
|
||||
"""
|
||||
Retrieve tasks.
|
||||
"""
|
||||
if status:
|
||||
tasks = crud.task.get_by_status(db, status=status)
|
||||
else:
|
||||
tasks = crud.task.get_multi(db, skip=skip, limit=limit)
|
||||
return tasks
|
||||
try:
|
||||
import traceback
|
||||
import sqlite3
|
||||
from sqlalchemy import text
|
||||
from app.db.session import db_file
|
||||
|
||||
print(f"Getting tasks with status: {status}, skip: {skip}, limit: {limit}")
|
||||
|
||||
# Try the normal SQLAlchemy approach first
|
||||
try:
|
||||
if status:
|
||||
tasks = crud.task.get_by_status(db, status=status)
|
||||
else:
|
||||
tasks = crud.task.get_multi(db, skip=skip, limit=limit)
|
||||
return tasks
|
||||
except Exception as e:
|
||||
print(f"Error getting tasks with SQLAlchemy: {e}")
|
||||
print(traceback.format_exc())
|
||||
# Continue to fallback
|
||||
|
||||
# Fallback to direct SQLite approach
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
if status:
|
||||
cursor.execute("SELECT * FROM task WHERE status = ? LIMIT ? OFFSET ?",
|
||||
(status.value, limit, skip))
|
||||
else:
|
||||
cursor.execute("SELECT * FROM task LIMIT ? OFFSET ?", (limit, skip))
|
||||
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# Convert to Task objects
|
||||
tasks = []
|
||||
for row in rows:
|
||||
task_dict = dict(row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Convert to object with attributes
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
tasks.append(TaskResult(**task_dict))
|
||||
|
||||
conn.close()
|
||||
return tasks
|
||||
except Exception as e:
|
||||
print(f"Error getting tasks with direct SQLite: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
print(f"Global error in read_tasks: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error retrieving tasks: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
|
||||
@ -176,13 +235,71 @@ def read_task(
|
||||
"""
|
||||
Get task by ID.
|
||||
"""
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if not task:
|
||||
try:
|
||||
import traceback
|
||||
import sqlite3
|
||||
from app.db.session import db_file
|
||||
|
||||
print(f"Getting task with ID: {task_id}")
|
||||
|
||||
# Try the normal SQLAlchemy approach first
|
||||
try:
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if task:
|
||||
return task
|
||||
# Fall through to direct SQLite if task not found
|
||||
except Exception as e:
|
||||
print(f"Error getting task with SQLAlchemy: {e}")
|
||||
print(traceback.format_exc())
|
||||
# Continue to fallback
|
||||
|
||||
# Fallback to direct SQLite approach
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
|
||||
task_dict = dict(row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Convert to object with attributes
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
conn.close()
|
||||
return TaskResult(**task_dict)
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error getting task with direct SQLite: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error retrieving task: {str(e)}"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise any HTTP exceptions
|
||||
except Exception as e:
|
||||
print(f"Global error in read_task: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error retrieving task: {str(e)}"
|
||||
)
|
||||
return task
|
||||
|
||||
|
||||
@router.put("/{task_id}", response_model=Task)
|
||||
@ -195,14 +312,136 @@ def update_task(
|
||||
"""
|
||||
Update a task.
|
||||
"""
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if not task:
|
||||
try:
|
||||
import traceback
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime
|
||||
from app.db.session import db_file
|
||||
|
||||
print(f"Updating task with ID: {task_id}, data: {task_in}")
|
||||
|
||||
# Handle datetime conversion for due_date if present
|
||||
if hasattr(task_in, "due_date") and task_in.due_date is not None:
|
||||
if isinstance(task_in.due_date, str):
|
||||
try:
|
||||
task_in.due_date = datetime.fromisoformat(task_in.due_date.replace('Z', '+00:00'))
|
||||
except Exception as e:
|
||||
print(f"Error parsing due_date: {e}")
|
||||
|
||||
# Try the normal SQLAlchemy approach first
|
||||
try:
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
updated_task = crud.task.update(db, db_obj=task, obj_in=task_in)
|
||||
return updated_task
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error updating task with SQLAlchemy: {e}")
|
||||
print(traceback.format_exc())
|
||||
# Continue to fallback
|
||||
|
||||
# Fallback to direct SQLite approach
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# First check if task exists
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
|
||||
# Convert Pydantic model to dict, excluding unset values
|
||||
updates = {}
|
||||
model_data = task_in.model_dump(exclude_unset=True) if hasattr(task_in, "model_dump") else task_in.dict(exclude_unset=True)
|
||||
|
||||
# Only include fields that were provided in the update
|
||||
for key, value in model_data.items():
|
||||
if value is not None: # Skip None values
|
||||
updates[key] = value
|
||||
|
||||
if not updates:
|
||||
# No updates provided
|
||||
task_dict = dict(row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Return the unchanged task
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
conn.close()
|
||||
return TaskResult(**task_dict)
|
||||
|
||||
# Format datetime objects
|
||||
if 'due_date' in updates and isinstance(updates['due_date'], datetime):
|
||||
updates['due_date'] = updates['due_date'].isoformat()
|
||||
|
||||
# Add updated_at timestamp
|
||||
updates['updated_at'] = datetime.utcnow().isoformat()
|
||||
|
||||
# Build the SQL update statement
|
||||
set_clause = ", ".join([f"{key} = ?" for key in updates.keys()])
|
||||
params = list(updates.values())
|
||||
params.append(task_id) # For the WHERE clause
|
||||
|
||||
cursor.execute(f"UPDATE task SET {set_clause} WHERE id = ?", params)
|
||||
conn.commit()
|
||||
|
||||
# Return the updated task
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
updated_row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if updated_row:
|
||||
task_dict = dict(updated_row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Convert to object with attributes
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
return TaskResult(**task_dict)
|
||||
else:
|
||||
raise Exception("Task was updated but could not be retrieved")
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error updating task with direct SQLite: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error updating task: {str(e)}"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise any HTTP exceptions
|
||||
except Exception as e:
|
||||
print(f"Global error in update_task: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error updating task: {str(e)}"
|
||||
)
|
||||
task = crud.task.update(db, db_obj=task, obj_in=task_in)
|
||||
return task
|
||||
|
||||
|
||||
@router.delete("/{task_id}", response_model=Task)
|
||||
@ -214,14 +453,87 @@ def delete_task(
|
||||
"""
|
||||
Delete a task.
|
||||
"""
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if not task:
|
||||
try:
|
||||
import traceback
|
||||
import sqlite3
|
||||
from app.db.session import db_file
|
||||
|
||||
print(f"Deleting task with ID: {task_id}")
|
||||
|
||||
# First, get the task to return it later
|
||||
task_to_return = None
|
||||
|
||||
# Try the normal SQLAlchemy approach first
|
||||
try:
|
||||
task = crud.task.get(db, id=task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
task_to_return = task
|
||||
task = crud.task.remove(db, id=task_id)
|
||||
return task
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error deleting task with SQLAlchemy: {e}")
|
||||
print(traceback.format_exc())
|
||||
# Continue to fallback
|
||||
|
||||
# Fallback to direct SQLite approach
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# First save the task data for the return value
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
|
||||
task_dict = dict(row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Delete the task
|
||||
cursor.execute("DELETE FROM task WHERE id = ?", (task_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Convert to object with attributes
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
return TaskResult(**task_dict)
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error deleting task with direct SQLite: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error deleting task: {str(e)}"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise any HTTP exceptions
|
||||
except Exception as e:
|
||||
print(f"Global error in delete_task: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error deleting task: {str(e)}"
|
||||
)
|
||||
task = crud.task.remove(db, id=task_id)
|
||||
return task
|
||||
|
||||
|
||||
@router.post("/{task_id}/complete", response_model=Task)
|
||||
@ -233,10 +545,89 @@ def complete_task(
|
||||
"""
|
||||
Mark a task as completed.
|
||||
"""
|
||||
task = crud.task.mark_completed(db, task_id=task_id)
|
||||
if not task:
|
||||
try:
|
||||
import traceback
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from app.db.session import db_file
|
||||
|
||||
print(f"Marking task {task_id} as completed")
|
||||
|
||||
# Try the normal SQLAlchemy approach first
|
||||
try:
|
||||
task = crud.task.mark_completed(db, task_id=task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
return task
|
||||
except Exception as e:
|
||||
print(f"Error completing task with SQLAlchemy: {e}")
|
||||
print(traceback.format_exc())
|
||||
# Continue to fallback
|
||||
|
||||
# Fallback to direct SQLite approach
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_file))
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# First check if task exists
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
|
||||
# Update task to completed status
|
||||
now = datetime.utcnow().isoformat()
|
||||
cursor.execute(
|
||||
"UPDATE task SET completed = ?, status = ?, updated_at = ? WHERE id = ?",
|
||||
(1, "done", now, task_id)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# Get the updated task
|
||||
cursor.execute("SELECT * FROM task WHERE id = ?", (task_id,))
|
||||
updated_row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if updated_row:
|
||||
task_dict = dict(updated_row)
|
||||
# Convert completed to boolean
|
||||
if 'completed' in task_dict:
|
||||
task_dict['completed'] = bool(task_dict['completed'])
|
||||
|
||||
# Convert to object with attributes
|
||||
class TaskResult:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
return TaskResult(**task_dict)
|
||||
else:
|
||||
raise Exception("Task was completed but could not be retrieved")
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise the 404 exception
|
||||
except Exception as e:
|
||||
print(f"Error completing task with direct SQLite: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error completing task: {str(e)}"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise # Re-raise any HTTP exceptions
|
||||
except Exception as e:
|
||||
print(f"Global error in complete_task: {e}")
|
||||
print(traceback.format_exc())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found",
|
||||
)
|
||||
return task
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error completing task: {str(e)}"
|
||||
)
|
@ -35,7 +35,8 @@ if DB_DIR is None:
|
||||
|
||||
class Settings(BaseSettings):
|
||||
PROJECT_NAME: str = "Task Manager API"
|
||||
API_V1_STR: str = "/api/v1"
|
||||
# No API version prefix - use direct paths
|
||||
API_PREFIX: str = ""
|
||||
SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||
# 60 minutes * 24 hours * 8 days = 8 days
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
|
||||
|
34
main.py
34
main.py
@ -68,47 +68,21 @@ async def validation_exception_handler(request: Request, exc: Exception):
|
||||
content=error_detail,
|
||||
)
|
||||
|
||||
# Include the API router with the version prefix
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
# Add support for compatibility with non-versioned endpoints
|
||||
from fastapi import Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
# Create a catch-all route for /tasks paths to redirect to versioned API
|
||||
@app.get("/tasks", include_in_schema=False)
|
||||
@app.post("/tasks", include_in_schema=False)
|
||||
@app.get("/tasks/{task_id:path}", include_in_schema=False)
|
||||
@app.put("/tasks/{task_id:path}", include_in_schema=False)
|
||||
@app.delete("/tasks/{task_id:path}", include_in_schema=False)
|
||||
@app.post("/tasks/{task_id:path}/complete", include_in_schema=False)
|
||||
async def redirect_to_versioned_api(request: Request, task_id: str = None):
|
||||
"""
|
||||
Redirect unversioned API requests to the versioned API path
|
||||
"""
|
||||
target_url = str(request.url)
|
||||
# Replace the /tasks part with /api/v1/tasks
|
||||
versioned_url = target_url.replace("/tasks", f"{settings.API_V1_STR}/tasks", 1)
|
||||
|
||||
# Add debugging info
|
||||
print(f"Redirecting from {target_url} to {versioned_url}")
|
||||
|
||||
# Use 307 to preserve the method and body
|
||||
return RedirectResponse(url=versioned_url, status_code=307)
|
||||
# Include the API router directly (no version prefix)
|
||||
app.include_router(api_router)
|
||||
|
||||
|
||||
@app.get("/", tags=["info"])
|
||||
def api_info():
|
||||
"""
|
||||
API information endpoint with links to documentation and versioned endpoints
|
||||
API information endpoint with links to documentation and endpoints
|
||||
"""
|
||||
return {
|
||||
"name": settings.PROJECT_NAME,
|
||||
"version": "1.0.0",
|
||||
"description": "A RESTful API for managing tasks",
|
||||
"endpoints": {
|
||||
"api": f"{settings.API_V1_STR}",
|
||||
"tasks": f"{settings.API_V1_STR}/tasks",
|
||||
"tasks": "/tasks",
|
||||
"docs": "/docs",
|
||||
"redoc": "/redoc",
|
||||
"health": "/health",
|
||||
|
Loading…
x
Reference in New Issue
Block a user