Initial import: property management SaaS + security hardening + admin dashboard

Property Management Network — Next.js 16 (App Router), Better Auth,
Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend.

Includes:
- Security hardening: access-control/IDOR fixes, TLS-by-default DB layer,
  constant-time cron auth, strict security headers, atomic AI quota gating,
  HTML/email output encoding, demo-backdoor disabled in production.
- Superadmin dashboard at /admin (overview/MRR, server-paginated users with
  ban/impersonate/plan/delete, billing, platform activity + admin audit log,
  AI usage, system health) via the Better Auth admin plugin.
- Seed/migration utility scripts under scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-06-23 20:36:07 -04:00
co-authored by Claude Opus 4.8
commit 857b9a7811
291 changed files with 38996 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
import { drizzle } from "drizzle-orm/node-postgres"
import { Pool, types } from "pg"
import * as schema from "./schema"
// ── pg type parsers ───────────────────────────────────────────────
// Make the driver return the same value shapes the app relied on under
// Supabase/PostgREST, so the ~hundreds of existing read sites keep working:
// numeric -> JS number (was parsed as number by PostgREST)
// date -> "YYYY-MM-DD" string
// timestamp / timestamptz -> ISO 8601 string
types.setTypeParser(1700, (v) => (v === null ? null : parseFloat(v))) // numeric
types.setTypeParser(1082, (v) => v) // date (identity string)
types.setTypeParser(1114, (v) => (v === null ? null : new Date(v + "Z").toISOString())) // timestamp
types.setTypeParser(1184, (v) => (v === null ? null : new Date(v).toISOString())) // timestamptz
const globalForDb = globalThis as unknown as { pool?: Pool }
// ── TLS policy ────────────────────────────────────────────────────
// Production MUST use verified TLS so credentials and tenant data are
// never sent in plaintext over the network. The default below is
// encrypted + certificate-verified. Behavior is controlled explicitly
// via DATABASE_SSL:
// "disable" -> ssl: false (ONLY for local dev / unix-socket Postgres)
// "no-verify" -> encrypted but unverified (self-signed certs)
// "require" / unset / default -> encrypted + verified (recommended)
// When verifying, an optional custom CA can be supplied via DATABASE_CA.
function resolveSsl(): false | { rejectUnauthorized: boolean; ca?: string } {
switch (process.env.DATABASE_SSL) {
case "disable":
return false
case "no-verify":
return { rejectUnauthorized: false }
default: {
const ca = process.env.DATABASE_CA
return ca
? { rejectUnauthorized: true, ca }
: { rejectUnauthorized: true }
}
}
}
export const pool =
globalForDb.pool ??
new Pool({
connectionString: process.env.DATABASE_URL,
ssl: resolveSsl(),
})
if (process.env.NODE_ENV !== "production") globalForDb.pool = pool
export const db = drizzle(pool, { schema })
export { schema }