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
The auth tests expected '/' to show user email (old counter page).
Now regular users redirect to '/exchange' after login/signup.
Updated tests to:
- Expect /exchange URL after login/signup
- Check for 'Exchange Bitcoin' heading instead of email
- Update logout tests to verify /exchange access
- Update invite redirect tests for /exchange
New exchange.spec.ts with comprehensive E2E coverage:
- Exchange page access and UI elements (price, buy/sell, slider)
- Slot selection with availability
- Confirmation form with trade details
- Access control (regular user vs admin)
- My Trades page
- Admin Trades page with tabs
- Exchange API permission tests
New /admin/trades page for managing exchange trades:
- Upcoming trades tab with user contact info
- History tab with status/user search filters
- Complete/No-show/Cancel actions for admin
- Trade details: direction, amounts, rates, premium
Update navigation:
- Admin home redirects to /admin/trades
- Regular user home redirects to /exchange
- Header shows 'Trades' for admin
New /trades page for viewing user's Bitcoin trades:
- Upcoming trades with cancel functionality
- Trade history (completed, cancelled, no-show)
- Direction badges (BUY/SELL with colors)
- EUR ↔ BTC amounts in readable format
- Rate and premium display
Update Header navigation:
- Exchange replaces Book for regular users
- My Trades replaces Appointments
Update profile page test for new nav items.
New /exchange page for Bitcoin trading:
- Buy/Sell direction toggle with visual feedback
- Amount slider (€100-€3000 in €20 increments)
- Live price display with auto-refresh every 60s
- Direction-specific pricing (buy +5%, sell -5%)
- Sats amount displayed in BTC format (0.XX XXX XXX)
- Date selection with availability indicators
- Time slot selection grid
- Confirmation panel with trade summary
Regenerate frontend types from updated OpenAPI schema.
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.
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 #7: Profile validation logic was embedded in page component.
Changes:
- Create utils/validation.ts with shared validation functions:
- validateEmail: email format validation
- validateTelegram: handle format with @ prefix
- validateSignal: username length validation
- validateNostrNpub: bech32 format validation
- validateProfileFields: combined validation
- Update profile/page.tsx to use shared validation
- Both frontend and backend now read validation rules from
shared/constants.json for consistency
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