Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening

Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-02 13:42:34 -04:00
co-authored by Claude Opus 4.8
parent 969d5d4c8a
commit c9968531e4
282 changed files with 41530 additions and 4013 deletions
@@ -0,0 +1,33 @@
-- NOTE: made idempotent (IF NOT EXISTS / guarded constraint) by hand.
-- The admin tables/columns below were previously applied to some databases via
-- `drizzle-kit push` without a migration file, so migration history had drifted.
-- Guarding these statements lets 0001 provision a fresh database (creating the
-- admin objects + app_settings) while safely no-op'ing on already-migrated DBs.
CREATE TABLE IF NOT EXISTS "admin_audit_log" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"admin_id" text,
"action" text NOT NULL,
"target_user_id" text,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
"ip_address" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "app_settings" (
"key" text PRIMARY KEY NOT NULL,
"value" jsonb DEFAULT '{}'::jsonb NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "session" ADD COLUMN IF NOT EXISTS "impersonated_by" text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "role" text DEFAULT 'user';--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "banned" boolean DEFAULT false;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "ban_reason" text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "ban_expires" timestamp;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'admin_audit_log_admin_id_user_id_fk'
) THEN
ALTER TABLE "admin_audit_log" ADD CONSTRAINT "admin_audit_log_admin_id_user_id_fk" FOREIGN KEY ("admin_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
END IF;
END $$;
+20
View File
@@ -0,0 +1,20 @@
CREATE TABLE "account_members" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"owner_id" text NOT NULL,
"member_id" text,
"email" text NOT NULL,
"role" text DEFAULT 'member' NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"invite_token" text DEFAULT gen_random_uuid()::text NOT NULL,
"accepted_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "account_members_invite_token_unique" UNIQUE("invite_token")
);
--> statement-breakpoint
ALTER TABLE "profiles" ADD COLUMN "brand_name" text;--> statement-breakpoint
ALTER TABLE "profiles" ADD COLUMN "brand_logo_url" text;--> statement-breakpoint
ALTER TABLE "profiles" ADD COLUMN "brand_color" text;--> statement-breakpoint
ALTER TABLE "profiles" ADD COLUMN "hide_powered_by" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "account_members" ADD CONSTRAINT "account_members_owner_id_profiles_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "account_members" ADD CONSTRAINT "account_members_member_id_profiles_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
+13
View File
@@ -0,0 +1,13 @@
CREATE TABLE "api_keys" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"name" text NOT NULL,
"key_hash" text NOT NULL,
"key_prefix" text NOT NULL,
"last_used_at" timestamp with time zone,
"revoked_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "api_keys_key_hash_unique" UNIQUE("key_hash")
);
--> statement-breakpoint
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "profiles" ADD COLUMN "calendar_token" text DEFAULT gen_random_uuid()::text;--> statement-breakpoint
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_calendar_token_unique" UNIQUE("calendar_token");
@@ -0,0 +1,17 @@
CREATE TABLE "accounting_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 NOT NULL,
"expires_at" timestamp with time zone,
"realm_id" text,
"org_name" text,
"status" text DEFAULT 'active' NOT NULL,
"last_sync_at" timestamp with time zone,
"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 "accounting_connections" ADD CONSTRAINT "accounting_connections_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
+38
View File
@@ -0,0 +1,38 @@
CREATE TABLE "webhook_deliveries" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"endpoint_id" uuid NOT NULL,
"event" text NOT NULL,
"payload" jsonb NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"attempts" integer DEFAULT 0 NOT NULL,
"max_attempts" integer DEFAULT 5 NOT NULL,
"next_attempt_at" timestamp with time zone DEFAULT now() NOT NULL,
"response_status" integer,
"response_body" text,
"error" text,
"delivered_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "webhook_endpoints" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"url" text NOT NULL,
"description" text,
"events" text[] DEFAULT '{}'::text[] NOT NULL,
"secret" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"source" text DEFAULT 'dashboard' NOT NULL,
"last_success_at" timestamp with time zone,
"last_error_at" timestamp with time zone,
"last_error" text,
"failure_count" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_endpoint_id_webhook_endpoints_id_fk" FOREIGN KEY ("endpoint_id") REFERENCES "public"."webhook_endpoints"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "webhook_endpoints" ADD CONSTRAINT "webhook_endpoints_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1,19 @@
CREATE TABLE "signature_requests" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"lease_id" uuid,
"provider" text NOT NULL,
"external_id" text,
"status" text DEFAULT 'sent' NOT NULL,
"signer_email" text NOT NULL,
"signer_name" text,
"document_name" text,
"signed_document_url" text,
"last_error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"completed_at" timestamp with time zone,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "signature_requests" ADD CONSTRAINT "signature_requests_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "signature_requests" ADD CONSTRAINT "signature_requests_lease_id_leases_id_fk" FOREIGN KEY ("lease_id") REFERENCES "public"."leases"("id") ON DELETE set null ON UPDATE no action;
@@ -0,0 +1,2 @@
ALTER TABLE "properties" ADD COLUMN "latitude" double precision;--> statement-breakpoint
ALTER TABLE "properties" ADD COLUMN "longitude" double precision;
@@ -0,0 +1,2 @@
ALTER TABLE "profiles" ADD COLUMN "paypal_subscription_id" text;--> statement-breakpoint
ALTER TABLE "profiles" ADD COLUMN "billing_provider" text;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+63
View File
@@ -8,6 +8,69 @@
"when": 1782223467789,
"tag": "0000_next_red_skull",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1782928287616,
"tag": "0001_furry_christian_walker",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1782935722973,
"tag": "0002_quiet_freak",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1782975002382,
"tag": "0003_api_keys",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1782978054678,
"tag": "0004_yellow_switch",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1782980416142,
"tag": "0005_large_black_queen",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1782980797596,
"tag": "0006_webhooks",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1782981198010,
"tag": "0007_chubby_sinister_six",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1782992530365,
"tag": "0008_dazzling_white_tiger",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1782994066547,
"tag": "0009_amusing_blackheart",
"breakpoints": true
}
]
}