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>
45 lines
1.1 KiB
TypeScript
45 lines
1.1 KiB
TypeScript
import { headers } from "next/headers"
|
|
import { db } from "@/lib/db"
|
|
import { admin_audit_log } from "@/lib/db/schema"
|
|
|
|
export type AdminAction =
|
|
| "plan_change"
|
|
| "ban"
|
|
| "unban"
|
|
| "set_role"
|
|
| "impersonate"
|
|
| "stop_impersonate"
|
|
| "delete_user"
|
|
| "resend_verification"
|
|
| "maintenance_mode"
|
|
| "ai_provider"
|
|
|
|
/**
|
|
* Append one immutable row to admin_audit_log. Call this for EVERY mutating
|
|
* admin action (the caller must already have passed getAdminSession()).
|
|
*/
|
|
export async function logAdminAction(opts: {
|
|
adminId: string
|
|
action: AdminAction
|
|
targetUserId?: string | null
|
|
metadata?: Record<string, unknown>
|
|
}) {
|
|
let ip: string | null = null
|
|
try {
|
|
const h = await headers()
|
|
ip =
|
|
h.get("x-forwarded-for")?.split(",")[0]?.trim() ??
|
|
h.get("x-real-ip") ??
|
|
null
|
|
} catch {
|
|
// headers() unavailable outside a request — fine.
|
|
}
|
|
await db.insert(admin_audit_log).values({
|
|
admin_id: opts.adminId,
|
|
action: opts.action,
|
|
target_user_id: opts.targetUserId ?? null,
|
|
metadata: opts.metadata ?? {},
|
|
ip_address: ip,
|
|
})
|
|
}
|