OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/api/editor.js
// Provide the information that the editor needs to render the input.
// The editor will minimally request what it might need, and cache aggressively,
// this API ust provides generously as asked.

const router = require('express').Router()

const ourbigbook = require('ourbigbook')
const ourbigbook_nodejs_webpack_safe = require('ourbigbook/nodejs_webpack_safe');

const auth = require('../auth')
const front = require('../front/js')
const lib = require('./lib')

router.post('/id-completions', auth.optional, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const body = lib.validateParam(req, 'body')
    const query = lib.validateParam(body, 'query', {
      validators: [front.isString, value => value.length <= 256],
    })
    const username = lib.validateParam(body, 'username', {
      validators: [front.isString, value => /^[a-z0-9][a-z0-9-]{0,255}$/.test(value)],
    })
    // Match public IDs, including non-header anchors. Escape LIKE wildcards literally.
    const escapeLike = value => value.replace(/[!%_]/g, '!$&')
    const prefix = `@${username}/`
    const explicitUser = query.startsWith('@')
    const column = sequelize.getQueryInterface().queryGenerator.quoteIdentifier('idid')
    const like = pattern => `${column} LIKE ${sequelize.escape(pattern)} ESCAPE '!'`
    const start = escapeLike(explicitUser ? query : prefix + query)
    const pattern = explicitUser ? `${escapeLike(query)}%` : `${escapeLike(prefix)}%${escapeLike(query)}%`
    const rows = await sequelize.models.Id.findAll({
      attributes: ['idid', 'ast_json'],
      where: sequelize.literal(like(pattern)),
      order: [
        [sequelize.literal(`CASE WHEN ${like(start + '%')} THEN 0 ELSE 1 END`), 'ASC'],
        [sequelize.fn('LENGTH', sequelize.col('idid')), 'ASC'],
        ['idid', 'ASC'],
      ],
      limit: 100,
    })
    const context = ourbigbook.convertInitContext({ output_format: ourbigbook.OUTPUT_FORMAT_ID })
    const titles = Object.fromEntries(rows.map(row => [row.idid,
      ourbigbook.getIdCompletionTitle(ourbigbook.AstNode.fromJSON(row.ast_json, context), context),
    ]))
    return res.json({ ids: Object.keys(titles), titles })
  } catch (error) { next(error) }
})

router.post('/fetch-files', auth.optional, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const body = lib.validateParam(req, 'body')
    const paths = lib.validateParam(body, 'paths', {
      validators: [ front.isArrayOf(front.isString) ],
      defaultValue: [],
    })
    const rows = await sequelize.models.File.findAll({
      where: { path: paths },
      include: [
        {
          model: sequelize.models.Id,
          as: 'toplevelId',
        }
      ],
      order: [[ 'path', 'ASC' ]]
    })
    return res.json({ files: rows })
  } catch(error) { next(error); }
})

// Has to be post to be able to send body data. We don't want to URL encode to not blow up URL size limits.
// https://stackoverflow.com/questions/978061/http-get-with-request-body
router.post('/get-noscopes-base-fetch', auth.optional, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const body = lib.validateParam(req, 'body')
    const ids = lib.validateParam(body, 'ids', {
      validators: [ front.isArrayOf(front.isString) ],
      defaultValue: [],
    })
    const ignore_paths_set = lib.validateParam(body, 'ignore_paths_set', {
      validators: [ front.isArrayOf(front.isString) ],
      defaultValue: [],
    })
    const rows = await ourbigbook_nodejs_webpack_safe.get_noscopes_base_fetch_rows(sequelize, ids, ignore_paths_set)
    return res.json({ rows })
  } catch(error) { next(error); }
})

router.post('/id-exists', auth.optional, async function(req, res, next) {
  try {
    const sequelize = req.app.get('sequelize')
    const body = lib.validateParam(req, 'body')
    const idid = lib.validateParam(body, 'idid', {
      validators: [front.isString],
      defaultValue: undefined,
    })
    const exists = (await sequelize.models.Id.count({ where: { idid } })) > 0
    return res.json({ exists })
  } catch(error) { next(error); }
})

module.exports = router