Skip to content
Blog » Building a Calendar Sync Tool: A Claude Code Experiment

Building a Calendar Sync Tool: A Claude Code Experiment

A practical experiment in building a self-hosted calendar synchronisation tool using Claude Code as the primary development assistant. From architecture to deployment, here’s how AI-assisted development worked in practice.

Project Overview

This project is a self-hosted calendar synchronisation tool that syncs busy times between multiple Google calendars. The primary motivation was twofold: (1) the practical need to keep personal and work calendars in sync, and (2) an experiment to build an entire application using Claude Code as the primary development tool.

The application runs locally or on a cloud server, syncs calendars bidirectionally, and gives the user full control over their data.

Development Approach: Claude Code as Primary Developer

The entire codebase was developed through conversations with Claude Code. This included:

  • Architecture decisions and code structure
  • Python backend implementation with FastAPI
  • Frontend JavaScript/HTML development
  • Terraform infrastructure as code
  • Unit test creation
  • Production debugging and OAuth troubleshooting
  • Git workflow and deployment automation

All work was tracked in a PROJECT_JOURNAL.md file that Claude Code maintained throughout the development process, documenting decisions, bugs encountered, and solutions implemented.

Technical Stack

Component Technology
Backend Python 3.11, FastAPI
Authentication Google OAuth 2.0, JWT tokens
Scheduling APScheduler
Frontend Vanilla JavaScript, HTML, CSS
Database File-based JSON storage
Infrastructure Terraform, IONOS Cloud
Containerisation Docker, Docker Compose
Reverse Proxy Nginx with Let’s Encrypt SSL

Application Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Calendar Sync Application                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │   FastAPI    │  │  APScheduler │  │   Web UI     │       │
│  │   Backend    │  │  (30 min)    │  │  (Vanilla JS)│       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
│         │                │                   │               │
│         ▼                ▼                   ▼               │
│  ┌──────────────────────────────────────────────────┐       │
│  │                  Sync Engine                      │       │
│  │  - Event filtering (all-day, declined, free)     │       │
│  │  - Sync marker system ([AUTO-SYNC] prefix)       │       │
│  │  - Bidirectional loop prevention                 │       │
│  └──────────────────────────────────────────────────┘       │
│         │                                                    │
│         ▼                                                    │
│  ┌──────────────────────────────────────────────────┐       │
│  │              Google Calendar API                  │       │
│  │  - Per-user OAuth tokens                          │       │
│  │  - Read/write calendar access                     │       │
│  └──────────────────────────────────────────────────┘       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Backend Components

Core Files Structure

app/
├── main.py              # FastAPI application, routes, startup
├── models.py            # Pydantic models for all data structures
├── google_auth.py       # OAuth manager, credential storage
├── sync_engine.py       # Event sync logic, filtering, markers
├── scheduler.py         # APScheduler background jobs
├── auth/
│   ├── dependencies.py  # JWT authentication dependencies
│   └── jwt_handler.py   # Token creation and verification
├── routers/
│   ├── auth_router.py   # Login, logout, OAuth callbacks
│   ├── user_router.py   # Calendar connections
│   └── rules_router.py  # Sync rule CRUD operations
├── services/
│   └── rule_validator.py # Graph-based cycle detection
└── storage/
    ├── user_store.py    # User data persistence
    └── config_store.py  # Configuration persistence

Sync Engine Features

  • Event Filtering: Excludes all-day events, declined events, tentative events, and “free” time blocks
  • Sync Markers: All synced events get [AUTO-SYNC] prefix to prevent re-syncing
  • Bidirectional Support: A↔B sync allowed (protected by markers); circular chains A→B→C→A blocked via DFS graph validation
  • Per-User Credentials: Tokens stored in data/users/{user_id}/calendar_tokens/

API Endpoints

Method Endpoint Purpose
GET /api/status Dashboard status, scheduler info
POST /api/sync Trigger manual sync
GET /api/history Sync history log
GET /api/rules List sync rules
POST /api/rules Create sync rule
PUT /api/rules/{id} Update sync rule
DELETE /api/rules/{id} Delete sync rule
POST /api/rules/validate Validate rule without saving
GET /api/user/calendars List connected calendars
POST /api/user/calendars/connect Start OAuth flow
GET /api/auth/login Google OAuth login
POST /api/auth/refresh Refresh JWT token

Infrastructure: IONOS Cloud with Terraform

The infrastructure was provisioned using Terraform with the IONOS Cloud provider. This was part of the experiment to see how Claude Code would handle infrastructure as code.

Terraform Module Structure

infrastructure/terraform/
├── modules/
│   └── server/
│       ├── main.tf          # Server, datacenter, networking
│       ├── variables.tf     # Configurable parameters
│       ├── outputs.tf       # IP addresses, IDs
│       └── cloud-init.yaml  # Server bootstrap script
└── environments/
    ├── shared/              # Shared resources
    ├── development/         # Dev environment config
    └── production/          # Prod environment config

Resources Provisioned

  • IONOS Datacenter: Logical container for resources
  • vCPU Server: Ubuntu 24.04 LTS, configurable cores/RAM/disk
  • Public IP Block: Static IP for DNS
  • LAN: Public network access
  • Firewall Rules: SSH (22), HTTP (80), HTTPS (443), App (8000)
  • Cloud-init: Automated Docker, nginx, certbot installation

Server Configuration

The cloud-init script bootstraps the server with:

  • Docker and Docker Compose
  • Nginx reverse proxy
  • Let’s Encrypt SSL via Certbot
  • Automatic app deployment from GitHub

Unit Tests

Test files cover the core sync functionality:

test_rule_validator.py (12 tests)

  • Cycle detection in rule graphs
  • Bidirectional rule validation
  • Disabled rule exclusion from validation
  • Error messages for circular chains

test_sync_engine.py (11 tests)

  • Sync marker filtering
  • Event filtering (all-day, declined, free)
  • Event creation and updates
  • Cleanup of deleted source events

test_rules_api.py (18 tests)

  • CRUD operations on sync rules
  • Validation endpoint
  • Available calendars endpoint
  • Toggle enable/disable

Test Fixtures (conftest.py)

  • Mock Google Calendar client
  • Test user fixtures
  • Sample event data

OAuth Implementation Details

Login Flow

  1. User clicks “Sign in with Google”
  2. Redirect to Google OAuth consent screen
  3. Callback receives authorisation code
  4. Exchange code for tokens via HTTP POST
  5. Create user record, issue JWT tokens
  6. Redirect to dashboard with tokens in URL

Calendar Connection Flow

  1. User clicks “Connect Calendar”
  2. OAuth flow with calendar scope
  3. Tokens stored in user’s directory
  4. Calendar list fetched via Google API

Data Storage

data/
├── users/
│   └── {user-id}/
│       ├── user.json               # User profile
│       ├── sync_rules.json         # User's sync rules
│       └── calendar_tokens/
│           ├── personal@gmail.com.json
│           └── work@company.com.json
├── logs/
│   └── calendar-sync.log
└── sync_history.json

Deployment

Docker Compose Configuration

  • Single container running FastAPI with Uvicorn
  • Volume mounts for config and data persistence
  • Automatic restart policy

Production Deployment Commands

bash
ssh deploy@server
cd ~/apps/calendar-sync-prod
git pull origin main
docker compose build --no-cache
docker compose up -d

Development Timeline

Date Work Completed
Nov 25, 2025 Initial sync prototype
Nov 29, 2025 Project structure, GitHub setup, Claude Code upgrade
Dec 15, 2025 Sync rules management UI with loop prevention
Dec 15, 2025 OAuth fixes, scope mismatch handling
Dec 15, 2025 Per-user credential system
Dec 15, 2025 Dashboard status endpoint fix
 
 

Leave a Reply

Your email address will not be published. Required fields are marked *