Commit Graph

16 Commits

Author SHA1 Message Date
david aa8f7af55d feat: implement offline sync endpoints with Last-Write-Wins strategy
Add comprehensive offline synchronization support for habits and entries:

## Infrastructure (Phase 1)
- Add UpdatedAt and DeletedAt timestamps to Habit and HabitEntry entities
- Implement soft delete with Delete(), Touch(), and IsDeleted() methods
- Create SQL migration with optimized composite indexes for sync queries
- Add GetChangesSince() and SoftDelete() to both repositories
- Update all Find* methods to exclude soft-deleted records
- 13 comprehensive TDD tests for sync repository methods

## HTTP Endpoints (Phase 2)
- GET /api/v1/sync/changes: retrieve all changes since timestamp
- POST /api/v1/sync/batch: apply client changes with conflict resolution
- Implement Last-Write-Wins strategy using UpdatedAt timestamps
- Add authentication and rate limiting (100 req/min)
- Validate user ownership for all sync operations
- 9 tests for sync handlers (3 queries + 6 commands)

## Technical Details
- Composite indexes: (user_id, updated_at) for optimal query performance
- No pagination: atomic sync operations for data consistency
- Upsert behavior: create resources if not found on server
- DTOs with full entity state including timestamps
- Swagger documentation updated for new endpoints

All 220+ tests passing ✓
2025-12-12 00:11:46 +01:00
david 788b6cf430 Add data export functionality with gzip compression
- Add FindByUserID method to HabitEntryRepository (JOIN with habits)
- Implement ExportUserDataHandler to export all user data
- Add GET /api/v1/export endpoint with gzip compression
- Apply strict rate limiting (1 export per hour per user)
- Export includes all habits (active + archived) and entries
- Add i18n translations for export errors (en/es)
- Update all test mocks to implement new repository method
- Export format: JSON with gzip (~10x compression ratio)
2025-12-03 22:32:22 +01:00
david 935f742ac9 Add filtering support to GET /api/v1/habits endpoint
Implemented comprehensive filtering capabilities for the habits list endpoint:
- Filter by type (BOOLEAN, COUNTER, VALUE)
- Filter by frequency (DAILY, WEEKLY, MONTHLY)
- Filter by archived status
- Text search in habit name and description
- All filters can be combined
- Filters work with pagination

Technical changes:
- Added FilterParams to GetUserHabitsQuery
- Created HabitFilter struct in repository interface
- Implemented dynamic SQL query building in SQLite repository
- Updated HTTP handler to parse filter query parameters
- Added comprehensive tests for repository and handler filtering
- Updated all test mocks with new filter methods
2025-12-02 01:02:39 +01:00
david 38e640c617 Add pagination support to GET /api/v1/habits endpoint
- Create pagination package with Params and Response structs
- Add FindActiveByUserIDWithPagination and CountActiveByUserID methods to HabitRepository interface
- Implement pagination in SQLite repository using LIMIT and OFFSET
- Update GetUserHabitsHandler to support optional pagination parameters
- Modify HTTP endpoint to parse 'page' and 'page_size' query params (default: page=1, page_size=50, max=100)
- Add GetUserHabitsResponse DTO with pagination metadata
- Maintain backward compatibility - endpoint works with and without pagination params
- Add comprehensive tests for pagination logic in repository, handler, and pagination package
- Update all mock repositories to implement new pagination methods
2025-12-01 23:53:00 +01:00
david f780c69806 Add tests for RequestPasswordResetHandler and fix test compilation errors
- Add comprehensive tests for RequestPasswordResetHandler covering success and error cases
- Fix timezone-related test failures after removal of User.Timezone field:
  - Remove Timezone assertions from login_user_test.go
  - Remove Timezone field from RegisterRequest in integration tests
  - Update ValidateRegistration test cases (no longer validates timezone)
  - Update migrations_test to check current user table schema
- Fix syntax errors in user_repository_test.go (duplicate closing braces on lines 114 and 210)
2025-12-01 23:04:40 +01:00
david 5d92820591 Remove timezone from User model and pass as request parameter
Timezone is now sent from the client on each request that needs it,
instead of storing it in the database. This simplifies the model and
allows timezone to be dynamic (useful for traveling users).

Changes:
- Remove timezone field from User entity
- Remove timezone from user registration
- GET /habits/today now requires ?timezone= query param
- Add migration to drop timezone column from database
- Update related tests
2025-11-29 16:18:04 +01:00
david f37c1ac19b Improve email verification flow and error handling
- Send verification email before creating user to prevent orphaned accounts
- Detect SMTP authentication errors and fail fast without retries
- Add field-level validation errors for better frontend UX
- Configure docker-compose with explicit environment variables
- Document dollar sign escaping in .env.example (use $$)
- Implement welcome email on successful verification
- Clean up unnecessary comments
2025-11-28 11:32:51 +01:00
david 00f6b51228 Add optional email verification and registration control
Implemented email service infrastructure with SMTP support and optional email verification for self-hosted deployments. Registration flow now supports open/closed modes and hardcoded Apocapoc branding.

Key features:
- Email service with SMTP and template rendering
- Optional email verification (auto-verified without SMTP config)
- Registration modes: open/closed for access control
- Hardcoded Apocapoc branding (AppName, AppURL, DefaultFrom)
- Separate registration and login flows (registration no longer returns tokens)
2025-11-28 08:21:51 +01:00
david 4290ee3455 Switch to pure-Go SQLite driver for cross-compilation
- Replace mattn/go-sqlite3 with modernc.org/sqlite (pure-Go, no CGO required)
- Update GoReleaser config to disable CGO for ARM64 builds
- Change driver name from 'sqlite3' to 'sqlite' in all sql.Open calls
- Enables successful cross-compilation for ARM64 without cross-compiler toolchain
- All tests passing with new driver
2025-11-28 01:36:18 +01:00
david 1b69bec12f Fix integration tests to comply with password requirements
CI/CD Pipeline / Test (push) Has been cancelled
CI/CD Pipeline / Lint (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Image (push) Has been cancelled
Update test passwords from 'password123' to 'Password123!' to meet security requirements (uppercase, lowercase, digit, special char).

Fix migrations test to match actual habit_entries schema (removed non-existent deleted_at column).
2025-11-27 23:38:36 +01:00
david bbe0757ab6 Add refresh token authentication system
CI/CD Pipeline / Test (push) Has been cancelled
CI/CD Pipeline / Lint (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Image (push) Has been cancelled
Implement complete refresh token flow for improved security:
- Short-lived access tokens (configurable, default 1h)
- Long-lived refresh tokens (configurable, default 7d)
- Automatic token rotation on refresh
- Token revocation for proper logout

Domain layer:
- Add RefreshToken entity with validation and revocation
- Add RefreshTokenRepository interface

Application layer:
- Add RefreshTokenHandler for token refresh operations
- Add RevokeTokenHandler for single token revocation
- Add RevokeAllTokensHandler for user-wide revocation

Infrastructure layer:
- Implement SQLite RefreshTokenRepository
- Add refresh_tokens table migration with indexes
- Add parseDuration helper for flexible time configuration

HTTP layer:
- Add POST /api/v1/auth/refresh endpoint
- Add POST /api/v1/auth/logout endpoint
- Update login/register to return refresh tokens
- Improve Swagger documentation with clear descriptions

Configuration:
- Update .env.example with secure token expiry defaults
- Add support for minute/hour/day duration formats

Tests:
- Fix test suite to work with new signatures
- All existing tests passing
2025-11-27 09:57:54 +01:00
david 10d45fc34e Add COUNTER type with auto-increment and IsNegative field
COUNTER type:
- Only accepts integer values (rejects decimals)
- Auto-increment behavior: each mark adds to existing value
- Supports decrement via negative values (e.g., -2)
- Enforces minimum value of 0 (never negative)
- Default increment is 1 if no value provided

VALUE type:
- Accepts decimal values
- Replaces value (no auto-increment)

IsNegative field:
- New boolean field on Habit entity
- Persisted in database (is_negative column)
- Available in all DTOs and HTTP responses
- Marks habits as "negative" (e.g., candy consumption)

Examples:
- COUNTER: 5 + (-2) = 3, 2 + (-3) = 0, 0 + (-1) = 0
- VALUE: 5000 → 12000 = 12000 (replacement)
2025-11-27 08:58:39 +01:00
david 9f673bfca3 Refactor habit entries: remove soft delete and add smart pagination
Remove soft delete from HabitEntry:
- Eliminate DeletedAt field from entity and database
- Change UnmarkHabit from soft delete to hard delete
- Simplify all queries removing deleted_at checks
- Update migration to remove deleted_at column

Add date filtering and smart pagination to GetHabitEntries:
- Support optional from/to date parameters
- Implement intelligent pagination rules:
  * No date range: pagination required
  * Date range > 1 year: pagination required
  * Date range ≤ 1 year: pagination optional
- Pagination defaults (page=1, limit=50) only when required
- Return metadata with total count, page, and limit

Technical improvements:
- Cleaner codebase without soft delete complexity
- Better performance (no filtering in queries)
- More intuitive API with flexible pagination
- Comprehensive test coverage for validation rules
2025-11-26 16:33:40 +01:00
david e87b7df979 Rename project to apocapoc-api
Update module name and all imports from habit-tracker-api to apocapoc-api.
This reflects the project's new branding as part of the apocapoc ecosystem
(apocapoc-api, apocapoc-web, apocapoc-android).
2025-11-26 11:21:17 +01:00
david 3e8883d878 Add habit HTTP endpoints
- Create DTOs for HTTP requests and responses
- Implement habit handlers (create, get today's, mark)
- Register routes in router: POST /habits, GET /habits/today, POST /habits/{id}/mark
- Add missing repository methods (FindByID, FindByHabitID, FindPendingByHabitID, Delete)
- Wire up dependencies in main.go
- Tested with curl: create, list, mark habits work correctly
2025-11-26 00:41:05 +01:00
david 0977d3a58b Implement SQLite persistence layer (TDD)
- Add SQLite and UUID dependencies (go-sqlite3, google/uuid)
- Create database connection with automatic migrations
- Implement UserRepository with full CRUD operations
- Implement HabitRepository with JSON serialization for arrays
- Implement HabitEntryRepository with date range queries
- Add comprehensive test coverage for all repositories
- Fix User entity to default timezone to UTC when empty
- All tests passing with TDD approach (Red-Green-Refactor)
2025-11-25 14:29:51 +01:00