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:
co-authored by
Claude Opus 4.8
parent
969d5d4c8a
commit
c9968531e4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user