@terreno/api 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/__tests__/versionCheck.test.d.ts +1 -0
  2. package/dist/__tests__/versionCheck.test.js +263 -0
  3. package/dist/expressServer.js +2 -2
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/models/versionConfig.d.ts +17 -0
  7. package/dist/models/versionConfig.js +66 -0
  8. package/dist/terrenoApp.js +2 -2
  9. package/dist/vendor/wesleytodd-openapi/index.d.ts +27 -0
  10. package/dist/vendor/wesleytodd-openapi/index.js +176 -0
  11. package/dist/vendor/wesleytodd-openapi/lib/convert-yaml.d.ts +2 -0
  12. package/dist/vendor/wesleytodd-openapi/lib/convert-yaml.js +13 -0
  13. package/dist/vendor/wesleytodd-openapi/lib/generate-doc.d.ts +2 -0
  14. package/dist/vendor/wesleytodd-openapi/lib/generate-doc.js +148 -0
  15. package/dist/vendor/wesleytodd-openapi/lib/layer-schema.d.ts +2 -0
  16. package/dist/vendor/wesleytodd-openapi/lib/layer-schema.js +12 -0
  17. package/dist/vendor/wesleytodd-openapi/lib/minimum-doc.d.ts +6 -0
  18. package/dist/vendor/wesleytodd-openapi/lib/minimum-doc.js +11 -0
  19. package/dist/vendor/wesleytodd-openapi/lib/ui.d.ts +1 -0
  20. package/dist/vendor/wesleytodd-openapi/lib/ui.js +37 -0
  21. package/dist/vendor/wesleytodd-openapi/lib/validate.d.ts +2 -0
  22. package/dist/vendor/wesleytodd-openapi/lib/validate.js +168 -0
  23. package/dist/versionCheckPlugin.d.ts +15 -0
  24. package/dist/versionCheckPlugin.js +106 -0
  25. package/package.json +9 -2
  26. package/src/__tests__/versionCheck.test.ts +132 -0
  27. package/src/expressServer.ts +1 -2
  28. package/src/index.ts +2 -0
  29. package/src/models/versionConfig.ts +92 -0
  30. package/src/terrenoApp.ts +1 -2
  31. package/src/vendor/wesleytodd-openapi/LICENSE +15 -0
  32. package/src/vendor/wesleytodd-openapi/index.js +189 -0
  33. package/src/vendor/wesleytodd-openapi/lib/convert-yaml.js +13 -0
  34. package/src/vendor/wesleytodd-openapi/lib/generate-doc.js +153 -0
  35. package/src/vendor/wesleytodd-openapi/lib/layer-schema.js +13 -0
  36. package/src/vendor/wesleytodd-openapi/lib/minimum-doc.js +12 -0
  37. package/src/vendor/wesleytodd-openapi/lib/ui.js +71 -0
  38. package/src/vendor/wesleytodd-openapi/lib/validate.js +152 -0
  39. package/src/versionCheckPlugin.ts +81 -0
  40. package/tsconfig.json +1 -1
@@ -0,0 +1,71 @@
1
+ // Vendored from https://github.com/wesleytodd/express-openapi (branch: express-5)
2
+ // License: ISC (see ../LICENSE)
3
+ 'use strict'
4
+ const path = require('path')
5
+ const serve = require('serve-static')
6
+
7
+ module.exports.serveSwaggerUI = function serveSwaggerUI (documentUrl, opts = {}) {
8
+ const { plugins, ...options } = opts
9
+
10
+ return [serve(path.resolve(require.resolve('swagger-ui-dist'), '..'), { index: false }),
11
+ function returnUiInit (req, res, next) {
12
+ if (req.path.endsWith('/swagger-ui-init.js')) {
13
+ res.type('.js')
14
+ res.send(`window.onload = function () {
15
+ window.ui = SwaggerUIBundle({
16
+ url: '${documentUrl}',
17
+ dom_id: '#swagger-ui',
18
+ ${plugins?.length ? `plugins: [${plugins}],` : ''}
19
+ ...${JSON.stringify(options)}
20
+ })
21
+ }`
22
+ )
23
+ } else {
24
+ next()
25
+ }
26
+ },
27
+ function renderSwaggerHtml (req, res) {
28
+ res.type('html').send(renderHtmlPage('Swagger UI', `
29
+ <link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
30
+ `, `
31
+ <div id="swagger-ui"></div>
32
+ <script src="./swagger-ui-bundle.js"></script>
33
+ <script src="./swagger-ui-standalone-preset.js"></script>
34
+ <script src="./swagger-ui-init.js"></script>
35
+ `))
36
+ }
37
+ ]
38
+ }
39
+
40
+ function renderHtmlPage (title, head, body) {
41
+ return `<!DOCTYPE html>
42
+ <html>
43
+ <head>
44
+ <title>${title}</title>
45
+ <meta charset="utf-8"/>
46
+ <meta name="viewport" content="width=device-width, initial-scale=1">
47
+ <style>
48
+ html {
49
+ box-sizing: border-box;
50
+ overflow: -moz-scrollbars-vertical;
51
+ overflow-y: scroll;
52
+ }
53
+ *,
54
+ *:before,
55
+ *:after {
56
+ box-sizing: inherit;
57
+ }
58
+ body {
59
+ margin: 0;
60
+ padding: 0;
61
+ background: #fafafa;
62
+ }
63
+ </style>
64
+ ${head}
65
+ </head>
66
+ <body>
67
+ ${body}
68
+ </body>
69
+ </html>
70
+ `
71
+ }
@@ -0,0 +1,152 @@
1
+ // Vendored from https://github.com/wesleytodd/express-openapi (branch: express-5)
2
+ // License: ISC (see ../LICENSE)
3
+ 'use strict'
4
+ const Ajv = require('ajv')
5
+ const addFormats = require('ajv-formats')
6
+ const addKeywords = require('ajv-keywords')
7
+ const httpErrors = require('http-errors')
8
+
9
+ const BASE_REQ_SCHEMA = {
10
+ type: 'object',
11
+ required: ['headers', 'params', 'query'],
12
+ properties: {
13
+ headers: {
14
+ type: 'object',
15
+ required: [],
16
+ properties: {}
17
+ },
18
+ params: {
19
+ type: 'object',
20
+ required: [],
21
+ properties: {}
22
+ },
23
+ query: {
24
+ type: 'object',
25
+ required: [],
26
+ properties: {}
27
+ },
28
+ body: {
29
+ type: 'object',
30
+ required: [],
31
+ properties: {}
32
+ }
33
+ }
34
+ }
35
+
36
+ module.exports = function makeValidatorMiddleware (middleware, schema, opts) {
37
+ let ajv
38
+ let validate
39
+
40
+ function makeValidator () {
41
+ const reqSchema = structuredClone(BASE_REQ_SCHEMA)
42
+
43
+ // Compile req schema on first request
44
+ // Build param validation
45
+ schema.parameters && schema.parameters.forEach((p) => {
46
+ switch (p.in) {
47
+ case 'path':
48
+ reqSchema.properties.params.properties[p.name] = p.schema
49
+ p.required && !reqSchema.properties.params.required.includes(p.name) && reqSchema.properties.params.required.push(p.name)
50
+ break
51
+ case 'query':
52
+ reqSchema.properties.query.properties[p.name] = p.schema
53
+ p.required && !reqSchema.properties.query.required.includes(p.name) && reqSchema.properties.query.required.push(p.name)
54
+ break
55
+ case 'header': {
56
+ const name = p.name.toLowerCase()
57
+ reqSchema.properties.headers.properties[name] = p.schema
58
+ p.required && !reqSchema.properties.headers.required.includes(p.name) && reqSchema.properties.headers.required.push(name)
59
+ break
60
+ }
61
+ }
62
+ })
63
+
64
+ // Compile req body schema
65
+ schema.requestBody && Object.entries(schema.requestBody.content)
66
+ .forEach(([contentType, { schema }]) => {
67
+ switch (contentType) {
68
+ case 'application/json':
69
+ reqSchema.properties.body = schema
70
+ break
71
+ default:
72
+ throw new TypeError(`Validation of content type not supported: ${contentType}`)
73
+ }
74
+ })
75
+
76
+ // Add components for references
77
+ reqSchema.components = middleware.document && middleware.document.components
78
+
79
+ return ajv.compile(reqSchema)
80
+ }
81
+
82
+ return function validateMiddleware (req, res, next) {
83
+ // Restrict validation to only "route" layers
84
+ // This prevents running any validation
85
+ // if we are in a .use call which could
86
+ // be a non-routable request thus
87
+ if (!req.route) {
88
+ return next()
89
+ }
90
+
91
+ // Create ajv instance on first request
92
+ if (!ajv) {
93
+ ajv = new Ajv({
94
+ coerceTypes: opts.coerce === 'false' ? opts.coerce : true,
95
+ strict: opts.strict === true ? opts.strict : false
96
+ })
97
+ addFormats(ajv)
98
+
99
+ if (middleware.router && !middleware.router.handle) {
100
+ return next()
101
+ }
102
+ if (opts.keywords) { addKeywords(ajv, opts.keywords) }
103
+ }
104
+
105
+ if (!validate) {
106
+ validate = makeValidator()
107
+ }
108
+
109
+ // Validate request
110
+ let r = req
111
+ if (opts.coerce !== true) {
112
+ r = makeReqCopy(req)
113
+ } else {
114
+ // Redifine query and params as normal objects we can write to, so we can coerce the data
115
+ Object.defineProperty(req, 'query', {
116
+ value: { ...req.query },
117
+ writable: true,
118
+ enumerable: true,
119
+ configurable: true
120
+ })
121
+
122
+ Object.defineProperty(req, 'params', {
123
+ value: { ...req.params },
124
+ writable: true,
125
+ enumerable: true,
126
+ configurable: true
127
+ })
128
+ }
129
+ const validationStatus = validate(r)
130
+ if (validationStatus === true) {
131
+ return next()
132
+ }
133
+
134
+ // build error?
135
+ const err = new Error('Request validation failed')
136
+ err.validationErrors = validate.errors
137
+ err.validationSchema = validate.schema
138
+ next(httpErrors(400, err))
139
+ }
140
+ }
141
+
142
+ // This is because ajv modifies the original data,
143
+ // preventing this requires that we dont pass the
144
+ // actual req. An issue has been opened (@TODO open the issue)
145
+ function makeReqCopy (req) {
146
+ return JSON.parse(JSON.stringify({
147
+ headers: req.headers,
148
+ params: req.params,
149
+ query: req.query,
150
+ body: req.body
151
+ }))
152
+ }
@@ -0,0 +1,81 @@
1
+ import type express from "express";
2
+
3
+ import {asyncHandler} from "./api";
4
+ import {VersionConfig} from "./models/versionConfig";
5
+ import type {TerrenoPlugin} from "./terrenoPlugin";
6
+
7
+ export type VersionCheckStatus = "ok" | "warning" | "required";
8
+
9
+ export interface VersionCheckResponse {
10
+ message?: string;
11
+ status: VersionCheckStatus;
12
+ updateUrl?: string;
13
+ }
14
+
15
+ const DEFAULT_WARNING_MESSAGE =
16
+ "A new version is available. Please update for the best experience.";
17
+ const DEFAULT_REQUIRED_MESSAGE = "This version is no longer supported. Please update to continue.";
18
+
19
+ /**
20
+ * TerrenoPlugin that adds a public GET /version-check endpoint for upgrade enforcement.
21
+ * Compares client build number against admin-configured thresholds per platform.
22
+ */
23
+ export class VersionCheckPlugin implements TerrenoPlugin {
24
+ register(app: express.Application): void {
25
+ app.get(
26
+ "/version-check",
27
+ asyncHandler(async (req, res) => {
28
+ const versionParam = req.query.version;
29
+ const platform = req.query.platform as string | undefined;
30
+
31
+ const version =
32
+ typeof versionParam === "string"
33
+ ? parseInt(versionParam, 10)
34
+ : typeof versionParam === "number"
35
+ ? versionParam
36
+ : undefined;
37
+
38
+ if (version === undefined || Number.isNaN(version)) {
39
+ return res.json({status: "ok" as VersionCheckStatus});
40
+ }
41
+
42
+ const platformNormalized = platform === "web" || platform === "mobile" ? platform : "web";
43
+
44
+ const config = await VersionConfig.findOneOrNone({_singleton: "config"});
45
+
46
+ if (!config) {
47
+ return res.json({status: "ok" as VersionCheckStatus});
48
+ }
49
+
50
+ const requiredVersion =
51
+ platformNormalized === "web"
52
+ ? (config.webRequiredVersion ?? 0)
53
+ : (config.mobileRequiredVersion ?? 0);
54
+ const warningVersion =
55
+ platformNormalized === "web"
56
+ ? (config.webWarningVersion ?? 0)
57
+ : (config.mobileWarningVersion ?? 0);
58
+
59
+ const response: VersionCheckResponse = {
60
+ status: "ok",
61
+ };
62
+
63
+ if (requiredVersion > 0 && version < requiredVersion) {
64
+ response.status = "required";
65
+ response.message = config.requiredMessage ?? DEFAULT_REQUIRED_MESSAGE;
66
+ if (config.updateUrl) {
67
+ response.updateUrl = config.updateUrl;
68
+ }
69
+ } else if (warningVersion > 0 && version < warningVersion) {
70
+ response.status = "warning";
71
+ response.message = config.warningMessage ?? DEFAULT_WARNING_MESSAGE;
72
+ if (config.updateUrl) {
73
+ response.updateUrl = config.updateUrl;
74
+ }
75
+ }
76
+
77
+ return res.json(response);
78
+ })
79
+ );
80
+ }
81
+ }
package/tsconfig.json CHANGED
@@ -56,5 +56,5 @@
56
56
  "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
57
57
  },
58
58
  "exclude": ["node_modules"],
59
- "include": ["src/**/*.ts*", "src/*.ts", "types.d.ts"]
59
+ "include": ["src/**/*.ts*", "src/*.ts", "src/**/*.js", "types.d.ts"]
60
60
  }