summaryrefslogtreecommitdiff
path: root/src/app/db/service/base/many.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/db/service/base/many.js')
-rw-r--r--src/app/db/service/base/many.js205
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,
+ });
+}