This PR implements four backend features covering upload security, automated admin reporting, account suspension notifications, and backup integrity verification.
Files:
src/lib/ipfs/uploadValidator.js(new)src/app/api/materials/upload/route.js(new)
What changed:
Introduced a byte-stream validator that inspects magic number / file header signatures before any file is dispatched to Pinata. The new src/app/api/materials/upload route applies this validation in addition to the existing MIME type allowlist and size checks. Files whose headers do not match their declared MIME type are rejected with HTTP 422 before pinning. Invalid files never reach Pinata, and no MongoDB records are written for rejected uploads.
Supported signatures: PDF, ZIP, OLE2 compound documents (.doc/.xls/.ppt), OOXML containers (.docx/.xlsx/.pptx), JPEG, PNG, WEBP. Plain-text files are validated via a binary-null-byte heuristic.
Files:
src/lib/email/adminReport.js(new)scripts/weekly-admin-stats.mjs(new)
What changed:
Added an aggregator service (adminReport.js) that queries MongoDB for the previous week's completed sales, total revenue, new user registrations, new material uploads, and active listing counts. A clean HTML summary email (with plain-text fallback) is generated and sent to all addresses in ADMIN_REPORT_EMAILS.
The companion cron script (weekly-admin-stats.mjs) is a standalone Node.js entrypoint with no Next.js dependency, intended to run every Monday via cron:
0 8 * * 1 node scripts/weekly-admin-stats.mjs
Structured JSON is logged at every step so output can be piped into any log aggregator and delivery is confirmed in the log output.
Files:
src/lib/email/suspensionNotifier.js(new)src/app/api/admin/users/suspend/route.js(new)
What changed:
Added a suspensionNotifier module exporting sendSuspensionEmail and sendReactivationEmail. Each sends a branded HTML email (with plain-text fallback) explaining the status change, the stated reason, appeal instructions, and a link to community guidelines.
The new POST /api/admin/users/suspend route accepts { userId, action, reason }, updates the user's status field in MongoDB, writes an audit log entry, then dispatches the appropriate notification email. Email failures are caught and logged without blocking the admin action response. The emailSent boolean is returned in the response payload for confirmation.
Files:
scripts/verify-backup.mjs(new)
What changed: Added a standalone verification script that:
- Locates the most recent
.gzbackup archive (or accepts an explicit path via CLI argument). - Runs
mongorestore --dryRunagainst it to confirm the archive is well-formed and parseable without writing any data. - Probes the extracted dump directory for required collections (
users,materials,purchases). - Emits a structured JSON summary of all checks (pass/fail) to stdout.
- Sends a failure alert email to
ADMIN_REPORT_EMAILSif any check fails. - Deletes the temp extraction directory on exit regardless of outcome.
Exit code 0 = all checks passed. Exit code 1 = at least one check failed.
Intended to run automatically after each backup job:
node scripts/backup-mongodb.mjs && node scripts/verify-backup.mjs
| Variable | Used by | Purpose |
|---|---|---|
ADMIN_REPORT_EMAILS |
#363, #364 | Comma-separated admin email recipients |
SUPPORT_EMAIL |
#369 | Appeal contact address shown in suspension emails |
All existing SMTP_* / EMAIL_* variables are reused for email dispatch.
- Upload a PDF with a
.pdfextension but JPEG header bytes → expect HTTP 422 - Upload a valid PDF → passes validation and is pinned to Pinata
- Run
node scripts/weekly-admin-stats.mjswithMONGODB_URIandADMIN_REPORT_EMAILSset → confirm email received with correct stats -
POST /api/admin/users/suspendwithaction: "suspend"→ userstatusbecomes"suspended", suspension email delivered -
POST /api/admin/users/suspendwithaction: "reactivate"→ userstatusbecomes"active", reactivation email delivered - Run
node scripts/verify-backup.mjsagainst a valid.gzbackup → exits 0 with PASSED summary - Run
node scripts/verify-backup.mjsagainst a corrupt archive → exits 1 and sends alert email
Closes #363, #364, #365, #369