-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
246 lines (219 loc) · 12.2 KB
/
app.js
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Setup Application Insights if applicable, this must be done before loading any other depdendencies
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const config = require('./config');
if (config.APPLICATIONINSIGHTS_CONNECTION_STRING) {
const appInsights = require('applicationinsights')
console.log('Setting up application insights')
try {
appInsights.setup().setSendLiveMetrics(true).setAutoCollectConsole(true).start();
console.log('✅ ApplicationInsights ready')
} catch (err) {
console.error('❌ ApplicationInsights failed\n', err)
}
}
if (['true', true].includes(config.DEBUG)) {
console.log('====================\n ENVIRONMENT\n====================');
console.log('Environment variables', process.env);
console.log('Starting app with this config', config);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Import dependencies
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const http = require('http'); // For hosting the web server
const fs = require('fs'); // For working with the file system
const path = require('path'); // For combining paths
const yamljs = require('yamljs'); // For converting YAML to JSON
const express = require('express'); // Main system for running the API
const morgan = require('morgan'); // For outputing information about the requests
const cors = require('cors'); // For handeling CORS
const swaggerUi = require('swagger-ui-express'); // For hosting and displaying the APIs documentation
const OpenApiValidator = require('express-openapi-validator'); // Validates all routes based on the requested resource
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` }) // Load different .env files based on NODE_ENV
const WSDLClient = require('./lib/WSDLClient/WSDLClient'); // WSDLClient is initialized here for pre-loading all WSDL-files into memory
WSDLClient.loadAllFiles(); // Load all files into memory
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Determine variables and constants
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const host = config.hostname; // Get the hosting address
const port = config.port; // Get the hosting port
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create App
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const app = express();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Register middleware
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.use(express.json()); // Automatically parse JSON body
app.use(morgan('dev')); // Output request information to stdout
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Handle CORS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const corsOptions = {
origin: true,
credentials: true
}
app.use(cors(corsOptions));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Documentation & Validation
// Host SwaggerUI and validate incoming requests based on OpenAPI 3.0 spesification files
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.use('/assets/', express.static(path.join(__dirname, '/assets')))
const swaggerUIOptions = {
deepLinking: false,
displayOperationId: true,
customCss: '.topbar { background-color: #B2DCDA!important; } .topbar-wrapper img { content:url(\'/assets/images/vtfk-logo.svg\'); height: 100px; }',
customfavIcon: '/assets/images/favicon.ico',
customSiteTitle: 'Matrikkel API Dokumentasjon'
}
const oasDocumentationEndpoints = [];
const routeChildren = fs.readdirSync(path.join(__dirname, 'routes'));
if (routeChildren && Array.isArray(routeChildren)) {
for (let i = 0; i < routeChildren.length; i++) {
const oasSpecPath = path.join(__dirname, 'routes', routeChildren[i], 'openapispec.yaml');
if (fs.existsSync(oasSpecPath)) {
// Load the file as JSON and determine what the endpoint will be
const oasJSON = yamljs.load(oasSpecPath)
const oasDocEndpoint = '/api/' + routeChildren[i] + '/docs';
oasDocumentationEndpoints.push(oasDocEndpoint);
// Host the documentation
app.use(oasDocEndpoint, swaggerUi.serve, swaggerUi.setup(oasJSON, swaggerUIOptions));
// Register the API validator
app.use(
OpenApiValidator.middleware({
apiSpec: oasSpecPath,
validateRequests: true
})
)
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Authentication
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Import dependencies
const passport = require('passport'); // Engine for authenticating using different strategies
const headerAPIKeyStrategy = require('./auth/authentication/apikey'); // Passport strategy for authenticating with APIKey
// Register strategies
passport.use(headerAPIKeyStrategy);
// Use strategies
app.all('*',
passport.authenticate(['headerapikey'], { session: false }),
(req, res, next) => {
// Setup some custom properties that should be usable in the routes and middleware
req.custom = {};
req.__metadata = {};
// This function triggers when a request has been successfully authenticated
req.custom.timestamp = new Date();
next();
}
);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Routes
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// v1 routes
app.use('/api/v1/*', require('./routes/v1/beforeRouteMiddleware'));
app.use('/api/v1/matrikkelenheter', require('./routes/v1/matrikkelenheter'));
app.use('/api/v1/store', require('./routes/v1/store'));
app.use('/api/v1/raw', require('./routes/v1/raw'));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common functions
// Commin functionality for the send response and send error middleware
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function determineDocumentationLinks (req) {
const requestedHost = req.protocol + '://' + req.get('host');
let documentation;
// Attempt to determine what documentation is correct for the failed request
if (req.openapi && req.openapi.expressRoute) {
// Attempt to match the express route with the API version
let route = req.openapi.expressRoute;
if (req.openapi.expressRoute.startsWith('/')) {
route = route.substring(1);
}
const split = route.split('/');
if (split.length >= 2) {
const reconstructedRoute = '/' + split[0] + '/' + split[1] + '/docs';
if (oasDocumentationEndpoints.includes(reconstructedRoute)) {
documentation = {
full: encodeURI(requestedHost + reconstructedRoute)
}
if (req.openapi.schema && req.openapi.schema.operationId && req.openapi.schema.tags) {
documentation.method = encodeURI(requestedHost + reconstructedRoute + '/#/' + req.openapi.schema.tags[0] + '/' + req.openapi.schema.operationId)
}
}
}
}
return documentation
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Send response
// All routes sets the req.response object so that it can be sent to the requestor by a common function
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.use('/*', (req, res, next) => {
let response;
if (req.query.metadata) {
let itemCount = 0;
if (req.response && Array.isArray(req.response)) {
itemCount = req.response.length;
}
response = {
__metadata: {
uri: req.protocol + '://' + req.get('host') + req.baseUrl,
operationId: req.openapi ? req.openapi.schema.operationId : '',
durationMS: (new Date().getMilliseconds()) - req.custom.timestamp.getMilliseconds(),
items: itemCount,
...req.__metadata
},
data: req.response
}
const documentation = determineDocumentationLinks(req);
if (documentation) { response.__metadata.documentation = documentation; }
} else {
response = req.response;
}
res.type('json').send(JSON.stringify(response, null, 2));
})
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Error handling
// This will catch any errors that occured anywhere in the routes and handle them
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.use((err, req, res, next) => {
console.log('❌ Error occured ❌');
// Construct an error object
let error = {}
// Setup the error object based on type
if (typeof err === 'object') {
// Get all enumurable and non-enumurable property from the error object
Object.getOwnPropertyNames(err).forEach((key) => {
error[key] = err[key];
})
} else if (typeof err === 'string') {
error.message = err;
} else {
error = err;
}
// Attempt to link to documentation
const documentation = determineDocumentationLinks(req);
if (documentation) { error.documentation = documentation; }
// Output the error
console.error(error);
// Send the error
res.status(err.status || 500).json(error);
next();
})
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Host the server
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const server = http.createServer(app);
server.listen(port, host, () => {
let hostname = host;
if (host === '0.0.0.0') { hostname = 'localhost' }
// Output the root adress the server is listening on
console.log('Root endpoint:')
console.log('Your server is listening on port %d (http://%s:%d)', port, hostname, port);
// Output API endpoint documentation URLs
console.log('\nDocumentation endpoints:')
oasDocumentationEndpoints.forEach((endpoint) => {
console.log('http://%s:%d' + endpoint, hostname, port);
})
})