IsUp - Architecture Overview (do.dev Integrated)
System Architecture
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────────┐ │
│ │ isup-dev (Next.js 16 + React 19) │ │
│ │ │ │
│ │ Uses: @workspace/ui, @do/catalyst, Tailwind 4, TypeScript │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Dashboard │ │ Status Pages │ │ Landing │ │ Docs │ │ │
│ │ │ (Protected) │ │ (Public) │ │ (Public) │ │ (Public) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
└─────────────────────────────────────────┼────────────────────────────────────────────┘
│
┌─────────────────────┴─────────────────────┐
│ │
▼ ▼
┌───────────────────────────────────┐ ┌───────────────────────────────────┐
│ WorkOS AuthKit │ │ billing-dev API │
│ │ │ │
│ • SSO / OAuth (Google, GitHub) │ │ • Stripe subscriptions │
│ • Session management │ │ • Entitlements & limits │
│ • User metadata sync │ │ • Usage metering │
│ • Role-based access │ │ • Customer portal │
│ │ │ │
└───────────────────────────────────┘ └───────────────────────────────────┘
│ │
└─────────────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ CONVEX (Real-time Backend) │
│ │
│ Shared schema from @workspace/convex │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ users │ │ monitors │ │ incidents │ │ statusPages │ │
│ │ (shared) │ │ (isup) │ │ (isup) │ │ (isup) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │alertContacts│ │organizations│ │ roles │ │
│ │ (isup) │ │ (shared) │ │ (shared) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────┼─────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ Upstash Redis │ │ Tinybird │ │ Resend │
│ │ │ (ClickHouse) │ │ │
│ • Rate limiting │ │ │ │ • Alert emails │
│ • Job queues │ │ • Check results │ │ • Status updates │
│ • Caching │ │ • Time-series │ │ • Notifications │
│ │ │ • Analytics │ │ │
└───────────────────┘ └───────────────────┘ └───────────────────┘
│
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ MONITORING WORKERS (Cloudflare) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ isup-checker (Cloudflare Workers) │ │
│ │ │ │
│ │ Deployed to: us-east, us-west, eu-west, eu-central, asia-east, oceania │ │
│ │ Cron: Every minute (* * * * *) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ HTTP Check │ │ SSL Check │ │ DNS Check │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Cloudflare KV │ │ Cloudflare Queue │ │ Durable Objects │ │
│ │ │ │ │ │ │ │
│ │ • Monitor cache │ │ • results-queue │ │ • State machine │ │
│ │ • Last check │ │ • incident-queue │ │ • Rate limiting │ │
│ │ • Config sync │ │ • notify-queue │ │ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘Authentication Flow (WorkOS - Same as do-dev)
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ Authentication Flow │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. User visits isup.dev │
│ │ │
│ ▼ │
│ 2. middleware.ts checks WorkOS session │
│ │ │
│ ├── Has session ────▶ Allow access to dashboard │
│ │ │
│ └── No session ────▶ Redirect to /sign-in │
│ │ │
│ ▼ │
│ 3. WorkOS AuthKit handles OAuth │
│ │ │
│ ├── Google OAuth │
│ ├── GitHub OAuth │
│ └── Email/Password │
│ │ │
│ ▼ │
│ 4. On success, sync to Convex │
│ │ │
│ ├── Create/update user in Convex │
│ ├── Assign appId: "isup" │
│ ├── Generate userId: "usr_xxx" │
│ ├── Create personal organization: "org_xxx" │
│ └── Sync metadata to WorkOS │
│ │ │
│ ▼ │
│ 5. Check billing-dev for entitlements │
│ │ │
│ └── GET /entitlement?userId={workosUserId} │
│ Returns: plan, limits, features │
│ │
└──────────────────────────────────────────────────────────────────────────────────────┘Middleware Configuration
// apps/isup-dev/middleware.ts (same pattern as do-dev)
import { authkitMiddleware } from "@workos-inc/authkit-nextjs";
export default authkitMiddleware({
middlewareAuth: {
enabled: true,
unauthenticatedPaths: [
"/",
"/sign-in",
"/sign-up",
"/pricing",
"/docs",
"/docs/(.*)",
"/status/(.*)", // Public status pages
"/api/webhooks/(.*)",
"/api/public/(.*)",
],
},
});
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};Billing Integration Flow
┌──────────────────────────────────────────────────────────────────────────────────────┐
│ Billing Flow (via billing-dev) │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ isup-dev │ │ billing-dev │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ 1. POST /checkout │ │
│ │ { userId, priceId } │ │
│ │─────────────────────────────────▶│ │
│ │ │ │
│ │ 2. Returns Stripe checkout URL │ │
│ │◀─────────────────────────────────│ │
│ │ │ │
│ │ 3. User completes checkout │ ┌─────────────┐ │
│ │─────────────────────────────────────────▶│ Stripe │ │
│ │ │ └──────┬──────┘ │
│ │ │ │ │
│ │ │ 4. Webhook │ │
│ │ │◀─────────────┘ │
│ │ │ │
│ │ │ 5. Update org plan │
│ │ │ Create entitlements │
│ │ │ │
│ │ 6. GET /entitlement │ │
│ │─────────────────────────────────▶│ │
│ │ │ │
│ │ 7. Returns plan + limits │ │
│ │◀─────────────────────────────────│ │
│ │ │ │
│ │ { │ │
│ │ plan: "isup-pro", │ │
│ │ limits: { │ │
│ │ monitors: 50, │ │
│ │ intervalMin: 60, │ │
│ │ retentionDays: 365 │ │
│ │ }, │ │
│ │ features: [ │ │
│ │ "isup.access", │ │
│ │ "isup.api", │ │
│ │ "isup.status_pages" │ │
│ │ ] │ │
│ │ } │ │
│ │ │ │
└──────────┴──────────────────────────────────┴─────────────────────────────────────────┘Convex Schema (IsUp Tables)
// Addition to tools/convex/convex/schema.ts
// ============================================
// IsUp Tables (appId: "isup")
// ============================================
monitors: defineTable({
appId: v.literal("isup"),
userId: v.id("users"),
organizationId: v.string(),
name: v.string(),
type: v.union(
v.literal("http"),
v.literal("ping"),
v.literal("port"),
v.literal("keyword"),
v.literal("ssl"),
v.literal("dns"),
v.literal("heartbeat")
),
url: v.string(),
intervalSeconds: v.number(),
timeoutSeconds: v.number(),
regions: v.array(v.string()),
// HTTP options
httpMethod: v.optional(v.string()),
httpHeaders: v.optional(v.any()),
httpBody: v.optional(v.string()),
expectedStatusCodes: v.optional(v.array(v.number())),
// Keyword options
keyword: v.optional(v.string()),
keywordType: v.optional(v.union(v.literal("present"), v.literal("absent"))),
// Port options
port: v.optional(v.number()),
// SSL options
sslExpiryAlertDays: v.optional(v.array(v.number())),
// Thresholds
responseTimeThreshold: v.optional(v.number()),
// State
status: v.union(v.literal("active"), v.literal("paused"), v.literal("deleted")),
currentState: v.union(v.literal("up"), v.literal("down"), v.literal("degraded"), v.literal("unknown")),
lastCheckedAt: v.optional(v.number()),
lastStateChangeAt: v.optional(v.number()),
// Denormalized stats
uptimePercent24h: v.optional(v.number()),
uptimePercent7d: v.optional(v.number()),
uptimePercent30d: v.optional(v.number()),
avgResponseTime24h: v.optional(v.number()),
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_org", ["organizationId"])
.index("by_status", ["status"])
.index("by_state", ["currentState"])
.index("by_next_check", ["status", "lastCheckedAt"]),
alertContacts: defineTable({
appId: v.literal("isup"),
userId: v.id("users"),
organizationId: v.string(),
name: v.string(),
type: v.union(
v.literal("email"),
v.literal("slack"),
v.literal("discord"),
v.literal("telegram"),
v.literal("webhook"),
v.literal("sms"),
v.literal("voice")
),
config: v.any(),
isVerified: v.boolean(),
isDefault: v.boolean(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_org", ["organizationId"]),
monitorAlertContacts: defineTable({
monitorId: v.id("monitors"),
alertContactId: v.id("alertContacts"),
})
.index("by_monitor", ["monitorId"])
.index("by_contact", ["alertContactId"]),
incidents: defineTable({
appId: v.literal("isup"),
monitorId: v.id("monitors"),
organizationId: v.string(),
startedAt: v.number(),
endedAt: v.optional(v.number()),
durationSeconds: v.optional(v.number()),
cause: v.string(),
errorMessage: v.optional(v.string()),
affectedRegions: v.array(v.string()),
resolvedBy: v.optional(v.union(v.literal("auto"), v.literal("manual"))),
notes: v.optional(v.string()),
notificationsSent: v.array(v.object({
contactId: v.id("alertContacts"),
sentAt: v.number(),
type: v.string(),
success: v.boolean(),
})),
createdAt: v.number(),
})
.index("by_monitor", ["monitorId"])
.index("by_org", ["organizationId"])
.index("by_time", ["startedAt"])
.index("by_active", ["monitorId", "endedAt"]),
statusPages: defineTable({
appId: v.literal("isup"),
userId: v.id("users"),
organizationId: v.string(),
slug: v.string(),
name: v.string(),
description: v.optional(v.string()),
customDomain: v.optional(v.string()),
customDomainVerified: v.optional(v.boolean()),
theme: v.optional(v.any()),
isPublic: v.boolean(),
passwordHash: v.optional(v.string()),
allowIndexing: v.boolean(),
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_org", ["organizationId"])
.index("by_slug", ["slug"])
.index("by_domain", ["customDomain"]),
statusPageMonitors: defineTable({
statusPageId: v.id("statusPages"),
monitorId: v.id("monitors"),
displayName: v.optional(v.string()),
sortOrder: v.number(),
})
.index("by_page", ["statusPageId"])
.index("by_monitor", ["monitorId"]),Tinybird Schema (Time-Series)
-- Check results (high-volume writes)
DATASOURCE check_results
ENGINE = MergeTree
ENGINE_SORTING_KEY monitor_id, timestamp
ENGINE_PARTITION_KEY toYYYYMM(timestamp)
ENGINE_TTL timestamp + INTERVAL 90 DAY
SCHEMA >
monitor_id String,
organization_id String,
timestamp DateTime64(3),
region String,
status Enum8('up' = 1, 'down' = 2, 'degraded' = 3),
response_time_ms UInt32,
status_code Nullable(UInt16),
error_message Nullable(String),
dns_time_ms Nullable(UInt32),
connect_time_ms Nullable(UInt32),
tls_time_ms Nullable(UInt32),
ttfb_ms Nullable(UInt32),
ssl_expiry_date Nullable(DateTime),
ssl_issuer Nullable(String)
-- Hourly aggregations (materialized view)
DATASOURCE check_results_hourly
ENGINE = SummingMergeTree
ENGINE_SORTING_KEY monitor_id, hour, region
SCHEMA >
monitor_id String,
hour DateTime,
region String,
check_count UInt64,
up_count UInt64,
avg_response_time Float64,
max_response_time UInt32,
min_response_time UInt32Cloudflare Worker Architecture
Worker Structure
tools/workers/isup-checker/
├── src/
│ ├── index.ts # Main entry
│ ├── checks/
│ │ ├── http.ts
│ │ ├── ssl.ts
│ │ ├── dns.ts
│ │ └── keyword.ts
│ ├── queue/
│ │ ├── results.ts
│ │ └── incidents.ts
│ └── utils/
│ ├── convex.ts
│ └── tinybird.ts
├── wrangler.toml
└── package.jsonwrangler.toml
name = "isup-checker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[triggers]
crons = ["* * * * *"]
[[kv_namespaces]]
binding = "MONITORS_KV"
id = "xxx"
[[queues.producers]]
queue = "isup-results"
binding = "RESULTS_QUEUE"
[[queues.producers]]
queue = "isup-incidents"
binding = "INCIDENTS_QUEUE"
[[queues.consumers]]
queue = "isup-results"
max_batch_size = 100
max_batch_timeout = 30
[env.us-east]
vars = { REGION = "us-east" }
[env.eu-west]
vars = { REGION = "eu-west" }
[env.asia]
vars = { REGION = "asia" }Data Flow
Check Execution
1. Cron fires (every minute)
│
▼
2. Worker reads monitors from KV (cached from Convex)
│
▼
3. Filter monitors due for check
│
▼
4. Execute checks in parallel (per region)
│
├── us-east: 50 monitors
├── eu-west: 50 monitors
└── asia: 50 monitors
│
▼
5. Queue results to RESULTS_QUEUE
│
▼
6. Results consumer processes batch
│
├── Write to Tinybird (time-series)
├── Update KV state cache
└── Check for state changes
│
▼
7. If state changed → Queue to INCIDENTS_QUEUE
│
▼
8. Incident consumer
│
├── Create/update incident in Convex
├── Get alert contacts
└── Send notificationsProject Structure
dodotdev/
├── apps/
│ ├── do-dev/ # Existing
│ └── isup-dev/ # NEW
│ ├── app/
│ │ ├── (marketing)/
│ │ ├── (dashboard)/
│ │ ├── (status)/
│ │ └── api/
│ ├── components/
│ ├── hooks/
│ ├── lib/
│ └── package.json
├── packages/
│ ├── ui/ # Shared
│ ├── catalyst/ # Shared
│ └── convex/ # Add IsUp tables
├── tools/
│ ├── convex/
│ │ └── convex/
│ │ ├── isup/ # NEW
│ │ │ ├── monitors.ts
│ │ │ ├── incidents.ts
│ │ │ └── statusPages.ts
│ │ └── schema.ts
│ └── workers/ # NEW
│ └── isup-checker/
└── billing-dev/ # Existing