Deploy on DigitalOcean App Platform (GitHub-source build) + consolidate audit-fixes

Deploy config:
- .do/app.yaml: build the Dockerfile directly from GitHub (deploy_on_push) instead
  of a pre-built DOCR image; NEXT_PUBLIC_* set RUN_AND_BUILD_TIME with the
  propertymanagement.network domain so they bake into the client bundle; add
  custom domains block (apex + www); wire Sentry DSN (server + browser).

Included pending work from the audit-fixes branch:
- AI provider abstraction (OpenAI/Anthropic, admin-selectable; Anthropic default)
- Per-landlord e-signature (DocuSign OAuth + Dropbox Sign) + migration 0010
- Outbound webhooks / Zapier integration
- PayPal removal (Stripe-only billing)
- Storage hardening (fail-loud when Spaces unconfigured), security fixes

Verified: full production Docker build (same build-args as DO) passes clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-03 04:45:24 -04:00
co-authored by Claude Opus 4.8
parent 917a06ee85
commit 5495b94924
86 changed files with 7647 additions and 1182 deletions
+91 -47
View File
@@ -1,30 +1,28 @@
# ─────────────────────────────────────────────────────────────────────────────
# DigitalOcean App Platform spec — Property Management Network
#
# Deploy: doctl apps create --spec .do/app.yaml
# Deploy: doctl apps create --spec .do/app.yaml (or the DO MCP apps-create)
# Update: doctl apps update <APP_ID> --spec .do/app.yaml
#
# SOURCE: image-based from DigitalOcean Container Registry (DOCR). The app's git
# lives on self-hosted Gitea, which App Platform cannot pull, so we build the
# Docker image ourselves and push it to DOCR. See DIGITALOCEAN.md for the full
# build/push/deploy walkthrough.
# SOURCE: App Platform builds the Dockerfile directly from GitHub
# (github.com/silkoserfo/property-management-network). Pushes to `main`
# auto-redeploy (deploy_on_push). No DOCR image build/push needed.
#
# SECRETS: values marked `type: SECRET` are placeholders — set the real values in
# the App Platform dashboard (App → Settings → Environment Variables) or via
# `doctl`. Never commit real secrets to this file.
# the App Platform dashboard (App → Settings → Environment Variables) or via the
# create spec. Never commit real secrets to this file.
# ─────────────────────────────────────────────────────────────────────────────
name: property-management-network
region: nyc
services:
- name: web
# Pre-built image pushed to DOCR (repository must exist in your registry).
image:
registry_type: DOCR
repository: property-management-network
tag: latest
deploy_on_push:
enabled: true
# Built by App Platform from GitHub using the repo Dockerfile.
github:
repo: silkoserfo/property-management-network
branch: main
deploy_on_push: true
dockerfile_path: Dockerfile
instance_count: 1
instance_size_slug: apps-s-1vcpu-1gb
http_port: 3000
@@ -37,32 +35,35 @@ services:
failure_threshold: 3
envs:
# ── App URLs ──────────────────────────────────────────────────────────
# ${APP_URL} resolves to the app's public URL at runtime. NOTE: the client
# bundle bakes NEXT_PUBLIC_APP_URL at *image build* time (see Dockerfile /
# DIGITALOCEAN.md), so build the image with the same URL you serve on.
# NEXT_PUBLIC_* are inlined into the client bundle at BUILD time, so they
# must be RUN_AND_BUILD_TIME with the literal domain we serve on.
- key: NEXT_PUBLIC_APP_URL
scope: RUN_TIME
value: ${APP_URL}
scope: RUN_AND_BUILD_TIME
value: https://propertymanagement.network
- key: BETTER_AUTH_URL
scope: RUN_TIME
value: ${APP_URL}
value: https://propertymanagement.network
- key: NEXT_PUBLIC_APP_NAME
scope: RUN_TIME
scope: RUN_AND_BUILD_TIME
value: Property Management Network
# ── Database (managed Postgres — use the PRIVATE host; see DIGITALOCEAN.md) ──
# ── Admin & auth policy ───────────────────────────────────────────────
- key: ADMIN_EMAILS
scope: RUN_TIME
value: leon@phluit.com
- key: REQUIRE_EMAIL_VERIFICATION
scope: RUN_TIME
value: "true"
# ── Database (managed Postgres — PRIVATE host, direct port 25060) ──
- key: DATABASE_URL
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
# Verified TLS (encrypted + certificate-checked). DO Managed Postgres uses
# a CA that isn't in the system trust store, so paste the cluster's CA cert
# into DATABASE_CA: DO control panel → Database → Connection Details →
# "Download CA certificate", then paste its PEM contents as the DATABASE_CA
# secret in the App Platform dashboard. Without a valid CA the app will
# refuse to connect (fail loud) rather than run unverified.
# Emergency fallback ONLY (not for production): DATABASE_SSL=no-verify is
# encrypted but does NOT verify the server certificate.
# Verified TLS: DO's Managed Postgres CA isn't in the system trust store, so
# paste the cluster CA PEM (repo root ca-certificate.crt) into DATABASE_CA.
# With `require` + a valid CA the app connects verified; without a valid CA
# it fails loud rather than run unverified.
- key: DATABASE_SSL
scope: RUN_TIME
value: require
@@ -70,8 +71,7 @@ services:
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
# Schema is migrated out-of-band (as doadmin), NOT on boot — the app user
# intentionally lacks DDL rights. Keep this false; run migrations manually.
# Schema is migrated out-of-band (as doadmin), NOT on boot.
- key: RUN_MIGRATIONS_ON_START
scope: RUN_TIME
value: "false"
@@ -81,14 +81,15 @@ services:
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
# Google OAuth (optional — leave blank to disable the Google button).
- key: GOOGLE_CLIENT_ID
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
value: ""
- key: GOOGLE_CLIENT_SECRET
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
value: ""
# ── Stripe ────────────────────────────────────────────────────────────
- key: STRIPE_SECRET_KEY
@@ -99,22 +100,21 @@ services:
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
# No STRIPE_*_PRICE_ID vars — prices are resolved by lookup key and
# auto-created on first checkout (lib/stripe/prices.ts). Going live only
# needs the two live secrets above + the live publishable key below.
# ── OpenAI ────────────────────────────────────────────────────────────
- key: OPENAI_API_KEY
# ── AI provider (Anthropic default; OpenAI optional) ──────────────────
- key: ANTHROPIC_API_KEY
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
- key: ANTHROPIC_MODEL
scope: RUN_TIME
value: claude-haiku-4-5
- key: OPENAI_API_KEY
scope: RUN_TIME
type: SECRET
value: ""
# ── Email (SMTP — SMTP2GO) ────────────────────────────────────────────
# The app sends mail via SMTP only (nodemailer). Email is silently skipped
# unless SMTP_HOST + SMTP_USER + SMTP_PASS are all set — password resets,
# email verification, rent/overdue/lease reminders, team invites, and
# payment links all depend on this. EMAIL_FROM is a bare address; the app
# wraps it as "Property Management Network <…>".
- key: SMTP_HOST
scope: RUN_TIME
value: mail.smtp2go.com
@@ -133,10 +133,9 @@ services:
scope: RUN_TIME
value: postmaster@propertymanagement.network
# ── Cloudflare Turnstile (site key is public; baked into the client bundle
# at image build time — keep it in sync when you build) ──
# ── Cloudflare Turnstile (site key public; baked at build time) ──
- key: NEXT_PUBLIC_TURNSTILE_SITE_KEY
scope: RUN_TIME
scope: RUN_AND_BUILD_TIME
value: 0x4AAAAAADuDQverznfv1a60
- key: TURNSTILE_SECRET_KEY
scope: RUN_TIME
@@ -165,8 +164,53 @@ services:
scope: RUN_TIME
value: https://nyc3.cdn.digitaloceanspaces.com
# ── Accounting sync (optional — per-landlord QuickBooks / Xero OAuth) ──
- key: QBO_CLIENT_ID
scope: RUN_TIME
type: SECRET
value: ""
- key: QBO_CLIENT_SECRET
scope: RUN_TIME
type: SECRET
value: ""
- key: QBO_ENVIRONMENT
scope: RUN_TIME
value: production
- key: XERO_CLIENT_ID
scope: RUN_TIME
type: SECRET
value: ""
- key: XERO_CLIENT_SECRET
scope: RUN_TIME
type: SECRET
value: ""
- key: XERO_SALES_ACCOUNT_CODE
scope: RUN_TIME
value: "200"
- key: XERO_EXPENSE_ACCOUNT_CODE
scope: RUN_TIME
value: "400"
# ── Error monitoring (Sentry — DSN is public; browser DSN baked at build) ──
- key: SENTRY_DSN
scope: RUN_TIME
value: https://ef6aa585a080711e14a855b6cc024e9a@o4509830676873216.ingest.us.sentry.io/4511667160219648
- key: NEXT_PUBLIC_SENTRY_DSN
scope: RUN_AND_BUILD_TIME
value: https://ef6aa585a080711e14a855b6cc024e9a@o4509830676873216.ingest.us.sentry.io/4511667160219648
- key: SENTRY_ENVIRONMENT
scope: RUN_TIME
value: production
# ── Cron (Bearer token the DO Function sends to /api/cron/*) ──
- key: CRON_SECRET
scope: RUN_TIME
type: SECRET
value: REPLACE_IN_DASHBOARD
# ── Custom domains (DNS hosted on Cloudflare — set CNAMEs there, DNS-only) ──
domains:
- domain: propertymanagement.network
type: PRIMARY
- domain: www.propertymanagement.network
type: ALIAS