Commit graph

164 commits

Author SHA1 Message Date
94497f9200
test: add unit tests for price history feature 2025-12-22 15:47:20 +01:00
e3b047f782
feat: add price history GET and POST endpoints 2025-12-22 15:43:46 +01:00
b077c93351
feat: add Bitfinex price fetcher for BTC/EUR 2025-12-22 15:42:59 +01:00
3f3822f7c0
feat: add PriceHistoryResponse schema 2025-12-22 15:42:31 +01:00
7339858a02
feat: add PriceHistory model for storing exchange price data 2025-12-22 15:42:11 +01:00
0957d88785
chore: promote httpx from dev to regular dependency 2025-12-22 15:41:40 +01:00
20f7af7ffd
generated ts 2025-12-22 09:31:19 +01:00
976038c806
no parallel running to avoid flaky tests 2025-12-22 09:31:00 +01:00
e7e3c97102
refactor(backend): standardize model-to-response conversion naming
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.
2025-12-22 09:16:05 +01:00
fdab4a5dac
refactor(frontend): extract validation utilities to shared module
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
2025-12-22 09:13:03 +01:00
53aa54d6c9
refactor(backend): clean up router registration pattern
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
2025-12-22 09:10:26 +01:00
db7a0dbe28
refactor(backend): extract date range validation utilities
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.)
2025-12-22 00:02:41 +01:00
0dd84e90a5
refactor(backend): extract pagination utilities
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).
2025-12-22 00:00:24 +01:00
09560296aa
refactor: derive Permission type from generated OpenAPI schema
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.
2025-12-21 23:55:47 +01:00
21698203fe
refactor(auth): unify authorization patterns with MANAGE_OWN_PROFILE permission
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.
2025-12-21 23:50:06 +01:00
81cd34b0e7
refactor(frontend): consolidate shared styles into centralized style system
- Create comprehensive shared.ts with design tokens and categorized styles:
  - layoutStyles: main, loader, content variants
  - cardStyles: card, tableCard, cardHeader
  - tableStyles: complete table styling
  - paginationStyles: pagination controls
  - formStyles: inputs, labels, errors
  - buttonStyles: primary, secondary, accent, danger variants
  - badgeStyles: status badges with color variants
  - bannerStyles: error/success banners
  - modalStyles: modal overlay and content
  - toastStyles: toast notifications
  - utilityStyles: divider, emptyState, etc.

- Refactor all page components to use shared styles:
  - page.tsx (counter)
  - audit/page.tsx
  - booking/page.tsx
  - appointments/page.tsx
  - profile/page.tsx
  - invites/page.tsx
  - admin/invites/page.tsx
  - admin/availability/page.tsx

- Reduce code duplication significantly (each page now has only
  truly page-specific styles)
- Maintain backwards compatibility with sharedStyles export
2025-12-21 23:45:47 +01:00
4d9edd7fd4
refactor: make mock_enqueue_job fixture opt-in instead of autouse
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.
2025-12-21 23:29:48 +01:00
6f3e729b25
refactor: move process_random_number_job import to module level in tests 2025-12-21 23:25:31 +01:00
89fbdb37bd
fix: handle malformed JSON payloads in worker with error logging 2025-12-21 23:24:28 +01:00
ca97ff1aa8
refactor: use JOB_RANDOM_NUMBER constant instead of magic string in worker 2025-12-21 23:23:46 +01:00
027a6fb64c
fix: guard pool initialization with asyncio.Lock to prevent race condition 2025-12-21 23:23:21 +01:00
b33e5e425a
Add trap for cleanup in e2e.sh script
- Added cleanup function to kill background processes
- Trap on EXIT ensures cleanup happens on normal exit, errors, or Ctrl+C
- Prevents orphaned backend/worker processes if script is interrupted
2025-12-21 23:17:17 +01:00
a8ad6e6384
Extract duplicated DATABASE_URL parsing to database.py
- 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
2025-12-21 23:16:29 +01:00
405dfd526e
Replace fixed timeout with polling in random-jobs E2E test
- Removed brittle 1-second waitForTimeout
- Use toPass() with polling to wait for job outcome
- Check for specific user's email instead of exact row count
- More robust under slow worker conditions or CI load
2025-12-21 23:14:59 +01:00
18284c5e63
Use explicit join in random-jobs endpoint to avoid potential N+1 query
- 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
2025-12-21 23:14:08 +01:00
6b572aa81b
Use connection pool for job enqueueing instead of per-request
- 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
2025-12-21 23:13:22 +01:00
7ec987c78d
Phase 6: E2E Test
- Update scripts/e2e.sh to start worker alongside backend
- Create frontend/e2e/random-jobs.spec.ts with 3 tests:
  - Counter increment creates random job outcome visible to admin
  - Admin can view empty random jobs list
  - Regular user cannot access random jobs page
2025-12-21 23:00:54 +01:00
b8470b77a7
Phase 5: Frontend Admin Page
- Create /admin/random-jobs/page.tsx with outcomes table
- Add 'admin-random-jobs' to PageId type in Header
- Add 'Random Jobs' nav item to ADMIN_NAV_ITEMS
- Display: ID, Job ID, Triggered By, Value, Duration, Status, Created At
- Uses VIEW_AUDIT permission
2025-12-21 22:55:56 +01:00
b3ed81e8fd
Phase 4: API Endpoint
- 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
2025-12-21 22:53:54 +01:00
7beb213cf5
Phase 3: Outcome storage
- 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
2025-12-21 22:50:35 +01:00
6ca0ae88dd
Phase 2: Job enqueueing from counter
- 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
2025-12-21 22:44:31 +01:00
10c0316603
Phase 1: Add pgqueuer infrastructure
- 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)
2025-12-21 22:37:04 +01:00
15bae15731
Phase 1: Add pgqueuer infrastructure and worker skeleton
- Add pgqueuer dependency to pyproject.toml
- Create worker.py with basic setup:
  - Independent database connection using asyncpg
  - Install pgqueuer schema on startup
  - Register dummy job handler
  - Start consumer loop
- Add 'make worker' command
- Update 'make dev' to run worker alongside backend/frontend

Validation:
- Worker starts successfully
- pgqueuer tables exist in database
- All existing tests pass
2025-12-21 22:25:37 +01:00
607f872c71
fix pre-commit hook and code quality fixes 2025-12-21 22:14:48 +01:00
d2fc802ef9
code quality in place 2025-12-21 22:12:43 +01:00
d55382d16f
Add pre-commit framework
- Install pre-commit
- Configure .pre-commit-config.yaml with:
  - ruff (lint + format) for Python
  - bandit for Python security
  - eslint for TypeScript
  - prettier for TypeScript
  - shared constants validation
- Add Makefile targets: pre-commit, lint
2025-12-21 22:01:38 +01:00
521848217d
Add Vitest coverage for frontend tests
- Install @vitest/coverage-v8
- Configure coverage in vitest.config.ts
- Add npm script: test:coverage
- Add coverage/ to gitignore
2025-12-21 22:00:47 +01:00
37de6f70e0
Add Prettier for TypeScript formatting
- Install prettier
- Configure .prettierrc.json and .prettierignore
- Add npm scripts: format, format:check
- Add Makefile target: format-frontend
- Format all frontend files
2025-12-21 21:59:26 +01:00
4b394b0698
Add ESLint for TypeScript/React linting
- Install eslint, typescript-eslint, eslint-plugin-react-hooks
- Configure eslint.config.js with flat config format
- Add type: module to package.json
- Fix unused variable issues (prefix with underscore)
- Add Makefile targets: lint-frontend, fix-frontend
2025-12-21 21:58:41 +01:00
30583805cd
Add bandit for Python security linting
- Add bandit as dev dependency
- Configure in pyproject.toml (exclude venv/tests)
- Skip B101 (assert) and B311 (random for non-crypto)
- Add Makefile target: security-backend
2025-12-21 21:56:46 +01:00
6a2d7155cb
Add pytest-cov for test coverage
- Add pytest-cov as dev dependency
- Configure coverage in pyproject.toml
- Exclude tests, __pycache__, seed.py from coverage
- Enable branch coverage
- Add .coverage to gitignore
2025-12-21 21:55:19 +01:00
6c218130e9
Add ruff linter/formatter for Python
- 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
2025-12-21 21:54:26 +01:00
69bc8413e0
remove 2025-12-21 21:39:48 +01:00
621e12cdb5
fix tests 2025-12-21 21:35:35 +01:00
bf1e42a498
fixes 2025-12-21 19:04:38 +01:00
f81e7a88bd
Fix React CSS property conflict in availability page
- Replaced borderColor with full border property in dayCardActive
- Replaced borderColor with full border property in dayCardSource
- Replaced borderColor with full border property in dayCardTarget
- Prevents React warning about mixing shorthand and non-shorthand properties
- Fixes console error when using 'Clear all' button on availability page
2025-12-21 18:23:31 +01:00
74ed6f0c11
Revert to local timezone display for user-facing times
- Changed formatTime back to use toLocaleTimeString (local time)
- Changed formatDateTime back to use toLocaleString (local time)
- App now displays all times in user's local timezone
- Backend still stores times in UTC, frontend converts for display
2025-12-21 18:20:22 +01:00
a5b941e748
Fix timezone issue in formatDateTime for appointments page
- Changed formatDateTime to use UTC methods instead of toLocaleString
- Prevents timezone conversion when displaying appointment times
- Now booking at 11:45 shows as 11:45 in appointments page, not 12:45
- Consistent with formatTime which already uses UTC
- Manual formatting to match previous format: 'Mon, Jan 15, 11:45'
2025-12-21 18:18:40 +01:00
b900b52b3c
Fix infinite loop in booking page availability fetching
- Memoized dates array using useMemo to prevent recreation on every render
- Removed dates from useEffect dependency array since it's now stable
- Prevents cascade of API requests when opening booking page
- Dates only recalculate when minAdvanceDays or maxAdvanceDays change
2025-12-21 18:15:57 +01:00
63f40433cc
Disable dates with no availability on booking page
- Fetch availability for all dates on page load
- Track which dates have available slots in state
- Disable date buttons that have no availability
- Add visual styling for disabled dates (reduced opacity, not-allowed cursor)
- Prevent clicking on dates with no availability
- Improves UX by showing which dates are bookable at a glance
2025-12-21 18:13:12 +01:00