// Business layer for moderation appeals (Phase 6c). A thin wrapper over // appeals.db that returns the freshly-read row after each mutation, so callers // always hand the client the enriched (joined) shape rather than a bare // insert/update result. Ownership/type/duplicate validation lives in the // controllers (they hold the request context — caller identity, the mod_action // being appealed); this layer just performs the persistence. const appealsDb = require('./appeals.db') async function submit({ modActionId, discordUserId, actionType, userId, submittedText }) { const id = await appealsDb.insert({ modActionId, discordUserId, actionType, userId, submittedText }) return appealsDb.getById(id) } async function getById(id) { return appealsDb.getById(id) } // The caller's own appeals, newest first. async function listMine(userId) { return appealsDb.listForUser(userId) } // The caller's appealable actions (ban/mute with no active appeal). async function eligibleActions(discordUserId) { return appealsDb.eligibleActions(discordUserId) } // The active (pending/under_review) appeal for an action, or null. async function activeForAction(modActionId) { return appealsDb.activeForAction(modActionId) } // Owner-initiated withdraw: flip to 'withdrawn' and return the updated row. async function withdraw(id) { await appealsDb.setStatus(id, 'withdrawn') return appealsDb.getById(id) } // Staff queue read. async function queue({ statuses, limit, offset }) { return appealsDb.listQueue({ statuses, limit, offset }) } // Staff claim: pending -> under_review, stamping the handler. async function claim(id, { handlerUserId, handlerTag }) { await appealsDb.setUnderReview(id, { handlerUserId, handlerTag }) return appealsDb.getById(id) } // Staff resolution (approved/denied) with the reversal outcome already decided // by the controller (which owns the best-effort bot call). async function resolve(id, opts) { await appealsDb.resolve(id, opts) return appealsDb.getById(id) } // Appeals for a Discord id (admin per-user view). async function listForDiscordUser(discordUserId) { return appealsDb.listForDiscordUser(discordUserId) } // Number of appeals still awaiting first triage (status = 'pending'), for the // dashboard badge. async function pendingCount() { const rows = await appealsDb.countByStatus() const row = rows.find((r) => r.status === 'pending') return row ? Number(row.c) : 0 } module.exports = { submit, getById, listMine, eligibleActions, activeForAction, withdraw, queue, claim, resolve, listForDiscordUser, pendingCount, }