121 lines
5.6 KiB
Python
121 lines
5.6 KiB
Python
from __future__ import annotations
|
|
import json
|
|
from sqlalchemy import select
|
|
from .db import SessionLocal
|
|
from .orm import UserORM, RefreshTokenORM, PackORM, LearnerORM, MasteryRecordORM, EvidenceEventORM
|
|
from .models import PackData, LearnerState, MasteryRecord, EvidenceEvent
|
|
from .auth import verify_password
|
|
|
|
def get_user_by_username(username: str):
|
|
with SessionLocal() as db:
|
|
return db.execute(select(UserORM).where(UserORM.username == username)).scalar_one_or_none()
|
|
|
|
def get_user_by_id(user_id: int):
|
|
with SessionLocal() as db:
|
|
return db.get(UserORM, user_id)
|
|
|
|
def authenticate_user(username: str, password: str):
|
|
user = get_user_by_username(username)
|
|
if user is None or not verify_password(password, user.password_hash) or not user.is_active:
|
|
return None
|
|
return user
|
|
|
|
def store_refresh_token(user_id: int, token_id: str):
|
|
with SessionLocal() as db:
|
|
db.add(RefreshTokenORM(user_id=user_id, token_id=token_id, is_revoked=False))
|
|
db.commit()
|
|
|
|
def refresh_token_active(token_id: str) -> bool:
|
|
with SessionLocal() as db:
|
|
row = db.execute(select(RefreshTokenORM).where(RefreshTokenORM.token_id == token_id)).scalar_one_or_none()
|
|
return row is not None and not row.is_revoked
|
|
|
|
def revoke_refresh_token(token_id: str):
|
|
with SessionLocal() as db:
|
|
row = db.execute(select(RefreshTokenORM).where(RefreshTokenORM.token_id == token_id)).scalar_one_or_none()
|
|
if row:
|
|
row.is_revoked = True
|
|
db.commit()
|
|
|
|
def list_packs_for_user(user_id: int | None = None, include_unpublished: bool = False):
|
|
with SessionLocal() as db:
|
|
stmt = select(PackORM)
|
|
if not include_unpublished:
|
|
stmt = stmt.where(PackORM.is_published == True)
|
|
rows = db.execute(stmt).scalars().all()
|
|
out = []
|
|
for r in rows:
|
|
if r.policy_lane == "community":
|
|
out.append(PackData.model_validate(json.loads(r.data_json)))
|
|
elif user_id is not None and r.owner_user_id == user_id:
|
|
out.append(PackData.model_validate(json.loads(r.data_json)))
|
|
return out
|
|
|
|
def get_pack(pack_id: str):
|
|
with SessionLocal() as db:
|
|
row = db.get(PackORM, pack_id)
|
|
return None if row is None else PackData.model_validate(json.loads(row.data_json))
|
|
|
|
def get_pack_row(pack_id: str):
|
|
with SessionLocal() as db:
|
|
return db.get(PackORM, pack_id)
|
|
|
|
def upsert_pack(pack: PackData, submitted_by_user_id: int, policy_lane: str = "personal", is_published: bool = False):
|
|
with SessionLocal() as db:
|
|
row = db.get(PackORM, pack.id)
|
|
payload = json.dumps(pack.model_dump())
|
|
if row is None:
|
|
row = PackORM(
|
|
id=pack.id,
|
|
owner_user_id=submitted_by_user_id if policy_lane == "personal" else None,
|
|
policy_lane=policy_lane,
|
|
title=pack.title,
|
|
subtitle=pack.subtitle,
|
|
level=pack.level,
|
|
data_json=payload,
|
|
is_published=is_published if policy_lane == "personal" else False,
|
|
)
|
|
db.add(row)
|
|
else:
|
|
row.owner_user_id = submitted_by_user_id if policy_lane == "personal" else row.owner_user_id
|
|
row.policy_lane = policy_lane
|
|
row.title = pack.title
|
|
row.subtitle = pack.subtitle
|
|
row.level = pack.level
|
|
row.data_json = payload
|
|
if policy_lane == "personal":
|
|
row.is_published = is_published
|
|
db.commit()
|
|
|
|
def create_learner(owner_user_id: int, learner_id: str, display_name: str = ""):
|
|
with SessionLocal() as db:
|
|
if db.get(LearnerORM, learner_id) is None:
|
|
db.add(LearnerORM(id=learner_id, owner_user_id=owner_user_id, display_name=display_name))
|
|
db.commit()
|
|
|
|
def learner_owned_by_user(user_id: int, learner_id: str) -> bool:
|
|
with SessionLocal() as db:
|
|
learner = db.get(LearnerORM, learner_id)
|
|
return learner is not None and learner.owner_user_id == user_id
|
|
|
|
def load_learner_state(learner_id: str):
|
|
with SessionLocal() as db:
|
|
records = db.execute(select(MasteryRecordORM).where(MasteryRecordORM.learner_id == learner_id)).scalars().all()
|
|
history = db.execute(select(EvidenceEventORM).where(EvidenceEventORM.learner_id == learner_id)).scalars().all()
|
|
return LearnerState(
|
|
learner_id=learner_id,
|
|
records=[MasteryRecord(concept_id=r.concept_id, dimension=r.dimension, score=r.score, confidence=r.confidence, evidence_count=r.evidence_count, last_updated=r.last_updated) for r in records],
|
|
history=[EvidenceEvent(concept_id=h.concept_id, dimension=h.dimension, score=h.score, confidence_hint=h.confidence_hint, timestamp=h.timestamp, kind=h.kind, source_id=h.source_id) for h in history],
|
|
)
|
|
|
|
def save_learner_state(state: LearnerState):
|
|
with SessionLocal() as db:
|
|
db.query(MasteryRecordORM).filter(MasteryRecordORM.learner_id == state.learner_id).delete()
|
|
db.query(EvidenceEventORM).filter(EvidenceEventORM.learner_id == state.learner_id).delete()
|
|
for r in state.records:
|
|
db.add(MasteryRecordORM(learner_id=state.learner_id, concept_id=r.concept_id, dimension=r.dimension, score=r.score, confidence=r.confidence, evidence_count=r.evidence_count, last_updated=r.last_updated))
|
|
for h in state.history:
|
|
db.add(EvidenceEventORM(learner_id=state.learner_id, concept_id=h.concept_id, dimension=h.dimension, score=h.score, confidence_hint=h.confidence_hint, timestamp=h.timestamp, kind=h.kind, source_id=h.source_id))
|
|
db.commit()
|
|
return state
|