- Add get_available_slots() and _expand_availability_to_slots() to ExchangeService
- Update routes/exchange.py to use ExchangeService.get_available_slots()
- Remove all business logic from get_available_slots endpoint
- Add AvailabilityRepository to ExchangeService dependencies
- Add Availability and BookableSlot imports to ExchangeService
- Fix import path for validate_date_in_range (use date_validation module)
- Remove unused user_repo variable and import from routes/invites.py
- Fix mypy error in ValidationError by adding proper type annotation
- Create AvailabilityService with get_availability_for_range(), set_availability_for_date(), and copy_availability()
- Move slot validation logic to service
- Update routes/availability.py to use AvailabilityService
- Remove all direct database queries from routes
- Create PriceService with get_recent_prices() and fetch_and_store_price()
- Update routes/audit.py to use PriceService instead of direct queries
- Use PriceHistoryMapper consistently
- Update test to patch services.price.fetch_btc_eur_price
Replace hardcoded (0, 15, 30, 45) tuple with computed range based on
the SLOT_DURATION_MINUTES constant. This ensures the validation stays
in sync if the slot duration is ever changed.
Add test to verify slot minute boundary validation.
- Fix E2E test assertion for buy/sell direction change
- Add data-testid to date buttons for reliable e2e selection
- Update e2e tests to use data-testid instead of fragile weekday matching
- Add tests for regular user cannot complete/no-show trades (COMPLETE_EXCHANGE permission)
- Add endpoint to search users by email (case-insensitive)
- Limit results to 10 for autocomplete purposes
- Require VIEW_ALL_EXCHANGES permission (admin only)
- Add tests for search functionality and access control
The complete_trade and mark_no_show endpoints now use the dedicated
COMPLETE_EXCHANGE permission instead of CANCEL_ANY_EXCHANGE, which
better reflects the semantics of these operations.
The agreed_price depends on trade direction (buy/sell) and must be
calculated on the frontend. Returning a buy-side-only agreed_price
from the API was misleading and unused.
Frontend already calculates the direction-aware price correctly.
- Updated auth-context.tsx to use new exchange permissions
(CREATE_EXCHANGE, VIEW_OWN_EXCHANGES, etc.) instead of old
appointment permissions (BOOK_APPOINTMENT, etc.)
- Updated exchange/page.tsx, trades/page.tsx, admin/trades/page.tsx
to use correct permission constants
- Updated profile/page.test.tsx mock permissions
- Updated admin/availability/page.tsx to use constants.exchange
instead of constants.booking
- Added /api/exchange/slots endpoint to return available slots
for a date, filtering out already booked slots
- Fixed E2E tests:
- exchange.spec.ts: Wait for button to be enabled before clicking
- permissions.spec.ts: Use more specific heading selector
- price-history.spec.ts: Expect /exchange redirect for regular users
Admin trade management:
- GET /api/admin/trades/upcoming: Upcoming booked trades (sorted by time)
- GET /api/admin/trades/past: Past trades with filters
- status, start_date, end_date, user_search
- POST /api/admin/trades/{id}/complete: Mark as completed (after slot time)
- POST /api/admin/trades/{id}/no-show: Mark as no-show (after slot time)
- POST /api/admin/trades/{id}/cancel: Admin cancel trade
AdminExchangeResponse includes user contact info for admin view.
Add GET /api/exchange/price endpoint:
- Available to regular users (BOOK_APPOINTMENT permission)
- Returns current BTC/EUR price with admin premium applied
- Uses cached price from PriceHistory if not stale
- Fetches fresh price from Bitfinex if needed
- Returns is_stale flag when price is older than 5 minutes
- Includes exchange configuration (min/max EUR, increment)
- Handles fetch failures gracefully (returns stale price with error)
Use asyncio.wait with FIRST_EXCEPTION to:
- Properly name tasks for better error logging
- Cancel remaining tasks when one fails
- Log which specific manager failed before propagating the exception
The POST /api/audit/price-history/fetch endpoint now requires
FETCH_PRICE permission instead of VIEW_AUDIT, which is more
semantically correct since it's a write operation.
- create_mock_httpx_client() for mocking AsyncClient with various configs
- create_bitfinex_ticker_response() for creating ticker response arrays
Reduces test boilerplate significantly.
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.