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
+23 -22
View File
@@ -3,6 +3,7 @@ import { eq } from "drizzle-orm"
import { db } from "@/lib/db"
import { profiles } from "@/lib/db/schema"
import { getSessionUser } from "@/lib/session"
import { getEffectiveOwnerId } from "@/lib/account"
import {
getDashboardStats,
getRecentRentPayments,
@@ -33,20 +34,11 @@ function GreetingBanner({ name }: { name: string }) {
const dateStr = now.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" })
return (
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-8">
<div>
<h2 className="text-xl font-bold text-white">
{greeting}, {name?.split(" ")[0] ?? "there"} 👋
</h2>
<p className="text-sm text-white/40 mt-0.5">{dateStr}</p>
</div>
<div className="flex items-center gap-2 rounded-xl border border-emerald-500/20 bg-emerald-500/5 px-4 py-2">
<div className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400" />
</div>
<span className="text-xs font-medium text-emerald-400">All systems operational</span>
</div>
<div className="mb-8">
<h2 className="text-xl font-bold text-white">
{greeting}, {name?.split(" ")[0] ?? "there"} 👋
</h2>
<p className="text-sm text-white/40 mt-0.5">{dateStr}</p>
</div>
)
}
@@ -96,23 +88,32 @@ export default async function DashboardPage() {
const user = await getSessionUser()
if (!user) redirect("/login")
// Fetch profile for greeting
// Data is scoped to the effective owner's portfolio (team access).
const ownerId = await getEffectiveOwnerId(user.id)
// Fetch profile for greeting (the logged-in user's own name / onboarding state).
const profile = await db.query.profiles.findFirst({
where: eq(profiles.id, user.id),
columns: { full_name: true },
columns: { full_name: true, onboarding_completed: true },
})
const [stats, recentPayments, openMaintenance, expiringLeases, monthlyRevenue, expenseBreakdown] = await Promise.all([
getDashboardStats(user.id),
getRecentRentPayments(user.id),
getOpenMaintenanceRequests(user.id),
getExpiringLeases(user.id),
getMonthlyRevenue(user.id),
getExpenseBreakdown(user.id),
getDashboardStats(ownerId),
getRecentRentPayments(ownerId),
getOpenMaintenanceRequests(ownerId),
getExpiringLeases(ownerId),
getMonthlyRevenue(ownerId),
getExpenseBreakdown(ownerId),
])
const hasData = stats.totalProperties > 0
// New users who haven't finished onboarding and have no data go through the
// dedicated onboarding flow first.
if (!(profile as { onboarding_completed?: boolean })?.onboarding_completed && !hasData) {
redirect("/onboarding")
}
const rentTrendPct = stats.rentCollectedLastMonth > 0
? Math.round(((stats.rentCollectedThisMonth - stats.rentCollectedLastMonth) / stats.rentCollectedLastMonth) * 100)
: null