Implement role-based account journeys with database and browser acceptance suites
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
parent
75750c0036
commit
1127f852dd
24 changed files with 1554 additions and 148 deletions
|
|
@ -8,6 +8,7 @@ pooling, securing, and observing those connections.
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from threading import RLock
|
||||
from contextlib import contextmanager
|
||||
from importlib.resources import files
|
||||
from typing import Any, Iterable, Iterator, Mapping, Protocol, cast
|
||||
|
|
@ -80,6 +81,32 @@ class PostgresUserEngineStore:
|
|||
|
||||
def __init__(self, connection: PostgresConnection) -> None:
|
||||
self.connection = connection
|
||||
self._lifecycle_lock = RLock()
|
||||
self._transaction_depth = 0
|
||||
self._transaction_failed = False
|
||||
|
||||
@contextmanager
|
||||
def tenant_lifecycle_guard(self, tenant: str) -> Iterator[None]:
|
||||
# Session locks survive the service's local commits and cover provider
|
||||
# side effects. Separate processes use the same tenant-scoped DB lock.
|
||||
with self._lifecycle_lock:
|
||||
key = "user-engine:tenant-lifecycle:" + tenant
|
||||
with self._cursor() as cursor:
|
||||
cursor.execute("SELECT pg_advisory_lock(hashtextextended(%s, 0))", (key,))
|
||||
try:
|
||||
yield
|
||||
except BaseException:
|
||||
# An aborted transaction cannot execute the unlock query.
|
||||
self.connection.rollback()
|
||||
raise
|
||||
finally:
|
||||
with self._cursor() as cursor:
|
||||
cursor.execute("SELECT pg_advisory_unlock(hashtextextended(%s, 0))", (key,))
|
||||
|
||||
def outbox_history(self) -> tuple[OutboxEvent, ...]:
|
||||
with self._cursor() as cursor:
|
||||
cursor.execute("SELECT payload FROM user_engine_outbox_events ORDER BY occurred_at, event_id")
|
||||
return tuple(cast(OutboxEvent, self._decode_payload_row("outbox_events", row)) for row in cursor.fetchall())
|
||||
|
||||
@property
|
||||
def schema_version(self) -> str | None:
|
||||
|
|
@ -97,16 +124,26 @@ class PostgresUserEngineStore:
|
|||
|
||||
@contextmanager
|
||||
def transaction(self) -> Iterator[None]:
|
||||
begin = getattr(self.connection, "begin", None)
|
||||
if callable(begin):
|
||||
begin()
|
||||
outer = self._transaction_depth == 0
|
||||
if outer:
|
||||
self._transaction_failed = False
|
||||
begin = getattr(self.connection, "begin", None)
|
||||
if callable(begin): begin()
|
||||
self._transaction_depth += 1
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
self.connection.rollback()
|
||||
except BaseException:
|
||||
self._transaction_failed = True
|
||||
if outer: self.connection.rollback()
|
||||
raise
|
||||
else:
|
||||
self.connection.commit()
|
||||
if outer:
|
||||
if self._transaction_failed:
|
||||
self.connection.rollback()
|
||||
raise RuntimeError("nested transaction failed")
|
||||
self.connection.commit()
|
||||
finally:
|
||||
self._transaction_depth -= 1
|
||||
|
||||
def save_user(self, user: User) -> None:
|
||||
self._upsert_record(user)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue