diff --git a/.gitignore b/.gitignore index 90797b6..15d8a13 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ htmlcov/ # Local Node artifacts used for tooling in this repo package.json package-lock.json + +# Local AI assistant instructions (not for the repo) +CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 5f58de6..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,446 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Development Commands - -### Local Development (Non-Docker) - -```bash -# Set environment for development -export DJANGO_SETTINGS_MODULE=config.settings.development - -# Create and activate virtual environment -python3 -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate - -# Install dependencies -pip install -r requirements.txt - -# Database operations -python manage.py migrate -python manage.py makemigrations -python manage.py createsuperuser - -# Run development server -python manage.py runserver - -# Run tests -python manage.py test - -# Django shell -python manage.py shell - -# Collect static files (for production) -python manage.py collectstatic --noinput -``` - -### Docker Development - -```bash -# Development with live reload -make dev -make dev-build # Rebuild containers - -# Production mode -make prod -make build # Build production containers -make deploy # Full deployment (pull, build, migrate, restart) - -# Database operations -make migrate -make makemigrations -make createsuperuser -make dbshell # Access database shell -make backup # Backup database - -# Monitoring -make logs # All container logs -make logs-web # Web container logs only -make logs-celery # Celery worker logs -make status # Container status - -# Control -make stop -make restart -make clean # Stop and remove all containers/volumes - -# Shell access -make shell # Django shell -make test # Run tests in container -``` - -### Celery (Background Tasks) - -```bash -# Local development (non-Docker) -# Terminal 1: Run Celery worker -celery -A config worker -l info - -# Terminal 2: Run Celery beat (scheduled tasks) -celery -A config beat -l info - -# In Docker, Celery runs automatically as separate services -``` - -## High-Level Architecture - -### Django Apps - -**users/** - User authentication and management -- Custom User model with email-based authentication (no username) -- Three-tier verification: email verification → admin approval → login -- JWT token authentication with refresh tokens -- Device token management for push notifications -- Rate-limited registration (5/hour) and login (10/hour) - -**tasks/** - Core task management -- Hierarchical tasks with parent/subtask relationships -- Task status (pending, in_progress, completed, cancelled) and priority (low, medium, high, urgent) -- Recurrence patterns (daily, weekly, biweekly, monthly, yearly, or custom RRULE for day-of-week/nth-weekday/day-of-month control) -- Time tracking with start/stop timer functionality -- Tag system with colors and icons for organization -- Task sharing with permission levels (viewer/editor) -- Soft-delete architecture (is_deleted flag) for sync compatibility - -**sync/** - Mobile app synchronization -- Offline-first bidirectional sync protocol -- Timestamp-based conflict detection with three resolution strategies (local, server, merged) -- Multi-device support with independent sync tokens per device -- Incremental sync (delta updates) and full sync support -- Uses sync_id (UUID) separate from Django id for mobile compatibility -- Soft-delete propagation to enable client-side deletion - -**notifications/** - Email notifications and reminders -- Daily email digest (overdue + due today tasks) -- Timezone-aware scheduling (6-7 AM in user's local time) -- Deduplication (one email per user per day) -- Celery-based background processing -- Recurring task safety net (creates missed recurrences) - -### Key Architectural Patterns - -**UUID Primary Keys** - All models use UUID as primary key for distributed system compatibility and security. - -**Dual ID System** - Each entity has both `id` (Django PK) and `sync_id` (for mobile sync). Sync protocol uses `sync_id` to maintain consistency across devices. - -**Soft Delete Pattern** - All entities have `is_deleted` boolean instead of hard deletes. Dual managers enable filtering: -- `objects` - Default manager (excludes deleted) -- `all_objects` - Includes deleted items (used in sync) - -**Multiple Serializers Per Context** - Different serializers for list/detail/sync endpoints to optimize performance: -- `TaskListSerializer` - Lightweight for list views -- `TaskSerializer` - Full details with nested subtasks -- `TaskSyncSerializer` - Uses sync_id, includes is_deleted - -**Permission System** - Custom `IsOwnerOrReadOnlyIfShared` permission: -- Read access: Owner OR users with shared access -- Write access: Owner only -- Security filtering in serializers prevents tag/task leakage - -**Custom Authentication Backend** - `EmailVerifiedApprovedBackend` enforces: -1. Email verification check -2. Admin approval check -3. Superusers bypass both checks -4. Stores error state in session for web UI - -**Timezone-Aware Operations** - All time calculations use user's timezone: -- Daily emails scheduled in user's local 6-7 AM -- Overdue calculation relative to user's local date -- UTC storage, converted for display and logic - -**Mobile App Support** - `AllowMobileAppFramingMiddleware` detects mobile app via User-Agent and removes X-Frame-Options to allow WebView embedding while maintaining security for web browsers. - -### Sync Protocol Details - -The sync endpoint (`POST /api/sync/`) handles offline-first synchronization: - -**Request Format:** -```json -{ - "device_id": "unique-device-id", - "last_sync_token": "previous-token-or-null", - "changes": { - "tasks": [...], - "tags": [...], - "time_entries": [...] - } -} -``` - -**Response Format:** -```json -{ - "sync_token": "new-sync-token", - "server_time": "2025-01-10T12:00:00Z", - "server_changes": { - "tasks": [...], - "tags": [...], - "time_entries": [...] - }, - "conflicts": [...] -} -``` - -**Conflict Detection:** Compares `updated_at` timestamp between client's last sync and server's current state. If server modified entity since last sync, conflict is flagged. - -**Conflict Resolution Strategies:** -- **local** - Apply client changes (overwrite server) -- **server** - Keep server version (discard client) -- **merged** - Accept manually merged data - -### Celery Task Schedule - -Configured in `config/celery.py`: - -**send_daily_task_email** - Runs every hour -- Fetches users with email notifications enabled -- Converts UTC to user's timezone -- Sends email only if user's local time is 6-7 AM -- Checks for existing notification to prevent duplicates -- Groups tasks: overdue + due today (pending/in_progress only) - -**process_recurring_tasks** - Runs daily at midnight -- Safety net for recurring task creation -- Processes completions from last 48 hours -- Creates next recurrence if missing -- Prevents duplicate recurrences - -### Settings Modules - -**config.settings.development** - Local development -- DEBUG=True -- SQLite database -- Console email backend -- Minimal security - -**config.settings.production** - Production deployment -- DEBUG=False -- PostgreSQL required -- SMTP email backend -- Full security (HTTPS, CSP, HSTS) -- Gunicorn with multiple workers - -**config.settings.selfhosted** - Docker/self-hosted -- Environment-based configuration -- PostgreSQL with health checks -- Redis for Celery -- Configurable workers and timeout -- WhiteNoise for static files - -### Database Models Reference - -**Task Model Fields:** -- Hierarchy: `parent` (ForeignKey to self) -- Status: pending, in_progress, completed, cancelled -- Priority: low, medium, high, urgent -- Recurrence: `recurrence` (none/daily/weekly/biweekly/monthly/yearly/custom), `recurrence_rule` (RRULE string, used when `recurrence='custom'`, parsed via `dateutil.rrule`), `recurrence_end_date` -- Timing: `due_date`, `due_time`, `completed_at`, `created_at`, `updated_at` -- Soft delete: `is_deleted` (filters via `SoftDeleteManager`) -- Sync: `sync_id` (UUID for mobile sync) -- Relationships: `tags` (ManyToMany), `time_entries` (reverse FK) - -**User Model Fields:** -- Authentication: `email` (unique), `password`, `first_name`, `last_name` -- Verification: `email_verified`, `is_approved`, `approved_by` -- Preferences: `timezone`, `reminder_minutes_before`, `notifications_enabled`, `email_notifications_enabled` -- UUID primary key for security - -**TimeEntry Model:** -- `start_time`, `end_time`, `duration` (auto-calculated) -- Foreign keys: `task`, `user` -- Soft delete: `is_deleted` - -**TaskShare Model:** -- `shared_by`, `shared_with` (User FKs) -- `permission_level`: viewer, editor -- Can share tasks or tags -- Generic relation pattern - -### API Endpoint Overview - -**Authentication:** -- `POST /api/users/register/` - Create account (rate limited 5/hour) -- `POST /api/users/token/` - Login (rate limited 10/hour) -- `POST /api/users/token/refresh/` - Refresh JWT -- `POST /api/users/verify-email/` - Verify email token -- `POST /api/users/resend-verification/` - Resend verification email - -**Tasks:** -- `GET/POST /api/tasks/` - List/create tasks (supports filtering by status, priority, tags, search) -- `GET/PUT/DELETE /api/tasks//` - Task detail operations -- `POST /api/tasks//start-timer/` - Start time tracking -- `POST /api/tasks/time-entries//stop/` - Stop timer -- `GET/POST /api/tasks/tags/` - Tag management -- `GET/POST /api/tasks/shares/` - Share management - -**Sync:** -- `POST /api/sync/` - Main sync endpoint (rate limited 100/hour) -- `GET /api/sync/conflicts/` - List pending conflicts -- `POST /api/sync/conflicts//resolve/` - Resolve conflict - -**Notifications:** -- `GET /api/notifications/` - List notifications -- `POST /api/notifications//read/` - Mark as read -- `POST /api/notifications/mark-all-read/` - Mark all read -- `GET /api/notifications/unread-count/` - Get unread count - -### Environment Variables - -Key variables (see `.env.example` and `stack.env.example`): - -**Django Core:** -- `SECRET_KEY` - Django secret (generate with `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"`) -- `DEBUG` - Debug mode (False in production) -- `ALLOWED_HOSTS` - Comma-separated hosts -- `CSRF_TRUSTED_ORIGINS` - HTTPS origins -- `SITE_DOMAIN` - Domain for email links - -**Database:** -- `DATABASE_URL` - Connection string (e.g., `postgresql://user:pass@host:5432/db`) - -**Redis & Celery:** -- `REDIS_URL` - Redis connection (e.g., `redis://localhost:6379/0`) - -**Email:** -- `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USE_TLS` -- `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD` -- `DEFAULT_FROM_EMAIL` - Sender address -- `EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS` - Token validity (default: 24) - -**Docker/Gunicorn:** -- `GUNICORN_WORKERS` - Worker processes (default: 3) -- `GUNICORN_TIMEOUT` - Request timeout (default: 60) -- `WEB_PORT` - External port (default: 8000) - -### Testing - -Tests located in each app's `tests.py`. Run with: -```bash -python manage.py test # All tests -python manage.py test users # Specific app -python manage.py test users.tests.TestUserModel # Specific test -``` - -Docker: -```bash -make test # Run tests in container -``` - -### Deployment Notes - -**Docker Deployment:** -- Production uses PostgreSQL (postgres:16-alpine) -- Redis for Celery message broker (redis:7-alpine) -- Web service runs Gunicorn with configurable workers -- Celery worker and beat run as separate services -- Health checks on all services -- Named volumes for persistence (db_data, redis_data, static_files) -- Networks: frontend (web-facing), backend (internal services) - -**Manual Deployment:** -- Use `config.settings.production` settings module -- Requires PostgreSQL (not SQLite) -- Configure SMTP for email -- Use Gunicorn behind Nginx reverse proxy -- Set up systemd services for Gunicorn, Celery worker, Celery beat -- Configure SSL/TLS certificates (Let's Encrypt recommended) -- Enable firewall (UFW) with SSH, HTTP, HTTPS only - -**Production Checklist:** -- Set DEBUG=False -- Use strong SECRET_KEY -- Configure ALLOWED_HOSTS (no wildcards) -- Set CSRF_TRUSTED_ORIGINS for HTTPS -- Use PostgreSQL (not SQLite) -- Configure email backend (SMTP) -- Enable security headers (in settings) -- Set up SSL/TLS certificates -- Configure database backups -- Set up log rotation -- Monitor error logs and uptime - -### Common Development Patterns - -**Creating Recurring Tasks:** When a recurring task is marked complete, the next recurrence is automatically created in `tasks/models.py` `create_next_recurrence()` method, which calls `calculate_next_due_date()`. For `recurrence='custom'`, the next date is computed by evaluating `recurrence_rule` (an RRULE string) via `dateutil.rrule`; the web UI's Custom recurrence builder (in `templates/tasks/_recurrence_fields.html` and `static/js/app.js`) generates these strings for day-of-week, nth-weekday, and day-of-month patterns. Celery task `process_recurring_tasks()` runs as safety net. - -**Time Entry Workflow:** -1. `POST /api/tasks//start-timer/` creates TimeEntry with start_time -2. Running timer tracked in UI -3. `POST /api/time-entries//stop/` sets end_time and calculates duration - -**Email Verification Flow:** -1. User registers → `email_verified=False`, `is_approved=False` -2. EmailVerificationToken created with 24-hour expiry -3. User clicks link → `email_verified=True`, token marked used -4. Admins notified via email -5. Admin approves in Django admin → `is_approved=True` -6. User notified and can now login - -**Adding New Celery Tasks:** -1. Create task function in app's `tasks.py` with `@shared_task` decorator -2. Add to beat schedule in `config/celery.py` if periodic -3. Import in app's `__init__.py` to ensure task registration -4. Restart Celery worker and beat services - -**Extending User Model:** The User model is in `users/models.py`. Add fields there, create migration, update serializers and admin if needed. - -**Security Filtering:** When adding new endpoints that query related objects, filter by user in viewset's `get_queryset()`. Example from TaskViewSet: -```python -def get_queryset(self): - return Task.objects.filter(user=self.request.user) -``` - -### File Structure Reference - -``` -config/ # Django project configuration - settings/ # Environment-specific settings - base.py # Shared settings - development.py # Local development - production.py # Production deployment - selfhosted.py # Docker/self-hosted - celery.py # Celery configuration - urls.py # Root URL configuration - wsgi.py # WSGI entry point - -users/ # User management app - models.py # User, DeviceToken, EmailVerificationToken - views.py # Registration, login, profile - serializers.py # User serializers - backends.py # EmailVerifiedApprovedBackend - utils.py # Email utilities - -tasks/ # Task management app - models.py # Task, Tag, TimeEntry, TaskShare - views.py # Task CRUD, filtering, timers - serializers.py # Multiple serializers per context - middleware.py # AllowMobileAppFramingMiddleware - permissions.py # IsOwnerOrReadOnlyIfShared - -sync/ # Mobile sync app - models.py # SyncLog, SyncConflict - views.py # Sync endpoint, conflict resolution - serializers.py # SyncSerializer with sync_id - -notifications/ # Notifications app - models.py # Notification, ScheduledReminder - views.py # Notification CRUD - tasks.py # Celery tasks (daily email, recurring tasks) - -templates/ # Django templates - base.html # Base template with dark mode - tasks/ # Task-related templates - users/ # User-related templates - -static/ # Static assets - css/ # Stylesheets - js/ # JavaScript - -docker/ # Docker-related files -docker-compose.yml # Production Docker setup -docker-compose.dev.yml # Development overrides -Dockerfile # Production image -Dockerfile.dev # Development image -Makefile # Docker command shortcuts -```