💾 Archived View for iich.space › src › app › post.ts captured on 2021-12-03 at 14:04:38.

View Raw

More Information

-=-=-=-=-=-=-

import { Request, Status } from '@/gemini';
import { Handler, Router } from '@/mission-control';

import { BoardType, Thread } from '~/db/models';
import {
  createReply,
  createThread,
  getBoardByName,
  getPartialThreadById,
  hasBan,
} from '~/db/queries';
import withCertificateIfSigning from '~/middleware/withCertificateIfSigning';
import withQuery from '~/middleware/withQuery';
import withThrottling from '~/middleware/withThrottling';
import { getFingerprint } from '~/util/identity';
import { processImageUpload } from '~/util/images';
import { withValidToken } from '~/util/tokens';
import { hasProfanity } from '~/util/word-filter';

const boardExists: Handler = (_, res, { params }) => {
  const board = getBoardByName(params.name);

  if (board === undefined) {
    res.sendStatus(Status.NOT_FOUND);
    res.end();
  }
};

const isImageBoard: Handler = (_, res, { params }) => {
  const board = getBoardByName(params.name);

  if (board.type !== BoardType.Image) {
    res.sendStatus(Status.NOT_FOUND);
    res.end();
  }
};

const shouldRefuse = (req: Request, comment: string) =>
  hasProfanity(comment) || hasBan(req.remote);

const isLocked = (thread: Thread) => thread.locked === 1;

const router = new Router<Handler>();

router.use(
  '/:name/threads/:id/:signed/:token/text',
  boardExists,
  withCertificateIfSigning,
  withQuery('Comment'),
  withThrottling,
  withValidToken,
  async (req, res, { params }) => {
    const comment = req.query!;

    if (shouldRefuse(req, comment)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    const thread = getPartialThreadById(parseInt(params.id, 10));
    const fingerprint = getFingerprint(req, params.signed);

    if (isLocked(thread)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    createReply(thread.id, comment, fingerprint, null, req.remote);

    res.redirect(thread.path);
  },
);

router.use(
  '/:name/threads/:id/:signed/:token/image',
  isImageBoard,
  withCertificateIfSigning,
  withThrottling,
  withValidToken,
  async (req, res, { params }) => {
    const file = req.file!;
    const comment = file.token || '[Image]';

    if (shouldRefuse(req, comment)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    const board = getBoardByName(params.name);
    const sha = await processImageUpload(file);
    const thread = getPartialThreadById(parseInt(params.id, 10));
    const fingerprint = getFingerprint(req, params.signed);

    if (isLocked(thread)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    createReply(thread.id, comment, fingerprint, sha, req.remote);

    res.redirect(`gemini://${req.url.host}/${board.name}/threads/${thread.id}`);
  },
);

router.use(
  '/:name/:signed/:token/text',
  boardExists,
  withCertificateIfSigning,
  withQuery('Comment'),
  withThrottling,
  withValidToken,
  async (req, res, { params }) => {
    const comment = req.query!;

    if (shouldRefuse(req, comment)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    if (hasBan(req.remote)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    const board = getBoardByName(params.name);
    const fingerprint = getFingerprint(req, params.signed);
    const thread = createThread(
      board.id,
      comment,
      fingerprint,
      null,
      req.remote,
    );

    res.redirect(thread.path);
  },
);

router.use(
  '/:name/:signed/:token/image',
  isImageBoard,
  withCertificateIfSigning,
  async (req, res) => {
    const file = req.file!;

    if (file.token === '') {
      res.sendStatus(
        Status.BAD_REQUEST,
        'A comment is required for new image threads using the token field',
      );
      return res.end();
    }
  },
  withThrottling,
  withValidToken,
  async (req, res, { params }) => {
    const file = req.file!;
    const comment = file.token;

    if (shouldRefuse(req, comment)) {
      res.sendStatus(Status.BAD_REQUEST);
      return res.end();
    }

    const board = getBoardByName(params.name);
    const sha = await processImageUpload(file);
    const fingerprint = getFingerprint(req, params.signed);
    const thread = createThread(
      board.id,
      comment,
      fingerprint,
      sha,
      req.remote,
    );

    res.redirect(`gemini://${req.url.host}/${board.name}/threads/${thread.id}`);
  },
);

export default router;