From bd44a6acbe70edd944595db1b216fea837838aba Mon Sep 17 00:00:00 2001 From: jableader Date: Sat, 1 Nov 2025 17:14:18 +1100 Subject: [PATCH] feat(api): finalize v2 cutover, add household members endpoint, consolidate DTOs, and enforce Argon2 hashing --- api/dtos.py | 8 +++++ api/households.py | 28 +++++++++++++++++ api/meals_v2.py | 6 ++-- openapi.json | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 api/dtos.py diff --git a/api/dtos.py b/api/dtos.py new file mode 100644 index 0000000..28f75b0 --- /dev/null +++ b/api/dtos.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from common import ApiModel + + +class MemberRef(ApiModel): + id: int + display_name: str diff --git a/api/households.py b/api/households.py index 55160e8..4414830 100644 --- a/api/households.py +++ b/api/households.py @@ -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 diff --git a/api/meals_v2.py b/api/meals_v2.py index 796a3e8..74edfb8 100644 --- a/api/meals_v2.py +++ b/api/meals_v2.py @@ -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): diff --git a/openapi.json b/openapi.json index 121a3a1..e3dbfb4 100644 --- a/openapi.json +++ b/openapi.json @@ -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": {