async def create(conn): # Households table await conn.execute( """ CREATE TABLE IF NOT EXISTS Household ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE ); """ ) # Membership table await conn.execute( """ CREATE TABLE IF NOT EXISTS HouseholdMember ( user_id INTEGER NOT NULL, household_id INTEGER NOT NULL, role TEXT NOT NULL, PRIMARY KEY (user_id, household_id), FOREIGN KEY(user_id) REFERENCES User(id) ON DELETE CASCADE, FOREIGN KEY(household_id) REFERENCES Household(id) ON DELETE CASCADE ); """ ) # Invitations table await conn.execute( """ CREATE TABLE IF NOT EXISTS HouseholdInvitation ( id INTEGER PRIMARY KEY, household_id INTEGER NOT NULL, email TEXT NOT NULL, invited_by_user_id INTEGER NOT NULL, token TEXT NOT NULL UNIQUE, expires_at DATETIME NOT NULL, status TEXT NOT NULL, FOREIGN KEY(household_id) REFERENCES Household(id) ON DELETE CASCADE, FOREIGN KEY(invited_by_user_id) REFERENCES User(id) ON DELETE SET NULL ); """ ) # Indices await conn.execute("CREATE INDEX IF NOT EXISTS idx_household_slug ON Household(slug);") await conn.execute( "CREATE INDEX IF NOT EXISTS idx_household_member_household ON HouseholdMember(household_id);" ) async def member_ids_in_household(conn, household_id: int, user_ids: list[int]) -> set[int]: """Return the subset of user_ids that are members of the given household. Uses HouseholdMember join User to ensure users exist. """ if not user_ids: return set() placeholders = ",".join(["?"] * len(user_ids)) query = f""" SELECT u.id FROM HouseholdMember hm JOIN User u ON u.id = hm.user_id WHERE hm.household_id = ? AND u.id IN ({placeholders}) """ valid: set[int] = set() async with conn.execute(query, (household_id, *sorted(user_ids))) as c: async for row in c: valid.add(int(row[0])) return valid async def are_members(conn, household_id: int, user_ids: list[int]) -> bool: """True only if all provided user_ids are members of the household.""" if not user_ids: return True found = await member_ids_in_household(conn, household_id, user_ids) return found == set(user_ids)