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
|
- `GET /`: Get API information and available endpoints
|
||||||
|
|
||||||
### Task Management (Versioned API)
|
### Task Management
|
||||||
|
|
||||||
- `GET /api/v1/tasks`: Get all tasks
|
- `GET /tasks`: Get all tasks
|
||||||
- `POST /api/v1/tasks`: Create a new task
|
- `POST /tasks`: Create a new task
|
||||||
- `GET /api/v1/tasks/{task_id}`: Get a specific task
|
- `GET /tasks/{task_id}`: Get a specific task
|
||||||
- `PUT /api/v1/tasks/{task_id}`: Update a task
|
- `PUT /tasks/{task_id}`: Update a task
|
||||||
- `DELETE /api/v1/tasks/{task_id}`: Delete a task
|
- `DELETE /tasks/{task_id}`: Delete a task
|
||||||
- `POST /api/v1/tasks/{task_id}/complete`: Mark a task as completed
|
- `POST /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`
|
|
||||||
|
|
||||||
### Health and Diagnostic Endpoints
|
### Health and Diagnostic Endpoints
|
||||||
|
|
||||||
@ -56,7 +45,7 @@ The following endpoints are maintained for backward compatibility and will redir
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'POST' \
|
curl -X 'POST' \
|
||||||
'https://your-domain.com/api/v1/tasks/' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/' \
|
||||||
-H 'accept: application/json' \
|
-H 'accept: application/json' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
@ -73,7 +62,7 @@ curl -X 'POST' \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'GET' \
|
curl -X 'GET' \
|
||||||
'https://your-domain.com/api/v1/tasks/' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/' \
|
||||||
-H 'accept: application/json'
|
-H 'accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -81,7 +70,7 @@ curl -X 'GET' \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'GET' \
|
curl -X 'GET' \
|
||||||
'https://your-domain.com/api/v1/tasks/1' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||||
-H 'accept: application/json'
|
-H 'accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -89,7 +78,7 @@ curl -X 'GET' \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'PUT' \
|
curl -X 'PUT' \
|
||||||
'https://your-domain.com/api/v1/tasks/1' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||||
-H 'accept: application/json' \
|
-H 'accept: application/json' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
@ -103,7 +92,7 @@ curl -X 'PUT' \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'DELETE' \
|
curl -X 'DELETE' \
|
||||||
'https://your-domain.com/api/v1/tasks/1' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1' \
|
||||||
-H 'accept: application/json'
|
-H 'accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -111,7 +100,7 @@ curl -X 'DELETE' \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X 'POST' \
|
curl -X 'POST' \
|
||||||
'https://your-domain.com/api/v1/tasks/1/complete' \
|
'https://taskmanagerapi-ttkjqk.backend.im/tasks/1/complete' \
|
||||||
-H 'accept: application/json'
|
-H 'accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -21,11 +21,70 @@ def read_tasks(
|
|||||||
"""
|
"""
|
||||||
Retrieve tasks.
|
Retrieve tasks.
|
||||||
"""
|
"""
|
||||||
if status:
|
try:
|
||||||
tasks = crud.task.get_by_status(db, status=status)
|
import traceback
|
||||||
else:
|
import sqlite3
|
||||||
tasks = crud.task.get_multi(db, skip=skip, limit=limit)
|
from sqlalchemy import text
|
||||||
return tasks
|
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)
|
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
|
||||||
@ -176,13 +235,71 @@ def read_task(
|
|||||||
"""
|
"""
|
||||||
Get task by ID.
|
Get task by ID.
|
||||||
"""
|
"""
|
||||||
task = crud.task.get(db, id=task_id)
|
try:
|
||||||
if not task:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Task not found",
|
detail=f"Error retrieving task: {str(e)}"
|
||||||
)
|
)
|
||||||
return task
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{task_id}", response_model=Task)
|
@router.put("/{task_id}", response_model=Task)
|
||||||
@ -195,14 +312,136 @@ def update_task(
|
|||||||
"""
|
"""
|
||||||
Update a task.
|
Update a task.
|
||||||
"""
|
"""
|
||||||
task = crud.task.get(db, id=task_id)
|
try:
|
||||||
if not task:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Task not found",
|
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)
|
@router.delete("/{task_id}", response_model=Task)
|
||||||
@ -214,14 +453,87 @@ def delete_task(
|
|||||||
"""
|
"""
|
||||||
Delete a task.
|
Delete a task.
|
||||||
"""
|
"""
|
||||||
task = crud.task.get(db, id=task_id)
|
try:
|
||||||
if not task:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Task not found",
|
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)
|
@router.post("/{task_id}/complete", response_model=Task)
|
||||||
@ -233,10 +545,89 @@ def complete_task(
|
|||||||
"""
|
"""
|
||||||
Mark a task as completed.
|
Mark a task as completed.
|
||||||
"""
|
"""
|
||||||
task = crud.task.mark_completed(db, task_id=task_id)
|
try:
|
||||||
if not task:
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail="Task not found",
|
detail=f"Error completing task: {str(e)}"
|
||||||
)
|
)
|
||||||
return task
|
|
@ -35,7 +35,8 @@ if DB_DIR is None:
|
|||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
PROJECT_NAME: str = "Task Manager API"
|
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)
|
SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||||
# 60 minutes * 24 hours * 8 days = 8 days
|
# 60 minutes * 24 hours * 8 days = 8 days
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
|
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,
|
content=error_detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Include the API router with the version prefix
|
# Include the API router directly (no version prefix)
|
||||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
app.include_router(api_router)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", tags=["info"])
|
@app.get("/", tags=["info"])
|
||||||
def api_info():
|
def api_info():
|
||||||
"""
|
"""
|
||||||
API information endpoint with links to documentation and versioned endpoints
|
API information endpoint with links to documentation and endpoints
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"name": settings.PROJECT_NAME,
|
"name": settings.PROJECT_NAME,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A RESTful API for managing tasks",
|
"description": "A RESTful API for managing tasks",
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"api": f"{settings.API_V1_STR}",
|
"tasks": "/tasks",
|
||||||
"tasks": f"{settings.API_V1_STR}/tasks",
|
|
||||||
"docs": "/docs",
|
"docs": "/docs",
|
||||||
"redoc": "/redoc",
|
"redoc": "/redoc",
|
||||||
"health": "/health",
|
"health": "/health",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user