A Next.js background orchestrator that automatically syncs tasks from Tududi (self-hosted task manager) to Google Calendar. Tasks are intelligently ranked by priority, type (Focus/Noise), and project, then scheduled into calendar gaps with Pomodoro-style break events.
- Automatic Task Scheduling: Fetches tasks from Tududi and schedules them into Google Calendar gaps
- Smart Task Ranking: Weighted scoring algorithm considers:
- Priority (35%): High/medium/low priority weighting
- Task Type (20%): Focus vs Noise tasks
- Project Importance (20%): Per-project weighting
- Urgency (15%): Based on due dates
- Energy Level (10%): Focus tasks get higher energy scores
- Energy-Aware Scheduling: Focus tasks are prioritized for peak hours (configurable), Noise tasks fill remaining gaps
- Pomodoro Break Events: Automatic break insertion:
- 15-minute breaks after tasks < 1 hour
- 30-minute breaks after tasks ≥ 1 hour
- Emoji Status Tracking: Visual indicators in task names:
- 📅 Task scheduled to calendar
⚠️ Problem detected (event deleted or overdue)- ❌ Past due date
- Automatic Re-scheduling: Incomplete tasks are automatically re-scheduled with priority boost after configurable timeout (default: 12 hours)
- Per-Project Calendar Mapping: Different projects can sync to different Google Calendars
- Customizable Scheduling Windows: Set different hours for each day of the week
- Peak Hours: Define your most productive hours for Focus tasks
- Duration Matrix: Configure estimated task duration by type and priority
- Ranking Weights: Adjust the algorithm to match your workflow
- Sync Interval: Configure how often the sync runs (default: 15 minutes)
- SQLite Database: Local storage for sync state and configuration
- Background Sync: node-cron scheduler runs automatically
- Mutex Protection: Prevents concurrent sync cycles
- Idempotent Operations: Safe to retry without duplicates
- Health Monitoring: Built-in health check endpoint
- Docker Ready: Production deployment with Docker Compose
- Framework: Next.js 15 with App Router
- Language: TypeScript
- Database: SQLite with Drizzle ORM
- Scheduler: node-cron
- Authentication: Google OAuth2
- Styling: Tailwind CSS
- Testing: Vitest
- Deployment: Docker Compose
- Docker and Docker Compose
- Google Cloud Console account (for OAuth credentials)
- Tududi instance (self-hosted)
git clone <repository-url>
cd calendar-task-sync
# Copy environment template
cp .env.example .env
# Edit .env with your credentials
nano .envEdit .env with your settings:
# Database (default is fine for Docker)
DATABASE_URL=/app/data/app.db
# Google OAuth - Required
# Get these from Google Cloud Console → APIs & Services → Credentials
GOOGLE_CLIENT_ID=your_google_client_id_here
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/google/callback
# Tududi API - Optional (can configure in UI)
TUDUDI_API_KEY=your_tududi_api_key
TUDUDI_API_URL=https://your-tududi-instance.com/api/v1
# App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=production# Build and start
docker compose up -d
# View logs
docker compose logs -f
# Check health
curl http://localhost:3000/api/health- Open http://localhost:3000 in your browser
- Go to Settings
- Configure Tududi connection (API URL and key)
- Connect Google Calendar account
- Set up calendar mappings and scheduling preferences
- Click "Sync Now" or wait for automatic sync
calendar-task-sync/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (dashboard)/ # Dashboard UI
│ │ │ ├── page.tsx # Main dashboard
│ │ │ ├── settings/ # Configuration page
│ │ │ └── layout.tsx # Dashboard layout
│ │ ├── api/ # API routes
│ │ │ ├── auth/google/ # OAuth endpoints
│ │ │ ├── dashboard/ # Dashboard data API
│ │ │ ├── health/ # Health check
│ │ │ └── sync/ # Manual sync trigger
│ │ └── actions/ # Server actions
│ ├── db/ # Database schema & client
│ │ ├── schema.ts # Drizzle ORM schema
│ │ └── index.ts # Database client
│ ├── lib/ # Core libraries
│ │ ├── engine/ # Scheduling engine (pure functions)
│ │ │ ├── ranker.ts # Task ranking algorithm
│ │ │ ├── gap-finder.ts # Calendar gap detection
│ │ │ ├── scheduler.ts # Task placement
│ │ │ ├── emoji.ts # Emoji prefix management
│ │ │ └── types.ts # Type definitions
│ │ ├── tududi/ # Tududi API client
│ │ ├── google/ # Google OAuth & Calendar
│ │ └── sync/ # Sync orchestrator
│ │ ├── orchestrator.ts # Main sync loop
│ │ ├── mutex.ts # Concurrency protection
│ │ └── db-operations.ts # Database operations
│ └── __tests__/ # Unit tests
├── instrumentation.ts # Cron initialization
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose config
└── .env.example # Environment template
- Go to Google Cloud Console
- Create a new project or select existing
- Navigate to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Configure consent screen (External type for personal use)
- Add scopes:
https://www.googleapis.com/auth/calendarandhttps://www.googleapis.com/auth/calendar.events - Create OAuth client ID (Web application type)
- Add authorized redirect URI:
http://localhost:3000/api/auth/google/callback - Copy Client ID and Client Secret to
.env
- Ensure your Tududi instance is accessible
- Generate an API key from Tududi settings
- Note your Tududi API base URL (e.g.,
https://tududi.example.com/api/v1) - Configure in the app Settings page
In the Settings page, you can configure:
- Calendar Mappings: Map each Tududi project to a Google Calendar
- Scheduling Windows: Set available hours per day
- Peak Hours: Your most productive time for Focus tasks
- Duration Matrix: Estimated time for tasks by type/priority
- Break Rules: Configure break durations
- Ranking Weights: Adjust task scoring algorithm
- Sync Settings: Interval, timezone, and re-schedule timeout
- Background Sync: node-cron runs the sync cycle at configured intervals
- Fetch Tasks: Retrieves tasks from Tududi API (filtered by due date and status)
- Rank Tasks: Applies weighted scoring to prioritize tasks
- Find Gaps: Queries Google Calendar free/busy to find available slots
- Schedule: Places tasks in calendar gaps, respecting peak hours and breaks
- Update Tududi: Adds 📅 emoji prefix to scheduled task names
- Monitor: On next sync, checks for completions and overdue tasks
- Re-schedule: Automatically re-schedules incomplete tasks after timeout
GET /api/health- Health check with sync statusGET /api/auth/google- Initiate Google OAuthGET /api/auth/google/callback- OAuth callbackPOST /api/sync/trigger- Manual sync triggerGET /api/dashboard/stats- Dashboard statisticsGET /api/dashboard/scheduled- Scheduled tasks listGET /api/dashboard/activity- Recent activity
# Install dependencies
npm install
# Set up database
npx drizzle-kit push
# Seed default config
npm run db:seed
# Run dev server
npm run dev
# Run tests
npm test
# Run tests with coverage
npm run test:coverage# Open Drizzle Studio
npm run db:studio
# Push schema changes
npm run db:pushThe project includes comprehensive unit tests:
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run specific test file
npx vitest run src/__tests__/engine/ranker.test.tsTest Coverage:
- Engine: 90.59% (ranker, gap-finder, scheduler, emoji)
- Google: 85.67% (OAuth, Calendar API)
- Total: 87 tests passing
# Production deployment
docker compose up -d
# View logs
docker compose logs -f
# Restart
docker compose restart
# Stop
docker compose down- Build the application:
npm run build - Set environment variables
- Start with:
node .next/standalone/server.js
- Check health endpoint:
curl http://localhost:3000/api/health - Verify cron is initialized in logs
- Check Google OAuth connection in Settings
- Verify Tududi API credentials
- Check Google Calendar has available slots
- Review duration matrix settings
- Check sync logs for errors
- Ensure
./datadirectory exists and is writable - Check SQLite WAL mode is enabled
- Verify DATABASE_URL path
- Pure Scheduling Engine: Core algorithm has no side effects, making it fully testable
- SQLite: Lightweight, zero-config database perfect for single-user deployment
- No Provider Abstraction: Direct Google Calendar integration keeps code simple
- Single-User: No authentication layer needed for personal use
- instrumentation.ts: Canonical Next.js pattern for background jobs in standalone mode
MIT License - See LICENSE file for details
Contributions welcome! Please ensure:
- TypeScript compiles without errors (
npx tsc --noEmit) - All tests pass (
npm test) - Follow existing code patterns
- Add tests for new features
For issues and feature requests, please use the GitHub issue tracker.