# Setup

This page walks you through standing up the service from scratch, including the database. Use it when you’re creating a new environment (or learning how the pieces fit). Laptop-only: Local developer testing. Exhaustive Cloudflare / CI checklist: setup.md.

flowchart TD
  A["1. Database (Supabase)"] --> B["2. Auth (Cognito scopes)"]
  B --> C["3. Worker (Cloudflare)"]
  C --> D["4. Point apps at the base URL"]
  D --> E["5. Optional: CI release pipeline"]

# Do you need to set this up?

Situation What to do
Integrating with the shared Inovus production service Skip deploy — use https://notifications.totumcloud.com and ask for a publisher API key or M2M client if you need to publish
New environment (staging / another tenant) Follow From scratch below (or the full setup.md)
Local exploration only Local developer testing — API keys first, then optional M2M

# From scratch (including the database)

You’ll end up with: a Postgres table in Supabase, Cognito publisher credentials, a Cloudflare Worker API, and apps pointed at that API.

Prerequisites: Node ≥ 20, pnpm ≥ 9, Cloudflare account, Supabase org, admin on your Cognito user pool.

# Step 1 — Create the database (Supabase)

  1. Create a project at supabase.com/dashboard (pick a region close to your users). Note the project ref from the URL (e.g. abcdefghijklmnop).

  2. Apply the schema from supabase/migrations/0001_notifications.sql.

    Option A — SQL editor: open the migration file, paste into the Supabase SQL editor, run it.

    Option B — CLI:

    npx supabase link --project-ref <PROJECT_REF>
    npx supabase db push
  3. What that migration creates:

    • notifications table (title, body, severity, action URL, dedupe key, metadata, read state…)
    • Indexes for inbox listing, unread badge, retention purge
    • Partial unique index for race-safe dedupe on (user_guid, source, dedupe_key)
    • RLS enabled with no policies (deny-all). Only the Worker’s service role should touch this table.
  4. From Project Settings → API, copy:

    • Project URLSUPABASE_URL (e.g. https://abcdefghijklmnop.supabase.co)
    • service_role key → Worker secret SUPABASE_SERVICE_ROLE_KEY

Warning: Never put the service role key in browser code, git, or the anon key slot. Do not enable Supabase Auth for this table, and do not add permissive RLS policies — Cognito JWTs can’t drive Supabase RLS; the Worker is the only trusted path.

One Supabase project per environment (staging and production are separate).

# Step 2 — Configure Cognito

Uses your existing user pool(s) — user sign-in does not change.

If resource servers / M2M / client secrets are new: Cognito explained.
Production safety / checklists / rollback: Cognito M2M reference.
Full console how-to: cognito-setup.md.

Publisher config (important):

Worker env Purpose Create a Cognito user group?
PUBLISHER_API_KEYS=name:secret,… Easiest backend publish via X-Api-Keyapi-keys.md No
PUBLISHER_SCOPES=notifications/publish Lets M2M / backend Cognito tokens publish No — create a resource server scope instead
PUBLISHER_GROUPS=… Lets human user tokens publish Only if you want that — otherwise omit

Use API keys for a quick bootstrap, or PUBLISHER_SCOPES for Cognito M2M. End users need neither setting to read their inbox.

  1. Issuer (this is AUTH_ISSUER):

    https://cognito-idp.<region>.amazonaws.com/<userPoolId>

    Multiple pools: comma-separate them.

  2. Resource server (App integration → Resource servers → Create):

    • Identifier: notifications
    • Custom scope: publish → full scope string notifications/publish
  3. One confidential app client per producer (e.g. Assess):

    • Client credentials flow enabled
    • Allow notifications/publish
    • That client id becomes the notification source on every publish
    • Copy Client ID + Client secret from the app client page
  4. Fetch an M2M token (cache until expires_in) — PowerShell on Windows:

    $ClientId = "…"       # App client → Client ID
    $ClientSecret = "…"   # App client → Client secret
    $CognitoDomain = "https://<prefix>.auth.<region>.amazoncognito.com"
    
    curl.exe -X POST "$CognitoDomain/oauth2/token" `
      -H "Content-Type: application/x-www-form-urlencoded" `
      -u "${ClientId}:${ClientSecret}" `
      -d "grant_type=client_credentials&scope=notifications/publish"

    (Use curl.exe, not curl — PowerShell aliases curl to Invoke-WebRequest. Bash: cognito-setup.md.)

  5. End users: nothing extra — any valid pool token can read its own inbox.

  6. Optional human publishers only: create Cognito group → add admins → set PUBLISHER_GROUPS to that group name. Skip if you only have M2M publishers.

# Step 3 — Deploy the Cloudflare Worker

Preferred: Cloudflare dashboard for Worker + Variables & Secrets + domain + cron, then connect Git (Workers Builds) for code. Full tables: setup.md §3.2 (Build / Deploy commands are spelled out there).

Summary:

  1. Workers & Pages → Create Worker twice — names inovus-notifications-staging and inovus-notifications.
  2. On each Worker → Settings → Variables and Secrets, add plaintext Variables (AUTH_ISSUER, SUPABASE_URL, DB_DRIVER=supabase, PUBLISHER_SCOPES, …) and Secrets (SUPABASE_SERVICE_ROLE_KEY, optional PUBLISHER_API_KEYS).
  3. Production → Settings → Domains & Routes → Custom Domainnotifications.totumcloud.com.
  4. Each Worker → Triggers → Cron Triggers0 3 * * * (retention purge).
  5. Settings → Builds (connect this repo). Root directory = repo root. Build command = leave empty. Deploy command:
    • Staging: pnpm --filter @inovus-medical/notifications-api deploy:staging
    • Production: pnpm --filter @inovus-medical/notifications-api deploy:production

Laptop one-shot: from packages/api, pnpm deploy:staging / pnpm deploy:production after wrangler login.
CLI-only standup: setup.md §3.4.

# Step 4 — Point applications at the API

new NotificationsClient({
  baseUrl: 'https://notifications.totumcloud.com', // or your staging URL
  getAccessToken: () => auth.getValidAccessToken(),
});

Publishers use an API key or M2M token getter instead — see Publishing and api-keys.md.

# Step 5 — Smoke test

  1. GET https://<host>/health{ "status": "ok" } (proves Worker + DB)
  2. Open /docs — authorize with a user token, GET /v2/notifications
  3. With an M2M token, POST /v2/notifications with a known userGuid + dedupeKey
  4. With that user’s token, confirm the item appears
  5. Publish again with the same key → 200 + "duplicate": true

# Step 6 — Optional CI / releases

GitHub Actions can publish the npm client and deploy staging → production. Plain-language flow (what a changeset is): releasing.md. Secrets and Environments: setup.md §4.


# Local development (no cloud required)

Full walkthrough: Local developer testing (boot → API-key publish → user inbox → optional M2M).

Short version:

git clone <this repo> && cd org.inovus.notifications
pnpm install
cd packages/api && cp .dev.vars.example .dev.vars
pnpm --filter @inovus-medical/notifications-api dev   # http://localhost:8787

Set AUTH_ISSUER, DB_DRIVER=memory, and PUBLISHER_API_KEYS=local:<long-random-secret> in .dev.vars, restart, then publish with X-Api-Key and list with a user Bearer token. Details and PowerShell examples are in the local guide.


# Security checklist (please don’t skip)

  • [ ] Migration applied; RLS left deny-all (no extra policies)
  • [ ] Service role key only in Worker secrets — never in the SPA
  • [ ] Browser tokens are user tokens — not publisher M2M secrets or API keys
  • [ ] Cross-user publish only from trusted backends (M2M or named API keys)
  • [ ] Self-publish is fine in the browser (/self or convenience API)
  • [ ] Producers always send a meaningful dedupeKey

# More detail

Topic Doc
Local laptop dry run local-development.md
Full ops + CI secrets table setup.md
Cognito “why” (resource servers, M2M) cognito-explained.md
Publisher API keys (easy publish) api-keys.md
Cognito M2M production reference authentication/cognito-m2m-notifications.md
Cognito how-to cognito-setup.md
Schema / SQL 0001_notifications.sql
Memory vs Supabase adapters.md
New DB backends adapters.md
Repo commands & invariants Developer reference

Next: Everyday usage.