Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
63f016d
ci: build @openpartner/db before typecheck
Apr 24, 2026
ba67b70
fix(safe-fetch): strip IPv6 brackets before IP check
Apr 24, 2026
a6db3a6
ci: quote NETWORK_ENCRYPTION_KEY in workflow env
Apr 24, 2026
fa6fcb1
refactor: carve Network + human-auth out of OSS
Apr 24, 2026
ea41a6f
feat(auth): partner invite + magic-link signin
Apr 24, 2026
abbb0dc
refactor(mail): drop dev mailbox, mail via Postmark or stdout fallback
Apr 24, 2026
999b050
fix(portal): admins don't mint partner credentials or create partner …
Apr 24, 2026
911fc5c
feat(admin): partner revocation with session kill + attribution skip
Apr 24, 2026
bce56f0
feat(admin): revoke notifications + optional reason + signin handling
Apr 24, 2026
acf8eb5
feat(admin): UI-managed program settings (name + support email)
Apr 24, 2026
c28f197
feat(auth): admin personas, SMTP support, first-run install wizard
Apr 24, 2026
fa0d0c7
feat(mail): UI-managed SMTP/Postmark config, encrypted at rest
Apr 25, 2026
a922e30
fix(install): split first-run into a three-step wizard
Apr 25, 2026
59526b7
docs: refresh for admin personas, first-run wizard, UI-managed mail
Apr 25, 2026
6d837a4
fix(critical): signin email-spam vector, /install TOCTOU, last-admin …
Apr 25, 2026
96fba9f
fix(correctness): revoke kills api keys, install retryable, schema gaps
Apr 25, 2026
7d076f3
fix(polish): dup-email 409, empty smtp host, spa bounce, migration down
Apr 25, 2026
73bdf58
feat(partner): getting-started checklist on first-run dashboard
Apr 25, 2026
9d45d11
feat(stripe-webhook): merchant billing flow via client_reference_id
Apr 25, 2026
8c2e04a
feat(stripe-webhook): accept comma-separated STRIPE_WEBHOOK_SECRET
Apr 25, 2026
2269948
chore(stripe): provisioning script for products + prices
Apr 25, 2026
e981bbd
feat(billing): refund handling, metered usage billing, V2 checkout fix
Apr 26, 2026
7342196
feat(deploy): in-process scheduler + prod-ready DO App Platform spec
Apr 26, 2026
9901899
chore(deploy): switch DATABASE_URL to externally-provisioned secret
Apr 26, 2026
73d02a6
fix(deploy): app.yaml validates against DO API
Apr 26, 2026
e4e0d97
fix(deploy): force devDeps during portal build (tsc, vite)
Apr 26, 2026
0086a7a
fix(db): handle managed-postgres TLS (sslmode=require)
Apr 26, 2026
888281e
fix(db): strip sslmode from URL so explicit ssl config wins
Apr 26, 2026
d895e65
Multi-tenant refactor + Network integration (8 commits) (#10)
keithfawcett Apr 27, 2026
cc8149f
feat(network): aggregate + report Network-originated payout volume (#13)
keithfawcett Apr 27, 2026
05a20e5
feat(network): vendor admin Billing page + proxy routes (#14)
keithfawcett Apr 27, 2026
579dbe9
feat(network): partner-role Network surfaces (Discover, Apply, My par…
Apr 28, 2026
c795868
feat(portal): multi-tenant landing + signup; brand assets; URL prefix…
Apr 28, 2026
c994ba7
feat(portal): platform-level Creator account on multi-tenant deploy
Apr 28, 2026
02230cc
chore(portal): rename vendor CTAs from "program" to "brand"
Apr 28, 2026
5dc4282
feat(portal): rewrite Landing in marketing voice; unified email signin
Apr 28, 2026
f6df774
fix(portal): escape hyphen in handle/slug pattern attributes
Apr 28, 2026
0e01bf0
fix(creator-portal): split Set-Cookie + drop Cloudflare cookies
Apr 28, 2026
596a577
chore(portal): brand signup is account creation, not program creation
Apr 28, 2026
113fbac
chore(deploy): boot-time secrets_probe — surface which envs populated
Apr 28, 2026
53f1c3a
fix(signin): also send activation link for unactivated admins
Apr 28, 2026
f870067
fix(auth): magic-link verify redirects to tenant home, not Landing
Apr 28, 2026
09c379e
feat(landing): auto-redirect signed-in visitors to their portal
Apr 28, 2026
9619e35
feat(auth): platform identity + workspace picker
Apr 28, 2026
2c100d6
chore(portal): wire name + autocomplete on auth inputs for autofill
Apr 28, 2026
2834b0f
feat(signup): auto-enroll hosted brands on the Network with vendorTok…
Apr 28, 2026
5beacc9
feat(brand): account deletion (GDPR right of erasure) — phase 1
Apr 28, 2026
10c2d5a
feat(brand): account-deletion phase 2 — Stripe + Network + obligation…
Apr 28, 2026
028b2eb
chore: pg advisory lock on scheduler + open /partners/:id/revoke to s…
Apr 28, 2026
8da1bcd
feat(network): one-click auto-enroll for existing tenants + brand rename
Apr 28, 2026
0512194
fix(emails): partner invite + signin emails name the brand
Apr 29, 2026
57871f4
fix(portal): tenant-scope absolute Links on the partner Dashboard
Apr 29, 2026
b4d0ed8
fix(portal): hide Network sub-nav until the brand is connected
Apr 29, 2026
8c9a00c
refactor(links): destinationUrl belongs to the brand, not the partner
Apr 29, 2026
095d497
feat(programs): scope partner share-links to granted campaigns
Apr 29, 2026
97b55bd
feat(programs): admin grant management UI + creator-side Offering hint
Apr 29, 2026
d1118c5
feat(onboarding): brand + creator getting-started checklists
Apr 29, 2026
cd7bd77
fix(network-proxy): bump timeouts past App Platform cold-start; surfa…
Apr 29, 2026
2a85444
fix(network-membership): heal tenants with empty networkUrl after verify
Apr 29, 2026
8052ead
fix(network-partner): graceful empty state for partners not pushed to…
Apr 29, 2026
6eda2bb
fix(network-push): apply NETWORK_URL fallback in dispatch too
Apr 29, 2026
b7e912f
fix(signup): re-claim slug when no admin has activated yet
Apr 29, 2026
9d9002f
fix(settings/onboarding): default brand name to Tenant.displayName
Apr 29, 2026
9542677
feat(mail): brand-aware From + Reply-To on platform fallback; surface…
Apr 29, 2026
f1aaccb
feat(mail): fall back Reply-To to oldest activated admin email
Apr 29, 2026
482c46f
feat(campaigns): optional start + end dates with proper lifecycle gates
Apr 29, 2026
aa15d9c
feat(campaigns): 7-day-before-end notifications to brand + active par…
Apr 29, 2026
61f6ce3
chore(campaigns): help-tooltip on Attribution model
Apr 29, 2026
c9e67d0
chore(portal): theme the Description textarea + Campaign select on Of…
Apr 29, 2026
8a9f253
feat(admin): per-partner campaign scoping at invite + drop offering p…
Apr 29, 2026
d0bf37f
feat(campaigns): optional bulk-grant to existing partners on create
Apr 29, 2026
78753ca
fix(creator-portal): wrap proxy in try/catch + log upstream errors
Apr 29, 2026
a1f833b
feat(creator-signup): live handle availability check
Apr 29, 2026
51d7aae
chore(creator-profile): theme the Bio textarea (was browser-default w…
Apr 29, 2026
5f31f42
fix(partners): validate campaignIds against tenant before grant insert
Apr 29, 2026
ead6a4e
chore(portal): themed Textarea + sweep raw form elements
Apr 29, 2026
071b66b
chore(network-client): log upstream error body for diagnosis
Apr 29, 2026
e71e820
fix(network-signup): instanceUrl must put /api before /t/<slug>
Apr 29, 2026
8ccbee2
feat(router): /r/:slug/:linkKey for multi-tenant share links
Apr 29, 2026
7015019
feat(clicks): POST /clicks for federated click ingestion
Apr 29, 2026
f3e3cf3
feat(creator-portal): UI for custom domains + share-link management
Apr 29, 2026
d3fd27b
feat(creator-portal): conditional CTAs based on per-offering myStatus
Apr 29, 2026
9c5b209
feat(creator-portal): proxy passthrough for new profile endpoints
Apr 29, 2026
6f2858e
feat(creator-portal): Tier 1 profile editor + public profile page
Apr 29, 2026
e8aa294
feat(network): proxy creator-directory search to brand admins
Apr 29, 2026
141017d
feat(admin-portal): brand-side creator discovery directory
Apr 29, 2026
27a3505
fix(creator-profile): keep audience locations as raw text while typing
Apr 29, 2026
5fd70e1
chore(portal): collapsible Admin + Network sidebar sections
Apr 29, 2026
74c95f9
fix(portal): collapsible nav header inherits font-family only
Apr 29, 2026
f04b918
chore(portal): match Network admin page typography to rest of site
Apr 29, 2026
8f42ba4
feat(dashboard): per-Link breakdown via ?includeLinks=true
Apr 30, 2026
267fc4b
feat(creator-portal): per-link breakdown on My partnerships
Apr 30, 2026
1ecd576
feat(invite-to-apply): brand UI + creator deeplink handling
Apr 30, 2026
18c08ed
feat(creator-portal): "Recommended for you" strip on Discover
Apr 30, 2026
9eef36c
feat(import): partner-roster CSV importer for migrations
Apr 30, 2026
ed2975c
feat(coupons): coupon-code attribution path
Apr 30, 2026
9b35101
feat(coupons): auto-mint on PartnerCampaign grant
Apr 30, 2026
413bbcc
feat(creator-portal): coupon codes alongside share URL on Share links
Apr 30, 2026
f485aff
feat(stripe-webhook): auto-redeem OpenPartner coupons from discount e…
Apr 30, 2026
4dd29f2
feat(coupons): per-code redemption + revenue stats (90d)
Apr 30, 2026
230ed58
feat(creator-portal): per-coupon redemption + revenue stats
Apr 30, 2026
4140cd9
feat(coupons): integration verification gate
Apr 30, 2026
b7ab436
chore(portal): sticky sidebar so middle nav actually scrolls
Apr 30, 2026
f7ec85e
fix(migration): guard Coupon grant when openpartner_app role is absent
Apr 30, 2026
e449286
fix(db): carry sslmode from DATABASE_URL to DATABASE_URL_APP
Apr 30, 2026
1e7a5f7
fix(db): reconcile openpartner_app role on every boot
Apr 30, 2026
9db501f
feat(network): heartbeat sender + local-truth partnerCount
Apr 30, 2026
f8bc62e
feat(invite): show deeplink to brand for spam-fallback DM
Apr 30, 2026
e09409b
ci: publish @openpartner/sdk to npm on tagged releases
Apr 30, 2026
c173777
ci: switch sdk publish to npm Trusted Publishing (OIDC)
Apr 30, 2026
106345f
fix(portal): use tenant-prefixed Links across brand workspace
Apr 30, 2026
0ba17c1
refactor(portal): promote Programs + Coupons to dedicated pages
Apr 30, 2026
2c2abd2
feat(uploads): brand logos + creator avatar uploads
Apr 30, 2026
172493c
security: address Semgrep findings before public release
Apr 30, 2026
f814a38
brand: GitHub social preview / OG image
Apr 30, 2026
9134974
Add Buy Me a Coffee funding option
keithfawcett Apr 30, 2026
65573ab
Update CODE_OF_CONDUCT.md
keithfawcett Apr 30, 2026
572c47f
brand(og): match marketing-site hero copy + emerald accent
Apr 30, 2026
b9a68ab
docs(readme): align pricing with marketing site + Stripe products
Apr 30, 2026
1158874
governance: CODEOWNERS + SECURITY.md ahead of public flip
Apr 30, 2026
830008c
brand(og): drop eyebrow pill, center headline
Apr 30, 2026
a198f3c
chore(deps): bump vite to 6 + vitest to 3 to clear dependabot alerts
Apr 30, 2026
dc589ec
chore(lint): unblock pnpm lint across the workspace
Apr 30, 2026
12af8fa
security: address CodeQL findings before public release
Apr 30, 2026
73c501c
feat(billing): per-tenant plan + 14-day trial + Stripe lifecycle
Apr 30, 2026
54bf780
feat(billing): admin Billing page + onboarding step
Apr 30, 2026
570860a
feat(billing): close trial-loop hole + soft 402 gate + posthog
Apr 30, 2026
0abe763
fix(account-deletion): correct column + status enums
Apr 30, 2026
c05fd30
fix(billing): inline plan picker + posthog snippet hardening
Apr 30, 2026
37ec4a0
fix(stripe-webhook): resolve tenant for merchant subscription events
Apr 30, 2026
a6183eb
feat(signup): brand/creator tab switcher above the form
May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 105 additions & 24 deletions .do/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,35 @@
name: openpartner
region: nyc

# Managed Postgres. For production, bump to a paid plan and upgrade the
# `production` flag. The connection string is injected into services
# that reference ${openpartner-db.DATABASE_URL}.
databases:
- name: openpartner-db
engine: PG
version: '16'
production: false
cluster_name: openpartner-db
db_name: openpartner
db_user: openpartner
# Database is provisioned externally (e.g., a separate Coherence cluster
# with a dedicated `openpartner` database inside it). DATABASE_URL is set
# as an encrypted secret on each service below. Add this app to the
# cluster's Trusted Sources after the first deploy so it can reach pg.
#
# If you'd rather have App Platform provision a dedicated cluster, paste
# this block back in:
#
# databases:
# - name: openpartner-db
# engine: PG
# version: '16'
# production: true
# cluster_name: openpartner-db
# db_name: openpartner
# db_user: openpartner
#
# and change the DATABASE_URL env vars below to:
# value: ${openpartner-db.DATABASE_URL}

services:
# Core API. Runs migrations on first boot via docker-entrypoint.sh.
- name: api
dockerfile_path: apps/api/Dockerfile
source_dir: /
github:
repo: getcoherence/openpartner
branch: main
deploy_on_push: true
http_port: 4601
instance_count: 1
instance_size_slug: basic-xs
Expand All @@ -54,17 +66,39 @@ services:
- key: API_PORT
value: '4601'
- key: DATABASE_URL
value: ${openpartner-db.DATABASE_URL}
type: SECRET
scope: RUN_TIME
# App-pool connection — connects as openpartner_app (a NOLOGIN-by-
# default role provisioned by migration 20260507020000_app_role.ts).
# Required in multi-tenant deploys for RLS to actually engage at
# runtime; in single-tenant self-host you can leave it unset and
# let everything run as the migration role.
- key: DATABASE_URL_APP
type: SECRET
scope: RUN_TIME
# Password used to provision openpartner_app on migrate. Pair with
# DATABASE_URL_APP. Rotation: change here, redeploy to re-run the
# idempotent role migration which alters the password in place.
- key: OPENPARTNER_APP_DB_PASSWORD
type: SECRET
scope: RUN_TIME
- key: OPENPARTNER_MODE
value: flat
- key: MAIL_TRANSPORT
value: postmark
# single | multi. Hosted deploys default to multi; flip to single
# if running a single-tenant fork on App Platform.
- key: OPENPARTNER_TENANCY
value: multi
- key: POSTMARK_MESSAGE_STREAM
value: outbound
# Enables the in-process scheduler (usage reporting + payouts).
# DO App Platform has no native cron, so the api process runs them
# itself. Set to '0' on replica nodes if you ever scale to instance_count > 1.
- key: OPENPARTNER_ENABLE_SCHEDULER
value: '1'
- key: ADMIN_API_KEY
type: SECRET
scope: RUN_TIME
- key: NETWORK_ENCRYPTION_KEY
- key: SECRETS_ENCRYPTION_KEY
type: SECRET
scope: RUN_TIME
- key: POSTMARK_SERVER_TOKEN
Expand All @@ -76,6 +110,8 @@ services:
- key: PORTAL_URL
type: SECRET
scope: RUN_TIME
# Stripe — set as live values when ready to launch. STRIPE_WEBHOOK_SECRET
# accepts a comma-separated list (one per Event destination).
- key: STRIPE_SECRET_KEY
type: SECRET
scope: RUN_TIME
Expand All @@ -85,13 +121,35 @@ services:
- key: STRIPE_FLAT_PRICE_ID
type: SECRET
scope: RUN_TIME
# Metered usage prices — created by `setup-stripe.mjs` against the
# live key. Optional in self-host but required for the percentage
# portion of Flex/Revshare/Network billing to actually fire.
- key: STRIPE_FLAT_USAGE_PRICE_ID
type: SECRET
scope: RUN_TIME
- key: STRIPE_REVSHARE_USAGE_PRICE_ID
type: SECRET
scope: RUN_TIME
- key: STRIPE_NETWORK_PRICE_ID
type: SECRET
scope: RUN_TIME
- key: STRIPE_NETWORK_USAGE_PRICE_ID
type: SECRET
scope: RUN_TIME
- key: METRICS_TOKEN
type: SECRET
scope: RUN_TIME

# Click router. Separate component so we can point a marketing apex
# (go.yourdomain.com) at it without routing through the portal's
# ingress.
- name: router
dockerfile_path: apps/router/Dockerfile
source_dir: /
github:
repo: getcoherence/openpartner
branch: main
deploy_on_push: true
http_port: 4701
instance_count: 1
instance_size_slug: basic-xxs
Expand All @@ -107,7 +165,8 @@ services:
- key: ROUTER_PORT
value: '4701'
- key: DATABASE_URL
value: ${openpartner-db.DATABASE_URL}
type: SECRET
scope: RUN_TIME
- key: COOKIE_DOMAIN
type: SECRET
scope: RUN_TIME
Expand All @@ -122,21 +181,43 @@ services:
static_sites:
- name: portal
source_dir: /
github:
repo: getcoherence/openpartner
branch: main
deploy_on_push: true
# Force devDependencies (tsc, vite) during build — App Platform sets
# NODE_ENV=production by default which would have pnpm skip them.
# NODE_ENV stays unset on this static site at runtime; nothing executes.
build_command: |
corepack enable
corepack prepare pnpm@9.11.0 --activate
pnpm install --frozen-lockfile
NPM_CONFIG_PRODUCTION=false pnpm install --frozen-lockfile
pnpm --filter @openpartner/db build
pnpm --filter @openpartner/portal build
output_dir: apps/portal/dist
catchall_document: index.html
envs:
- key: NODE_ENV
value: production
# PostHog product analytics. VITE_-prefixed so Vite inlines the
# value into the static bundle at build time. BUILD_TIME scope is
# required: the static site has no run-time process to read env
# vars from. Without this declaration the values you set in the
# DO UI may not flow into the build (depending on App Platform
# version). Same project key as studio-website for unified
# dashboards.
- key: VITE_POSTHOG_KEY
type: SECRET
scope: BUILD_TIME
- key: VITE_POSTHOG_HOST
scope: BUILD_TIME
value: https://us.i.posthog.com

# App-level routing. /api/* goes to the api service; everything else
# falls through to the static portal. The router is addressed by its
# own subdomain — see `domains` below.
# App-level routing.
# /api/* → api (path stripped before forward)
# /r/* → router (path preserved — router serves /r/:linkKey directly)
# / → portal (App Platform's static-site default)
#
# Once you wire `r.openpartner.dev` to the router component via the
# `domains` block, you can remove the /r rule — the subdomain handles it.
ingress:
rules:
- match:
Expand All @@ -147,9 +228,9 @@ ingress:
preserve_path_prefix: false
- match:
path:
prefix: /
prefix: /r
component:
name: portal
name: router

# Wire custom domains once the app is up. Set these in the App Platform
# UI, or uncomment + fill below and `doctl apps update`.
Expand Down
89 changes: 67 additions & 22 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,30 @@
# Values: selfhost | flat | revshare
OPENPARTNER_MODE=selfhost

# Database
# Tenancy mode — single tenant (self-host bootstrap) vs multi-tenant
# (hosted, /t/<slug>/... URL routing). Values: single | multi
# Self-host customers leave this at 'single'. Hosted operators set
# 'multi' and use POST /signup to provision tenants.
OPENPARTNER_TENANCY=single

# Database — privileged connection used by migrations, signup, the
# Stripe webhook handler, and the in-process scheduler. Bypasses RLS.
DATABASE_URL=postgres://openpartner:openpartner@localhost:5433/openpartner

# Optional: app-role connection string used by every tenant-scoped
# request. Run as a non-superuser without BYPASSRLS so RLS engages as
# defense-in-depth alongside the per-request app.tenant_id filter.
# When unset, tenant-scoped requests fall back to DATABASE_URL and RLS
# is bypassed (app-level filtering still applies). Self-hosters opt in
# by setting both this and OPENPARTNER_APP_DB_PASSWORD.
DATABASE_URL_APP=

# Password used to provision the openpartner_app role on migrate. The
# 20260507020000_app_role.ts migration creates the role with this
# password (rotates it if the role already exists). Skipped with a
# notice if unset. Pair with DATABASE_URL_APP pointing at this role.
OPENPARTNER_APP_DB_PASSWORD=

# Service ports
ROUTER_PORT=4701
API_PORT=4601
Expand All @@ -17,10 +38,9 @@ PORTAL_PORT=5673
# In production: set to your marketing apex (e.g. .yourdomain.com)
COOKIE_DOMAIN=localhost

# Session secret — generate a random 32+ char string for prod
SESSION_SECRET=dev-only-replace-in-prod

# Admin API key for bootstrap (curl/CI). Rotate via POST /api-keys once live.
# Bootstrap / headless admin bearer. Human admins sign in via the /install
# wizard (first run) or by receiving an emailed magic link. Keep this set
# for CI, scripts, and emergency access; rotate once human admins exist.
# Generate: node -e "console.log('op_' + require('crypto').randomBytes(24).toString('hex'))"
ADMIN_API_KEY=op_devonly_replace_me_with_64_hex_chars_before_any_real_use_xxxx

Expand All @@ -35,30 +55,55 @@ STRIPE_FLAT_PRICE_ID=
VELOCITY_MAX=20
VELOCITY_WINDOW_MS=60000

# Network federation — AES-256-GCM master key (32 raw bytes, either hex
# (64 chars) or base64 (44 chars)). REQUIRED in production; dev uses a
# weak fallback and logs a warning.
# Public portal URL. Required in production so CORS has an explicit
# origin allowlist (we refuse to boot with it empty). Defaults to
# localhost:5673 in dev.
PORTAL_URL=http://localhost:5673

# Optional: extra CORS origins beyond PORTAL_URL, comma-separated.
CORS_EXTRA_ORIGINS=

# -- Secret encryption at rest --
# 32-byte master key (hex or base64) used to encrypt SMTP passwords /
# Postmark tokens / future Config secrets. REQUIRED in production; dev
# uses a fixed fallback with a warning so restarts keep decrypting.
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
NETWORK_ENCRYPTION_KEY=
SECRETS_ENCRYPTION_KEY=

# Mail for admin + partner invite / signin links.
#
# Preferred: configure from the admin Settings → Email delivery page
# (stored encrypted at rest in Config). The env vars below are
# fallbacks used only when UI config is empty — useful for hosted /
# managed tiers where the operator wants to force the transport.
#
# SMTP_HOST + MAIL_FROM → nodemailer SMTP (Gmail, SES, Mailgun,
# SendGrid, Postmark SMTP, Postfix, …)
# POSTMARK_SERVER_TOKEN + MAIL_FROM → Postmark HTTP API
# neither → stdout (dev only)
#
# MAIL_FROM must be quoted if it contains a display name, e.g.
# MAIL_FROM="Acme Partners <partners@acme.com>"
MAIL_FROM=

# Router URL override used when federating Network partnerships. If unset,
# we infer from the vendor's instance URL by swapping API port 4601 → 4701.
NETWORK_ROUTER_URL=
# -- SMTP fallback --
SMTP_HOST=
SMTP_PORT=587
SMTP_SECURE=
SMTP_USER=
SMTP_PASSWORD=

# Mail delivery for magic-link auth.
# dev → writes to the DevMessage table; admins read at /admin/dev-mailbox.
# postmark → POSTs to api.postmarkapp.com/email (requires the vars below).
MAIL_TRANSPORT=dev
MAIL_FROM=OpenPartner <no-reply@example.com>
# -- Postmark (alternative) --
POSTMARK_SERVER_TOKEN=
# Optional — defaults to the "outbound" transactional stream.
POSTMARK_MESSAGE_STREAM=outbound

# Public portal URL that magic links point at. Defaults to localhost:5673
# for dev. Set this in prod so emails link to your real hostname.
PORTAL_URL=http://localhost:5673

# Optional: bearer token required to scrape /metrics. Leave blank and
# /metrics is open (fine on internal networks). Set it when /metrics is
# reachable from the public internet.
METRICS_TOKEN=

# Optional: URL of the OpenPartner Network coordinator. When set, hosted
# multi-tenant signups auto-register with the Network (admin still
# confirms via the magic link); self-host installs see a "Connect to
# Network" button under Settings → Network. Leave empty to opt out.
NETWORK_URL=
41 changes: 41 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# GitHub CODEOWNERS file.
#
# Lines map a path glob to a GitHub user / team. The last matching line
# wins. When a PR touches a path, GitHub auto-requests review from the
# matching owner(s); when the protected-branch rule "Require review from
# Code Owners" is on, the PR can't merge without their approval.
#
# Keep this list short and meaningful — every entry is a hard
# review-block. Default reviewer covers everything; specific paths
# below add no extra owners (yet) but reserve the slots for when a
# team forms around the repo.

# Default reviewer for everything not matched below.
* @keithfawcett

# Build, deploy, and CI configuration. Changes here affect the release
# pipeline and supply chain — surface them clearly.
/.github/ @keithfawcett
/.do/ @keithfawcett
/Dockerfile @keithfawcett
/docker-compose*.yml @keithfawcett
/apps/api/Dockerfile @keithfawcett
/apps/portal/Dockerfile @keithfawcett
/apps/router/Dockerfile @keithfawcett

# Cryptography + auth + storage of secrets. Anything under here gets
# extra eyes because a regression is hard to catch in review.
/apps/api/src/crypto.ts @keithfawcett
/apps/api/src/auth.ts @keithfawcett
/apps/api/src/db.ts @keithfawcett
/apps/api/src/tenancy.ts @keithfawcett
/packages/db/src/ssl.ts @keithfawcett

# Database migrations. Bad migrations are the most expensive class of
# mistake — explicitly route to the maintainer even when they're small.
/packages/db/migrations/ @keithfawcett

# Network-protocol contract. Any wire-shape change must go through the
# maintainer because it's the federation API openpartner-network builds
# against.
/docs/network-protocol.md @keithfawcett
3 changes: 3 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These are supported funding model platforms

buy_me_a_coffee: keithf
Loading
Loading