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>
51 lines
2.4 KiB
TypeScript
51 lines
2.4 KiB
TypeScript
/**
|
|
* Exercises the REAL admin-queries against the live DB to prove the admin
|
|
* dashboard data layer works end-to-end. Run after seed-users + seed-admin.
|
|
* Run: npx tsx scripts/verify-admin.ts
|
|
*/
|
|
import { config } from "dotenv"
|
|
config({ path: ".env.local" })
|
|
process.env.DATABASE_SSL = process.env.DATABASE_SSL ?? "disable"
|
|
|
|
async function main() {
|
|
const q = await import("../lib/db/admin-queries")
|
|
const { pool } = await import("../lib/db")
|
|
|
|
const overview = await q.getAdminOverviewStats()
|
|
const dist = await q.getPlanDistribution()
|
|
const users = await q.getUsersPage({ page: 1, pageSize: 5 })
|
|
const ai = await q.getAiUsageAggregates()
|
|
const sys = await q.getSystemCounts()
|
|
|
|
console.log("── Overview ─────────────────────────────")
|
|
console.log(` total users: ${overview.totalUsers}`)
|
|
console.log(` paid / free: ${overview.paidUsers} / ${overview.freeUsers}`)
|
|
console.log(` MRR / ARR: $${overview.mrr} / $${overview.arr}`)
|
|
console.log(` lifetime revenue: $${overview.lifetimeRevenue}`)
|
|
console.log(` properties/tenants: ${overview.totalProperties} / ${overview.totalTenants}`)
|
|
console.log(` AI calls this month:${overview.aiCallsThisMonth}`)
|
|
console.log(` active (30d): ${overview.activeUsers30d}`)
|
|
console.log(` plan distribution: ${JSON.stringify(dist)}`)
|
|
console.log("── Users page (first 5) ─────────────────")
|
|
console.log(` total matched: ${users.total} (pageCount ${users.pageCount})`)
|
|
for (const u of users.rows) {
|
|
console.log(` ${u.email} [${u.plan}] props=${u.propertyCount} tenants=${u.tenantCount} role=${u.role ?? "user"} banned=${u.banned ?? false}`)
|
|
}
|
|
console.log("── AI usage ─────────────────────────────")
|
|
console.log(` this month total: ${ai.totalThisMonth}`)
|
|
console.log(` by type: ${JSON.stringify(ai.byType)}`)
|
|
console.log("── System counts ────────────────────────")
|
|
console.log(` ${JSON.stringify(sys.counts)}`)
|
|
|
|
// sanity assertions
|
|
const ok = overview.totalUsers >= 11 && users.total >= 11
|
|
console.log(`\n${ok ? "✓ PASS" : "✗ CHECK"} — expected >= 11 users (10 landlords + admin)`)
|
|
|
|
await pool.end()
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error("verify-admin failed:", e)
|
|
process.exit(1)
|
|
})
|