Refactor household models and repository queries to use dynamic column selection; remove email from invitation schema in OpenAPI

This commit is contained in:
jableader 2025-12-13 14:52:52 +11:00
parent 3e4e4fc2ae
commit e8f44c839a
3 changed files with 15 additions and 39 deletions

View file

@ -6,15 +6,13 @@ from common import ApiModel
class Household(ApiModel): class Household(ApiModel):
KEYS: ClassVar[list[str]] = ["id", "name", "slug"] KEYS: ClassVar[list[str]] = ["id", "name", "slug"]
id: int id: int
name: str name: str
slug: str slug: str
class HouseholdMember(ApiModel): class HouseholdMember(ApiModel):
KEYS: ClassVar[list[str]] = ["user_id", "household_id", "role"] KEYS: ClassVar[list[str]] = ["id", "display_name", "role"]
id: int id: int
display_name: str display_name: str
role: str role: str
@ -24,16 +22,13 @@ class HouseholdInvitation(ApiModel):
KEYS: ClassVar[list[str]] = [ KEYS: ClassVar[list[str]] = [
"id", "id",
"household_id", "household_id",
"email",
"invited_by_user_id", "invited_by_user_id",
"token", "token",
"expires_at", "expires_at",
"status", "status",
] ]
id: int id: int
household_id: int household_id: int
email: str
invited_by_user_id: int invited_by_user_id: int
token: str token: str
expires_at: str expires_at: str

View file

@ -83,9 +83,10 @@ async def are_members(conn, household_id: int, user_ids: list[int]) -> bool:
async def list_for_user(conn, user_id: int) -> List[Household]: async def list_for_user(conn, user_id: int) -> List[Household]:
results: List[Household] = [] results: List[Household] = []
cols = ", ".join([f"h.{k}" for k in Household.KEYS])
async with conn.execute( async with conn.execute(
""" f"""
SELECT h.id, h.name, h.slug FROM Household h SELECT {cols} FROM Household h
JOIN HouseholdMember m ON m.household_id = h.id JOIN HouseholdMember m ON m.household_id = h.id
WHERE m.user_id = ? WHERE m.user_id = ?
ORDER BY h.id ORDER BY h.id
@ -93,7 +94,7 @@ async def list_for_user(conn, user_id: int) -> List[Household]:
(user_id,), (user_id,),
) as c: ) as c:
async for row in c: async for row in c:
results.append(Household(id=int(row[0]), name=row[1], slug=row[2])) results.append(Household(**{k: v for k, v in zip(Household.KEYS, row)}))
return results return results
@ -117,9 +118,10 @@ async def create_for_user(conn, name: str, slug: str, user_id: int) -> Household
async def list_members(conn, household_id: int) -> List[HouseholdMember]: async def list_members(conn, household_id: int) -> List[HouseholdMember]:
members: List[HouseholdMember] = [] members: List[HouseholdMember] = []
cols = ", ".join([f"u.{k}" for k in ["id", "display_name"]] + ["m.role"])
async with conn.execute( async with conn.execute(
""" f"""
SELECT u.id, u.display_name, m.role SELECT {cols}
FROM HouseholdMember m FROM HouseholdMember m
JOIN User u ON u.id = m.user_id JOIN User u ON u.id = m.user_id
WHERE m.household_id = ? WHERE m.household_id = ?
@ -129,7 +131,7 @@ async def list_members(conn, household_id: int) -> List[HouseholdMember]:
) as c: ) as c:
async for row in c: async for row in c:
members.append( members.append(
HouseholdMember(id=int(row[0]), display_name=row[1], role=row[2]) HouseholdMember(**{k: v for k, v in zip(HouseholdMember.KEYS, row)})
) )
return members return members
@ -151,14 +153,15 @@ async def create_invitation(
async def get_invitation_by_token(conn, token: str) -> HouseholdInvitation | None: async def get_invitation_by_token(conn, token: str) -> HouseholdInvitation | None:
cols = ", ".join(HouseholdInvitation.KEYS)
async with conn.execute( async with conn.execute(
"SELECT id, household_id, status FROM HouseholdInvitation WHERE token = ?", f"SELECT {cols} FROM HouseholdInvitation WHERE token = ?",
(token,), (token,),
) as c: ) as c:
row = await c.fetchone() row = await c.fetchone()
if not row: if not row:
return None return None
return HouseholdInvitation(id=int(row[0]), household_id=int(row[1]), status=row[2]) return HouseholdInvitation(**{k: v for k, v in zip(HouseholdInvitation.KEYS, row)})
async def accept_invitation(conn, invitation_id: int, user_id: int, household_id: int): async def accept_invitation(conn, invitation_id: int, user_id: int, household_id: int):
@ -173,11 +176,12 @@ async def accept_invitation(conn, invitation_id: int, user_id: int, household_id
async def get_household_by_id(conn, household_id: int) -> Household | None: async def get_household_by_id(conn, household_id: int) -> Household | None:
cols = ", ".join(Household.KEYS)
async with conn.execute( async with conn.execute(
"SELECT id, name, slug FROM Household WHERE id = ?", f"SELECT {cols} FROM Household WHERE id = ?",
(household_id,), (household_id,),
) as c: ) as c:
row = await c.fetchone() row = await c.fetchone()
if not row: if not row:
return None return None
return Household(id=int(row[0]), name=row[1], slug=row[2]) return Household(**{k: v for k, v in zip(Household.KEYS, row)})

View file

@ -381,16 +381,6 @@
} }
} }
], ],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateInvitationBody"
}
}
}
},
"responses": { "responses": {
"200": { "200": {
"description": "Successful Response", "description": "Successful Response",
@ -1764,19 +1754,6 @@
], ],
"title": "CreateHouseholdBody" "title": "CreateHouseholdBody"
}, },
"CreateInvitationBody": {
"properties": {
"email": {
"type": "string",
"title": "Email"
}
},
"type": "object",
"required": [
"email"
],
"title": "CreateInvitationBody"
},
"CurrentShoppingList": { "CurrentShoppingList": {
"properties": { "properties": {
"outstandingItems": { "outstandingItems": {