diff options
Diffstat (limited to 'src/app/db/service/base/many.js')
| -rw-r--r-- | src/app/db/service/base/many.js | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/src/app/db/service/base/many.js b/src/app/db/service/base/many.js new file mode 100644 index 0000000..1791694 --- /dev/null +++ b/src/app/db/service/base/many.js @@ -0,0 +1,205 @@ +/** + * 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, + }); +} |
