// Player · Appeals — a player appeals one of their own ban/mute mod_actions. // Ownership is proven by matching the action against the caller's linked Discord // identity (see appeals.controller); the staff side of the queue lives in // admin/moderation.router.js. // // Mounted at /api/v1/player/appeals by player/index.js, which already applied // `noindex, requireAuth`. No extra gate — every handler is self-scoped. // // Declaration order: GET /eligible is a literal path and sits ahead of the only // :param route (POST /:id/withdraw), which is a different method at a different // depth, so nothing here can shadow anything else. const express = require('express') const { body, param } = require('express-validator') const appeals = require('./appeals.controller') const validate = require('../../../middleware/validate') const { accountChangeLimiter } = require('../../../middleware/rateLimit') const appealsRouter = express.Router() appealsRouter.get( '/', // #swagger.tags = ['Player · Appeals'] // #swagger.summary = 'List the caller’s moderation appeals' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'The caller’s appeals', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Appeal" } } } } } */ /* #swagger.responses[403] = { description: 'Account not active (disabled/banned)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ appeals.listMine, ) appealsRouter.get( '/eligible', // #swagger.tags = ['Player · Appeals'] // #swagger.summary = 'List the caller’s ban/mute actions eligible for appeal' // #swagger.description = 'The caller’s ban/mute mod_actions that have no active appeal. Returns an empty array when the caller has no linked Discord account (the UI shows a “link Discord” hint).' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'Appealable actions', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/AppealEligibleAction" } } } } } */ /* #swagger.responses[403] = { description: 'Account not active (disabled/banned)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ appeals.listEligible, ) appealsRouter.post( '/', // #swagger.tags = ['Player · Appeals'] // #swagger.summary = 'Submit a moderation appeal for one of the caller’s actions' // #swagger.description = 'Opens an appeal for a ban/mute mod_action that belongs to the caller (its target matches the caller’s linked Discord identity) and has no active appeal.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/CreateAppealRequest" } } } } */ /* #swagger.responses[201] = { description: 'Appeal created', content: { "application/json": { schema: { $ref: "#/components/schemas/Appeal" } } } } */ /* #swagger.responses[400] = { description: 'Validation error, or the action type is not appealable', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */ /* #swagger.responses[403] = { description: 'The action does not belong to the caller', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Mod action not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[409] = { description: 'An appeal for this action is already open', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ accountChangeLimiter, body('mod_action_id').isInt({ min: 1 }).toInt(), body('submitted_text').isString().trim().isLength({ min: 1, max: 4000 }), validate, appeals.create, ) appealsRouter.post( '/:id/withdraw', // #swagger.tags = ['Player · Appeals'] // #swagger.summary = 'Withdraw one of the caller’s pending appeals' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Appeal id (must belong to the caller).' } /* #swagger.responses[200] = { description: 'The withdrawn appeal', content: { "application/json": { schema: { $ref: "#/components/schemas/Appeal" } } } } */ /* #swagger.responses[404] = { description: 'No such appeal for the caller', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[409] = { description: 'Appeal is already resolved', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt({ min: 1 }), validate, appeals.withdraw, ) module.exports = appealsRouter