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
+1
View File
@@ -254,6 +254,7 @@ export function getEnvHealth() {
"STRIPE_WEBHOOK_SECRET",
"NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"SMTP_HOST",
"SMTP_USER",
"GOOGLE_CLIENT_ID",
@@ -0,0 +1,17 @@
CREATE TABLE "esign_connections" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"provider" text NOT NULL,
"access_token" text NOT NULL,
"refresh_token" text,
"expires_at" timestamp with time zone,
"account_id" text,
"base_uri" text,
"account_name" text,
"status" text DEFAULT 'active' NOT NULL,
"last_error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "esign_connections" ADD CONSTRAINT "esign_connections_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -71,6 +71,13 @@
"when": 1782994066547,
"tag": "0009_amusing_blackheart",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1783017593260,
"tag": "0010_esign_connections",
"breakpoints": true
}
]
}
+26
View File
@@ -631,6 +631,32 @@ export const signature_requests = pgTable("signature_requests", {
updated_at: updatedAt(),
})
// ============================================================
// E-SIGN CONNECTIONS (per-landlord DocuSign OAuth / Dropbox Sign API key)
// ============================================================
// One row per (owner, provider). Each landlord connects THEIR OWN e-signature
// account, so leases are sent from their brand with their audit trail. DocuSign
// uses OAuth (access + refresh tokens); Dropbox Sign uses an API key stored in
// `access_token`. All secrets are AES-256-GCM encrypted (see lib/crypto.ts).
export const esign_connections = pgTable("esign_connections", {
id: uuid("id").primaryKey().defaultRandom(),
user_id: text("user_id")
.notNull()
.references(() => profiles.id, { onDelete: "cascade" }),
provider: text("provider").$type<"docusign" | "dropbox_sign">().notNull(),
access_token: text("access_token").notNull(), // encrypted (DocuSign access token / Dropbox Sign API key)
refresh_token: text("refresh_token"), // encrypted (DocuSign only)
expires_at: tstz("expires_at"),
// DocuSign account id + base uri from /oauth/userinfo (null for Dropbox Sign).
account_id: text("account_id"),
base_uri: text("base_uri"),
account_name: text("account_name"),
status: text("status").$type<"active" | "error" | "revoked">().notNull().default("active"),
last_error: text("last_error"),
created_at: createdAt(),
updated_at: updatedAt(),
})
// ============================================================
// WEBHOOK ENDPOINTS (outbound webhooks / Zapier integration)
// ============================================================