- [x] Wire routers in main with minimal app code
- [x] Create api/deps module for get_db, cookie_person, and error_response - [x] Use settings.py (DOOF_DB) for DB path in main and deps
This commit is contained in:
parent
53b07343d1
commit
f51c90f922
7 changed files with 57 additions and 38 deletions
|
|
@ -7,7 +7,7 @@ from fastapi.encoders import jsonable_encoder
|
||||||
|
|
||||||
import persons
|
import persons
|
||||||
from common import ProblemDetails, ApiModel
|
from common import ProblemDetails, ApiModel
|
||||||
from main import get_db, cookie_person, error_response
|
from api.deps import get_db, cookie_person, error_response
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||||
|
|
||||||
|
|
|
||||||
41
api/deps.py
Normal file
41
api/deps.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Annotated, Optional, AsyncGenerator
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
from fastapi import Cookie, Depends, Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
import db
|
||||||
|
import persons
|
||||||
|
from common import ProblemDetails
|
||||||
|
from settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
# Dependency to create SQLite connection
|
||||||
|
async def get_db() -> AsyncGenerator[aiosqlite.Connection, None]:
|
||||||
|
sql_db = await db.connect(settings.database_path)
|
||||||
|
try:
|
||||||
|
yield sql_db
|
||||||
|
finally:
|
||||||
|
await sql_db.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def cookie_person(
|
||||||
|
user_id: Annotated[int, Cookie(alias="user_id")], conn: aiosqlite.Connection = Depends(get_db)
|
||||||
|
) -> Optional[persons.Person]:
|
||||||
|
return await persons.get_by_id(conn, user_id)
|
||||||
|
|
||||||
|
|
||||||
|
def error_response(request: Optional[Request], status_code: int, message: str) -> JSONResponse:
|
||||||
|
body = ProblemDetails(
|
||||||
|
title=message,
|
||||||
|
status=status_code,
|
||||||
|
type=f"https://httpstatuses.com/{status_code}",
|
||||||
|
instance=str(request.url) if request else None,
|
||||||
|
)
|
||||||
|
return JSONResponse(
|
||||||
|
content=body.model_dump(by_alias=True),
|
||||||
|
status_code=status_code,
|
||||||
|
media_type="application/problem+json",
|
||||||
|
)
|
||||||
|
|
@ -10,7 +10,7 @@ import meals
|
||||||
import persons
|
import persons
|
||||||
import shopping
|
import shopping
|
||||||
from common import ProblemDetails
|
from common import ProblemDetails
|
||||||
from main import get_db, cookie_person, error_response # temporary imports during extraction
|
from api.deps import get_db, cookie_person, error_response
|
||||||
from common import ApiModel, Field
|
from common import ApiModel, Field
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import recipes
|
||||||
import persons
|
import persons
|
||||||
from common import Page, ProblemDetails, ApiModel
|
from common import Page, ProblemDetails, ApiModel
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from main import get_db, cookie_person, error_response
|
from api.deps import get_db, cookie_person, error_response
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/recipes", tags=["recipes"])
|
router = APIRouter(prefix="/recipes", tags=["recipes"])
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import persons
|
||||||
import recipes
|
import recipes
|
||||||
import shopping
|
import shopping
|
||||||
from common import ProblemDetails
|
from common import ProblemDetails
|
||||||
from main import get_db, cookie_person, error_response # temporary during extraction
|
from api.deps import get_db, cookie_person, error_response
|
||||||
|
|
||||||
router = APIRouter(prefix="/shopping", tags=["shopping"])
|
router = APIRouter(prefix="/shopping", tags=["shopping"])
|
||||||
|
|
||||||
|
|
|
||||||
32
main.py
32
main.py
|
|
@ -18,6 +18,8 @@ import shopping
|
||||||
|
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from common import ProblemDetails, Page, ApiModel
|
from common import ProblemDetails, Page, ApiModel
|
||||||
|
from settings import settings
|
||||||
|
from api.deps import get_db, cookie_person, error_response
|
||||||
|
|
||||||
|
|
||||||
class CamelCaseRoute(APIRoute):
|
class CamelCaseRoute(APIRoute):
|
||||||
|
|
@ -29,35 +31,9 @@ class CamelCaseRoute(APIRoute):
|
||||||
|
|
||||||
app = FastAPI(title="Doof API", version="1.0.0", description="Doof Backend API")
|
app = FastAPI(title="Doof API", version="1.0.0", description="Doof Backend API")
|
||||||
api_v1 = APIRouter(route_class=CamelCaseRoute)
|
api_v1 = APIRouter(route_class=CamelCaseRoute)
|
||||||
DATABASE_PATH = os.environ.get("DOOF_DB", "./data/doof.sqlite")
|
DATABASE_PATH = settings.database_path
|
||||||
|
|
||||||
# Dependency to create SQLite connection
|
# get_db, cookie_person, and error_response are imported from api.deps
|
||||||
async def get_db():
|
|
||||||
sql_db = await db.connect(DATABASE_PATH)
|
|
||||||
try:
|
|
||||||
yield sql_db
|
|
||||||
finally:
|
|
||||||
await sql_db.close()
|
|
||||||
|
|
||||||
|
|
||||||
async def cookie_person(
|
|
||||||
user_id: Annotated[int, Cookie(alias="user_id")], conn: aiosqlite.Connection = Depends(get_db)
|
|
||||||
) -> Optional[persons.Person]:
|
|
||||||
return await persons.get_by_id(conn, user_id)
|
|
||||||
|
|
||||||
|
|
||||||
def error_response(request: Optional[Request], status_code: int, message: str) -> JSONResponse:
|
|
||||||
body = ProblemDetails(
|
|
||||||
title=message,
|
|
||||||
status=status_code,
|
|
||||||
type=f"https://httpstatuses.com/{status_code}",
|
|
||||||
instance=str(request.url) if request else None,
|
|
||||||
)
|
|
||||||
return JSONResponse(
|
|
||||||
content=body.model_dump(by_alias=True),
|
|
||||||
status_code=status_code,
|
|
||||||
media_type="application/problem+json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# OpenAPI reusable responses for ProblemDetails
|
# OpenAPI reusable responses for ProblemDetails
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,15 @@ Acceptance criteria
|
||||||
## Phase 1 — API structure and lifecycle
|
## Phase 1 — API structure and lifecycle
|
||||||
- [ ] Extract routers by feature
|
- [ ] Extract routers by feature
|
||||||
- [x] api/recipes.py
|
- [x] api/recipes.py
|
||||||
- [ ] api/meals.py
|
|
||||||
- [x] api/meals.py
|
- [x] api/meals.py
|
||||||
- [x] api/persons.py
|
- [x] api/persons.py
|
||||||
- [x] api/shopping.py
|
- [x] api/shopping.py
|
||||||
- [x] api/auth.py
|
- [x] api/auth.py
|
||||||
- [ ] Wire routers in main with minimal app code
|
- [x] Wire routers in main with minimal app code
|
||||||
- [ ] Move dev reverse proxy setup into a lifespan handler and ensure httpx client is closed
|
- [ ] Move dev reverse proxy setup into a lifespan handler and ensure httpx client is closed
|
||||||
- [x] Add /healthz endpoint (simple JSON: {"status": "ok"})
|
- [x] Add /healthz endpoint (simple JSON: {"status": "ok"})
|
||||||
|
- [x] Create api/deps module for get_db, cookie_person, and error_response
|
||||||
|
- [x] Use settings.py (DOOF_DB) for DB path in main and deps
|
||||||
|
|
||||||
Acceptance criteria
|
Acceptance criteria
|
||||||
- main.py primarily wires app, routers, settings, and lifespan
|
- main.py primarily wires app, routers, settings, and lifespan
|
||||||
|
|
@ -146,14 +147,15 @@ Note: We can adopt this structure gradually without moving DB code immediately;
|
||||||
- 2025-10-18: Created api package and scaffolded routers (recipes, meals, persons, shopping, auth) with placeholders
|
- 2025-10-18: Created api package and scaffolded routers (recipes, meals, persons, shopping, auth) with placeholders
|
||||||
- 2025-10-18: Extracted recipes routes to api/recipes.py and wired router; added /healthz
|
- 2025-10-18: Extracted recipes routes to api/recipes.py and wired router; added /healthz
|
||||||
- 2025-10-18: Extracted persons routes to api/persons.py and wired router
|
- 2025-10-18: Extracted persons routes to api/persons.py and wired router
|
||||||
|
- 2025-10-18: Extracted meals, shopping, and auth routes; created api/deps and switched DB path to settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next actions
|
## Next actions
|
||||||
Begin Phase 1 work in small steps:
|
- Phase 1: Move dev reverse proxy into a lifespan handler (startup/shutdown) and close AsyncClient cleanly
|
||||||
- Create api package and extract the first router (e.g., recipes) without logic changes
|
- Phase 2: Add OpenAPI cookie security scheme and normalize 201 Created + Location
|
||||||
- Wire the router in main.py and run tests
|
- Phase 3: Plan DB PRAGMAs and indexes; add transaction scoping per request
|
||||||
- Prepare lifespan handler for dev reverse proxy, but keep behavior identical
|
- Phase 5: Add fixtures for DB/auth and tests for health + 201 Location
|
||||||
|
|
||||||
### Health endpoint plan (Phase 1 target)
|
### Health endpoint plan (Phase 1 target)
|
||||||
- Path: GET /healthz
|
- Path: GET /healthz
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue