Major Features: - Invoice Expenses: Allow linking billable expenses to invoices with automatic total calculations - Add expenses to invoices via "Generate from Time/Costs" workflow - Display expenses in invoice view, edit forms, and PDF exports - Track expense states (approved, invoiced, reimbursed) with automatic unlinking on invoice deletion - Update PDF generator and CSV exports to include expense line items - Enhanced PDF Invoice Editor: Complete redesign using Konva.js for visual drag-and-drop layout design - Add 40+ draggable elements (company info, invoice data, shapes, text, advanced elements) - Implement comprehensive properties panel for precise element customization (position, fonts, colors, opacity) - Add canvas toolbar with alignment tools, zoom controls, and layer management - Support keyboard shortcuts (copy/paste, duplicate, arrow key positioning) - Save designs as JSON for editing and generate clean HTML/CSS for rendering - Add real-time preview with live data - Uploads Persistence: Implement Docker volume persistence for user-uploaded files - Add app_uploads volume to all Docker Compose configurations - Ensure company logos and avatars persist across container rebuilds and restarts - Create migration script for existing installations - Update directory structure with proper permissions (755 for dirs, 644 for files) Database & Backend: - Add invoice_pdf_design_json column to settings table via Alembic migration - Extend Invoice model with expenses relationship - Update admin routes for PDF layout designer endpoints - Enhance invoice routes to handle expense linking/unlinking Frontend & UI: - Redesign PDF layout editor template with Konva.js canvas (2484 lines, major overhaul) - Update invoice edit/view templates to display and manage expenses - Add expense sections to invoice forms with unlink functionality - Enhance UI components with keyboard shortcuts support - Update multiple templates for consistency and accessibility Testing & Documentation: - Add comprehensive test suites for invoice expenses, PDF layouts, and uploads persistence - Create detailed documentation for all new features (5 new docs) - Include migration guides and troubleshooting sections Infrastructure: - Update docker-compose files (main, example, remote, remote-dev, local-test) with uploads volume - Configure pytest for new test modules - Add template filters for currency formatting and expense display This update significantly enhances TimeTracker's invoice management capabilities, improves the PDF customization experience, and ensures uploaded files persist reliably across deployments.
9.6 KiB
Uploads Persistence in TimeTracker
Overview
This document explains how TimeTracker handles persistent file uploads (company logos and user avatars) across container rebuilds and restarts.
Problem Statement
Prior to this implementation, uploaded files (company logos and user avatars) were stored directly in the container's filesystem at app/static/uploads/. When containers were rebuilt or redeployed, these files were lost because they were not stored in a persistent volume.
Solution
We've implemented Docker volume persistence for the uploads directory, ensuring that all uploaded files persist across:
- Container rebuilds
- Container restarts
- Application updates
- Docker Compose down/up cycles
Technical Implementation
Directory Structure
app/static/uploads/
├── logos/ # Company logo files
│ ├── .gitkeep
│ └── [uploaded logo files]
└── avatars/ # User avatar files
├── .gitkeep
└── [uploaded avatar files]
Docker Volume Configuration
All Docker Compose files have been updated to include the app_uploads volume:
services:
app:
volumes:
- app_data:/data
- app_logs:/app/logs
- app_uploads:/app/app/static/uploads # Persistent uploads volume
volumes:
app_data:
driver: local
app_uploads:
driver: local
Updated Docker Compose Files
The following Docker Compose configurations have been updated:
- docker-compose.yml - Main production configuration
- docker-compose.example.yml - Example configuration for new users
- docker-compose.remote.yml - Remote deployment configuration
- docker-compose.local-test.yml - Local testing with SQLite
- docker-compose.remote-dev.yml - Remote development configuration
Note: Overlay files (docker-compose.analytics.yml, docker-compose.https-*.yml) don't need changes as they extend the base configuration.
Migration Guide
For New Installations
If you're setting up TimeTracker for the first time, the uploads persistence is automatically configured. Simply run:
docker-compose up -d
For Existing Installations
If you're upgrading from a version without uploads persistence, follow these steps:
Step 1: Backup Existing Uploads (if any)
# Create a backup of existing uploads
docker cp timetracker-app:/app/app/static/uploads ./uploads_backup
Step 2: Update Docker Compose Configuration
Pull the latest changes:
git pull origin main
# or
git pull origin develop
Step 3: Run Migration Script
python migrations/ensure_uploads_persistence.py
The migration script will:
- Create the required directory structure
- Set proper permissions (755)
- Create
.gitkeepfiles for git tracking - Verify Docker volume configuration
Step 4: Restart Containers
# Stop containers
docker-compose down
# Start with new configuration
docker-compose up -d
Step 5: Restore Backups (if needed)
If you had existing uploads, restore them:
# Copy logos back
docker cp ./uploads_backup/logos/. timetracker-app:/app/app/static/uploads/logos/
# Copy avatars back (if any)
docker cp ./uploads_backup/avatars/. timetracker-app:/app/app/static/uploads/avatars/
# Fix permissions
docker exec timetracker-app chown -R timetracker:timetracker /app/app/static/uploads
Step 6: Verify
- Log in to TimeTracker
- Go to Admin → Settings
- Check if your company logo is still displayed
- Try uploading a new logo to test the functionality
File Upload Locations
Company Logo
- Storage Path:
/app/app/static/uploads/logos/ - URL Path:
/uploads/logos/{filename} - Database Field:
settings.company_logo_filename - Max Size: 5MB
- Supported Formats: PNG, JPG, JPEG, GIF, SVG, WEBP
User Avatars
- Storage Path:
/app/app/static/uploads/avatars/ - URL Path:
/uploads/avatars/{filename} - Database Field:
users.avatar_filename - Max Size: 2MB (configurable)
- Supported Formats: PNG, JPG, JPEG, GIF, WEBP
Volume Management
Inspecting the Volume
# List all volumes
docker volume ls
# Inspect the uploads volume
docker volume inspect timetracker_app_uploads
# View volume contents
docker run --rm -v timetracker_app_uploads:/data alpine ls -lah /data
Backing Up the Volume
# Create a backup archive
docker run --rm \
-v timetracker_app_uploads:/data \
-v $(pwd):/backup \
alpine tar czf /backup/uploads_backup_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .
Restoring from Backup
# Restore from backup archive
docker run --rm \
-v timetracker_app_uploads:/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && tar xzf /backup/uploads_backup_YYYYMMDD_HHMMSS.tar.gz"
Removing the Volume (⚠️ Caution)
# Stop containers first
docker-compose down
# Remove the volume (this will delete all uploaded files!)
docker volume rm timetracker_app_uploads
# Recreate with new containers
docker-compose up -d
Permissions
The uploads directory uses the following permissions:
-
Directory Permission:
755(rwxr-xr-x)- Owner (timetracker): Read, Write, Execute
- Group: Read, Execute
- Others: Read, Execute
-
File Permission:
644(rw-r--r--)- Owner (timetracker): Read, Write
- Group: Read
- Others: Read
Security Considerations
File Type Validation
All uploaded files are validated to ensure they match allowed file types:
- Logos: PNG, JPG, JPEG, GIF, SVG, WEBP
- Avatars: PNG, JPG, JPEG, GIF, WEBP
File Size Limits
- Company Logo: 5MB maximum
- User Avatar: 2MB maximum (configurable)
Filename Sanitization
All uploaded files are renamed with UUID-based filenames to:
- Prevent path traversal attacks
- Avoid filename collisions
- Remove potentially malicious filenames
Access Control
- Logo uploads: Admin users only (or users with
manage_settingspermission) - Avatar uploads: Authenticated users (their own avatar only)
Troubleshooting
Uploaded Files Disappear After Restart
Symptom: Files uploaded before the persistence update are lost after container restart.
Solution:
- Verify the uploads volume is properly mounted:
docker inspect timetracker-app | grep -A 10 Mounts - Ensure the volume exists:
docker volume ls | grep uploads - Check if files are in the volume:
docker exec timetracker-app ls -lah /app/app/static/uploads/logos/
Permission Denied Errors
Symptom: "Permission denied" when uploading files.
Solution:
- Check directory permissions:
docker exec timetracker-app ls -ld /app/app/static/uploads/ - Fix permissions if needed:
docker exec timetracker-app chown -R timetracker:timetracker /app/app/static/uploads docker exec timetracker-app chmod -R 755 /app/app/static/uploads
Files Not Accessible via Web
Symptom: Uploaded files return 404 errors when accessed via browser.
Solution:
- Verify Flask is serving static files correctly
- Check if files exist:
docker exec timetracker-app ls /app/app/static/uploads/logos/ - Check file permissions allow reading:
docker exec timetracker-app ls -l /app/app/static/uploads/logos/
Volume Not Created
Symptom: Volume doesn't exist after docker-compose up.
Solution:
- Stop all containers:
docker-compose down - Verify your docker-compose.yml has the uploads volume defined
- Start containers again:
docker-compose up -d - Check if volume was created:
docker volume ls | grep uploads
Testing
Manual Testing
-
Upload a logo:
- Log in as admin
- Go to Admin → Settings
- Upload a company logo
- Note the logo filename from the database
-
Restart container:
docker-compose restart app -
Verify persistence:
- Refresh the Settings page
- Verify the logo is still displayed
-
Rebuild container:
docker-compose down docker-compose up -d -
Verify persistence after rebuild:
- Log in again
- Verify the logo is still displayed
Automated Testing
Run the persistence tests:
# Run all tests
pytest tests/test_uploads_persistence.py -v
# Run specific test
pytest tests/test_uploads_persistence.py::test_logo_upload_creates_file -v
Best Practices
- Regular Backups: Schedule regular backups of the uploads volume
- Volume Naming: Use consistent volume names across environments
- Monitoring: Monitor volume disk usage to prevent out-of-space issues
- Documentation: Keep documentation updated when modifying upload behavior
- Testing: Test file uploads after any infrastructure changes
Related Documentation
Version History
- v1.0.0 (2024): Initial implementation of uploads persistence
- Added Docker volume support for uploads directory
- Updated all Docker Compose configurations
- Created migration scripts and documentation
- Added automated tests for persistence verification
Support
If you encounter issues with uploads persistence:
- Check the Troubleshooting section above
- Review the logs:
docker-compose logs app - Check volume status:
docker volume inspect timetracker_app_uploads - Open an issue on GitHub with:
- Docker version
- Docker Compose version
- Relevant log output
- Steps to reproduce the issue