/** * Service API methods that affect multiple records * @module app/db/service/base/methods */ import * as db from "app/db/query"; import { reduceValidColumns } from "app/db/helpers"; import { PERMISSIONS } from "app/constants"; import debugModule from "debug"; /** * Debug logger */ const debug = debugModule("shoebox:service"); /** * API to query for multiple records by ID */ export function showMany(service) { const { Model, parent, resource } = service; const { childRelation } = service.options; const idAttribute = service.idAttributes[0]; return async function showManyMiddleware(request, response, next) { const { user, permission, body } = request; const ids = body.map((item) => item[idAttribute]).filter((id) => !!id); let data; try { if (parent) { // Fetch the immediate parent of the pivot table based on the name of the parent resource. // This instance is added to the `request.parents` object when performing the permissions check. const parentInstance = request.parents[parent.resource]; data = await parentInstance .related(childRelation) .query((builder) => builder.whereIn(idAttribute, ids)); } else { data = await db.showIDs({ Model, ids, }); } } catch (error) { debug(`${resource} Show error`); debug(error); return next(error); } if (!data) { response.locals = { data: [] }; next(); } else if ( permission === PERMISSIONS.ALLOW_FOR_OWNER && data.some((item) => item.get("user_id") !== user.user_id) ) { next(new Error("PermissionsError")); } else { response.locals = { data }; next(); } }; } /** * API to update multiple records */ export function updateMany(service) { return async function updateManyMiddleware(request, response, next) { const data = await handleUpdateManyWithTransaction( service, request, response ); response.locals.data = data; next(); }; } /** * Update multiple records using a transaction */ export async function handleUpdateManyWithTransaction( service, request, response ) { const { Model, idAttributes, columns } = service; const { bookshelf, privateFields } = service.options; const idAttribute = idAttributes[0]; return await bookshelf.transaction((transaction) => { const { data: instances } = response.locals; const { params, user, permission, body } = request; const instanceLookup = instances.reduce((lookup, instance) => { lookup[instance.id] = instance; return lookup; }, {}); const promises = body.map((item) => { const itemId = item[idAttribute]; if (!itemId) { return handleCreate({ Model, body: item, idAttributes, params, columns, privateFields, transaction, }); } else if (itemId in instanceLookup) { return handleUpdate({ instance: instanceLookup[itemId], body: item, user, permission, columns, privateFields, transaction, }); } else { throw new Error("item id not found on this record"); } }); return Promise.all(promises); }); } /** * API to destroy multiple records */ export function destroyMany(service) { const { Model, idAttributes } = service; const [idAttribute, ...parentIdAttributes] = idAttributes; return async function destroyManyMiddleware(request, response, next) { const idsToDelete = request.body[idAttribute]; let instances; try { instances = await db.showIDs({ Model, ids: idsToDelete }); instances.forEach((instance) => { parentIdAttributes.forEach((parentIdAttribute) => { if ( instance.get(parentIdAttribute) !== request.params[parentIdAttribute] ) { throw new Error("parent mismatch"); } }); }); await Promise.all(instances.map((instance) => instance.destroy())); } catch (error) { debug(`${service.resource} destroy many error`); debug(error); return next(new Error(error)); } response.locals.data = instances; response.locals.success = true; response.locals.id = idsToDelete; next(); }; } /** * Insert a single record */ export function handleCreate({ idAttributes, params, columns, body, Model, privateFields, transaction, }) { body = reduceValidColumns(body, columns, privateFields); if (idAttributes) { idAttributes.forEach((idAttribute) => { if (idAttribute in params && idAttribute in columns) { body[idAttribute] = parseInt(params[idAttribute]); } }); } return db.create({ Model: Model, data: body, transaction }); } /** * Update a single record */ export function handleUpdate({ instance, body, user, permission, columns, privateFields, transaction, }) { if ( permission === PERMISSIONS.ALLOW_FOR_OWNER && instance.get("user_id") !== user.user_id ) { throw new Error("PermissionsError"); } body = reduceValidColumns(body, columns, privateFields); return db.update({ instance, data: body, transaction, }); }