diff options
Diffstat (limited to 'src/app/db/service/base/index.js')
| -rw-r--r-- | src/app/db/service/base/index.js | 137 |
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; +} |
