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:
@@ -0,0 +1,74 @@
|
||||
# Optional all-in-one stack for Coolify's "Docker Compose" build pack.
|
||||
# Bundles the app + a PostgreSQL database + persistent volumes.
|
||||
#
|
||||
# If you instead provision Postgres separately in Coolify (recommended for
|
||||
# production), deploy with the Dockerfile build pack and skip this file.
|
||||
#
|
||||
# Set the referenced variables in Coolify's Environment Variables. Mark the
|
||||
# NEXT_PUBLIC_* and POSTGRES_* values as available at build time.
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
||||
NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Property Management Network}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgres://${POSTGRES_USER:-pmn}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-pmn}
|
||||
# Bundled Postgres talks plaintext over the private compose network.
|
||||
DATABASE_SSL: ${DATABASE_SSL:-disable}
|
||||
RUN_MIGRATIONS_ON_START: "true"
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||
STORAGE_DIR: /app/storage
|
||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
|
||||
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-}
|
||||
STRIPE_PRO_MONTHLY_PRICE_ID: ${STRIPE_PRO_MONTHLY_PRICE_ID:-}
|
||||
STRIPE_LANDLORD_MONTHLY_PRICE_ID: ${STRIPE_LANDLORD_MONTHLY_PRICE_ID:-}
|
||||
STRIPE_LIFETIME_PRICE_ID: ${STRIPE_LIFETIME_PRICE_ID:-}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@propertymanagement.network}
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
||||
NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Property Management Network}
|
||||
CRON_SECRET: ${CRON_SECRET}
|
||||
volumes:
|
||||
- app-storage:/app/storage
|
||||
expose:
|
||||
- "3000"
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 25s
|
||||
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-pmn}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-pmn}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-pmn} -d ${POSTGRES_DB:-pmn}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
app-storage:
|
||||
db-data:
|
||||
Reference in New Issue
Block a user