diff --git a/mft/routes/__init__.py b/mft/routes/__init__.py index b558fd5..518316d 100644 --- a/mft/routes/__init__.py +++ b/mft/routes/__init__.py @@ -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"]) diff --git a/mft/routes/auth.py b/mft/routes/auth.py index 1a9b5dd..1a0961b 100644 --- a/mft/routes/auth.py +++ b/mft/routes/auth.py @@ -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"} diff --git a/mft/routes/categories.py b/mft/routes/categories.py index 9e46813..0f5adc1 100644 --- a/mft/routes/categories.py +++ b/mft/routes/categories.py @@ -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() diff --git a/mft/routes/expense.py b/mft/routes/expense.py new file mode 100644 index 0000000..c3ebf05 --- /dev/null +++ b/mft/routes/expense.py @@ -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 diff --git a/mft/routes/health.py b/mft/routes/health.py index 8349c6a..f8187bd 100644 --- a/mft/routes/health.py +++ b/mft/routes/health.py @@ -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"}