summaryrefslogtreecommitdiff
path: root/src/app/db/service/base/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/db/service/base/index.js')
-rw-r--r--src/app/db/service/base/index.js137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/app/db/service/base/index.js b/src/app/db/service/base/index.js
new file mode 100644
index 0000000..188d86a
--- /dev/null
+++ b/src/app/db/service/base/index.js
@@ -0,0 +1,137 @@
+/**
+ * Abstract API service
+ * @module app/db/service/base/index
+ */
+
+import { Router } from "express";
+import {
+ checkAccessToken,
+ checkUserIsActive,
+} from "app/services/authentication/helpers";
+import { indexQueryBuilder, loadColumns } from "app/db/helpers";
+
+import * as serviceMethods from "app/db/service/base/methods";
+import { handleError } from "app/db/service/base/response";
+import {
+ createCustomRoute,
+ createCrudRoute,
+} from "app/db/service/base/helpers";
+
+/**
+ * By default, no API routes are enabled.
+ */
+// const enableAll = ["index", "show", "create", "update", "destroy"];
+const noRoutesEnabled = [];
+
+/**
+ * Create an API service.
+ * @param {Object} options the options dictionary
+ * @param {bookshelf} options.bookshelf the Bookshelf instance created by the server
+ * @param {Service} options.parent the Service that this inherits from
+ * @param {Model|string} options.Model the name of a Bookshelf model, or the model itself
+ * @param {string} options.name optional, the name of this resource. Used for logging
+ * @param {string[]} options.enabled[] specific CRUD APIs to enable
+ * @param {Object} options.hooks extra middleware functions for each endpoint
+ * @param {Object} options.hooks.before middleware called before a DB call
+ * @param {function[]} options.hooks.before.index[] middleware called before index queries
+ * @param {function[]} options.hooks.before.show[] middleware called before show queries
+ * @param {function[]} options.hooks.before.create[] middleware called before create queries
+ * @param {function[]} options.hooks.before.update[] middleware called before update queries
+ * @param {function[]} options.hooks.before.destroy[] middleware called before destroy queries
+ * @param {function} options.logging middleware hook which runs after create/update/destroy queries, to record events
+ * @param {Object} options.hooks.after middleware called after a DB call, similar to `before`
+ * @param {boolean} options.authenticate set to false to disable authentication for this service
+ * @param {Object} options.permissions per-API permissions
+ * @param {Object} options.permissions.read permissions applied when reading records
+ * @param {number[]} options.permissions.read.roles[] allow access with matching user role
+ * @param {boolean} options.permissions.read.owner allow access with matching `user_id`
+ * @param {Object} options.permissions.create permissions applied when creating records
+ * @param {Object} options.permissions.update permissions applied when editing records
+ * @param {Object} options.permissions.destroy permissions applied when destroying records
+ * @param {Object[]} options.routes[] additional list of non-CRUD routes
+ * @param {string} options.routes[].method method to use, get/post/put/delete
+ * @param {route} options.routes[].route endpoint to use, e.g. `/login`
+ * @param {function[]} options.routes[].handlers[] list of middleware functions
+ * @param {privateFields} options.privateFields list of fields which should not be editable through the API
+ * @param {Object} methods optional method lookup, for alternative CRUD middleware
+ * @return {Service} the service object
+ */
+export default async function Service(options, methods = serviceMethods) {
+ const service = { type: "base", options, children: {} };
+
+ const { bookshelf, parent, Model, enabled, routes, authenticate } = options;
+
+ /** Locate the Model specified in the configuration */
+ service.Model =
+ Model && typeof Model === "string" ? bookshelf.model(Model) : options.Model;
+
+ /** Use the model to identify the service's resource name */
+ service.resource = options.name || service.Model?.prototype.tableName;
+ if (!service.resource) {
+ throw new Error("No name or model defined for resource");
+ }
+
+ /** Get the ID attributes of this model, and any parent models */
+ if (service.Model) {
+ if (parent) {
+ service.idAttributes = [service.Model.prototype.idAttribute].concat(
+ parent.idAttributes
+ );
+ } else {
+ service.idAttributes = [service.Model.prototype.idAttribute];
+ }
+ }
+
+ /** Load the column names for this service's Model */
+ service.columns =
+ service.Model && (await loadColumns(bookshelf, service.Model));
+
+ /** Specify the `index` queryBuilder */
+ service.queryBuilder = options.queryBuilder || indexQueryBuilder;
+
+ // console.log("* Creating", service.resource, "service");
+
+ /** Instantiate the Express router. If this is a sub-router, merge the parent's params. */
+ service.router = Router({ mergeParams: !!parent });
+
+ /** Associate the parent resource with this resource */
+ if (parent) {
+ service.parent = parent;
+ }
+
+ /** Most APIs need access to the database, so supply this on the request object */
+ service.router.use((request, response, next) => {
+ request.service = service;
+ request.bookshelf = bookshelf;
+ next();
+ });
+
+ /** Most APIs authenticate, but this can be disabled (for the authentication service) */
+ if (authenticate !== false) {
+ service.router.use(checkAccessToken(options.authenticationOptions || {}));
+ service.router.use(checkUserIsActive);
+ }
+
+ /** Add custom routes first */
+ if (routes) {
+ routes.forEach(createCustomRoute(service, methods));
+ }
+
+ /** Add all enabled CRUD routes, if this service has a Model. */
+ const enabledRoutes = (service.Model && enabled) || noRoutesEnabled;
+ enabledRoutes.forEach(createCrudRoute(service, methods));
+
+ /** Add error-handling middleware */
+ service.router.use(handleError);
+
+ /** Method to attach a child service to this service's router
+ * @param {string} route base route of this service
+ * @param {Service} childService instance of the child service
+ */
+ service.use = (route, childService) => {
+ service.router.use(`${route}`, childService.router);
+ service.children[route] = childService;
+ };
+
+ return service;
+}