Skip to content

Repository files navigation

CauseGrid

A private, structured Industry Problem Intelligence Database. Professionals submit operational pain points through a structured form (not open forum posts) categorized by department, cost impact, frequency, and urgency, so the data can be aggregated into searchable dashboards and paid intelligence reports.

Built with Next.js (App Router) + TypeScript + MongoDB (Mongoose).

Build status — all 5 phases complete

  • Phase 1 — Foundation: project scaffold, validated env config, MongoDB connection layer, core data models.
  • Phase 2 — Auth & security: registration + email OTP verification, login, JWT access tokens + rotating opaque refresh tokens, logout, Zod validation on every endpoint, bcrypt hashing, rate limiting, RBAC.
  • Phase 3 — Structured problem submission: searchable/filterable dashboard backend, full-text search, Cloudinary uploads, anonymity controls, admin moderation.
  • Phase 4 — Monetization: paid reports with subscription-tier paywall, one-off purchases (mock payment fallback), company registry, admin stats + user management.
  • Phase 5 — Seeding & polish: realistic, idempotent seed script across 6 industries, real homepage.
  • Phase 6 — Frontend UI: full page set on top of the API — register/verify/login, searchable dashboard with filters, submission form with file upload, problem detail, reports list + paywalled detail with purchase flow, "my submissions", and an admin panel (stats, moderation queue, user management).
  • Phase 7 — Company flow + email notifications: inline "search or create a company" picker in the submission form (backed by /api/companies), plus automated emails (via the same SendGrid/console fallback) when a submission is approved/rejected/archived and when a report purchase completes.
  • Phase 8 — Production-readiness pass: password reset flow (/forgot-password/reset-password, OTP-based, revokes all sessions), change password + profile settings (/settings), an admin audit log (who approved/rejected/changed what, when), toast notifications in the UI, security response headers, centralized API error handling (every route wrapped so an unexpected error returns clean JSON instead of crashing), a CSV export endpoint gated to paying tiers, and custom 404/error pages.
  • Phase 9 — Analytics, admin data tables, and session management: a full analytics dashboard with 7 charts (Recharts) and today/week/ month/year/custom date filtering, backed by a single cached $facet aggregation query for speed; dedicated /admin/users and /admin/problems pages with search, multi-field sort, filters, real pagination, bulk moderation actions, and CSV/PDF export that always matches the current on-screen filters; and multi-device session management (view every active login, revoke one or all others) in /settings.
  • Phase 10 — Two-factor authentication (TOTP): RFC 6238-compliant TOTP implemented directly on Node's crypto (no dependency, verified against the official RFC 4226 test vector), QR-code setup flow, single-use backup codes, and a proper two-step login (a password-only "pending" token can never be used as a real session — this is enforced by an explicit, checked purpose field on every JWT, which a test caught missing and this fixes).
  • Phase 11 — Remaining basic + advanced features: in-app notification bell, saved searches with email/in-app alert digests (the concrete build-out of the "Compliance Alert" / "Vendor Matchmaking" ideas from the original product brief), API keys for programmatic/vendor access (Authorization: Bearer cg_live_...), self-service account deletion (anonymizes rather than hard-deletes, so shared submission history stays intact), company profile pages, Terms/Privacy pages with signup acceptance, and robots.txt/sitemap.xml.
  • Phase 12 — Theme system + homepage/SEO overhaul: the entire app (public pages, dashboard, settings, admin — everything, via the one shared layout) now supports 4 themes (Dark, Light, Midnight, High contrast), switchable from a toggle in the navbar with no flash of the wrong theme on load. Every hardcoded color in the codebase was migrated to CSS custom properties (app/globals.css); charts (Recharts) resolve the live theme's actual color values via a useThemeColors() hook since chart libraries can't consume Tailwind classes directly. The homepage was rebuilt (hero, how-it-works, features, insights preview, FAQ with FAQPage JSON-LD, final CTA, Organization/WebSite JSON-LD) and a real, statically-generated blog (/blog) was added with 4 full articles for SEO. Also fixed: a duplicate-React-key console error on the old homepage, and a nested-<form>-inside-<form> hydration bug in the company-creation flow that was silently breaking new company creation.

Themes

Toggle is in the navbar (works identically on every page, including admin, since there's one shared <Nav>). Persisted to localStorage, applied via a data-theme attribute on <html> with an inline before-paint script so there's no flash of the wrong theme. To add a fifth theme: add a [data-theme="yourname"] { ... } block in app/globals.css (copy an existing block's variable list) and add the name to THEMES in lib/client/theme.tsx.

Scheduling saved-search alerts

POST /api/cron/check-saved-searches finds newly-approved problems matching each active saved search and emails a digest. This app has no built-in scheduler (Next.js routes are request-driven), so you need to call this endpoint on a schedule yourself once deployed — e.g.:

  • Vercel: add a Vercel Cron entry in vercel.json hitting this route daily.
  • Anywhere else: a system crontab or GitHub Actions scheduled workflow running curl -X POST https://yourdomain.com/api/cron/check-saved-searches -H "x-cron-secret: $CRON_SECRET".

Set CRON_SECRET in your environment and send it as the x-cron-secret header — the endpoint refuses to run in production without it.

Analytics & admin data pages

Page What it does
/admin/analytics 8 summary stats + 7 charts (submissions over time, user growth, status/department/cost-impact/urgency breakdowns, top industries), date range presets + custom range, 30s server-side cache per query so repeat views are instant
/admin/users Search by name/email, filter by role/tier/active, sort any column, paginated, CSV/PDF export of the exact filtered view
/admin/problems Same pattern plus department/cost/urgency/industry filters, date range, checkbox multi-select with bulk approve/reject (one audit log entry + per-submitter emails)
/settings → Active sessions Every device/browser currently logged in, with IP + last-seen, one-click revoke per device or "log out all others"

New in Phase 8

Feature Where
Forgot/reset password /forgot-password, /reset-password, POST /api/auth/forgot-password, POST /api/auth/reset-password
Change password (logged in) /settings, PATCH /api/auth/change-password — revokes your other sessions, keeps this one
Profile update /settings, PATCH /api/auth/me
Audit log GET /api/admin/audit-log, shown in /admin — records moderation actions, role/tier/active changes, report publish/delete, industry creation
CSV export GET /api/problems/export — same filters as the dashboard, gated to pro/enterprise/admin
Toasts lib/client/toast.tsx, wired into admin actions, submission, report purchase
Security headers next.config.ts — X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
Centralized error handling withErrorHandling() in lib/api/respond.ts, applied to every route

Pages

Path Purpose
/ Homepage
/blog, /blog/[slug] SEO-oriented insights articles (statically generated)
/register, /verify, /login, /forgot-password, /reset-password Auth flow (OTP verification, password reset)
/dashboard Searchable/filterable problem intelligence feed
/submit Structured problem submission form (with file upload)
/problems/[id] Problem detail
/companies/[id] Company profile + its public submissions
/mine Your own submissions, any status
/saved-searches Manage saved search alerts
/settings Profile, password, 2FA, API keys, sessions, account deletion
/reports, /reports/[id] Paid reports list + paywalled detail with purchase
/admin, /admin/analytics, /admin/users, /admin/problems Stats, charts, full data tables, moderation (admin only)

Getting started

npm install
cp .env.example .env.local
# edit .env.local — at minimum set MONGODB_URI to a real MongoDB Atlas
# (or local) connection string, and set real JWT secrets:
#   openssl rand -base64 48
npm run dev

Then:

  1. Visit http://localhost:3000/api/health — confirms MongoDB is connected.
  2. Run npm run seed — populates industries, demo users, ~18 realistic problem submissions, and 3 published reports.
  3. Log in as the seeded admin: email from SEED_ADMIN_EMAIL (default admin@causegrid.dev), password from SEED_ADMIN_PASSWORD (default ChangeMe123!change this in .env.local).
  4. Or log in as a demo user: demo.user1@causegrid.dev / Demo1234!.

Why some services are "optional" in .env.local

SendGrid, Cloudinary, and Stripe keys are all optional. If they're missing, nothing crashes — each falls back to safe dev behavior instead:

Service Without credentials
SendGrid (email/OTP) Full email content, including OTP codes, is printed to the server console
Cloudinary (uploads) Returns a labeled placeholder image URL instead of erroring
Stripe (payments) Report purchases are auto-approved as a clearly-logged mock charge

This means you can develop and test the entire flow — register, verify by reading the OTP off the console, submit a problem with an "attachment", buy a report — before paying for any third-party service.

API reference

All request bodies are JSON (except /api/uploads, which is multipart/form-data) and validated with Zod. Auth uses httpOnly cookies set by login/register/refresh — no need to manually attach a bearer token if you're calling from a browser-based frontend on the same origin.

Auth

Method Path Auth Notes
POST /api/auth/register Creates unverified account, sends OTP
POST /api/auth/verify-otp Verifies email, logs in (sets cookies)
POST /api/auth/resend-otp Resends OTP (60s cooldown)
POST /api/auth/login Logs in (sets cookies)
POST /api/auth/refresh refresh cookie Rotates tokens
POST /api/auth/logout Revokes refresh token, clears cookies
GET /api/auth/me required Current user

Problems

Method Path Auth Notes
GET /api/problems optional Search/filter approved problems (q, department, costImpact, frequency, urgency, industry, sort, page, limit)
POST /api/problems required Submit a structured problem (→ pending_review)
GET /api/problems/[id] optional Detail (404 if not visible to you)
PATCH /api/problems/[id] admin Moderate: approve/reject/archive
DELETE /api/problems/[id] owner (pending only) / admin Delete
GET /api/problems/mine required Your own submissions, any status

Uploads, industries, companies

Method Path Auth Notes
POST /api/uploads required Multipart file field, ≤5MB, PNG/JPEG/WEBP/PDF
GET /api/industries Taxonomy list
POST /api/industries admin Add an industry
GET /api/companies List/search companies (?q=)
POST /api/companies required Register a company

Reports (monetization)

Method Path Auth Notes
GET /api/reports optional List published reports (previews for non-payers)
POST /api/reports admin Bundle approved problems into a report
GET /api/reports/[id] optional Full content if you have access, preview otherwise
PATCH /api/reports/[id] admin Edit / publish
DELETE /api/reports/[id] admin Delete
POST /api/reports/[id]/purchase required Buy access (auto-unlocked for pro/enterprise subscribers)

Admin

Method Path Auth Notes
GET /api/admin/stats admin User/problem/report/revenue dashboard numbers
GET /api/admin/users admin Paginated user list
PATCH /api/admin/users/[id] admin Change role/subscription/active status

Project structure

app/
  page.tsx                    # homepage
  api/                        # all routes described above
lib/
  config/env.ts                # Zod-validated environment config
  db/connectDB.ts              # cached MongoDB connection
  models/                      # Mongoose schemas (User, Company, Industry,
                                # Problem, Report, OtpToken, RefreshToken)
  auth/                        # password hashing, JWT, cookies, rate
                                # limiting, session management, RBAC guard
  services/                    # email, OTP, Cloudinary, payment, and the
                                # anonymity/paywall serializers
  validations/                 # Zod schemas per resource
  api/respond.ts               # shared response + body-parsing helpers
types/index.ts                 # shared enums (roles, departments, etc.)
scripts/seed.ts                # idempotent database seed script
.env.example

Security scan (this pass)

A page/route audit and a targeted code-level security review were done.

Page/route audit: every href/router.push in the frontend and every apiGet/apiPost/etc. call was cross-checked against actual page.tsx and route.ts files — no missing pages or dead links found. One real functional bug was found and fixed: the client never called /api/auth/refresh, so every logged-in session would silently break 15 minutes after login with no recovery. Fixed with automatic, de-duped, silent refresh-and-retry on a 401 (lib/client/api.ts).

Vulnerabilities found and fixed:

  1. ReDoS / regex injection/api/companies (unauthenticated) and /api/admin/users search built a MongoDB $regex filter directly from raw user input, allowing catastrophic-backtracking patterns to stall queries, and letting a "search box" act as a live regex engine. Fixed with escapeRegex() in lib/validations/shared.ts, applied at both call sites, verified with a test that the fix actually neutralizes a real ReDoS pattern.
  2. No rate limiting on the core public read endpointsGET /api/problems (the heaviest query in the app: aggregation + $lookup joins) plus problem/report detail, reports list, industries, and company profile had zero rate limiting. Beyond generic DoS/cost risk, this meant the entire structured dataset — the thing the business model is built on selling access to — could be scraped in bulk for free. All now rate-limited per-IP.
  3. File upload MIME-type spoofing/api/uploads only checked the client-supplied Content-Type, which is trivially spoofable (rename any file, set the form field's declared type to image/png). Added lib/services/fileSignature.ts, which checks the actual binary signature (PNG/JPEG/WEBP/PDF magic bytes) and rejects anything whose real content doesn't match an allowed type, regardless of what the client claimed.
  4. JWT purpose confusion (found during Phase 10, still noted here for completeness) — the 2FA "pending" token and a real access token were signed with the same secret with no discriminator, so a pending token (issued after only a password check) could have been replayed as a full session, bypassing the second factor entirely. Fixed with an explicit, checked purpose field on every JWT.

Reviewed and confirmed solid (no changes needed): every mutating route requires auth except the intentionally pre-auth ones (login/register/OTP/refresh/logout/cron); every per-resource [id] route (saved searches, API keys, sessions, notifications, problems, reports) scopes its query to the requesting user or checks ownership before mutating; no dangerouslySetInnerHTML anywhere (React's default escaping handles stored-content XSS); no secrets logged; cookies are httpOnly + sameSite=lax + secure in production; every request body goes through Zod validation (no route reads raw request.json() unvalidated); .env* is gitignored.

Known, accepted gaps (documented rather than silently left): npm audit flags high-severity CVEs in postcss/sharp, both bundled inside next itself — not directly installable fixes, and not reachable through this app's actual usage (next/image is never used, so sharp is dormant; postcss only processes our own build-time CSS, never user input). Will resolve automatically on the next Next.js release that bumps its bundled versions. CSRF relies on sameSite=lax cookies rather than a double-submit token — a reasonable default for this kind of app, but worth upgrading if you're handling real payments beyond the current mock fallback. Rate limiting is in-memory/per-process — noted elsewhere in this README as needing Redis if you scale horizontally.

Deployment notes

  • Database: use MongoDB Atlas (free tier is fine to start). Whitelist your deployment platform's IPs (or 0.0.0.0/0 for simplicity, with a strong DB user password).
  • Secrets: generate real JWT_ACCESS_SECRET / JWT_REFRESH_SECRET with openssl rand -base64 48 — do not use the defaults in production (the app will still boot with them, but that's insecure).
  • Cookies: secure is automatically enabled when NODE_ENV=production, which requires HTTPS — make sure your deployment serves over HTTPS.
  • Rate limiting: currently in-memory per Node process. Fine for a single instance; if you deploy multiple instances/serverless functions behind a load balancer, swap lib/auth/rateLimit.ts for a Redis-backed limiter (e.g. Upstash) — call sites don't need to change.
  • Seeding production: npm run seed refuses to run when NODE_ENV=production unless SEED_ALLOW_PROD=true is explicitly set, to prevent accidentally seeding demo data into a real database.
  • Recommended host: Vercel (zero-config for Next.js) with MongoDB Atlas.

About

Multi-tenant nonprofit management platform for organizations to manage campaigns, donors, donations, volunteers, events, fundraising, and impact reporting.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages