- C1: drop module-level app singleton in app/main.py so importing the package no longer validates Settings or creates DATA_DIR; run uvicorn with --factory in the Dockerfile. pytest now collects and passes with no ambient env vars. - I2: add missing app/auth, app/security, app/web __init__.py so setuptools discovers all five packages. - I3: resolve the Jinja2 template directory relative to __file__ instead of the process CWD. - I4: add .gitignore covering .env, data/, .venv/, caches and build artifacts so example deployment secrets cannot be committed. - I5: assert UserRepository.list_enabled() excludes disabled users. - M6: encode both operands before hmac.compare_digest in validate_csrf so a non-ASCII token yields 403 instead of an unhandled 500. - M9: remove unused relationship / HealthState imports. - M11: make session cookie https_only configurable via SESSION_HTTPS_ONLY (default unchanged: false). - M13: dispose SQLAlchemy engines in the db_session and client fixtures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
from datetime import datetime
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db.models import Activity, ActivityStatus, SyncUser
|
|
|
|
|
|
class UserRepository:
|
|
def __init__(self, session: Session) -> None:
|
|
self.session = session
|
|
|
|
def create(self, **values) -> SyncUser:
|
|
user = SyncUser(**values)
|
|
self.session.add(user)
|
|
self.session.commit()
|
|
return user
|
|
|
|
def get(self, user_id: int) -> SyncUser | None:
|
|
return self.session.get(SyncUser, user_id)
|
|
|
|
def list_enabled(self) -> list[SyncUser]:
|
|
return list(self.session.scalars(select(SyncUser).where(SyncUser.enabled.is_(True)).order_by(SyncUser.id)))
|
|
|
|
def list_all(self) -> list[SyncUser]:
|
|
return list(self.session.scalars(select(SyncUser).order_by(SyncUser.name)))
|
|
|
|
def update(self, user: SyncUser, **values) -> SyncUser:
|
|
for key, value in values.items():
|
|
setattr(user, key, value)
|
|
self.session.commit()
|
|
return user
|
|
|
|
|
|
class ActivityRepository:
|
|
def __init__(self, session: Session) -> None:
|
|
self.session = session
|
|
|
|
def get_or_create_discovered(
|
|
self,
|
|
*,
|
|
user_id: int,
|
|
mywhoosh_activity_id: str,
|
|
activity_name: str,
|
|
activity_timestamp: datetime | None,
|
|
) -> tuple[Activity, bool]:
|
|
existing = self.session.scalar(
|
|
select(Activity).where(
|
|
Activity.user_id == user_id,
|
|
Activity.mywhoosh_activity_id == mywhoosh_activity_id,
|
|
)
|
|
)
|
|
if existing is not None:
|
|
return existing, False
|
|
activity = Activity(
|
|
user_id=user_id,
|
|
mywhoosh_activity_id=mywhoosh_activity_id,
|
|
activity_name=activity_name,
|
|
activity_timestamp=activity_timestamp,
|
|
status=ActivityStatus.DISCOVERED,
|
|
last_completed_stage=ActivityStatus.DISCOVERED,
|
|
)
|
|
self.session.add(activity)
|
|
try:
|
|
self.session.commit()
|
|
except IntegrityError:
|
|
self.session.rollback()
|
|
existing = self.session.scalar(
|
|
select(Activity).where(
|
|
Activity.user_id == user_id,
|
|
Activity.mywhoosh_activity_id == mywhoosh_activity_id,
|
|
)
|
|
)
|
|
if existing is None:
|
|
raise
|
|
return existing, False
|
|
return activity, True
|