Add expense creation API endpoint
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
"""API routes aggregation."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from mft.routes import auth, categories, health
|
||||
|
||||
from mft.routes import auth, categories, expense, health
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Include all route modules
|
||||
api_router.include_router(health.router, tags=["health"])
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
api_router.include_router(categories.router, tags=["categories"])
|
||||
api_router.include_router(expense.router, tags=["expenses"])
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
"""Authentication routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from mft.auth import verify_token
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -19,8 +15,4 @@ async def validate(user_id: int = Depends(verify_token)):
|
||||
Returns:
|
||||
A simple status response with the authenticated user ID
|
||||
"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"user_id": user_id,
|
||||
"message": "Successfully authenticated"
|
||||
}
|
||||
return {"status": "ok", "user_id": user_id, "message": "Successfully authenticated"}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
"""Category endpoints."""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mft.auth import verify_token
|
||||
from mft.database import get_db
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
|
||||
68
mft/routes/expense.py
Normal file
68
mft/routes/expense.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from pydantic import BaseModel, Field
|
||||
from mft.auth import verify_token
|
||||
from mft.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ExpenseCreate(BaseModel):
|
||||
cid: int = Field(..., description="Category ID")
|
||||
value: int = Field(..., gt=0, description="Amount in cents")
|
||||
note: str | None = Field(None, description="Optional note")
|
||||
|
||||
|
||||
class Expense(BaseModel):
|
||||
id: int
|
||||
uid: int
|
||||
cid: int
|
||||
ts: str
|
||||
value: int
|
||||
note: str | None
|
||||
|
||||
|
||||
@router.post("/expenses", response_model=Expense, status_code=status.HTTP_201_CREATED)
|
||||
def create_expense(expense: ExpenseCreate, uid: int = Depends(verify_token)):
|
||||
"""
|
||||
Create a new expense entry.
|
||||
|
||||
Args:
|
||||
expense: Expense data (category ID, value in cents, optional note)
|
||||
uid: User ID from authentication token
|
||||
|
||||
Returns:
|
||||
The created expense with generated ID and timestamp
|
||||
|
||||
Raises:
|
||||
400: Invalid category ID or constraint violation
|
||||
"""
|
||||
timestamp = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO expense (uid, cid, ts, value, note)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(uid, expense.cid, timestamp, expense.value, expense.note),
|
||||
)
|
||||
conn.commit()
|
||||
expense_id = cursor.lastrowid
|
||||
|
||||
return Expense(
|
||||
id=expense_id,
|
||||
uid=uid,
|
||||
cid=expense.cid,
|
||||
ts=timestamp,
|
||||
value=expense.value,
|
||||
note=expense.note,
|
||||
)
|
||||
except sqlite3.IntegrityError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid request: constraint violation",
|
||||
) from e
|
||||
@@ -1,12 +1,8 @@
|
||||
"""Health check and status endpoints."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def status():
|
||||
"""Minimal status check to verify the API is up."""
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user