Replace complex notification system with a simple daily email:
- Send ONE email per day between 6-9 AM in user's timezone
- Show tasks due today and overdue tasks
- Only send if user has email_notifications enabled
- Remove all push notification logic
- Keep recurring task processor (runs daily at midnight)
This makes notifications much simpler and less intrusive while
still providing value to users.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update build contexts to pull from git repository URL instead of
local directory. This allows Portainer to build images directly
from the repository without requiring SSH access to the server.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Change healthchecks to use 'ps aux | grep' instead of 'pgrep' since
procps may not be available in existing images. This works with the
base Python image without requiring a rebuild.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This allows deploying from git repository in Portainer by providing
an example environment variables file. Users can copy this and fill
in their actual values in Portainer's environment variables section.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace celery inspect ping with pgrep check for reliability.
The inspect command was failing in healthcheck context while
the worker process itself was running fine.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix import error in notifications/tasks.py (timezone.datetime -> datetime)
- Add healthchecks to celery-worker and celery-beat containers
- Add procps package to Dockerfile for pgrep command
- Add email environment variables to celery containers
The import error was causing celery workers to crash when loading tasks.
Missing healthchecks prevented proper container health monitoring.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes ModuleNotFoundError by using Python's built-in zoneinfo module
instead of the external pytz dependency. zoneinfo is the standard
library solution for timezone handling in Python 3.9+.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes 500 error when user timezone is invalid or missing.
Now gracefully falls back to UTC if timezone conversion fails.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Tasks were incorrectly showing as overdue when they were due today.
The issue was that the overdue check was comparing the due date
against UTC's "today" instead of the user's local "today".
Now uses the user's timezone setting to determine the current date
when checking if a task is overdue. This ensures tasks due "today"
only become overdue when the user's local date advances to tomorrow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously priority sorting was alphabetical (high < low < medium < urgent)
instead of by importance. Now uses Django Case/When to map priority strings
to numeric values: urgent=4, high=3, medium=2, low=1.
Fixes both queryset sorting and list sorting for overdue filter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add custom scrollbar styles for Chromium/Webkit browsers to match the
dark theme aesthetic. Firefox already had proper scrollbar styling, but
Chrome was showing default light gray scrollbars.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds automatic creation of next task instance when recurring tasks are completed.
- Add calculate_next_due_date() and create_next_recurrence() methods to Task model
- Update task completion handlers in views and sync API to create next recurrence
- Add hourly Celery task to process any missed recurring tasks
- Support daily, weekly, biweekly, monthly, yearly recurrence patterns
- Respect recurrence_end_date limits
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed mobile overlay from 50% to 75% opacity (25% transparent)
to make the sidebar content more visible when open on mobile devices.
Updated rgba(0, 0, 0, 0.5) to rgba(0, 0, 0, 0.75)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Middleware now:
- Detects mobile app vs browser via User-Agent
- Mobile app: Removes frame-blocking headers (allows iframe)
- Browsers: Adds CSP and X-Frame-Options headers (security)
This ensures:
✓ Browsers get full CSP protection
✓ Mobile app can embed content
✓ No need for django-csp package
✓ All security managed in one place
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The CSP_FRAME_ANCESTORS = ("'none'",) setting in production.py was
blocking iframe embedding even after removing X-Frame-Options.
Updated middleware to:
- Detect Android WebView via 'wv' in User-Agent (more reliable)
- Remove both X-Frame-Options AND Content-Security-Policy headers
- This allows mobile app iframe embedding while keeping browser protection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created Django middleware that detects requests from the KeepItGoing
mobile app (via User-Agent) and removes X-Frame-Options header to
allow iframe embedding.
Changes:
- Created tasks/middleware/mobile_app.py with AllowMobileAppFramingMiddleware
- Added middleware to settings after XFrameOptionsMiddleware
- Detects Capacitor WebView User-Agent patterns
- Removes X-Frame-Options only for mobile app, keeps protection for browsers
This allows the mobile app to embed the website in an iframe while
maintaining clickjacking protection for regular web browsers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: The .app-layout.detail-closed class (2-class specificity)
was overriding .app-layout (1-class specificity) at mobile, creating
a 3-column grid "0 1fr 0" instead of single column "1fr".
The task-pane was placed in column 1 (0 width), making it invisible.
Fix: Explicitly override both .app-layout and .app-layout.detail-closed
with equal specificity in the 768px breakpoint.
Cache bust: v=6
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The task-pane was not displaying at mobile because it lacked explicit
grid positioning and width constraints. Added:
- grid-row: 2 and grid-column: 1 for explicit placement
- width: 100%, min-width: 0, max-width: 100% to ensure full width
- Moved header grid positioning into mobile breakpoint for clarity
Also updated header to have explicit grid-row: 1 positioning.
Cache bust: v=5
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The sidebar-toggle and mobile-overlay base definitions were placed
AFTER the media queries, causing them to override the responsive
styles. This prevented the hamburger menu from showing and broke
the sidebar drawer functionality.
Fixed by moving base definitions before the Mobile Responsive section.
CSS order is now correct:
1. Base styles (display: none for sidebar-toggle)
2. Media queries (display: flex at 768px)
This allows the media query to properly override the base style.
Updated cache version to v=4.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The flex-direction: column on header-user was causing buttons to
overflow outside the header bar. Changed approach to keep buttons
horizontal but make them smaller on very small screens.
Changes:
- Removed flex-direction: column from 480px breakpoint
- Keep buttons horizontal with smaller font size
- Reduced button min-height to 36px on small screens
- Reduced touch targets slightly (40px) for very small screens
- Smaller brand font size (0.9rem)
Updated cache version to v=3 to force reload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed critical mobile layout issues on phones (400-600px width):
Header improvements:
- Hide email on mobile to save space
- Compact user menu layout
- Adjust brand font size for mobile
- Better spacing with adjusted padding
Sidebar drawer fixes:
- Properly hide sidebar off-screen (translateX(-100%))
- Increase z-index to 100 for proper layering
- Add background and shadow for drawer effect
- Wider drawer (280px) for better touch targets
Touch target improvements:
- All buttons minimum 44px height (Apple HIG standard)
- Larger touch areas for sidebar toggle and theme toggle
- Improved checkbox sizing (24px)
Task list mobile optimization:
- Smaller fonts for tags and priority badges
- Text ellipsis for long tag names
- Better wrapping for task metadata
- Reduced padding for mobile screens
Modal and form improvements:
- Full-width detail pane on mobile
- Responsive modal sizing (95% width, max 400px)
- Better form field spacing
New 480px breakpoint:
- Ultra-small phone optimization
- Vertical stack for Profile/Logout buttons
- Further reduced font sizes and padding
Mobile utility classes:
- .mobile-only / .desktop-only for conditional display
- .mobile-full-width for full-width elements
All changes are CSS-only, no JavaScript modifications needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added SVG favicon based on KeepItGoing app icon (white checkmark on
blue #3B82F6 background). Favicon displays in browser tabs and bookmarks.
Changes:
- Created static/favicons/favicon.svg with app icon design
- Updated templates/base.html with favicon link tags
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed pip upgrade to use --user flag so the upgraded pip is
installed to /root/.local and gets copied to the runtime stage.
Previous version upgraded pip globally in builder stage but runtime
stage was still using base image's pip (25.0.1). Now the upgraded
pip is in .local/bin which is in the PATH in the runtime container.
This fixes CVE-2025-8869 in pip.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added pip upgrade step in Dockerfile builder stage to ensure latest
pip version is used. This fixes CVE-2025-8869 vulnerability found in
pip 25.0.1 (fixed in 25.3).
pip-audit output before fix:
Found 1 known vulnerability in 1 package
Name Version ID Fix Versions
pip 25.0.1 CVE-2025-8869 25.3
After rebuilding, pip will be upgraded to 25.3+ which resolves the
security vulnerability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Re-added SECURE_CONTENT_TYPE_NOSNIFF and SECURE_BROWSER_XSS_FILTER
to Django settings. These don't conflict with NPM's "Block Common
Exploits" feature - they provide defense-in-depth by ensuring headers
are set even if the request bypasses NPM.
Security header strategy:
- NPM (primary): CSP, HSTS, Referrer-Policy, Permissions-Policy,
X-Content-Type-Options, X-XSS-Protection via "Block Common Exploits"
- Django (backup): X-Frame-Options, X-Content-Type-Options,
X-XSS-Protection for defense-in-depth
This follows security best practice of setting headers at multiple
layers rather than relying on a single point of control.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed SECURE_CONTENT_TYPE_NOSNIFF and SECURE_BROWSER_XSS_FILTER
from Django settings to prevent duplicate headers. These headers are
now exclusively managed in Nginx Proxy Manager's Advanced config.
This fixes Mozilla Observatory error: "X-Content-Type-Options header
cannot be recognized" which was caused by the header being sent twice
(once from Django, once from NPM).
Security headers now managed in NPM:
- Content-Security-Policy
- X-Content-Type-Options
- X-XSS-Protection
- Referrer-Policy
- Permissions-Policy
X-Frame-Options kept in Django for defense-in-depth (doesn't conflict).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added pip-audit to scan Python dependencies for known CVEs and
security vulnerabilities. This tool checks installed packages
against vulnerability databases and reports any issues.
Usage after deploying:
docker exec keepitgoing-web pip-audit
Recommended to run monthly or before major updates to identify
packages that need security patches.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Since Nginx Proxy Manager is handling Content Security Policy headers
at the reverse proxy level, removed django-csp from Django to avoid
duplication and simplify configuration.
Changes:
- Removed django-csp from requirements.txt
- Removed CSPMiddleware from middleware stack
- Removed CSP_* settings from selfhosted.py
- Added comment noting CSP is configured in NPM
CSP is now exclusively managed in NPM's Advanced configuration with:
- default-src 'self'
- script-src/style-src 'self' 'unsafe-inline' (for Django admin)
- img-src 'self' data: https:
- frame-ancestors 'none'
- And other security directives
This keeps configuration in one place (NPM) and eliminates
dependency on django-csp package.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements CSP to prevent XSS attacks and complete Mozilla Observatory
security requirements.
Changes:
- Added django-csp>=3.8 to requirements.txt
- Added CSPMiddleware to middleware stack
- Configured CSP directives in selfhosted.py:
- default-src 'self' (only load resources from same origin)
- script-src/style-src allow 'unsafe-inline' (needed for Django admin)
- img-src allows https: and data: URIs
- frame-ancestors 'none' (prevent clickjacking)
- form-action 'self' (prevent form hijacking)
This policy balances security with Django admin functionality.
After deployment, Mozilla Observatory should show all green checks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added SILENCED_SYSTEM_CHECKS to suppress warnings for:
- security.W004: HSTS (handled by NPM with HSTS Enabled)
- security.W008: SSL redirect (handled by NPM with Force SSL)
These warnings are intentionally suppressed because the security
features are properly configured at the reverse proxy level.
After redeploying, 'manage.py check --deploy' will show:
"System check identified no issues (0 silenced)."
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Since Nginx Proxy Manager handles SSL termination, Force SSL, and HSTS:
- Set SECURE_HSTS_SECONDS = 0 (NPM sends HSTS headers)
- Added SECURE_PROXY_SSL_HEADER to trust X-Forwarded-Proto from NPM
- Added SESSION_COOKIE_HTTPONLY for additional session security
- Documented NPM configuration: Force SSL = On, HSTS Enabled = On
This prevents duplicate HSTS headers and ensures Django correctly
detects HTTPS connections behind the reverse proxy.
Cookie security (SESSION/CSRF_COOKIE_SECURE) remains enabled as these
are application-level settings independent of the reverse proxy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added UsersAPIRoot view that returns available user-related
endpoints when accessing /api/users/ directly.
All API endpoint groups now have discoverable root endpoints:
- /api/ - Main API root
- /api/users/ - User endpoints
- /api/tasks/ - Task endpoints (already had TaskListCreateAPIView)
- /api/sync/ - Sync endpoints (already had sync view)
- /api/notifications/ - Notification endpoints (already had NotificationListAPIView)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created /api/ endpoint that returns JSON with:
- API status
- Available endpoints
- Version information
This fixes the "Not Found" error when accessing /api/ and
provides a useful API discovery endpoint for clients.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The authentication backend was requiring ALL users (including superusers)
to have email_verified=True and is_approved=True. This created a
chicken-and-egg problem where the first superuser couldn't log in to
approve themselves.
Now superusers bypass these checks and can log in immediately after
creation via createsuperuser command. Regular users still require
email verification and admin approval.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Major changes:
- Switch from MariaDB to PostgreSQL 16
- Add all required environment variables to web service:
- SECRET_KEY, DEBUG, ALLOWED_HOSTS
- CSRF_TRUSTED_ORIGINS, CORS_ALLOWED_ORIGINS
- Email configuration (HOST, PORT, USER, PASSWORD, TLS)
- Gunicorn settings (WORKERS, TIMEOUT)
- Add SECRET_KEY to celery services (required for Django)
- Update all services to depend on 'postgres' instead of 'mariadb'
- Update DATABASE_URL to use PostgreSQL format
- Update .env.docker.example to reflect PostgreSQL and add WEB_PORT
All environment variables now properly declared in compose file
with ${VAR} syntax for Portainer/Docker compatibility.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Django requires CSRF_TRUSTED_ORIGINS to be set for POST requests
(including login) when using HTTPS or non-standard ports. Without
this setting, login attempts fail with "CSRF verification failed".
The setting is now configurable via CSRF_TRUSTED_ORIGINS environment
variable, similar to CORS_ALLOWED_ORIGINS.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- tasks.0002_initial: Remove duplicate user field on Tag model
- tasks.0005_fix_tasks_tags_table: Add PostgreSQL/MySQL/SQLite support
- Use SERIAL instead of AUTOINCREMENT for PostgreSQL
- Use UUID type for foreign keys in PostgreSQL
- tasks.0006_fix_task_shares_table: Add PostgreSQL/MySQL/SQLite support
- Use UUID type and TIMESTAMP in PostgreSQL
- Use CHAR(32) and DATETIME in MySQL/SQLite
These migrations now detect the database backend and use appropriate
syntax for each database type.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove duplicate AddField operation for tag.user field in
tasks/migrations/0002_initial.py. The migration was attempting
to add the user ForeignKey field twice, causing PostgreSQL to
fail with "column 'user_id' of relation 'tags' already exists".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace MySQL client with PostgreSQL client
- Use libpq-dev instead of libmysqlclient-dev
- Remove mysqlclient installation (using psycopg2-binary from requirements.txt)
- Install postgresql-client for pg_isready health checks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Auto-detect database type from DATABASE_URL
- Use pg_isready for PostgreSQL health check
- Use mysqladmin ping for MariaDB/MySQL
- Default to postgres if not specified
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Allows deployment when port 8000 is already in use (e.g., by Portainer)
- Defaults to 8000 if WEB_PORT not specified
- Use WEB_PORT=8001 (or any other port) to override
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>