Harden the API and the data layer after a security and performance audit
Security - Every route now requires a login and refuses ids outside the caller's workspace with 404: plan, leave-wishes, day-overrides and vacation-days were fully anonymous; every other router trusted client-supplied workspace/child/parent ids. Scraper sessions are bound to their owner. - Invitations can no longer be created for a foreign workspace, and a redeemer who is already a member (or a full workspace) is refused. - Production refuses the default JWT secret; API docs are hidden in production; a failed migration aborts startup instead of serving. - Login and register no longer reveal whether an email exists; inactive accounts are rejected; /auth/refresh is rate limited. - The FK scraper writes debug artefacts only when explicitly enabled, logs no scraped values or page text, clears the BankID QR as soon as it is scanned and purges finished sessions after five minutes. - Real FK page captures are removed from the tree (history rewrite follows); the parser tests use a synthetic fixture. Performance and correctness - Plan apply/sync run inside one locked transaction with batched writes instead of one commit per row, so a failure mid-batch leaves nothing half-applied and concurrent syncs no longer corrupt each other. - SQLite runs in WAL mode with a busy timeout; 14 indexes cover the filters that were full scans; argon2 hashing runs off the event loop; responses are gzipped. - Frontend: a 401 now refreshes the token once and retries, so sessions no longer die silently after 15 minutes; every data hook discards stale responses; logout clears state even when the server call fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsXCCcDfmV4Sps4ZQSvNEY
This commit is contained in:
@@ -5,6 +5,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.child import Child
|
||||
from app.repositories.child_repository import ChildRepository
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
@@ -50,7 +51,7 @@ class SQLiteChildRepository(ChildRepository):
|
||||
item.created_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> Child | None:
|
||||
@@ -74,7 +75,7 @@ class SQLiteChildRepository(ChildRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"Child {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -83,7 +84,7 @@ class SQLiteChildRepository(ChildRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM child WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"Child {id} not found")
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.day_override import DayOverride
|
||||
from app.repositories.day_override_repository import DayOverrideRepository
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
@@ -52,7 +53,7 @@ class SQLiteDayOverrideRepository(DayOverrideRepository):
|
||||
item.updated_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> DayOverride | None:
|
||||
@@ -78,7 +79,7 @@ class SQLiteDayOverrideRepository(DayOverrideRepository):
|
||||
""",
|
||||
(int(item.is_working_day), item.note, item.updated_at.isoformat(), str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"DayOverride {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -87,7 +88,7 @@ class SQLiteDayOverrideRepository(DayOverrideRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM day_override WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"DayOverride {id} not found")
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.leave_wish import LeaveWish
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.leave_wish_repository import LeaveWishRepository
|
||||
@@ -89,7 +90,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
item.updated_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> LeaveWish | None:
|
||||
@@ -144,7 +145,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"LeaveWish {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -153,7 +154,7 @@ class SQLiteLeaveWishRepository(LeaveWishRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM leave_wish WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"LeaveWish {id} not found")
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.parental_leave import ParentalLeaveFetch, TierData
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.parental_leave_repository import ParentalLeaveRepository
|
||||
@@ -90,7 +91,7 @@ class SQLiteParentalLeaveRepository(ParentalLeaveRepository):
|
||||
item.updated_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> ParentalLeaveFetch | None:
|
||||
@@ -150,7 +151,7 @@ class SQLiteParentalLeaveRepository(ParentalLeaveRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"ParentalLeaveFetch {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -161,7 +162,7 @@ class SQLiteParentalLeaveRepository(ParentalLeaveRepository):
|
||||
cursor = await self._conn.execute(
|
||||
"DELETE FROM parental_leave_fetch WHERE id = ?", (str(id),)
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"ParentalLeaveFetch {id} not found")
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.personal_data import PersonalData
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.personal_data_repository import PersonalDataRepository
|
||||
@@ -55,7 +56,7 @@ class SQLitePersonalDataRepository(PersonalDataRepository):
|
||||
item.deleted_at.isoformat() if item.deleted_at else None,
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> PersonalData | None:
|
||||
@@ -83,7 +84,7 @@ class SQLitePersonalDataRepository(PersonalDataRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"PersonalData {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -98,13 +99,13 @@ class SQLitePersonalDataRepository(PersonalDataRepository):
|
||||
"UPDATE personal_data SET deleted_at = ? WHERE uuid = ? AND deleted_at IS NULL",
|
||||
(datetime.now().isoformat(), str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"PersonalData {id} not found")
|
||||
|
||||
async def hard_delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM personal_data WHERE uuid = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"PersonalData {id} not found")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.planned_leave import PlannedLeaveDay
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.planned_leave_repository import PlannedLeaveRepository
|
||||
@@ -56,7 +57,7 @@ class SQLitePlannedLeaveRepository(PlannedLeaveRepository):
|
||||
item.updated_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> PlannedLeaveDay | None:
|
||||
@@ -83,7 +84,7 @@ class SQLitePlannedLeaveRepository(PlannedLeaveRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"PlannedLeaveDay {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -92,7 +93,7 @@ class SQLitePlannedLeaveRepository(PlannedLeaveRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM planned_leave_day WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"PlannedLeaveDay {id} not found")
|
||||
|
||||
@@ -177,5 +178,73 @@ class SQLitePlannedLeaveRepository(PlannedLeaveRepository):
|
||||
cursor = await self._conn.execute(
|
||||
"DELETE FROM planned_leave_day WHERE wish_id = ?", (str(wish_id),)
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return cursor.rowcount
|
||||
|
||||
async def create_many(self, items: list[PlannedLeaveDay]) -> None:
|
||||
if not items:
|
||||
return
|
||||
now = datetime.now()
|
||||
rows = []
|
||||
for item in items:
|
||||
item.created_at = item.created_at or now
|
||||
item.updated_at = item.updated_at or now
|
||||
rows.append(
|
||||
(
|
||||
str(item.id),
|
||||
str(item.workspace_id),
|
||||
str(item.child_id),
|
||||
str(item.parent_personal_data_uuid),
|
||||
item.date.isoformat(),
|
||||
item.day_type,
|
||||
int(item.is_dubbeldag),
|
||||
str(item.wish_id) if item.wish_id is not None else None,
|
||||
int(item.is_pinned),
|
||||
item.created_at.isoformat(),
|
||||
item.updated_at.isoformat(),
|
||||
)
|
||||
)
|
||||
await self._conn.executemany(
|
||||
"""
|
||||
INSERT INTO planned_leave_day
|
||||
(id, workspace_id, child_id, parent_personal_data_uuid, date,
|
||||
day_type, is_dubbeldag, wish_id, is_pinned, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
await commit(self._conn)
|
||||
|
||||
async def delete_many(self, ids: list[UUID]) -> int:
|
||||
if not ids:
|
||||
return 0
|
||||
str_ids = [str(i) for i in ids]
|
||||
total = 0
|
||||
chunk_size = 500
|
||||
for start in range(0, len(str_ids), chunk_size):
|
||||
chunk = str_ids[start : start + chunk_size]
|
||||
placeholders = ",".join("?" for _ in chunk)
|
||||
cursor = await self._conn.execute(
|
||||
f"DELETE FROM planned_leave_day WHERE id IN ({placeholders})", chunk
|
||||
)
|
||||
total += cursor.rowcount
|
||||
await commit(self._conn)
|
||||
return total
|
||||
|
||||
async def update_metadata_many(self, pairs: list[tuple[UUID, UUID | None, bool]]) -> None:
|
||||
if not pairs:
|
||||
return
|
||||
now = datetime.now().isoformat()
|
||||
rows = [
|
||||
(str(wish_id) if wish_id is not None else None, int(is_pinned), now, str(id_))
|
||||
for id_, wish_id, is_pinned in pairs
|
||||
]
|
||||
await self._conn.executemany(
|
||||
"""
|
||||
UPDATE planned_leave_day
|
||||
SET wish_id = ?, is_pinned = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
await commit(self._conn)
|
||||
|
||||
@@ -11,6 +11,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.tip_dismissal import TipDismissal
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.tip_dismissal_repository import TipDismissalRepository
|
||||
@@ -46,7 +47,7 @@ class SQLiteTipDismissalRepository(TipDismissalRepository):
|
||||
item.dismissed_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> TipDismissal | None:
|
||||
@@ -68,7 +69,7 @@ class SQLiteTipDismissalRepository(TipDismissalRepository):
|
||||
"UPDATE tip_dismissal SET data_hash = ?, dismissed_at = ? WHERE id = ?",
|
||||
(item.data_hash, item.dismissed_at.isoformat(), str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"TipDismissal {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -77,7 +78,7 @@ class SQLiteTipDismissalRepository(TipDismissalRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM tip_dismissal WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"TipDismissal {id} not found")
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
"""SQLite implementation of UnitOfWork.
|
||||
|
||||
Delegates to `app.db.connection.unit_of_work`: a locked `BEGIN IMMEDIATE`
|
||||
transaction on the shared connection. See that function's docstring and the
|
||||
backend-perf audit §3 for the full design.
|
||||
"""
|
||||
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import unit_of_work
|
||||
from app.repositories.unit_of_work import UnitOfWork
|
||||
|
||||
|
||||
class SQLiteUnitOfWork(UnitOfWork):
|
||||
def __init__(self, connection: aiosqlite.Connection) -> None:
|
||||
self._conn = connection
|
||||
|
||||
def __call__(self) -> AbstractAsyncContextManager[None]:
|
||||
return unit_of_work(self._conn)
|
||||
@@ -5,6 +5,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.user import User
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.user_repository import UserRepository
|
||||
@@ -23,6 +24,7 @@ class SQLiteUserRepository(UserRepository):
|
||||
email=row["email"],
|
||||
hashed_password=row["hashed_password"],
|
||||
created_at=datetime.fromisoformat(row["created_at"]),
|
||||
is_active=bool(row["is_active"]),
|
||||
)
|
||||
|
||||
async def create(self, item: User) -> User:
|
||||
@@ -30,8 +32,9 @@ class SQLiteUserRepository(UserRepository):
|
||||
item.created_at = item.created_at or now
|
||||
await self._conn.execute(
|
||||
"""
|
||||
INSERT INTO "user" (id, personal_data_uuid, email, hashed_password, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO "user"
|
||||
(id, personal_data_uuid, email, hashed_password, created_at, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
str(item.id),
|
||||
@@ -39,9 +42,10 @@ class SQLiteUserRepository(UserRepository):
|
||||
item.email,
|
||||
item.hashed_password,
|
||||
item.created_at.isoformat(),
|
||||
int(item.is_active),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> User | None:
|
||||
@@ -56,10 +60,10 @@ class SQLiteUserRepository(UserRepository):
|
||||
|
||||
async def update(self, id: UUID, item: User) -> User:
|
||||
cursor = await self._conn.execute(
|
||||
'UPDATE "user" SET email = ?, hashed_password = ? WHERE id = ?',
|
||||
(item.email, item.hashed_password, str(id)),
|
||||
'UPDATE "user" SET email = ?, hashed_password = ?, is_active = ? WHERE id = ?',
|
||||
(item.email, item.hashed_password, int(item.is_active), str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"User {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -68,7 +72,7 @@ class SQLiteUserRepository(UserRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute('DELETE FROM "user" WHERE id = ?', (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"User {id} not found")
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.vacation_days import VacationDays
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
from app.repositories.vacation_days_repository import VacationDaysRepository
|
||||
@@ -57,7 +58,7 @@ class SQLiteVacationDaysRepository(VacationDaysRepository):
|
||||
item.updated_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> VacationDays | None:
|
||||
@@ -83,7 +84,7 @@ class SQLiteVacationDaysRepository(VacationDaysRepository):
|
||||
""",
|
||||
(item.total_days, item.saved_days_in, item.updated_at.isoformat(), str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"VacationDays {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -92,7 +93,7 @@ class SQLiteVacationDaysRepository(VacationDaysRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM vacation_days WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"VacationDays {id} not found")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from uuid import UUID
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from app.db.connection import commit
|
||||
from app.entities.workspace import Workspace
|
||||
from app.entities.workspace_invitation import WorkspaceInvitation
|
||||
from app.repositories.filter_validator import validate_filters
|
||||
@@ -42,7 +43,7 @@ class SQLiteWorkspaceRepository(WorkspaceRepository):
|
||||
item.created_at.isoformat(),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> Workspace | None:
|
||||
@@ -55,7 +56,7 @@ class SQLiteWorkspaceRepository(WorkspaceRepository):
|
||||
"UPDATE workspace SET name = ?, planning_mode = ? WHERE id = ?",
|
||||
(item.name, item.planning_mode, str(id)),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"Workspace {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -64,7 +65,7 @@ class SQLiteWorkspaceRepository(WorkspaceRepository):
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
cursor = await self._conn.execute("DELETE FROM workspace WHERE id = ?", (str(id),))
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"Workspace {id} not found")
|
||||
|
||||
@@ -111,7 +112,7 @@ class SQLiteWorkspaceInvitationRepository(WorkspaceInvitationRepository):
|
||||
item.accepted_at.isoformat() if item.accepted_at else None,
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
return item
|
||||
|
||||
async def get_by_id(self, id: UUID) -> WorkspaceInvitation | None:
|
||||
@@ -141,7 +142,7 @@ class SQLiteWorkspaceInvitationRepository(WorkspaceInvitationRepository):
|
||||
str(id),
|
||||
),
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"WorkspaceInvitation {id} not found")
|
||||
refreshed = await self.get_by_id(id)
|
||||
@@ -152,7 +153,7 @@ class SQLiteWorkspaceInvitationRepository(WorkspaceInvitationRepository):
|
||||
cursor = await self._conn.execute(
|
||||
"DELETE FROM workspace_invitation WHERE id = ?", (str(id),)
|
||||
)
|
||||
await self._conn.commit()
|
||||
await commit(self._conn)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"WorkspaceInvitation {id} not found")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user