Skip to content

Repository files navigation

SynCal: Distributed Calendar Synchronization & Scheduling Infrastructure Platform

SynCal is a production-grade, highly resilient, multi-tenant scheduling intelligence platform. It features robust rate-limit honor rules per provider (Google, Microsoft, iCloud CalDAV, iCal), webhook buffering, and stateful workflow synchronization orchestration powered by Cadence.


System Architecture Overview

SynCal relies on a modern distributed architecture designed for maximum reliability, durability, and scalability:

  • Ingress Webhook Gateway (ingress): Ingests webhook notifications from Google Calendar and Microsoft Graph. It buffers messages to Redis Pub/Sub to prevent overloading the Cadence Cluster.
  • Pub/Sub Signal Consumer (consumer): A daemon that consumes buffered events and signals the relevant stateful sync workflows.
  • API Gateway (api): Exposes B2B tenant registration APIs, token ingestion endpoints (/v1/accounts/import), OAuth redirection pipelines, and booking slot availability calculators.
  • Cadence Workflow Engine: Runs long-lived, stateful orchestration workflows for calendar syncing (CalendarSyncWorkflow) and recurring webhook subscription renewals (WebhookRenewalWorkflow) on an external Cadence cluster.
  • React Dashboard UI: A dashboard designed following Apple's macOS Human Interface Guidelines (translucent interfaces, status indicators, and SLA/SLO latency graphs).
  • Databases:
    • PostgreSQL: Main local application store containing tables for tenants, accounts, calendars, and events.
    • Redis: Local caching, dynamic sliding-window rate-limiting, and Pub/Sub webhook queuing.

Prerequisites

Before running the platform, ensure you have the following installed on your machine:

  • Docker (and Docker Compose V2)
  • Go 1.23+ (optional, only if running or compiling tests locally without Docker)
  • Node.js 18+ (optional, only if running frontend dev server locally)

Quick Start using Makefile

The project includes a root Makefile that simplifies compiling, launching, checking status, and running tests.

Development Mode (Local Hot-Reloading)

For rapid development, you can run the frontend and backend directly on your host machine without rebuilding Docker containers:

  • Start React Frontend (Vite hot-reload dev server):
    make start-ui
  • Start Go Backend (Runs all 3 microservices concurrently in the background):
    make start-backend

1. Build Docker Images

Compile the React frontend static assets, the Nginx container, and build all three Go service binaries inside the multi-stage Go image:

make build

2. Start the Cluster

Launch the PostgreSQL database, the Redis service, the worker daemons, and the dashboard portal in the background:

make up

Note: On start, the PostgreSQL container will automatically seed the calendar database schema. The backend services (api and consumer) will connect directly to the external Cadence cluster at dev-cadence.auenkr.com:7933 using the domain default.

3. Display Running Endpoints & Portals

To view where all user interfaces and APIs are mapped, run:

make web

This prints the following service directory:

4. Monitor Infrastructure Logs

To tail runtime log outputs from the backend services, consumers, or Cadence workflow worker loops, execute:

make logs

5. Run Unit & Integration Tests

Execute the cryptographic envelope, token validation, and scheduling availability intersection tests:

make test

6. Stop Services

To pause execution and shut down the docker containers:

make down

7. Full Reset & Clean Volumes

To wipe all state (including active PostgreSQL database rows and cached Redis limits) to run a clean bootstrap:

make clean

API Reference

The full machine-readable spec lives at docs/openapi.yaml (OpenAPI 3.0). Import it into Postman, Insomnia, or Swagger UI to get interactive docs.

Authentication

API Header When to use
Tenant API Key X-API-Key: cs_live_<prefix>.<secret> All tenant integrations
Admin Bearer Authorization: Bearer <token> Internal dashboard only

Tenant API keys are generated from the Admin Console → Tenants → Keys. Keys are shown once at creation — store them in your secrets manager immediately.


Tenant-Facing Endpoints (API Key required)

These are the endpoints your integration code calls using X-API-Key.

Account Connection

Method Path Description
GET /v1/oauth/authorize Generate a provider OAuth URL to redirect your user to
GET /v1/oauth/callback Provider redirect target — handled automatically
POST /v1/accounts/import Import an account using existing OAuth tokens (Pattern B)
POST /v1/caldav/connect Connect a CalDAV server (iCloud, Nextcloud, Fastmail)
POST /v1/ical/connect Connect a read-only iCal .ics feed URL
GET /v1/tenant-portal/accounts List all connected accounts for your tenant
POST /v1/tenant-portal/oauth-url Server-side helper to build an OAuth authorize path

OAuth flow (Pattern A — recommended):

1. GET /v1/oauth/authorize?provider=google&tenant_id=<tid>&entity_id=<your-user-id>&redirect_uri=<your-callback>
   → Returns { "url": "https://accounts.google.com/..." }

2. Redirect user to that URL.

3. Provider calls back to /v1/oauth/callback → your redirect_uri receives:
   ?status=success&account_id=<uuid>&entity_id=<id>&provider=google&email=user@example.com

4. Use account_id to present a calendar picker (see Calendar endpoints below).

Token import (Pattern B):

curl -X POST http://localhost:8080/v1/accounts/import \
  -H "X-API-Key: cs_live_a1b2c3d4.zXyWvUt..." \
  -H "Content-Type: application/json" \
  -d '{
    "entity_id": "user-abc-123",
    "provider": "google",
    "email": "alice@example.com",
    "credentials": {
      "access_token": "ya29.a0...",
      "refresh_token": "1//04..."
    }
  }'

Calendar Management

Method Path Description
GET /v1/users/:userId/calendars List all calendars for a user (by entity_id)
PATCH /v1/calendars/:id/sync-direction Set inbound / outbound / both
GET /v1/calendars/:id/events List events (cached or live from provider)

GET /v1/users/:userId/calendars — List every calendar for a user across all their connected accounts (Google + Outlook etc. in one call):

curl http://localhost:8080/v1/users/user-abc-123/calendars \
  -H "X-API-Key: cs_live_a1b2c3d4.zXyWvUt..."
{
  "user_id": "user-abc-123",
  "count": 2,
  "calendars": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "alice@gmail.com",
      "provider": "google",
      "sync_direction": "both",
      "is_primary": true,
      "bootstrapped": true,
      "conflict_count": 0,
      "account_id": "...",
      "email": "alice@gmail.com",
      "account_status": "active"
    },
    {
      "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "name": "Work Calendar",
      "provider": "outlook",
      "sync_direction": "inbound",
      "is_primary": true,
      "bootstrapped": true,
      "conflict_count": 0
    }
  ]
}

PATCH /v1/calendars/:id/sync-direction — Change how a calendar syncs:

sync_direction Provider → SynCal SynCal → Provider
inbound
outbound
both
curl -X PATCH http://localhost:8080/v1/calendars/550e8400-.../sync-direction \
  -H "X-API-Key: cs_live_..." \
  -H "Content-Type: application/json" \
  -d '{"sync_direction": "inbound"}'
{ "calendar_id": "550e8400-...", "sync_direction": "inbound" }

Admin-only: To set direction to none (fully disable syncing), use PATCH /v1/calendars/:id/direction with an admin bearer token.


Sync Status

Method Path Description
GET /v1/calendars/:id/last-synced Last successful inbound & outbound timestamps
GET /v1/tenants/metrics/sync Aggregate sync health (conflicts, pending, lag percentiles)

GET /v1/calendars/:id/last-synced — Know exactly when a calendar last synced in each direction:

curl http://localhost:8080/v1/calendars/550e8400-.../last-synced \
  -H "X-API-Key: cs_live_..."
{
  "calendar_id": "550e8400-e29b-41d4-a716-446655440000",
  "last_inbound_sync": "2026-06-01T07:45:00Z",
  "last_outbound_sync": "2026-06-01T07:44:30Z"
}

Both fields are null when no sync of that type has run (e.g. inbound-only calendar → last_outbound_sync: null).


Events

Method Path Description
GET /v1/calendars/:id/events List events (DB cache; ?live=true fetches from provider)
GET /v1/calendars/:id/events/:eventId Get a single event
POST /v1/calendars/:id/events Create an event (async, 202 Accepted)
PATCH /v1/calendars/:id/events/:eventId Update an event (async, 202 Accepted)
DELETE /v1/calendars/:id/events/:eventId Delete an event (async, 202 Accepted)

Event writes are async — they return 202 Accepted with a job_id. The outbound worker pushes the change to the provider while honouring per-provider rate limits. Calendars with sync_direction=inbound reject write requests with 422 Unprocessable Entity.

# Create an event
curl -X POST http://localhost:8080/v1/calendars/550e8400-.../events \
  -H "X-API-Key: cs_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Quarterly Review",
    "start_time": "2026-06-15T14:00:00Z",
    "end_time": "2026-06-15T15:00:00Z",
    "timezone": "America/New_York"
  }'
{
  "job_id": "abc12345-...",
  "calendar_id": "550e8400-...",
  "operation": "create",
  "status": "queued"
}

Conflict Resolution

Method Path Description
GET /v1/calendars/:id/conflicts List events in conflict state
POST /v1/calendars/:id/events/:eventId/resolve Resolve a conflict
GET /v1/calendars/:id/pending List events waiting for tenant ACK (pending_inbound)
POST /v1/calendars/:id/events/:eventId/ack Acknowledge a pending inbound event

Resolve strategies: provider_wins · local_wins · last_write_wins · manual

curl -X POST http://localhost:8080/v1/calendars/550e8400-.../events/evt-uuid.../resolve \
  -H "X-API-Key: cs_live_..." \
  -d '{"strategy": "provider_wins"}'

Admin Endpoints (Bearer token required)

These are only accessible after signing in to the dashboard at http://localhost:3000.

Tenant & Account Management

Method Path Description
GET / POST /v1/tenants List / create tenants
PATCH / DELETE /v1/tenants/:id Update / delete a tenant
POST / GET /v1/tenants/:id/keys Generate / list API keys
GET /v1/tenants/:id/accounts List accounts with calendar details
DELETE /v1/tenants/:id/accounts/:accountId Disconnect an account
GET / POST /v1/accounts/:id/calendars/available Browse / add provider calendars
DELETE /v1/calendars/:id Stop tracking a calendar + unregister webhook
PATCH /v1/calendars/:id/direction Set direction incl. none
POST /v1/calendars/:id/force-sync Trigger an immediate sync
POST /v1/calendars/:id/re-register-webhook Re-register provider webhook (after URL change)
POST / GET /v1/tenants/:id/oauth-configs Configure / list BYO OAuth credentials
GET / POST /v1/tenant-webhooks List / register outbound webhook endpoints
DELETE /v1/tenant-webhooks/:id Remove an outbound webhook
PATCH /v1/accounts/:id/sync-strategy Set per-account sync strategy (fcfs / round_robin)
PATCH /v1/tenants/:id/sync-strategy Set tenant-default sync strategy

Rate Limits

Method Path Description
GET / POST /v1/tenants/:id/provider-rate-limits Configure per-provider quotas
GET / POST /v1/tenants/:id/rate-limits Configure tenant & user RPM limits
GET /v1/system/rate-buckets View active Redis rate-limit buckets
DELETE /v1/system/rate-buckets/reset Reset a stuck concurrency counter

Monitoring & DLQ

Method Path Description
GET /v1/system/metrics Platform-wide SLO metrics (scoped with ?tenant_id=)
GET /v1/system/metrics/rates Rolling sync success rates (30 min / 1 hr / 7 d / 30 d)
GET /v1/system/metrics/windows Time-window sync breakdown table
GET /v1/system/metrics/provider-breakdown Per-provider sync stats
GET /v1/system/metrics/provider-status Provider success / failure / rate-limited pie data
GET /v1/system/metrics/provider-windows Provider × window matrix
GET /v1/system/metrics/calendar-sync-rates Per-calendar sync rates across all windows
GET /v1/system/metrics/kafka Kafka consumer lag for the webhook topic
GET /v1/system/sync-feed Recent sync activity feed
GET /v1/system/dlq Dead-letter queue items
POST /v1/system/dlq/:id/retry Retry a failed DLQ item
DELETE /v1/system/dlq/:id Discard a DLQ item
GET /v1/system/calendars/:id/events Admin event viewer with optional live provider diff

Provider Webhook Ingress (called by providers, not by you)

Method Path Description
POST /v1/webhooks/google Google Calendar push notification receiver
POST /v1/webhooks/outlook Microsoft Graph push notification receiver

New Endpoints (v1.0 additions)

Three new tenant-facing endpoints were added in the latest release:

Method Path Auth Purpose
GET /v1/users/:userId/calendars API Key List all calendars for a user across every connected account
PATCH /v1/calendars/:id/sync-direction API Key Set inbound / outbound / both for a specific calendar
GET /v1/calendars/:id/last-synced API Key Retrieve the last successful sync timestamp per direction

See the sections above for full request/response examples.


Directory & File Layout

  • backend/Dockerfile - Multi-stage runner building all backend services.
  • backend/migrations/ - Database schema init scripts.
  • backend/cmd/ - Microservice endpoints (api, ingress, consumer).
  • backend/internal/ - Authentication middleware, dynamic credential resolvers, rate limiters, locks, and scheduling availability code.
  • backend/internal/workflows/ - Cadence workflow state loops and provider integration activities.
  • frontend/Dockerfile - Nginx SPA production container.
  • frontend/nginx.conf - Config mapping deep routing endpoints.
  • frontend/src/ - React components, sidebar navigation, consoles, and recharts panels.

About

Calendar Integration made easy.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages