Files
joakim f5f7bc3ad7 feat: Complete key:value format implementation and fix tag sync
Implement complete key:value format parsing for change log entries and fix
critical tag synchronization issue from server to client.

Key Changes:

1. Shared Key:Value Parser (NEW: internal/engine/parser.go)
   - Created ParseKeyValueFormat() for both edit and sync operations
   - Supports flexible whitespace: 'key:value' and 'key: value'
   - Handles comment skipping for edit files
   - Consolidates parsing logic (DRY principle)

2. Database Triggers - Tags Support (internal/engine/database.go)
   - Added tags to track_task_create trigger
   - Added tags to track_task_update trigger
   - Tags sorted alphabetically via SQL ORDER BY
   - Format: 'tags: alpha,bravo,charlie'

3. Task Creation - Tag Update Fix (internal/engine/task.go)
   - CreateTaskWithModifier() now triggers update after adding tags
   - Ensures tags appear in change log (UPDATE entry)
   - Fixes missing tags in initial CREATE entries

4. Edit Command - Use Shared Parser (cmd/edit.go)
   - Replaced custom parseEditedFile() with shared ParseKeyValueFormat()
   - Added tag sorting in parseTags()
   - Removed ~30 lines, improved maintainability

5. Sync Client - Complete Implementation (internal/sync/client.go)
   - NEW: applyChangeDataToTask() - parses all fields from change log
   - NEW: Helper functions for status, priority, tag parsing
   - FIXED: parseChanges() - sort by timestamp+ID before grouping
   - Added parent/child task ordering (prevents FK violations)
   - Enhanced tag sync in merge loop with task reload
   - Specific validation error messages per field

Critical Bug Fix:
- When CREATE and UPDATE have same timestamp, old code kept CREATE (no tags)
- New code sorts by ID as tiebreaker, ensuring UPDATE (with tags) is used
- Verified: Server->client tag sync now works correctly

Validation:
- Description must not be empty (both edit and sync)
- Recurrence validated (not negative, max 100 years)
- All timestamps parsed correctly (Unix epoch)
- Tags sorted alphabetically in all contexts

Testing:
- Fresh pull from server:  All tags present
- API-created tasks:  Tags sync correctly
- Local->server->client round-trip:  No data loss
- Same-second CREATE+UPDATE:  Correct entry processed
- Parent/child tasks:  Correct ordering

Files Changed:
- NEW: internal/engine/parser.go (+44 lines)
- Modified: internal/engine/database.go (+10 lines)
- Modified: internal/engine/task.go (+8 lines)
- Modified: cmd/edit.go (-25 lines net)
- Modified: internal/sync/client.go (+280 lines)
- Modified: srv/README.md (+1 line)

Total: +318 lines added, -25 removed, net +293 lines

This completes Phase 6: Full bidirectional sync with complete tag support.
2026-01-05 18:56:17 +01:00

370 lines
7.7 KiB
Markdown

# Opal-Task Server
Opal-task server provides a REST API for syncing tasks across multiple devices. This enables household task sharing and access from anywhere.
## Features
- **REST API** - Chi-based HTTP API for task management
- **API Key Authentication** - Secure authentication using bcrypt-hashed keys
- **Bidirectional Sync** - Push and pull changes with conflict resolution
- **Offline Support** - Queue changes when server is unreachable
- **Change Tracking** - Automatic change log with configurable retention
- **Single Database** - Shared household task database (v1)
## Quick Start
### 1. Build the Binary
```bash
cd opal-task
go build -o opal main.go
```
### 2. Generate API Key
On your server (with direct database access):
```bash
./opal server keygen --name "My Device" --db /var/lib/opal/opal.db
```
Output:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
API Key Generated Successfully
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Name: My Device
Key: oak_abc123...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ IMPORTANT: Save this key securely!
It will not be displayed again.
```
### 3. Start the Server
```bash
./opal server start --addr :8080 --db /var/lib/opal/opal.db
```
### 4. Configure Client
On your local machine or phone:
```bash
opal sync init --url https://opal.yourdomain.com --key oak_abc123...
```
Or use interactive mode:
```bash
opal sync init
# Enter server URL: https://opal.yourdomain.com
# Enter API key: oak_abc123...
```
### 5. Sync Your Tasks
```bash
# Full bidirectional sync
opal sync now
# Push local changes only
opal sync up
# Pull server changes only
opal sync down
# Initial merge (for existing local database)
opal sync merge --prefer-local
```
## Server Deployment
### Option 1: SystemD Service (Recommended)
**1. Install Binary**
```bash
# Build
go build -o opal main.go
# Copy to system location
sudo cp opal /usr/local/bin/
# Create data directory
sudo mkdir -p /var/lib/opal
sudo chown $USER:$USER /var/lib/opal
```
**2. Generate First API Key**
```bash
opal server keygen --name "Admin" --db /var/lib/opal/opal.db
# Save the generated key!
```
**3. Create SystemD Service**
Create `/etc/systemd/system/opal.service`:
```ini
[Unit]
Description=Opal Task API Server
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/var/lib/opal
ExecStart=/usr/local/bin/opal server start --addr :8080 --db /var/lib/opal/opal.db
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
**4. Enable and Start Service**
```bash
sudo systemctl daemon-reload
sudo systemctl enable opal
sudo systemctl start opal
sudo systemctl status opal
```
### Option 2: Direct Execution
```bash
# Run in background
nohup ./opal server start --addr :8080 --db /var/lib/opal/opal.db > opal.log 2>&1 &
```
## Reverse Proxy Setup
### Caddy (Recommended)
Add to your Caddyfile:
```
opal.yourdomain.com {
reverse_proxy localhost:8080
}
```
Reload Caddy:
```bash
sudo systemctl reload caddy
```
### Nginx
```nginx
server {
listen 443 ssl http2;
server_name opal.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## API Endpoints
### Authentication
All endpoints (except `/health`) require authentication via API key:
```
Authorization: Bearer oak_abc123...
```
### Health Check
```
GET /health
```
Returns server status.
### Tasks
```
GET /tasks - List tasks (with filters)
GET /tasks/{uuid} - Get specific task
POST /tasks - Create task
PUT /tasks/{uuid} - Update task
DELETE /tasks/{uuid} - Delete task
POST /tasks/{uuid}/complete - Mark task complete
POST /tasks/{uuid}/start - Start task
POST /tasks/{uuid}/stop - Stop task
```
**Query Parameters for GET /tasks:**
- `status` - pending, completed, deleted
- `project` - Project name
- `priority` - L, M, H, D
- `tag` - Tag name (can specify multiple)
### Tags
```
GET /tasks/{uuid}/tags - Get task tags
POST /tasks/{uuid}/tags - Add tag
DELETE /tasks/{uuid}/tags/{tag} - Remove tag
GET /tags - List all tags
```
### Projects
```
GET /projects - List all projects
```
### Sync
```
POST /sync/changes - Get changes since timestamp
POST /sync/push - Push local changes
```
### API Keys
```
GET /auth/keys - List API keys
DELETE /auth/keys/{id} - Revoke API key
```
## Client Configuration
Sync settings are stored in `~/.config/jade/opal.yml`:
```yaml
# Task settings
default_filter: status:pending
default_sort: due,priority
color_output: true
week_start_day: monday
default_due_time: ""
# Sync settings
sync_enabled: true
sync_url: https://opal.yourdomain.com
sync_api_key: oak_abc123...
sync_client_id: 550e8400-e29b-41d4-a716-446655440000
sync_strategy: last-write-wins
sync_queue_offline: true
```
## Sync Strategies
- **last-write-wins** (default) - Most recently modified version wins
- **server-wins** - Server version always wins
- **client-wins** - Client version always wins
## Change Log Retention
By default, the change log retains entries for 30 days. This can be configured in the database:
```sql
UPDATE sync_config SET value = '60' WHERE key = 'change_log_retention_days';
```
Or programmatically:
```go
engine.SetChangeLogRetentionDays(60)
```
Cleanup runs manually or can be scheduled:
```bash
# In your cron or systemd timer
./opal server cleanup --db /var/lib/opal/opal.db
```
## Troubleshooting
### Server won't start
- Check if port 8080 is already in use: `sudo lsof -i :8080`
- Verify database path exists and is writable
- Check logs: `journalctl -u opal -f`
### Client can't connect
- Test server health: `curl https://opal.yourdomain.com/health`
- Verify API key is correct
- Check firewall allows traffic on port 8080
- Ensure reverse proxy is properly configured
### Sync conflicts
- View conflict log: `opal sync log`
- Conflicts are automatically resolved based on strategy
- By default, last-write-wins is used
### Offline changes not syncing
- Check queue status: `opal sync status`
- Verify `sync_queue_offline: true` in config
- Clear queue if needed: Delete `~/.config/jade/sync_queue.json`
## Database Schema
### Core Tables
- `tasks` - Main task storage
- `tags` - Task tags (many-to-many)
- `working_set` - Display ID mapping (client-local)
### Sync Tables
- `users` - User accounts (currently single shared user)
- `api_keys` - Authentication keys
- `sync_state` - Per-client sync state
- `change_log` - Change tracking (key:value format)
- `sync_config` - Server configuration
## Security Considerations
- API keys are hashed with bcrypt before storage
- Never log full API keys
- Use HTTPS in production (via reverse proxy)
- Implement rate limiting for production use
- Backup database regularly
- Rotate API keys periodically
## Future Enhancements
- OAuth2 with Authentik for web/mobile clients
- Per-user task separation with sharing
- Real-time sync via WebSockets
- Web/mobile frontend (SvelteKit PWA)
- Access control (share specific tags/projects)
- Audit logging
- Database backup automation
## Support
For issues or questions:
- Check logs: `journalctl -u opal -f`
- View sync status: `opal sync status`
- View conflicts: `opal sync log`
- Test connectivity: `curl https://opal.yourdomain.com/health`
## License
Same as opal-task main project.