Files
panel/docs/features/RADICALE_SYNC.md
RKWojs d4f16d344d feat: Implement comprehensive Contacts Management System with API and UI integration
- 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.
2026-01-16 11:11:29 +01:00

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_USERNAME and RADICALE_PASSWORD
    • Ensure user exists in Radicale
    • Check Radicale authentication method (basic auth vs htpasswd)

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

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 .env or .env.local file 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

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