1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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;
}
|