Add expense creation API endpoint
This commit is contained in:
@@ -1,13 +1,9 @@
|
|||||||
"""API routes aggregation."""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from mft.routes import auth, categories, expense, health
|
||||||
from mft.routes import auth, categories, health
|
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
|
|
||||||
# Include all route modules
|
|
||||||
api_router.include_router(health.router, tags=["health"])
|
api_router.include_router(health.router, tags=["health"])
|
||||||
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||||
api_router.include_router(categories.router, tags=["categories"])
|
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 fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from mft.auth import verify_token
|
from mft.auth import verify_token
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@@ -19,8 +15,4 @@ async def validate(user_id: int = Depends(verify_token)):
|
|||||||
Returns:
|
Returns:
|
||||||
A simple status response with the authenticated user ID
|
A simple status response with the authenticated user ID
|
||||||
"""
|
"""
|
||||||
return {
|
return {"status": "ok", "user_id": user_id, "message": "Successfully authenticated"}
|
||||||
"status": "ok",
|
|
||||||
"user_id": user_id,
|
|
||||||
"message": "Successfully authenticated"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
"""Category endpoints."""
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from mft.auth import verify_token
|
from mft.auth import verify_token
|
||||||
from mft.database import get_db
|
from mft.database import get_db
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
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
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/status")
|
@router.get("/status")
|
||||||
async def status():
|
async def status():
|
||||||
"""Minimal status check to verify the API is up."""
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|||||||
Reference in New Issue
Block a user