1
0
mirror of synced 2026-01-22 18:03:38 -05:00
Files
docs/src/events/middleware.js
Peter Bengtsson c4a3a40feb Framework for submitting survey comments (#50632)
Co-authored-by: Rachael Sewell <rachmari@github.com>
2024-05-30 13:35:20 +00:00

109 lines
3.4 KiB
JavaScript

import express from 'express'
import { omit, without, mapValues } from 'lodash-es'
import QuickLRU from 'quick-lru'
import { schemas, hydroNames } from './lib/schema.js'
import catchMiddlewareError from '#src/observability/middleware/catch-middleware-error.js'
import { noCacheControl } from '#src/frame/middleware/cache-control.js'
import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js'
import { formatErrors } from './lib/middleware-errors.js'
import { publish as _publish } from './lib/hydro.js'
const router = express.Router()
const OMIT_FIELDS = ['type']
const allowedTypes = new Set(without(Object.keys(schemas), 'validation'))
const isProd = process.env.NODE_ENV === 'production'
const validators = mapValues(schemas, (schema) => getJsonValidator(schema))
// In production, fire and not wait to respond.
// _publish will send an error to failbot,
// so we don't get alerts but we still track it.
// This ends up being the same as try > await > catch > (do nothing).
async function publish(...args) {
if (isProd) {
_publish(...args)
return
}
return await _publish(...args)
}
const sentValidationErrors = new QuickLRU({
maxSize: 10_000,
maxAge: 1000 * 60,
})
router.post(
'/',
catchMiddlewareError(async function postEvents(req, res) {
noCacheControl(res)
// Make sure the type is supported before continuing
const { type } = req.body
if (!type || !allowedTypes.has(type)) {
return res.status(400).json({ message: 'Invalid type' })
}
// Validate the data matches the corresponding data schema
const validate = validators[type]
if (!validate(req.body)) {
const hash = `${req.ip}:${validate.errors
.map((error) => error.message + error.instancePath)
.join(':')}`
// This protects so we don't bother sending the same validation
// error, per user, more than once (per time interval).
// This helps if we're bombarded with junk bot traffic. So it
// protects our Hydro instance from being overloaded with things
// that aren't helping anybody.
if (!sentValidationErrors.has(hash)) {
sentValidationErrors.set(hash, true)
// Track validation errors in Hydro so that we can know if
// there's a widespread problem in events.ts
await publish(
formatErrors(validate.errors, req.body).map((error) => ({
schema: hydroNames.validation,
value: error,
})),
)
}
// We aren't helping bots spam us :)
return res.status(400).json(isProd ? {} : validate.errors)
}
await publish({
schema: hydroNames[type],
value: omit(req.body, OMIT_FIELDS),
})
return res.json({})
}),
)
router.post(
'/survey/preview/v1',
catchMiddlewareError(async function previewComment(req, res) {
noCacheControl(res)
const { comment, locale, url, vote } = req.body
console.log(`The comment was posted in ${locale} on ${url} with vote ${vote}`)
if (!comment || !comment.trim()) {
return res.status(400).json({ message: 'Empty comment' })
}
const signals = []
const rating = 1.0
// if (comment.includes('@') && !comment.includes(' ')) {
// // XXX Make it a simple email validator
// signals.push({
// email: 'Looks like an email address',
// })
// rating -= 0.1
// }
return res.json({ rating, signals })
}),
)
export default router