Refactor duty authentication and event type handling

- Introduced a new function `get_authenticated_username` to centralize Mini App authentication logic, improving code readability and maintainability.
- Updated the duty fetching logic to map unknown event types to "duty" for consistent API responses.
- Enhanced the `get_duties` function to include duties starting on the last day of the specified date range.
- Improved session management in the database layer to ensure rollback on exceptions.
- Added tests to validate the new authentication flow and event type handling.
This commit is contained in:
2026-02-18 09:24:51 +03:00
parent 50347038e9
commit 8697b9e30b
8 changed files with 94 additions and 56 deletions

View File

@@ -85,14 +85,15 @@ def get_duties(
) -> list[tuple[Duty, str]]:
"""Return list of (Duty, full_name) overlapping the given date range.
from_date/to_date are YYYY-MM-DD (first/last day of month in client's local calendar).
Duty.start_at and end_at are stored in UTC (ISO 8601 with Z); lexicographic comparison
with date strings yields correct overlap.
from_date/to_date are YYYY-MM-DD (inclusive). Duty.start_at and end_at are stored
in UTC (ISO 8601 with Z). Use to_date_next so duties starting on to_date are included
(start_at like 2025-01-31T09:00:00Z is > "2025-01-31" lexicographically).
"""
to_date_next = (datetime.fromisoformat(to_date + "T00:00:00") + timedelta(days=1)).strftime("%Y-%m-%d")
q = (
session.query(Duty, User.full_name)
.join(User, Duty.user_id == User.id)
.filter(Duty.start_at <= to_date, Duty.end_at >= from_date)
.filter(Duty.start_at < to_date_next, Duty.end_at >= from_date)
)
return list(q.all())

View File

@@ -40,7 +40,10 @@ class DutyInDb(DutyBase):
class DutyWithUser(DutyInDb):
"""Duty with full_name and event_type for calendar display."""
"""Duty with full_name and event_type for calendar display.
event_type: only these values are returned; unknown DB values are mapped to "duty" in the API.
"""
full_name: str
event_type: Literal["duty", "unavailable", "vacation"] = "duty"

View File

@@ -19,10 +19,13 @@ _SessionLocal = None
@contextmanager
def session_scope(database_url: str) -> Generator[Session, None, None]:
"""Context manager: yields a session and closes it on exit."""
"""Context manager: yields a session, rolls back on exception, closes on exit."""
session = get_session(database_url)
try:
yield session
except Exception:
session.rollback()
raise
finally:
session.close()