Ingests Square invoices/payments, extracts event scheduling from order notes, and pushes approved entries to a CalDAV calendar after human review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.5 KiB
Order to Calendar
Pulls invoices and payments from Square, lets a human review and edit each one, and pushes approved entries to a CalDAV calendar. Mobile-friendly (works as a PWA — "Add to Home Screen"), multi-user login, ships as a single Docker image plus Postgres.
How it works
- A background job (or the "Sync now" button) polls the Square Invoices and Payments APIs and
turns new/updated invoices and payments into candidate calendar entries with status
PENDING. - Signed-in users review the pending queue on any device, edit fields if needed (title, date/time, description, location, customer info, amount), and either Approve or Reject each one.
- Approving creates (or updates) an event on your CalDAV calendar. Nothing reaches the calendar without a human approving it first.
Requirements
- A Square account with API credentials (developer.squareup.com → your app → Credentials).
- A CalDAV calendar (Nextcloud, Fastmail, iCloud, or self-hosted) — see docs/caldav-setup.md.
- Docker + Docker Compose on the server you're deploying to.
Deploying
cp .env.example .env
# edit .env: JWT_SECRET, BOOTSTRAP_ADMIN_*, POSTGRES_*, SQUARE_*, CALDAV_*
docker compose up -d --build
The app listens on port 3000 (PORT in .env) — put your existing reverse proxy (nginx, Caddy,
Traefik, etc.) in front of it for TLS. On first boot it runs database migrations and creates the
initial admin account from BOOTSTRAP_ADMIN_EMAIL / BOOTSTRAP_ADMIN_PASSWORD. Log in as that
admin, then create accounts for everyone else from Admin → New user — there is no open
self-registration.
To go live, generate a real Square access token for SQUARE_ENVIRONMENT=production once you've
verified the sandbox flow end to end.
Updating
git pull
docker compose up -d --build
Migrations run automatically on container start; existing data in the db-data volume is preserved.
Local development
npm install
npm run prisma:generate
# start a local Postgres however you like, then create the schema:
DATABASE_URL=postgresql://... npx prisma migrate dev --schema server/prisma/schema.prisma
npm run dev:server # backend on :3000
npm run dev:web # frontend on :5173, proxies /api to :3000
Configuration reference
See .env.example for every environment variable, with comments.
Key ones worth knowing:
| Variable | Purpose |
|---|---|
SQUARE_LOCATION_ID |
Leave blank to sync every location on the account, or set a comma-separated list to scope it. |
SYNC_INTERVAL_CRON |
How often the background sync runs (default: every 15 minutes). |
PAYMENT_DEFAULT_DURATION_MINUTES |
Payments have no inherent duration in Square — this sets the calendar event length. |
CALDAV_CALENDAR_URL |
Must be the specific calendar collection URL, not just the server root, and must end with /. |
Architecture notes
- Idempotent sync: re-running sync never duplicates a candidate (unique on Square ID + source type + environment). Once a candidate is approved or rejected it's frozen — a later Square-side edit won't silently overwrite a human decision.
- Idempotent CalDAV writes: each approved candidate keeps its CalDAV event UID/URL/etag, so editing an already-approved item and re-approving updates the existing event instead of creating a duplicate.
- Auth: httpOnly JWT cookie sessions (no separate session store), passwords hashed with argon2id.