Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Booking calendars for a short-term rental manager and the owners of the apartments they run. The manager sees every property on one live tape chart; each owner signs in and sees only their own. Bookings come from Booking.com and Airbnb iCal feeds, and a Booking.com email triggers a sync within seconds.
Built with Next.js 16, Supabase (Postgres, Auth, Row Level Security, Realtime) and Vercel, on free tiers only.
Live demo: https://booking-calendar-demo.vercel.app. Fake data; sign in with one click as the manager or an owner.
| Owner view (phone) | Admin on a phone | Managing properties |
|---|---|---|
Manager (admin)
Owner
flowchart LR
subgraph Channels
BK[Booking.com]
AB[Airbnb]
end
BK -- "notification email" --> MB[Manager's mailbox]
MB -- "auto-forward" --> RS["Resend inbound<br/>(or Cloudflare Worker)"]
RS -- "signed webhook<br/>/api/inbound/resend" --> APP
CRON["Supabase pg_cron<br/>every 5 min"] -- "GET /api/cron/sync<br/>(bearer secret)" --> APP
ADM[Admin: Refresh now] --> APP
subgraph Vercel
APP["Next.js app<br/>sync service"]
end
APP -- "fetch .ics" --> BK
APP -- "fetch .ics" --> AB
APP -- "upsert / cancel<br/>(secret key)" --> DB[(Supabase Postgres<br/>RLS)]
DB -- "Realtime<br/>(RLS applied)" --> UI[Admin and owner browsers]
UI -- "reads as the signed-in user" --> DB
.ics feed and parse its events with node-ical. DTEND is the checkout day and stays exclusive.cancelled. Past and in-progress stays are kept, because feeds drop old events on their own.last_synced_at or last_sync_error on the channel, and log the run in sync_events."Today" is computed in Europe/Warsaw, where the apartments are, not in the server's UTC.
| Trigger | When | Notes |
|---|---|---|
| Seconds after Booking.com emails the manager | Finds the apartment by Booking.com property ID; when several apartments share that property (one room type each), the room name in the email decides. Syncs everything if unsure. Debounced for 20 s, with one follow-up sync 45 s later because Booking can update the feed after the email. | |
| Cron | Every 5 minutes (Supabase pg_cron + pg_net) | The safety net. The Vercel free plan only allows daily cron jobs, and GitHub Actions schedules can run hours late, so the database schedules it. A GitHub workflow stays as a backup. |
| Manual | "Refresh now" | For the whole portfolio or a single property. |
channels, which hold the iCal URLs, or to sync_events. Tests sign in as real users and prove that one owner cannot read another's data, including over Realtime (rls.test.ts, realtime.test.ts).ical.booking.com/…a1b2, and sync errors never contain the URL.owner, whatever the signup metadata says.guest_stays) with RLS (admin writes; owners read their own apartments only), and a hand-entered name is never overwritten by an email. Email bodies are never stored; look-alike sender domains are rejected.Next.js 16 (App Router, Server Actions, after()), TypeScript, Tailwind CSS 4 · Supabase Postgres, Auth (magic link + password), RLS, Realtime · node-ical · Vitest · GitHub Actions · Cloudflare Email Workers.
src/
app/ routes: /admin, /admin/properties, /admin/owners, /owner, /login, /api/*
components/ header, forms, live updates
lib/
sync/ iCal fetch, parse, diff, run, Supabase store, demo feeds
email-trigger/ payload, sender check, property matching, handler
calendar/ tape chart layout, occupancy, filters
admin/ owner/ page view models and data loading
realtime/ change -> toast mapping
supabase/
migrations/ schema, RLS policies, Realtime
seed.sql fake demo data
templates/ magic link and invite emails
tests/db/ integration tests against local Supabase
workers/email-trigger/ Cloudflare Email Worker
Most logic lives in pure functions with unit tests next to them. Pages only load data and render.
Requirements: Node 24 and Docker Desktop.
npm install
npx supabase start # local Postgres, Auth, Realtime, Mailpit
cp .env.example .env.local # then fill in the values from `npx supabase status -o env`
npm run sync # import the demo feeds into the seed data
npm run dev
Open http://127.0.0.1:3000 and sign in as [email protected] (or the owner [email protected]). The magic link arrives in Mailpit at http://127.0.0.1:54324.
The seed data points at demo:// feeds, which the app generates itself when DEMO_FEEDS=true. The whole sync pipeline runs for real without any Booking.com account, and one feed fails on purpose to show what sync errors look like.
| Command | What it does |
|---|---|
npm test | Unit tests |
npm run test:db | Integration tests (RLS, sync, Realtime); needs supabase start |
npm run lint / npm run typecheck | Static checks |
npm run db:reset | Recreate the local database from migrations and seed, then run npm run sync |
npm run db:types | Regenerate src/lib/database.types.ts |
npx supabase link and npx supabase db push. Add --include-seed only for a demo with fake data.
https://<your-app>/** to the redirect URLs.supabase/config.toml. The sign-in link goes to /auth/confirm?token_hash=…; the invitation goes to /invite?token={{ .Data.invite_token }}, a link that never expires and works until the invitation is accepted. Under Security notifications, turn on "Password changed" with password_changed_notification.html.NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEYCRON_SECRET, SYNC_TRIGGER_SECRET (long random strings)DEMO_MODE=true and DEMO_FEEDS=trueapp_url (your Vercel URL) and cron_secret (same value as CRON_SECRET). The sync-calendars pg_cron job (migration) then calls the sync every 5 minutes. Optionally, also add the GitHub repository variable and secret for the backup .With DEMO_MODE=true, the login page offers one-click sign-in as the demo manager or owner, and every change to properties, channels and owners is refused. Syncing, filters and live updates all keep working. Never enable it on a deployment with real data.
sync_events, not in memory, and the follow-up sync runs in after() within the free plan's 60 s limit.APP_URLCRON_SECRET…@<id>.resend.app address, add a Resend webhook for email.received pointing to /api/inbound/resend, and set RESEND_API_KEY (full access) and RESEND_WEBHOOK_SECRET in Vercel. A Cloudflare Email Worker alternative is in workers/email-trigger.