feat(api): finalize v2 cutover, add household members endpoint, consolidate DTOs, and enforce Argon2 hashing

This commit is contained in:
jableader 2025-11-01 17:14:18 +11:00
parent a973e5ce57
commit bd44a6acbe
4 changed files with 115 additions and 4 deletions

8
api/dtos.py Normal file
View file

@ -0,0 +1,8 @@
from __future__ import annotations
from common import ApiModel
class MemberRef(ApiModel):
id: int
display_name: str

View file

@ -88,6 +88,34 @@ async def whoami(household=Depends(get_household_from_slug)):
return WhoAmI(household_id=household["id"], household_slug=household["slug"])
# Members listing to unblock frontend
class HouseholdMember(ApiModel):
id: int
display_name: str
role: str
@scoped.get("/members", response_model=list[HouseholdMember])
async def list_members(
household=Depends(get_household_from_slug),
conn: aiosqlite.Connection = Depends(get_db),
):
members: list[HouseholdMember] = []
async with conn.execute(
"""
SELECT u.id, u.display_name, m.role
FROM HouseholdMember m
JOIN User u ON u.id = m.user_id
WHERE m.household_id = ?
ORDER BY lower(u.display_name), u.id
""",
(household["id"],),
) as c:
async for row in c:
members.append(HouseholdMember(id=int(row[0]), display_name=row[1], role=row[2]))
return members
# Invitations
class CreateInvitationBody(ApiModel):
email: str

View file

@ -11,16 +11,14 @@ import persons
import shopping
import ingredients
from common import ProblemDetails, ApiModel
from api.dtos import MemberRef
from api.deps import error_response
from api.deps import get_db, get_household_from_slug
router = APIRouter(prefix="/households/{householdSlug}/meals", tags=["meals"])
class MemberRef(ApiModel):
id: int
# Align outward schema to users/household members; use displayName
display_name: str
# MemberRef now imported from api.dtos
class MealRecipeIn(ApiModel):

View file

@ -308,6 +308,60 @@
]
}
},
"/api/v1/households/{householdSlug}/members": {
"get": {
"tags": [
"households"
],
"summary": "List Members",
"operationId": "list_members_api_v1_households__householdSlug__members_get",
"parameters": [
{
"name": "householdSlug",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Householdslug"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/HouseholdMember"
},
"title": "Response List Members Api V1 Households Householdslug Members Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
},
"403": {
"$ref": "#/components/responses/Problem403"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/api/v1/households/{householdSlug}/invitations": {
"post": {
"tags": [
@ -1500,6 +1554,29 @@
"type": "object",
"title": "HealthStatus"
},
"HouseholdMember": {
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"displayName": {
"type": "string",
"title": "Displayname"
},
"role": {
"type": "string",
"title": "Role"
}
},
"type": "object",
"required": [
"id",
"displayName",
"role"
],
"title": "HouseholdMember"
},
"HouseholdResponse": {
"properties": {
"id": {