Unlike JOB_RANDOM_NUMBER which is used for external job enqueueing,
JOB_FETCH_BITCOIN_PRICE is only used internally by the scheduler.
Move it to worker.py to clarify it's not part of the public job API.
When a duplicate timestamp occurs (rare but possible), return the
existing record instead of failing with a 500 error. This matches
the worker's ON CONFLICT DO NOTHING behavior.
Added test for duplicate timestamp handling.
Issue #8: Inconsistent naming for model-to-response conversion functions.
Changes:
- Rename build_invite_response to _to_invite_response (invites.py)
- Rename _map_counter_record to _to_counter_record_response (audit.py)
- Rename _map_sum_record to _to_sum_record_response (audit.py)
All conversion functions now follow the _to_X_response pattern,
using underscore prefix for module-private functions.
Issue #6: Multiple routers per file made main.py verbose.
Changes:
- Add 'routers' list export to booking.py and invites.py
- Update main.py to iterate over router lists for multi-router modules
- Keep explicit registration for single-router modules
- Cleaner separation between simple and complex route modules
Issue #5: Date validation logic was duplicated across availability
and booking routes.
Changes:
- Add date_validation.py with shared utilities:
- get_bookable_date_range: returns (min_date, max_date) tuple
- validate_date_in_range: validates date with contextual errors
- Update routes/availability.py to use shared utilities
- Update routes/booking.py to use shared utilities
- Remove redundant _get_date_range_bounds and _get_bookable_date_range
- Error messages now include context (book, set availability, etc.)
Issue #4: Pagination logic was repeated across multiple routes.
Changes:
- Add pagination.py with reusable utilities:
- calculate_total_pages: computes page count from total/per_page
- calculate_offset: computes offset for given page
- create_paginated_response: builds PaginatedResponse with metadata
- Update routes/audit.py to use pagination utilities
- Update routes/booking.py to use pagination utilities
- Update routes/invites.py to use pagination utilities
The utilities handle the common pagination math while routes
still manage their own query logic (filters, joins, ordering).
Issue #3: The frontend Permission enum was manually duplicated from
the backend. While full generation isn't practical, this change
ties the frontend constants to the generated OpenAPI types for
compile-time validation.
Changes:
- Update ConstantsResponse schema to use actual Permission/InviteStatus
enums (enables OpenAPI to include enum values)
- Import enums in schemas.py (no circular dependency issue)
- Update auth-context.tsx to derive PermissionType from generated schema
- Update meta route to return enum instances instead of string values
- Permission values are now type-checked against the OpenAPI schema
If a permission is added to the backend but not to the frontend's
Permission object, TypeScript will fail to compile. This provides
a safety net without requiring a complex build-time generation step.
Issue #2: The profile route used a custom role-based check instead
of the permission-based pattern used everywhere else.
Changes:
- Add MANAGE_OWN_PROFILE permission to backend Permission enum
- Add permission to ROLE_REGULAR role definition
- Update profile routes to use require_permission(MANAGE_OWN_PROFILE)
- Remove custom require_regular_user dependency
- Update frontend Permission constant and profile page
- Update invites page to use permission instead of role check
- Update profile tests with proper permission mocking
This ensures consistent authorization patterns across all routes.
Tests that call POST /api/counter/increment now explicitly request
the mock_enqueue_job fixture. This prevents the mock from masking
issues in other tests that don't need it.
- Added ASYNCPG_DATABASE_URL constant in database.py
- Updated jobs.py to import from database module
- Updated worker.py to import from database module
- Removed duplicate URL parsing logic from both files
- Changed from using scalars().all() with lazy='joined' relationship
- Now uses explicit join similar to other audit endpoints
- Guarantees single query regardless of SQLAlchemy async behavior
- Added get_job_pool() for lazy pool initialization
- Added close_job_pool() for graceful shutdown
- Hooked pool shutdown into FastAPI lifespan
- Reuses connections instead of creating new ones per enqueue
- Add RandomNumberOutcomeResponse schema to schemas.py
- Add GET /api/audit/random-jobs endpoint to routes/audit.py
- Returns list of outcomes (newest first, no pagination)
- Requires VIEW_AUDIT permission
- Add tests: admin can access, regular user forbidden, unauthenticated 401
- Add RandomNumberOutcome model to models.py
- Update worker.py to execute job logic:
- Generate random number 0-100
- Record execution duration
- Store outcome in database
- Add test_jobs.py with unit tests for job handler logic
- Add backend/jobs.py with enqueue_random_number_job function
- Modify counter increment endpoint to enqueue job after incrementing
- Add mock_enqueue_job fixture to conftest.py for all tests
- Add test_increment_enqueues_job_with_user_id to verify correct user_id
- Job is enqueued synchronously; failure causes request to fail
- Add pgqueuer dependency to pyproject.toml
- Create worker.py with schema installation and job handler registration
- Add make worker command to Makefile
- Update make dev to run worker alongside backend/frontend
- Use has_table() check for idempotent schema installation
- Register 'random_number' job handler (placeholder that logs for now)
- Add ruff as dev dependency
- Configure ruff in pyproject.toml with strict 88-char line limit
- Ignore B008 (FastAPI Depends pattern is standard)
- Allow longer lines in tests for readability
- Fix all lint issues in source files
- Add Makefile targets: lint-backend, format-backend, fix-backend
- Updated test to match new descriptive error message format
- Changed from 'not within available' to 'not within any available time ranges'
- All tests now passing
- Added specific slot time and date to availability error message
- Added appointment ID and context to 'not found' errors
- Added formatted appointment time to past appointment cancellation errors
- Added date context to slot overlap error messages
- All errors now provide actionable information to users
- Replaced all UTC imports with timezone imports
- Changed all datetime.now(UTC) to datetime.now(timezone.utc)
- Consistent with booking.py and more explicit about timezone usage
- Updated models.py, routes/auth.py, and routes/invites.py
- Added explicit joinedload(Appointment.user) to admin appointment queries
- Makes the eager loading intention clear and explicit
- Replaced comment-based documentation with actual query options
- Updated BookingRequest validator to use NOTE_MAX_LENGTH constant
- Replaced hardcoded 144 with constant for consistency
- Error message now includes the actual max length value
- Updated availability.py to use MIN_ADVANCE_DAYS constant instead of hardcoded timedelta(days=1)
- Ensures consistency between booking and availability date ranges
- Both now use the same constant from shared_constants
- Created _get_valid_minute_boundaries() helper that derives valid minutes from SLOT_DURATION_MINUTES
- Replaced hardcoded (0, 15, 30, 45) with dynamic calculation
- Error message now includes valid minute values for better clarity
- Created _to_appointment_response() helper function
- Replaced 5 duplicate AppointmentResponse constructions with helper calls
- Helper handles both explicit user_email and eager-loaded user relationship cases
- Added pagination with page/per_page query params
- Fixed N+1 query by using eager-loaded user relationship
- Removed unused _get_user_email helper function
- Updated frontend to handle paginated response
- Regenerated API types
Created shared_constants.py module that loads constants from the
shared JSON file. Updated availability.py and booking.py to import
from this module instead of hardcoding values.
This ensures backend and frontend stay in sync with the same source
of truth for booking configuration.