- Added new `contacts` and `project_contacts` database tables for managing contacts. - Created API endpoints for CRUD operations on contacts and linking them to projects. - Developed UI components including `ContactForm` and `ProjectContactSelector`. - Integrated navigation and translations for Polish language support. - Documented usage, features, and future enhancements for the contacts system. feat: Introduce DOCX Template System for generating documents from templates - Enabled creation and management of DOCX templates with placeholders for project data. - Documented the process for creating templates, uploading, and generating documents. - Included detailed information on available variables and custom data fields. - Implemented troubleshooting guidelines for common issues related to template generation. feat: Add Radicale CardDAV Sync Integration for automatic contact synchronization - Implemented automatic syncing of contacts to a Radicale server on create/update/delete actions. - Documented setup instructions, including environment variable configuration and initial sync script. - Provided troubleshooting steps for common sync issues and error codes. feat: Develop Route Planning Feature with Optimization using OpenRouteService API - Integrated multi-point routing and automatic optimization for project locations. - Documented setup, usage, and technical implementation details for route planning. - Included performance considerations and troubleshooting for common routing issues. chore: Remove unnecessary files and scripts from the codebase - Deleted temporary, debug-related, and test-specific files that are not needed in production. - Reviewed and ensured core application code and essential documentation remain intact.
8.7 KiB
Radicale CardDAV Sync Integration
This application now automatically syncs contacts to a Radicale CardDAV server whenever contacts are created, updated, or deleted.
Features
- ✅ Automatic Sync - Contacts are automatically synced when created or updated
- ✅ Automatic Deletion - Contacts are removed from Radicale when soft/hard deleted
- ✅ Non-Blocking - Sync happens asynchronously without slowing down the API
- ✅ Optional - Sync is disabled by default, enable by configuring environment variables
- ✅ VCARD 3.0 - Generates standard VCARD format with full contact details
Setup
1. Configure Environment Variables
Add these to your .env.local or production environment:
RADICALE_URL=http://localhost:5232
RADICALE_USERNAME=your_username
RADICALE_PASSWORD=your_password
Note: If these variables are not set, sync will be disabled and the app will work normally.
2. Radicale Server Setup
Make sure your Radicale server:
- Is accessible from your application server
- Has a user created with the credentials you configured
- Has a contacts collection at:
{username}/contacts/
3. One-Time Initial Sync
To sync all existing contacts to Radicale:
node export-contacts-to-radicale.mjs
This script will:
- Prompt for Radicale URL, username, and password
- Export all active contacts as VCARDs
- Upload them to your Radicale server
How It Works
When Creating a Contact
// POST /api/contacts
const contact = createContact(data);
// Sync to Radicale asynchronously (non-blocking)
syncContactAsync(contact);
return NextResponse.json(contact);
When Updating a Contact
// PUT /api/contacts/[id]
const contact = updateContact(contactId, data);
// Sync updated contact to Radicale
syncContactAsync(contact);
return NextResponse.json(contact);
When Deleting a Contact
// DELETE /api/contacts/[id]
deleteContact(contactId);
// Delete from Radicale asynchronously
deleteContactAsync(contactId);
return NextResponse.json({ message: "Contact deleted" });
VCARD Format
Each contact is exported with the following fields:
- UID:
contact-{id}@panel-app - FN/N: Full name and structured name
- ORG: Company
- TITLE: Position/Title
- TEL: Phone numbers (multiple supported - first as WORK, others as CELL)
- EMAIL: Email address
- NOTE: Contact type + notes
- CATEGORIES: Based on contact type (Projekty, Wykonawcy, Urzędy, etc.)
- REV: Last modified timestamp
VCARD Storage Path
VCARDs are stored at:
{RADICALE_URL}/{RADICALE_USERNAME}/contacts/contact-{id}.vcf
Example:
http://localhost:5232/admin/contacts/contact-123.vcf
🔧 Troubleshooting
Common Issues
Problem: Sync not working / contacts not appearing in Radicale
Check 1: Environment Variables
# Verify variables are set
echo $RADICALE_URL
echo $RADICALE_USERNAME
# Don't echo password for security
Check 2: Radicale Server Connectivity
# Test server is reachable
curl -I http://your-radicale-server:5232
# Test authentication
curl -u username:password http://your-radicale-server:5232/username/contacts/
Check 3: Application Logs Look for sync messages in your application console:
✅ Synced contact 123 to Radicale
❌ Failed to sync contact 456 to Radicale: 401 - Unauthorized
Problem: 401 Unauthorized errors
- Cause: Invalid credentials or user doesn't exist
- Solution:
- Verify
RADICALE_USERNAMEandRADICALE_PASSWORD - Ensure user exists in Radicale
- Check Radicale authentication method (basic auth vs htpasswd)
- Verify
Problem: 404 Not Found errors
- Cause: Contacts collection doesn't exist
- Solution:
- Create collection in Radicale:
/{username}/contacts/ - Verify collection URL matches
RADICALE_URL - Check Radicale collection rights and permissions
- Create collection in Radicale:
Problem: Network timeout or connection refused
- Cause: Radicale server not accessible from app server
- Solution:
- Check firewall rules
- Verify Radicale is running:
systemctl status radicale - Test with curl from app server
- If using Docker, ensure network connectivity
Problem: Contacts created but not syncing
- Cause: Environment variables not loaded or sync disabled
- Solution:
- Restart application after setting env vars
- Check
.envor.env.localfile exists - Verify Next.js loaded environment: check server startup logs
- Test with manual export script:
node export-contacts-to-radicale.mjs
Problem: Duplicate contacts in Radicale
- Cause: Re-running export script or UID conflicts
- Solution:
- UIDs are unique:
contact-{id}@panel-app - Existing contacts are overwritten on update
- Delete duplicates manually in Radicale if needed
- UIDs are unique:
Problem: VCARD format errors in Radicale
- Cause: Invalid characters or incomplete data
- Solution:
- Check contact has at least name field
- Special characters in names are escaped
- Phone/email fields are optional
- Review contact data for completeness
Monitoring Sync Status
Enable Detailed Logging
Edit src/lib/radicale-sync.js to increase logging verbosity:
// Add more console.log statements
console.log('Syncing contact:', contact);
console.log('VCARD:', vcard);
console.log('Response:', await response.text());
Check Radicale Server Logs
# Typical log location
tail -f /var/log/radicale/radicale.log
# Or check systemd journal
journalctl -u radicale -f
Manual Sync Test Test individual contact sync:
# Use the export script for a single contact
node export-contacts-to-radicale.mjs
# Select specific contact when prompted
Disable Sync Temporarily
Comment out environment variables to disable sync without removing configuration:
# .env.local
# RADICALE_URL=http://localhost:5232
# RADICALE_USERNAME=admin
# RADICALE_PASSWORD=secret
Application will function normally without sync enabled.
Manual Sync Endpoint
For manual sync control, you can trigger sync via API:
# Sync specific contact
POST /api/contacts/{id}/sync
# Response
{
"success": true,
"message": "Contact synced to Radicale"
}
Error Codes Reference
| Code | Meaning | Solution |
|---|---|---|
| 401 | Unauthorized | Check credentials |
| 403 | Forbidden | Verify user has write permissions |
| 404 | Not Found | Create contacts collection |
| 409 | Conflict | UID collision (rare) |
| 500 | Server Error | Check Radicale server logs |
| ECONNREFUSED | Connection Refused | Server not reachable |
| ETIMEDOUT | Timeout | Network/firewall issue |
📋 Configuration Reference
Required Environment Variables
RADICALE_URL=http://localhost:5232
RADICALE_USERNAME=your_username
RADICALE_PASSWORD=your_password
Default Settings
- Collection Path:
/{username}/contacts/ - VCARD Version: 3.0
- UID Format:
contact-{id}@panel-app - Sync Mode: Asynchronous (non-blocking)
- Retry Logic: None (fire-and-forget)
📂 Files Reference
| File | Purpose |
|---|---|
src/lib/radicale-sync.js |
Core sync logic, VCARD generation |
src/app/api/contacts/route.js |
Create sync trigger |
src/app/api/contacts/[id]/route.js |
Update/delete sync triggers |
export-contacts-to-radicale.mjs |
Bulk export utility |
🔒 Security Best Practices
✅ Do's:
- Use HTTPS for production Radicale servers
- Store credentials in environment variables (never in code)
- Use strong, unique passwords
- Limit Radicale user permissions to contacts collection only
- Regularly rotate credentials
- Use separate credentials per environment (dev/staging/prod)
❌ Don'ts:
- Don't commit credentials to git
- Don't use HTTP in production
- Don't share credentials between environments
- Don't log passwords or sensitive data
- Don't grant unnecessary permissions
🚀 Advanced Configuration
Custom Collection Path
Modify src/lib/radicale-sync.js:
const baseUrl = `${process.env.RADICALE_URL}/${process.env.RADICALE_USERNAME}/my-custom-collection/`;
Batch Sync Operations
For large-scale sync (future enhancement):
// Collect contacts, then sync in batches
const batchSize = 50;
// Implement batch logic
Webhook Integration
Future: Trigger webhooks on sync events:
// POST to webhook URL on sync success/failure
fetch(WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({ event: 'contact_synced', contact_id: id })
});
See Also: Contacts System | Main README | API Documentation