// Ad reports: members flag a broken or inappropriate ad (ads are auto-approved, // so this is the safety valve). Stored dual-mode (MySQL when DATABASE_URL, else // a JSON file in the volume) and surfaced to the admin. const fs = require('fs'); const path = require('path'); const db = require('./db'); let DATA_DIR = null; const J = { db: null, FILE: () => path.join(DATA_DIR, 'ad-reports.json'), load() { try { this.db = JSON.parse(fs.readFileSync(this.FILE(), 'utf8')); } catch (e) { this.db = { nextId: 1, items: [] }; } }, save() { try { fs.writeFileSync(this.FILE(), JSON.stringify(this.db)); } catch (e) {} } }; function init(opts) { DATA_DIR = opts.dataDir; } const REASONS = ['broken', 'inappropriate', 'spam', 'scam', 'other']; async function add(campaignId, reporterEmail, reason, note) { const now = Date.now(); const r = REASONS.includes(String(reason)) ? String(reason) : 'other'; const n = String(note || '').slice(0, 500); const cid = Number(campaignId) || 0; const who = String(reporterEmail || '').toLowerCase(); if (db.enabled()) { const ins = await db.q('INSERT INTO ad_reports (campaign_id,reporter,reason,note,ts) VALUES (?,?,?,?,?)', [cid, who, r, n, now]); return { id: ins.insertId, campaignId: cid, reason: r }; } if (!J.db) J.load(); const id = J.db.nextId++; J.db.items.push({ id, campaignId: cid, reporter: who, reason: r, note: n, ts: now, resolved: false }); J.save(); return { id, campaignId: cid, reason: r }; } // recent reports for the admin view (newest first) async function list(limit = 200) { if (db.enabled()) { const rows = await db.q('SELECT id,campaign_id,reporter,reason,note,ts,resolved FROM ad_reports ORDER BY ts DESC LIMIT ?', [limit]); return rows.map(x => ({ id: x.id, campaignId: x.campaign_id, reporter: x.reporter, reason: x.reason, note: x.note, ts: Number(x.ts), resolved: !!x.resolved })); } if (!J.db) J.load(); return J.db.items.slice().sort((a, b) => b.ts - a.ts).slice(0, limit); } async function resolve(id) { if (db.enabled()) { await db.q('UPDATE ad_reports SET resolved=1 WHERE id=?', [Number(id)]); return { ok: true }; } if (!J.db) J.load(); const it = J.db.items.find(x => x.id === Number(id)); if (it) { it.resolved = true; J.save(); } return { ok: true }; } // how many unresolved (for an admin badge) async function openCount() { if (db.enabled()) { const r = await db.q('SELECT COUNT(*) n FROM ad_reports WHERE resolved=0'); return r[0].n; } if (!J.db) J.load(); return J.db.items.filter(x => !x.resolved).length; } module.exports = { init, add, list, resolve, openCount, REASONS };