UNPKG

apialize

Version:

Turn a database model into a production ready REST(ish) CRUD API in a few lines.

190 lines (166 loc) 5.28 kB
const { express, apializeContext, ensureFn, asyncHandler, defaultNotFound, filterMiddlewareFns, buildHandlers, getProvidedValues, getOwnershipWhere, buildWhereClause, convertInstanceToPlainObject, extractBooleanOption, } = require('./utils'); const { validateData } = require('./validationMiddleware'); const { withTransactionAndHooks, optionsWithTransaction, notFoundWithRollback, } = require('./operationUtils'); function getUpdatableAttributes(model, idMapping) { const rawAttributes = model.constructor.rawAttributes; const updatableAttributes = []; for (const attributeName of Object.keys(rawAttributes)) { const isIdField = attributeName === idMapping; const isAutoGenerated = rawAttributes[attributeName]._autoGenerated; if (!isIdField && !isAutoGenerated) { updatableAttributes.push(attributeName); } } return updatableAttributes; } function buildNextValues( updatableAttributes, providedValues, rawAttributes, idMapping, id ) { const nextValues = {}; for (const attributeName of updatableAttributes) { if (Object.prototype.hasOwnProperty.call(providedValues, attributeName)) { nextValues[attributeName] = providedValues[attributeName]; } else { const defaultValue = rawAttributes[attributeName].defaultValue; if (typeof defaultValue !== 'undefined') { nextValues[attributeName] = defaultValue; } else { nextValues[attributeName] = null; } } } nextValues[idMapping] = id; return nextValues; } function validateOwnership(instance, ownershipWhere) { if (!ownershipWhere || Object.keys(ownershipWhere).length === 0) { return true; } for (const [key, expectedValue] of Object.entries(ownershipWhere)) { if (instance[key] !== expectedValue) { return false; } } return true; } function update(model, options = {}, modelOptions = {}) { ensureFn(model, 'update'); const { middleware = [], id_mapping = 'id', pre = null, post = null, } = options; const validate = extractBooleanOption(options, 'validate', true); const middlewareFunctions = filterMiddlewareFns(middleware); const router = express.Router({ mergeParams: true }); const handlers = buildHandlers( middlewareFunctions, async function (req, res) { const combinedOptions = Object.assign({}, options, { pre, post }); const payload = await withTransactionAndHooks( { model, options: combinedOptions, req, res, modelOptions, idMapping: id_mapping, }, async (context) => { const id = req.params.id; const providedValues = getProvidedValues(req); const ownershipWhere = getOwnershipWhere(req); const where = buildWhereClause(ownershipWhere, id_mapping, id); const findModelOptions = Object.assign({}, modelOptions, { where }); const findOptions = optionsWithTransaction( findModelOptions, context.transaction ); const existingInstance = await model.findOne(findOptions); if (!existingInstance) { return notFoundWithRollback(context); } context.existing = existingInstance; // Run validation if enabled (after we confirm the record exists) if (validate) { try { // For UPDATE, validate the full object that will replace the existing one await validateData(model, providedValues, { isPartial: false }); } catch (error) { if (error.name === 'ValidationError') { context.res.status(400).json({ success: false, error: error.message, details: error.details, }); return; } throw error; } } const updatableAttributes = getUpdatableAttributes( existingInstance, id_mapping ); const rawAttributes = existingInstance.constructor.rawAttributes; const nextValues = buildNextValues( updatableAttributes, providedValues, rawAttributes, id_mapping, id ); existingInstance.set(nextValues); context.nextValues = nextValues; const isOwnershipValid = validateOwnership( existingInstance, ownershipWhere ); if (!isOwnershipValid) { return notFoundWithRollback(context); } const fieldsToUpdate = Object.keys(nextValues); const saveModelOptions = Object.assign({}, modelOptions, { fields: fieldsToUpdate, }); const saveOptions = optionsWithTransaction( saveModelOptions, context.transaction ); await existingInstance.save(saveOptions); context.payload = { success: true }; return context.payload; } ); if (!res.headersSent) { res.json(payload); } } ); router.put('/:id', handlers); router.apialize = {}; return router; } module.exports = update;