런타임 계약과 학습자 흐름 보강

This commit is contained in:
Yun Chan 2026-06-29 08:12:14 +09:00
parent f456b8997a
commit 206018b088
56 changed files with 4306 additions and 1008 deletions

View file

@ -124,6 +124,20 @@ class ManagedUserMemoryInput:
)
@dataclass(slots=True)
class ManagedUserUpsertInput:
email: str
display_name: str
role: RoleName
admin_access: bool | None = None
cohort_ids: list[str] | None = None
user_id: str | None = None
external_id: str | None = None
affiliation: str | None = None
account_status: AccountStatus | None = None
reactivate: bool = False
@dataclass(slots=True)
class ManagedUserPatch:
display_name: str | None = None
@ -1123,31 +1137,19 @@ def _memory_upsert_managed_user(data: ManagedUserMemoryInput) -> ManagedUser:
return user
async def upsert_managed_user(
*,
email: str,
display_name: str,
role: RoleName,
admin_access: bool | None = None,
cohort_ids: list[str] | None = None,
user_id: str | None = None,
external_id: str | None = None,
affiliation: str | None = None,
account_status: AccountStatus | None = None,
reactivate: bool = False,
) -> ManagedUser:
normalized_email = _normalize_email(email)
normalized_external_id = _normalize_external_id(external_id, normalized_email)
async def upsert_managed_user(data: ManagedUserUpsertInput) -> ManagedUser:
normalized_email = _normalize_email(data.email)
normalized_external_id = _normalize_external_id(data.external_id, normalized_email)
manual_external_id = f"email:{normalized_email}"
desired_account_status = account_status or _initial_account_status(
desired_account_status = data.account_status or _initial_account_status(
email=normalized_email,
external_id=normalized_external_id,
reactivate=reactivate,
reactivate=data.reactivate,
)
try:
pool = get_pool()
async with pool.acquire() as conn:
if user_id is None and normalized_external_id != manual_external_id:
if data.user_id is None and normalized_external_id != manual_external_id:
row = await conn.fetchrow(
"""
UPDATE app.app_user SET
@ -1198,9 +1200,9 @@ async def upsert_managed_user(
""",
normalized_external_id,
normalized_email,
(display_name.strip() if display_name else normalized_email),
_cohort_value(cohort_ids),
affiliation or DEFAULT_AFFILIATION,
(data.display_name.strip() if data.display_name else normalized_email),
_cohort_value(data.cohort_ids),
data.affiliation or DEFAULT_AFFILIATION,
manual_external_id,
)
if row is not None:
@ -1270,13 +1272,13 @@ async def upsert_managed_user(
""",
normalized_external_id,
normalized_email,
(display_name.strip() if display_name else normalized_email),
_db_role(role),
_cohort_value(cohort_ids),
affiliation or DEFAULT_AFFILIATION,
reactivate,
(data.display_name.strip() if data.display_name else normalized_email),
_db_role(data.role),
_cohort_value(data.cohort_ids),
data.affiliation or DEFAULT_AFFILIATION,
data.reactivate,
desired_account_status,
admin_access,
data.admin_access,
)
if row is None:
_inactive_emails.add(normalized_email)
@ -1288,8 +1290,10 @@ async def upsert_managed_user(
raise
except Exception:
require_runtime_fallback_allowed("managed user")
current = _users.get(user_id or "") or _users.get(_email_index.get(normalized_email, ""))
fallback_uid = user_id or (current.user_id if current is not None else user_id_from_external_id(normalized_external_id))
current = _users.get(data.user_id or "") or _users.get(_email_index.get(normalized_email, ""))
fallback_uid = data.user_id or (
current.user_id if current is not None else user_id_from_external_id(normalized_external_id)
)
fallback_account_status = desired_account_status
if current is not None:
if current.account_status == "suspended":
@ -1299,14 +1303,14 @@ async def upsert_managed_user(
return _memory_upsert_managed_user(
ManagedUserMemoryInput(
email=normalized_email,
display_name=display_name,
role=role,
admin_access=admin_access,
display_name=data.display_name,
role=data.role,
admin_access=data.admin_access,
account_status=fallback_account_status,
cohort_ids=cohort_ids,
cohort_ids=data.cohort_ids,
user_id=fallback_uid,
affiliation=affiliation,
reactivate=reactivate,
affiliation=data.affiliation,
reactivate=data.reactivate,
)
)
@ -1799,13 +1803,15 @@ async def create_session(
raw_sid = secrets.token_urlsafe(32)
normalized_email = _normalize_email(email)
managed = await upsert_managed_user(
email=normalized_email,
display_name=display_name,
role=role,
cohort_ids=cohort_ids,
user_id=user_id,
external_id=external_id,
reactivate=False,
ManagedUserUpsertInput(
email=normalized_email,
display_name=display_name,
role=role,
cohort_ids=cohort_ids,
user_id=user_id,
external_id=external_id,
reactivate=False,
)
)
expires_at = time.time() + settings.session_ttl_seconds
user = SessionUser(