@appsemble/node-utils 0.21.2 → 0.22.0
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.
- package/EmailQuotaExceededError.d.ts +3 -0
- package/EmailQuotaExceededError.js +8 -0
- package/README.md +3 -3
- package/index.d.ts +3 -1
- package/index.js +3 -1
- package/jsonschema.d.ts +2 -1
- package/jsonschema.js +14 -3
- package/middleware/error.d.ts +2 -0
- package/middleware/error.js +27 -0
- package/middleware/{boom.test.js → error.test.js} +23 -12
- package/package.json +10 -7
- package/resource.d.ts +2 -1
- package/resource.js +39 -6
- package/server/controllers/action.js +61 -11
- package/server/controllers/appMembers.js +21 -4
- package/server/controllers/appMessages.js +20 -4
- package/server/controllers/assets.js +28 -5
- package/server/controllers/oauth2Provider.js +1 -1
- package/server/controllers/resources.js +46 -11
- package/server/controllers/resources.test.js +2 -2
- package/server/controllers/teams.js +7 -2
- package/server/routes/appRouter/blockAssetHandler.js +7 -2
- package/server/routes/appRouter/blockCssHandler.js +7 -2
- package/server/routes/appRouter/bulmaHandler.js +2 -3
- package/server/routes/appRouter/cssHandler.js +7 -2
- package/server/routes/appRouter/index.js +2 -3
- package/server/routes/appRouter/indexHandler.js +3 -4
- package/server/routes/appRouter/manifestHandler.js +7 -2
- package/server/routes/appRouter/screenshotHandler.js +14 -3
- package/server/routes/appRouter/serviceWorkerHandler.js +9 -1
- package/server/types.d.ts +4 -1
- package/tinyRouter.js +7 -2
- package/tinyRouter.test.js +3 -6
- package/middleware/boom.d.ts +0 -7
- package/middleware/boom.js +0 -32
- package/vitest.config.d.ts +0 -2
- /package/middleware/{boom.test.d.ts → error.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble Node Utilities
|
|
2
2
|
|
|
3
3
|
> NodeJS utilities used by Appsemble internally.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/node-utils)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.22.0)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -26,5 +26,5 @@ compatibility is not guaranteed.
|
|
|
26
26
|
|
|
27
27
|
## License
|
|
28
28
|
|
|
29
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.
|
|
29
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.22.0/LICENSE.md) ©
|
|
30
30
|
[Appsemble](https://appsemble.com)
|
package/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from './handleError.js';
|
|
|
7
7
|
export * from './interceptors.js';
|
|
8
8
|
export * from './logger.js';
|
|
9
9
|
export * from './middleware/loggerMiddleware.js';
|
|
10
|
-
export * from './middleware/
|
|
10
|
+
export * from './middleware/error.js';
|
|
11
11
|
export * from './middleware/conditional.js';
|
|
12
12
|
export * from './middleware/frontend.js';
|
|
13
13
|
export * from './parsers.js';
|
|
@@ -30,3 +30,5 @@ export * from './server/routes/appRouter/iconHandler.js';
|
|
|
30
30
|
export * from './server/controllers/index.js';
|
|
31
31
|
export * from './server/types.js';
|
|
32
32
|
export * from './koa.js';
|
|
33
|
+
export { bulmaVersion, faVersion, version } from './versions.cjs';
|
|
34
|
+
export * from './EmailQuotaExceededError.js';
|
package/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export * from './handleError.js';
|
|
|
7
7
|
export * from './interceptors.js';
|
|
8
8
|
export * from './logger.js';
|
|
9
9
|
export * from './middleware/loggerMiddleware.js';
|
|
10
|
-
export * from './middleware/
|
|
10
|
+
export * from './middleware/error.js';
|
|
11
11
|
export * from './middleware/conditional.js';
|
|
12
12
|
export * from './middleware/frontend.js';
|
|
13
13
|
export * from './parsers.js';
|
|
@@ -30,4 +30,6 @@ export * from './server/routes/appRouter/iconHandler.js';
|
|
|
30
30
|
export * from './server/controllers/index.js';
|
|
31
31
|
export * from './server/types.js';
|
|
32
32
|
export * from './koa.js';
|
|
33
|
+
export { bulmaVersion, faVersion, version } from './versions.cjs';
|
|
34
|
+
export * from './EmailQuotaExceededError.js';
|
|
33
35
|
//# sourceMappingURL=index.js.map
|
package/jsonschema.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type ValidatorResult } from 'jsonschema';
|
|
2
|
-
|
|
2
|
+
import { type Context } from 'koa';
|
|
3
|
+
export declare function handleValidatorResult(ctx: Context, result: ValidatorResult, msg?: string): void;
|
package/jsonschema.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
export function handleValidatorResult(result, msg = 'JSON schema validation failed') {
|
|
1
|
+
export function handleValidatorResult(ctx, result, msg = 'JSON schema validation failed') {
|
|
3
2
|
if (!result.valid) {
|
|
4
|
-
|
|
3
|
+
if (ctx.response === undefined) {
|
|
4
|
+
throw new Error(msg, { cause: result.errors });
|
|
5
|
+
}
|
|
6
|
+
ctx.response.status = 400;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 400,
|
|
9
|
+
error: 'Bad Request',
|
|
10
|
+
message: msg,
|
|
11
|
+
data: {
|
|
12
|
+
errors: result.errors,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
ctx.throw();
|
|
5
16
|
}
|
|
6
17
|
}
|
|
7
18
|
//# sourceMappingURL=jsonschema.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function errorMiddleware() {
|
|
2
|
+
return async (ctx, next) => {
|
|
3
|
+
try {
|
|
4
|
+
await next();
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
ctx.response.type = 'json';
|
|
8
|
+
if (ctx.status && ctx.body) {
|
|
9
|
+
ctx.app.emit('event', error, ctx);
|
|
10
|
+
}
|
|
11
|
+
else if (error instanceof Error &&
|
|
12
|
+
error.message === 'Fields "subject" and "body" must be a valid string') {
|
|
13
|
+
ctx.response.status = 400;
|
|
14
|
+
ctx.response.body = {
|
|
15
|
+
error: 'Bad Request',
|
|
16
|
+
message: 'Fields "subject" and "body" must be a valid string',
|
|
17
|
+
statusCode: 400,
|
|
18
|
+
};
|
|
19
|
+
ctx.app.emit('event', error, ctx);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { notFound, unauthorized } from '@hapi/boom';
|
|
2
1
|
import { request, setTestApp } from 'axios-test-instance';
|
|
3
2
|
import Koa from 'koa';
|
|
4
|
-
import {
|
|
5
|
-
describe('
|
|
3
|
+
import { errorMiddleware } from './error.js';
|
|
4
|
+
describe('errorMiddleware', () => {
|
|
6
5
|
let app;
|
|
7
6
|
beforeEach(async () => {
|
|
8
7
|
app = new Koa();
|
|
9
|
-
app.use(
|
|
8
|
+
app.use(errorMiddleware());
|
|
10
9
|
await setTestApp(app);
|
|
11
10
|
});
|
|
12
|
-
it('should catch
|
|
13
|
-
app.use(() => {
|
|
14
|
-
|
|
11
|
+
it('should catch Koa errors', async () => {
|
|
12
|
+
app.use((ctx) => {
|
|
13
|
+
ctx.response.status = 404;
|
|
14
|
+
ctx.response.body = {
|
|
15
|
+
statusCode: 404,
|
|
16
|
+
error: 'Not Found',
|
|
17
|
+
message: 'Error not found',
|
|
18
|
+
};
|
|
19
|
+
ctx.throw();
|
|
15
20
|
});
|
|
16
21
|
const response = await request.get('/');
|
|
17
22
|
expect(response).toMatchObject({
|
|
@@ -20,11 +25,10 @@ describe('boomMiddleware', () => {
|
|
|
20
25
|
statusCode: 404,
|
|
21
26
|
error: 'Not Found',
|
|
22
27
|
message: 'Error not found',
|
|
23
|
-
data: "It's nowhere to be seen!",
|
|
24
28
|
},
|
|
25
29
|
});
|
|
26
30
|
});
|
|
27
|
-
it('should rethrow non-
|
|
31
|
+
it('should rethrow non-Koa errors', async () => {
|
|
28
32
|
app.silent = true;
|
|
29
33
|
const error = new Error('This is a test error');
|
|
30
34
|
let result;
|
|
@@ -38,8 +42,15 @@ describe('boomMiddleware', () => {
|
|
|
38
42
|
expect(result).toBe(error);
|
|
39
43
|
});
|
|
40
44
|
it('should set Koa headers correctly', async () => {
|
|
41
|
-
app.use(() => {
|
|
42
|
-
|
|
45
|
+
app.use((ctx) => {
|
|
46
|
+
ctx.set('www-authenticate', 'Basic realm="Access to test data", charset="UTF-8"');
|
|
47
|
+
ctx.response.status = 401;
|
|
48
|
+
ctx.response.body = {
|
|
49
|
+
error: 'Unauthorized',
|
|
50
|
+
message: 'Not authorized!',
|
|
51
|
+
statusCode: 401,
|
|
52
|
+
};
|
|
53
|
+
ctx.throw();
|
|
43
54
|
});
|
|
44
55
|
const response = await request.get('/');
|
|
45
56
|
expect(response).toMatchObject({
|
|
@@ -55,4 +66,4 @@ describe('boomMiddleware', () => {
|
|
|
55
66
|
});
|
|
56
67
|
});
|
|
57
68
|
});
|
|
58
|
-
//# sourceMappingURL=
|
|
69
|
+
//# sourceMappingURL=error.test.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/node-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "NodeJS utilities used by Appsemble internally.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
"author": "Appsemble <info@appsemble.com> (https://appsemble.com)",
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": [
|
|
26
|
+
"./index.js",
|
|
27
|
+
"./index.ts"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
24
30
|
"files": [
|
|
25
31
|
"**/*.d.ts",
|
|
26
32
|
"**/*.js",
|
|
@@ -31,11 +37,10 @@
|
|
|
31
37
|
"test": "vitest"
|
|
32
38
|
},
|
|
33
39
|
"dependencies": {
|
|
34
|
-
"@appsemble/types": "0.
|
|
35
|
-
"@appsemble/utils": "0.
|
|
40
|
+
"@appsemble/types": "0.22.0",
|
|
41
|
+
"@appsemble/utils": "0.22.0",
|
|
36
42
|
"@formatjs/fast-memoize": "^2.0.0",
|
|
37
43
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
|
38
|
-
"@hapi/boom": "^10.0.0",
|
|
39
44
|
"@koa/cors": "^4.0.0",
|
|
40
45
|
"@odata/parser": "^0.2.0",
|
|
41
46
|
"@types/koa": "^2.0.0",
|
|
@@ -86,13 +91,11 @@
|
|
|
86
91
|
"yargs": "^17.0.0"
|
|
87
92
|
},
|
|
88
93
|
"devDependencies": {
|
|
89
|
-
"@sinonjs/fake-timers": "^10.0.0",
|
|
90
94
|
"@types/koa-compress": "^4.0.0",
|
|
91
95
|
"@types/koa-range": "^0.3.0",
|
|
92
|
-
"@types/sinonjs__fake-timers": "^8.0.0",
|
|
93
96
|
"axios-mock-adapter": "^1.0.0",
|
|
94
97
|
"axios-test-instance": "^7.0.0",
|
|
95
|
-
"vitest": "^0.
|
|
98
|
+
"vitest": "^0.34.0"
|
|
96
99
|
},
|
|
97
100
|
"peerDependencies": {
|
|
98
101
|
"prettier": "^2"
|
package/resource.d.ts
CHANGED
|
@@ -10,10 +10,11 @@ import { type File } from 'koas-body-parser';
|
|
|
10
10
|
*
|
|
11
11
|
* @param app The app to get the resource definition of
|
|
12
12
|
* @param resourceType The name of the resource definition to get.
|
|
13
|
+
* @param ctx Context used to throw back the errors.
|
|
13
14
|
* @param view The view that’s being used.
|
|
14
15
|
* @returns The matching resource definition.
|
|
15
16
|
*/
|
|
16
|
-
export declare function getResourceDefinition(app: App, resourceType: string, view?: string): ResourceDefinition;
|
|
17
|
+
export declare function getResourceDefinition(app: App, resourceType: string, ctx?: Context, view?: string): ResourceDefinition;
|
|
17
18
|
/**
|
|
18
19
|
* Extracts the IDs of resource request body.
|
|
19
20
|
*
|
package/resource.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { handleValidatorResult } from '@appsemble/node-utils';
|
|
3
|
-
import { notFound } from '@hapi/boom';
|
|
4
3
|
import { addMilliseconds, isPast, parseISO } from 'date-fns';
|
|
5
4
|
import { ValidationError, Validator } from 'jsonschema';
|
|
6
5
|
import parseDuration from 'parse-duration';
|
|
@@ -15,20 +14,54 @@ function stripResource({ $author, $created, $editor, $updated, ...data }) {
|
|
|
15
14
|
*
|
|
16
15
|
* @param app The app to get the resource definition of
|
|
17
16
|
* @param resourceType The name of the resource definition to get.
|
|
17
|
+
* @param ctx Context used to throw back the errors.
|
|
18
18
|
* @param view The view that’s being used.
|
|
19
19
|
* @returns The matching resource definition.
|
|
20
20
|
*/
|
|
21
|
-
export function getResourceDefinition(app, resourceType, view) {
|
|
21
|
+
export function getResourceDefinition(app, resourceType, ctx, view) {
|
|
22
22
|
var _a;
|
|
23
23
|
if (!app) {
|
|
24
|
-
|
|
24
|
+
if (ctx === undefined) {
|
|
25
|
+
throw new Error('App not found');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
ctx.response.status = 404;
|
|
29
|
+
ctx.response.body = {
|
|
30
|
+
statusCode: 404,
|
|
31
|
+
error: 'Not Found',
|
|
32
|
+
message: 'App not found',
|
|
33
|
+
};
|
|
34
|
+
ctx.throw();
|
|
35
|
+
}
|
|
25
36
|
}
|
|
26
37
|
const definition = (_a = app.definition.resources) === null || _a === void 0 ? void 0 : _a[resourceType];
|
|
27
38
|
if (!definition) {
|
|
28
|
-
|
|
39
|
+
if (ctx === undefined || ctx.response === undefined) {
|
|
40
|
+
throw new Error(`App does not have resources called ${resourceType}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
ctx.response.status = 404;
|
|
44
|
+
ctx.response.body = {
|
|
45
|
+
statusCode: 404,
|
|
46
|
+
error: 'Not Found',
|
|
47
|
+
message: `App does not have resources called ${resourceType}`,
|
|
48
|
+
};
|
|
49
|
+
ctx.throw();
|
|
50
|
+
}
|
|
29
51
|
}
|
|
30
52
|
if (view && !definition.views[view]) {
|
|
31
|
-
|
|
53
|
+
if (ctx === undefined) {
|
|
54
|
+
throw new Error(`View ${view} does not exist for resource type ${resourceType}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
ctx.response.status = 404;
|
|
58
|
+
ctx.response.body = {
|
|
59
|
+
statusCode: 404,
|
|
60
|
+
error: 'Not Found',
|
|
61
|
+
message: `View ${view} does not exist for resource type ${resourceType}`,
|
|
62
|
+
};
|
|
63
|
+
ctx.throw();
|
|
64
|
+
}
|
|
32
65
|
}
|
|
33
66
|
return definition;
|
|
34
67
|
}
|
|
@@ -166,7 +199,7 @@ export function processResourceBody(ctx, definition, knownAssetIds = [], knownEx
|
|
|
166
199
|
result.errors.push(new ValidationError('is not referenced from the resource', assetId, null, ['assets', assetId], 'binary', 'format'));
|
|
167
200
|
}
|
|
168
201
|
}
|
|
169
|
-
handleValidatorResult(result, 'Resource validation failed');
|
|
202
|
+
handleValidatorResult(ctx, result, 'Resource validation failed');
|
|
170
203
|
return [resource, preparedAssets, knownAssetIds.filter((id) => !reusedAssets.has(id))];
|
|
171
204
|
}
|
|
172
205
|
//# sourceMappingURL=resource.js.map
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { getRemapperContext, logger } from '@appsemble/node-utils';
|
|
1
|
+
import { getRemapperContext, logger, version } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, formatRequestAction, remap } from '@appsemble/utils';
|
|
3
|
-
import { badGateway, badRequest, methodNotAllowed, notFound } from '@hapi/boom';
|
|
4
3
|
import axios from 'axios';
|
|
5
4
|
import { get, pick } from 'lodash-es';
|
|
6
|
-
import
|
|
5
|
+
import { EmailQuotaExceededError } from '../../EmailQuotaExceededError.js';
|
|
7
6
|
/**
|
|
8
7
|
* These response headers are forwarded when proxying requests.
|
|
9
8
|
*/
|
|
@@ -17,11 +16,31 @@ const supportedActions = ['email', 'notify', 'request'];
|
|
|
17
16
|
async function handleEmail(ctx, app, action, options) {
|
|
18
17
|
const { mailer, method, request: { body: data }, user, } = ctx;
|
|
19
18
|
if (method !== 'POST') {
|
|
20
|
-
|
|
19
|
+
ctx.response.status = 405;
|
|
20
|
+
ctx.response.body = {
|
|
21
|
+
statusCode: 405,
|
|
22
|
+
message: 'Method must be POST for email actions',
|
|
23
|
+
error: 'Method Not Allowed',
|
|
24
|
+
};
|
|
25
|
+
ctx.throw();
|
|
21
26
|
}
|
|
22
27
|
const { email, reloadUser } = options;
|
|
23
28
|
await reloadUser({ context: ctx });
|
|
24
|
-
|
|
29
|
+
try {
|
|
30
|
+
await email({ action, data, mailer, user, options, context: ctx });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof EmailQuotaExceededError) {
|
|
34
|
+
ctx.response.status = 429;
|
|
35
|
+
ctx.response.body = {
|
|
36
|
+
statusCode: 429,
|
|
37
|
+
error: 'Too Many Requests',
|
|
38
|
+
message: error.message,
|
|
39
|
+
};
|
|
40
|
+
ctx.throw();
|
|
41
|
+
}
|
|
42
|
+
ctx.throw(error);
|
|
43
|
+
}
|
|
25
44
|
ctx.status = 204;
|
|
26
45
|
}
|
|
27
46
|
async function handleNotify(ctx, app, action, options) {
|
|
@@ -54,7 +73,13 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
54
73
|
data = JSON.parse(query.data);
|
|
55
74
|
}
|
|
56
75
|
catch {
|
|
57
|
-
|
|
76
|
+
ctx.response.status = 400;
|
|
77
|
+
ctx.response.body = {
|
|
78
|
+
status: 400,
|
|
79
|
+
message: 'data should be a JSON object.',
|
|
80
|
+
error: 'Bad Request',
|
|
81
|
+
};
|
|
82
|
+
ctx.throw();
|
|
58
83
|
}
|
|
59
84
|
}
|
|
60
85
|
const { applyAppServiceSecrets, reloadUser } = options;
|
|
@@ -69,14 +94,21 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
69
94
|
let axiosConfig = formatRequestAction(action, data, (remapper, d) => remap(remapper, d, remapperContext), remapperContext.context);
|
|
70
95
|
axiosConfig = await applyAppServiceSecrets({ axiosConfig, context: ctx, app });
|
|
71
96
|
if (axiosConfig.method.toUpperCase() !== method) {
|
|
72
|
-
|
|
97
|
+
ctx.response.status = 400;
|
|
98
|
+
ctx.response.body = {
|
|
99
|
+
statusCode: 400,
|
|
100
|
+
message: 'Method does not match the request action method',
|
|
101
|
+
error: 'Bad Request',
|
|
102
|
+
};
|
|
103
|
+
ctx.throw();
|
|
73
104
|
}
|
|
74
105
|
if (useBody) {
|
|
75
106
|
axiosConfig.data = data;
|
|
76
107
|
}
|
|
77
|
-
axiosConfig.headers['user-agent'] = `AppsembleServer/${
|
|
108
|
+
axiosConfig.headers['user-agent'] = `AppsembleServer/${version}`;
|
|
78
109
|
axiosConfig.responseType = 'stream';
|
|
79
110
|
axiosConfig.validateStatus = () => true;
|
|
111
|
+
axiosConfig.decompress = false;
|
|
80
112
|
let response;
|
|
81
113
|
logger.verbose(`Forwarding request to ${axios.getUri(axiosConfig)}`);
|
|
82
114
|
try {
|
|
@@ -84,7 +116,13 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
84
116
|
}
|
|
85
117
|
catch (err) {
|
|
86
118
|
logger.error(err);
|
|
87
|
-
|
|
119
|
+
ctx.response.status = 502;
|
|
120
|
+
ctx.response.body = {
|
|
121
|
+
statusCode: 502,
|
|
122
|
+
error: 'Bad Gateway',
|
|
123
|
+
message: 'Bad Gateway',
|
|
124
|
+
};
|
|
125
|
+
ctx.throw(502);
|
|
88
126
|
}
|
|
89
127
|
ctx.status = response.status;
|
|
90
128
|
ctx.set(pick(response.headers, allowResponseHeaders));
|
|
@@ -95,7 +133,13 @@ function createProxyHandler(useBody, options) {
|
|
|
95
133
|
const { pathParams: { appId, path }, } = ctx;
|
|
96
134
|
const app = await options.getApp({ context: ctx, query: { where: { id: appId } } });
|
|
97
135
|
if (!app) {
|
|
98
|
-
|
|
136
|
+
ctx.response.status = 404;
|
|
137
|
+
ctx.response.body = {
|
|
138
|
+
statusCode: 404,
|
|
139
|
+
error: 'Not Found',
|
|
140
|
+
message: 'App not found',
|
|
141
|
+
};
|
|
142
|
+
ctx.throw();
|
|
99
143
|
}
|
|
100
144
|
const appAction = get(app.definition, path);
|
|
101
145
|
const action = supportedActions.find((act) => act === (appAction === null || appAction === void 0 ? void 0 : appAction.type));
|
|
@@ -107,7 +151,13 @@ function createProxyHandler(useBody, options) {
|
|
|
107
151
|
case 'request':
|
|
108
152
|
return handleRequestProxy(ctx, app, appAction, useBody, options);
|
|
109
153
|
default:
|
|
110
|
-
|
|
154
|
+
ctx.response.status = 400;
|
|
155
|
+
ctx.response.body = {
|
|
156
|
+
statusCode: 400,
|
|
157
|
+
message: 'path does not point to a proxyable action',
|
|
158
|
+
error: 'Bad Request',
|
|
159
|
+
};
|
|
160
|
+
ctx.throw();
|
|
111
161
|
}
|
|
112
162
|
};
|
|
113
163
|
}
|
|
@@ -1,17 +1,34 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createGetAppMember({ getApp, getAppMembers }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const { pathParams: { appId, memberId }, } = ctx;
|
|
5
4
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
6
5
|
if (!app) {
|
|
7
|
-
|
|
6
|
+
ctx.response.status = 404;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
error: 'Not Found',
|
|
10
|
+
message: 'App not found',
|
|
11
|
+
};
|
|
12
|
+
ctx.throw();
|
|
8
13
|
}
|
|
9
14
|
if (app.definition.security === undefined) {
|
|
10
|
-
|
|
15
|
+
ctx.response.status = 404;
|
|
16
|
+
ctx.response.body = {
|
|
17
|
+
statusCode: 404,
|
|
18
|
+
error: 'Not Found',
|
|
19
|
+
message: 'App does not have a security definition',
|
|
20
|
+
};
|
|
21
|
+
ctx.throw();
|
|
11
22
|
}
|
|
12
23
|
const appMembers = await getAppMembers({ context: ctx, app, memberId });
|
|
13
24
|
if (appMembers.length !== 1) {
|
|
14
|
-
|
|
25
|
+
ctx.response.status = 404;
|
|
26
|
+
ctx.response.body = {
|
|
27
|
+
statusCode: 404,
|
|
28
|
+
error: 'Not Found',
|
|
29
|
+
message: 'App member not found',
|
|
30
|
+
};
|
|
31
|
+
ctx.throw();
|
|
15
32
|
}
|
|
16
33
|
ctx.body = appMembers[0];
|
|
17
34
|
};
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { getAppsembleMessages, mergeMessages, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, extractAppMessages, normalizeBlockName, } from '@appsemble/utils';
|
|
3
|
-
import { badRequest, notFound } from '@hapi/boom';
|
|
4
3
|
import tags from 'language-tags';
|
|
5
4
|
export function createGetMessages({ getApp, getAppMessages, getBlockMessages, }) {
|
|
6
5
|
return async (ctx) => {
|
|
7
6
|
var _a, _b, _c, _d, _e;
|
|
8
7
|
const { pathParams: { appId, language }, query: { merge, override = 'true' }, } = ctx;
|
|
9
8
|
if (!tags.check(language)) {
|
|
10
|
-
|
|
9
|
+
ctx.response.status = 400;
|
|
10
|
+
ctx.response.body = {
|
|
11
|
+
error: 'Bad Request',
|
|
12
|
+
message: '`Language “${language}” is invalid`',
|
|
13
|
+
};
|
|
14
|
+
ctx.throw();
|
|
11
15
|
}
|
|
12
16
|
const lang = language.toLowerCase();
|
|
13
17
|
const baseLanguage = tags(language)
|
|
@@ -16,7 +20,13 @@ export function createGetMessages({ getApp, getAppMessages, getBlockMessages, })
|
|
|
16
20
|
const baseLang = baseLanguage && String(baseLanguage).toLowerCase();
|
|
17
21
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
18
22
|
if (!app) {
|
|
19
|
-
|
|
23
|
+
ctx.response.status = 404;
|
|
24
|
+
ctx.response.body = {
|
|
25
|
+
status: 404,
|
|
26
|
+
message: 'App not found',
|
|
27
|
+
error: 'Not Found',
|
|
28
|
+
};
|
|
29
|
+
ctx.throw();
|
|
20
30
|
}
|
|
21
31
|
const appMessages = await getAppMessages({ context: ctx, app, language });
|
|
22
32
|
const blockPrefixes = [];
|
|
@@ -35,7 +45,13 @@ export function createGetMessages({ getApp, getAppMessages, getBlockMessages, })
|
|
|
35
45
|
const blockMessages = await getBlockMessages({ context: ctx, blockQuery, baseLang, lang });
|
|
36
46
|
if ((!appMessages.length || (merge && !appMessages.some((m) => m.language === lang))) &&
|
|
37
47
|
lang !== (app.definition.defaultLanguage || defaultLocale)) {
|
|
38
|
-
|
|
48
|
+
ctx.response.status = 404;
|
|
49
|
+
ctx.response.body = {
|
|
50
|
+
statusCode: 404,
|
|
51
|
+
message: `Language “${language}” could not be found`,
|
|
52
|
+
error: 'Not Found',
|
|
53
|
+
};
|
|
54
|
+
ctx.throw();
|
|
39
55
|
}
|
|
40
56
|
const baseLanguageMessages = override === 'true' && appMessages.find((m) => m.language === baseLang);
|
|
41
57
|
const languageMessages = override === 'true' && appMessages.find((m) => m.language === lang);
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { Permission } from '@appsemble/utils';
|
|
2
|
-
import { notFound } from '@hapi/boom';
|
|
3
2
|
import { extension } from 'mime-types';
|
|
4
3
|
export function createGetAssets({ checkRole, getApp, getAppAssets }) {
|
|
5
4
|
return async (ctx) => {
|
|
6
5
|
const { pathParams: { appId }, queryParams: { $skip, $top }, } = ctx;
|
|
7
6
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
8
7
|
if (!app) {
|
|
9
|
-
|
|
8
|
+
ctx.response.status = 404;
|
|
9
|
+
ctx.response.body = {
|
|
10
|
+
statusCode: 404,
|
|
11
|
+
message: 'App not found',
|
|
12
|
+
error: 'Not Found',
|
|
13
|
+
};
|
|
14
|
+
ctx.throw();
|
|
10
15
|
}
|
|
11
16
|
const findOptions = {
|
|
12
17
|
limit: $top,
|
|
@@ -22,13 +27,25 @@ export function createGetAssetById({ getApp, getAppAssets }) {
|
|
|
22
27
|
const { pathParams: { appId, assetId }, } = ctx;
|
|
23
28
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
24
29
|
if (!app) {
|
|
25
|
-
|
|
30
|
+
ctx.response.status = 404;
|
|
31
|
+
ctx.response.body = {
|
|
32
|
+
statusCode: 404,
|
|
33
|
+
message: 'App not found',
|
|
34
|
+
error: 'Not Found',
|
|
35
|
+
};
|
|
36
|
+
ctx.throw();
|
|
26
37
|
}
|
|
27
38
|
const assets = await getAppAssets({ app, context: ctx });
|
|
28
39
|
// Pick asset id over asset name
|
|
29
40
|
const asset = assets.find((a) => a.id === assetId) || assets.find((a) => a.name === assetId);
|
|
30
41
|
if (!asset) {
|
|
31
|
-
|
|
42
|
+
ctx.response.status = 404;
|
|
43
|
+
ctx.response.body = {
|
|
44
|
+
statusCode: 404,
|
|
45
|
+
message: 'Asset not found',
|
|
46
|
+
error: 'Not Found',
|
|
47
|
+
};
|
|
48
|
+
ctx.throw();
|
|
32
49
|
}
|
|
33
50
|
if (assetId !== asset.id) {
|
|
34
51
|
// Redirect to asset using current asset ID
|
|
@@ -60,7 +77,13 @@ export function createCreateAsset({ createAppAsset, getApp }) {
|
|
|
60
77
|
const { pathParams: { appId }, request: { body: { file: { contents, filename, mime }, name, }, }, } = ctx;
|
|
61
78
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
62
79
|
if (!app) {
|
|
63
|
-
|
|
80
|
+
ctx.response.status = 404;
|
|
81
|
+
ctx.response.body = {
|
|
82
|
+
statusCode: 404,
|
|
83
|
+
message: 'App not found',
|
|
84
|
+
error: 'Not Found',
|
|
85
|
+
};
|
|
86
|
+
ctx.throw();
|
|
64
87
|
}
|
|
65
88
|
const asset = await createAppAsset({
|
|
66
89
|
app,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function createGetUserInfo({ getAppUserInfo }) {
|
|
2
2
|
return async (ctx) => {
|
|
3
3
|
const { client, user } = ctx;
|
|
4
|
-
ctx.body = await getAppUserInfo({ context: ctx, client, user });
|
|
4
|
+
ctx.body = await getAppUserInfo({ context: ctx, client, user, ctx });
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=oauth2Provider.js.map
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { getRemapperContext, getResourceDefinition, logger, processResourceBody, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, remap } from '@appsemble/utils';
|
|
3
|
-
import { badRequest, internal, notFound } from '@hapi/boom';
|
|
4
3
|
function generateQuery(ctx, { parseQuery }) {
|
|
5
4
|
try {
|
|
6
5
|
return parseQuery({ $filter: ctx.queryParams.$filter, $orderby: ctx.queryParams.$orderby });
|
|
7
6
|
}
|
|
8
7
|
catch (error) {
|
|
9
8
|
if (error instanceof Error) {
|
|
10
|
-
|
|
9
|
+
ctx.response.status = 400;
|
|
10
|
+
ctx.response.body = {
|
|
11
|
+
statusCode: 400,
|
|
12
|
+
error: 'Bad Request',
|
|
13
|
+
message: 'Unable to process this query',
|
|
14
|
+
data: { syntaxError: error.message },
|
|
15
|
+
};
|
|
16
|
+
ctx.throw();
|
|
11
17
|
}
|
|
12
18
|
logger.error(error);
|
|
13
|
-
|
|
19
|
+
ctx.response.status = 500;
|
|
20
|
+
ctx.response.body = {
|
|
21
|
+
statusCode: 500,
|
|
22
|
+
error: 'Internal Server Error',
|
|
23
|
+
message: 'Unable to process this query',
|
|
24
|
+
};
|
|
25
|
+
ctx.throw();
|
|
14
26
|
}
|
|
15
27
|
}
|
|
16
28
|
export function createQueryResources(options) {
|
|
@@ -26,6 +38,7 @@ export function createQueryResources(options) {
|
|
|
26
38
|
resourceType,
|
|
27
39
|
action: 'query',
|
|
28
40
|
options,
|
|
41
|
+
ctx,
|
|
29
42
|
});
|
|
30
43
|
const findOptions = {
|
|
31
44
|
limit: $top,
|
|
@@ -51,7 +64,7 @@ export function createQueryResources(options) {
|
|
|
51
64
|
context: ctx,
|
|
52
65
|
});
|
|
53
66
|
const view = (_a = ctx.queryParams) === null || _a === void 0 ? void 0 : _a.view;
|
|
54
|
-
const resourceDefinition = getResourceDefinition(app, resourceType, view);
|
|
67
|
+
const resourceDefinition = getResourceDefinition(app, resourceType, ctx, view);
|
|
55
68
|
if (view) {
|
|
56
69
|
const context = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, user && {
|
|
57
70
|
sub: user.id,
|
|
@@ -78,6 +91,7 @@ export function createCountResources(options) {
|
|
|
78
91
|
action,
|
|
79
92
|
resourceType,
|
|
80
93
|
options,
|
|
94
|
+
ctx,
|
|
81
95
|
});
|
|
82
96
|
const { where } = generateQuery(ctx, options);
|
|
83
97
|
const findOptions = {
|
|
@@ -110,13 +124,14 @@ export function createGetResourceById(options) {
|
|
|
110
124
|
const { getApp, getAppResource, verifyResourceActionPermission } = options;
|
|
111
125
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
112
126
|
const view = (_a = ctx.queryParams) === null || _a === void 0 ? void 0 : _a.view;
|
|
113
|
-
const resourceDefinition = getResourceDefinition(app, resourceType, view);
|
|
127
|
+
const resourceDefinition = getResourceDefinition(app, resourceType, ctx, view);
|
|
114
128
|
const userQuery = await verifyResourceActionPermission({
|
|
115
129
|
app,
|
|
116
130
|
context: ctx,
|
|
117
131
|
action,
|
|
118
132
|
resourceType,
|
|
119
133
|
options,
|
|
134
|
+
ctx,
|
|
120
135
|
});
|
|
121
136
|
const findOptions = {
|
|
122
137
|
where: {
|
|
@@ -135,7 +150,13 @@ export function createGetResourceById(options) {
|
|
|
135
150
|
findOptions,
|
|
136
151
|
});
|
|
137
152
|
if (!resource) {
|
|
138
|
-
|
|
153
|
+
ctx.response.status = 404;
|
|
154
|
+
ctx.response.body = {
|
|
155
|
+
statusCode: 404,
|
|
156
|
+
error: 'Not Found',
|
|
157
|
+
message: 'Resource not found',
|
|
158
|
+
};
|
|
159
|
+
ctx.throw();
|
|
139
160
|
}
|
|
140
161
|
if (view) {
|
|
141
162
|
const context = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, user && {
|
|
@@ -157,8 +178,8 @@ export function createCreateResource(options) {
|
|
|
157
178
|
const { createAppResourcesWithAssets, getApp, verifyResourceActionPermission } = options;
|
|
158
179
|
const action = 'create';
|
|
159
180
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
160
|
-
const resourceDefinition = getResourceDefinition(app, resourceType);
|
|
161
|
-
await verifyResourceActionPermission({ app, context: ctx, action, resourceType, options });
|
|
181
|
+
const resourceDefinition = getResourceDefinition(app, resourceType, ctx);
|
|
182
|
+
await verifyResourceActionPermission({ app, context: ctx, action, resourceType, options, ctx });
|
|
162
183
|
const [processedBody, preparedAssets] = processResourceBody(ctx, resourceDefinition);
|
|
163
184
|
if (Array.isArray(processedBody) && !processedBody.length) {
|
|
164
185
|
ctx.body = [];
|
|
@@ -183,13 +204,14 @@ export function createUpdateResource(options) {
|
|
|
183
204
|
const { getApp, getAppAssets, getAppResource, updateAppResource, verifyResourceActionPermission, } = options;
|
|
184
205
|
const action = 'update';
|
|
185
206
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
186
|
-
const resourceDefinition = getResourceDefinition(app, resourceType);
|
|
207
|
+
const resourceDefinition = getResourceDefinition(app, resourceType, ctx);
|
|
187
208
|
const userQuery = await verifyResourceActionPermission({
|
|
188
209
|
app,
|
|
189
210
|
context: ctx,
|
|
190
211
|
action,
|
|
191
212
|
resourceType,
|
|
192
213
|
options,
|
|
214
|
+
ctx,
|
|
193
215
|
});
|
|
194
216
|
const findOptions = {
|
|
195
217
|
where: {
|
|
@@ -208,7 +230,13 @@ export function createUpdateResource(options) {
|
|
|
208
230
|
findOptions,
|
|
209
231
|
});
|
|
210
232
|
if (!oldResource) {
|
|
211
|
-
|
|
233
|
+
ctx.response.status = 404;
|
|
234
|
+
ctx.response.body = {
|
|
235
|
+
statusCode: 404,
|
|
236
|
+
error: 'Not Found',
|
|
237
|
+
message: 'Resource not found',
|
|
238
|
+
};
|
|
239
|
+
ctx.throw();
|
|
212
240
|
}
|
|
213
241
|
const appAssets = await getAppAssets({ context: ctx, app });
|
|
214
242
|
const [processedBody, preparedAssets, deletedAssetIds] = processResourceBody(ctx, resourceDefinition, appAssets.filter((asset) => asset.resourceId === resourceId).map((asset) => asset.id), oldResource.expires);
|
|
@@ -239,6 +267,7 @@ export function createDeleteResource(options) {
|
|
|
239
267
|
action,
|
|
240
268
|
resourceType,
|
|
241
269
|
options,
|
|
270
|
+
ctx,
|
|
242
271
|
});
|
|
243
272
|
const findOptions = {
|
|
244
273
|
where: {
|
|
@@ -257,7 +286,13 @@ export function createDeleteResource(options) {
|
|
|
257
286
|
findOptions,
|
|
258
287
|
});
|
|
259
288
|
if (!resource) {
|
|
260
|
-
|
|
289
|
+
ctx.response.status = 404;
|
|
290
|
+
ctx.response.body = {
|
|
291
|
+
statusCode: 404,
|
|
292
|
+
error: 'Not Found',
|
|
293
|
+
message: 'Resource not found',
|
|
294
|
+
};
|
|
295
|
+
ctx.throw();
|
|
261
296
|
}
|
|
262
297
|
await deleteAppResource({
|
|
263
298
|
app,
|
|
@@ -173,7 +173,7 @@ describe('createQueryResources', () => {
|
|
|
173
173
|
getAppUrl: mockGetAppUrl,
|
|
174
174
|
getAppMessages: mockGetAppMessages,
|
|
175
175
|
};
|
|
176
|
-
const resourceDefinition = getResourceDefinition(mockApp, 'mockResourceType', 'view');
|
|
176
|
+
const resourceDefinition = getResourceDefinition(mockApp, 'mockResourceType', mockCtx, 'view');
|
|
177
177
|
const remapperContext = await getRemapperContext(mockApp, mockApp.definition.defaultLanguage || defaultLocale, null, options, mockCtx);
|
|
178
178
|
const remappedResources = mockResources.map((resource) => remap(resourceDefinition.views.view.remap, resource, remapperContext));
|
|
179
179
|
const middleware = createQueryResources(options);
|
|
@@ -413,7 +413,7 @@ describe('createGetResourceById', () => {
|
|
|
413
413
|
getAppUrl: mockGetAppUrl,
|
|
414
414
|
getAppMessages: mockGetAppMessages,
|
|
415
415
|
};
|
|
416
|
-
const resourceDefinition = getResourceDefinition(mockApp, 'mockResourceType', 'view');
|
|
416
|
+
const resourceDefinition = getResourceDefinition(mockApp, 'mockResourceType', mockCtx, 'view');
|
|
417
417
|
const remapperContext = await getRemapperContext(mockApp, mockApp.definition.defaultLanguage || defaultLocale, null, options, mockCtx);
|
|
418
418
|
const remappedResource = remap(resourceDefinition.views.view.remap, mockResource, remapperContext);
|
|
419
419
|
const middleware = createGetResourceById(options);
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createGetTeams({ getApp, getAppTeams }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const { pathParams: { appId }, user, } = ctx;
|
|
5
4
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
6
5
|
if (!app) {
|
|
7
|
-
|
|
6
|
+
ctx.response.status = 404;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
error: 'Not Found',
|
|
10
|
+
message: 'App not found',
|
|
11
|
+
};
|
|
12
|
+
ctx.throw();
|
|
8
13
|
}
|
|
9
14
|
ctx.body = await getAppTeams({ context: ctx, app, user });
|
|
10
15
|
};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createBlockAssetHandler({ getBlockAsset }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const { params: { filename, name, version }, } = ctx;
|
|
5
4
|
const blockAsset = await getBlockAsset({ filename, name, version, context: ctx });
|
|
6
5
|
if (!blockAsset) {
|
|
7
|
-
|
|
6
|
+
ctx.response.status = 404;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
message: 'Block asset not found',
|
|
10
|
+
error: 'Not Found',
|
|
11
|
+
};
|
|
12
|
+
ctx.throw();
|
|
8
13
|
}
|
|
9
14
|
ctx.body = blockAsset.content;
|
|
10
15
|
ctx.type = blockAsset.mime;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createBlockCssHandler({ getApp, getAppBlockStyles }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const { params: { name }, } = ctx;
|
|
5
4
|
const app = await getApp({ context: ctx });
|
|
6
5
|
if (!app) {
|
|
7
|
-
|
|
6
|
+
ctx.response.status = 404;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
error: 'Not Found',
|
|
10
|
+
message: 'App not found',
|
|
11
|
+
};
|
|
12
|
+
ctx.throw();
|
|
8
13
|
}
|
|
9
14
|
const appBlockStyles = await getAppBlockStyles({ app, name, context: ctx });
|
|
10
15
|
const [style] = appBlockStyles;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { logger } from '@appsemble/node-utils';
|
|
2
|
+
import { bulmaVersion, logger } from '@appsemble/node-utils';
|
|
3
3
|
import { baseTheme } from '@appsemble/utils';
|
|
4
|
-
import bulma from 'bulma/package.json' assert { type: 'json' };
|
|
5
4
|
import { renderSync } from 'sass';
|
|
6
5
|
import stripBom from 'strip-bom';
|
|
7
6
|
const require = createRequire(import.meta.url);
|
|
@@ -76,7 +75,7 @@ export function createBulmaHandler({ createTheme, getTheme }) {
|
|
|
76
75
|
return async (ctx) => {
|
|
77
76
|
const { query } = ctx;
|
|
78
77
|
const theme = {
|
|
79
|
-
bulmaVersion
|
|
78
|
+
bulmaVersion,
|
|
80
79
|
primaryColor: (getQueryParameter(query.primaryColor) || baseTheme.primaryColor).toLowerCase(),
|
|
81
80
|
linkColor: (getQueryParameter(query.linkColor) || baseTheme.linkColor).toLowerCase(),
|
|
82
81
|
successColor: (getQueryParameter(query.successColor) || baseTheme.successColor).toLowerCase(),
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createCssHandler(type, { getAppStyles }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const app = await getAppStyles({ context: ctx, query: { attributes: [type], raw: true } });
|
|
5
4
|
if (!app) {
|
|
6
|
-
|
|
5
|
+
ctx.response.status = 404;
|
|
6
|
+
ctx.response.body = {
|
|
7
|
+
statusCode: 404,
|
|
8
|
+
error: 'Not Found',
|
|
9
|
+
message: 'App not found',
|
|
10
|
+
};
|
|
11
|
+
ctx.throw();
|
|
7
12
|
}
|
|
8
13
|
ctx.body = app[type];
|
|
9
14
|
ctx.type = 'css';
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { tinyRouter } from '@appsemble/node-utils';
|
|
3
|
+
import { faVersion, tinyRouter } from '@appsemble/node-utils';
|
|
4
4
|
import { noop, partialNormalized, partialSemver } from '@appsemble/utils';
|
|
5
|
-
import faPkg from '@fortawesome/fontawesome-free/package.json' assert { type: 'json' };
|
|
6
5
|
import mount from 'koa-mount';
|
|
7
6
|
import serve from 'koa-static';
|
|
8
7
|
import { createBlockAssetHandler } from './blockAssetHandler.js';
|
|
@@ -50,7 +49,7 @@ export function createAppRouter(options) {
|
|
|
50
49
|
},
|
|
51
50
|
{
|
|
52
51
|
route: /^\/fa\//,
|
|
53
|
-
get: mount(`/fa/${
|
|
52
|
+
get: mount(`/fa/${faVersion}`, serve(dirname(require.resolve('@fortawesome/fontawesome-free/package.json')))),
|
|
54
53
|
},
|
|
55
54
|
{
|
|
56
55
|
route: '/core.css',
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { createThemeURL, defaultLocale, getAppBlocks, mergeThemes } from '@appsemble/utils';
|
|
3
|
-
import faPkg from '@fortawesome/fontawesome-free/package.json' assert { type: 'json' };
|
|
4
|
-
import bulmaPkg from 'bulma/package.json' assert { type: 'json' };
|
|
5
3
|
import { organizationBlocklist } from '../../../organizationBlocklist.js';
|
|
6
4
|
import { makeCSP, render } from '../../../render.js';
|
|
7
|
-
|
|
8
|
-
export const
|
|
5
|
+
import { bulmaVersion, faVersion } from '../../../versions.cjs';
|
|
6
|
+
export const bulmaURL = `/bulma/${bulmaVersion}/bulma.min.css`;
|
|
7
|
+
export const faURL = `/fa/${faVersion}/css/all.min.css`;
|
|
9
8
|
export function createIndexHandler({ createSettings, getApp, getAppDetails, getAppMessages, getAppUrl, getCsp, getHost, }) {
|
|
10
9
|
return async (ctx) => {
|
|
11
10
|
var _a;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { baseTheme, normalize } from '@appsemble/utils';
|
|
2
|
-
import { notFound } from '@hapi/boom';
|
|
3
2
|
import { extension } from 'mime-types';
|
|
4
3
|
const iconSizes = [48, 144, 192, 512];
|
|
5
4
|
export function createManifestHandler({ getApp, getAppScreenshots }) {
|
|
6
5
|
return async (ctx) => {
|
|
7
6
|
const app = await getApp({ context: ctx });
|
|
8
7
|
if (!app) {
|
|
9
|
-
|
|
8
|
+
ctx.response.status = 404;
|
|
9
|
+
ctx.response.body = {
|
|
10
|
+
statusCode: 404,
|
|
11
|
+
message: 'Block asset not found',
|
|
12
|
+
error: 'Not Found',
|
|
13
|
+
};
|
|
14
|
+
ctx.throw();
|
|
10
15
|
}
|
|
11
16
|
const { defaultPage, description, name, theme = baseTheme } = app.definition;
|
|
12
17
|
const { themeColor = '#ffffff', splashColor = themeColor } = theme;
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import { notFound } from '@hapi/boom';
|
|
2
1
|
export function createScreenshotHandler({ getApp, getAppScreenshots }) {
|
|
3
2
|
return async (ctx) => {
|
|
4
3
|
const { id } = ctx.params;
|
|
5
4
|
const app = await getApp({ context: ctx });
|
|
6
5
|
if (!app) {
|
|
7
|
-
|
|
6
|
+
ctx.response.status = 404;
|
|
7
|
+
ctx.response.body = {
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
message: 'App not found',
|
|
10
|
+
error: 'Not Found',
|
|
11
|
+
};
|
|
12
|
+
ctx.throw();
|
|
8
13
|
}
|
|
9
14
|
const appScreenshots = await getAppScreenshots({ app, context: ctx });
|
|
10
15
|
const appScreenshot = appScreenshots.find((screenshot) => screenshot.id === Number.parseInt(id));
|
|
11
16
|
if (!appScreenshot) {
|
|
12
|
-
|
|
17
|
+
ctx.response.status = 404;
|
|
18
|
+
ctx.response.body = {
|
|
19
|
+
statusCode: 404,
|
|
20
|
+
message: 'Screenshot not found',
|
|
21
|
+
error: 'Not Found',
|
|
22
|
+
};
|
|
23
|
+
ctx.throw();
|
|
13
24
|
}
|
|
14
25
|
const { mime, screenshot } = appScreenshot;
|
|
15
26
|
ctx.type = mime;
|
|
@@ -8,7 +8,15 @@ export function createServiceWorkerHandler({ getApp, getBlocksAssetsPaths }) {
|
|
|
8
8
|
? readFile(new URL('../../../../../dist/app/service-worker.js', import.meta.url), 'utf8')
|
|
9
9
|
: ctx.fs.promises.readFile(filename, 'utf8'));
|
|
10
10
|
const app = await getApp({ context: ctx });
|
|
11
|
-
|
|
11
|
+
if (!app) {
|
|
12
|
+
ctx.response.status = 404;
|
|
13
|
+
ctx.response.body = {
|
|
14
|
+
statusCode: 404,
|
|
15
|
+
message: 'App does not exist.',
|
|
16
|
+
error: 'Not Found',
|
|
17
|
+
};
|
|
18
|
+
ctx.throw();
|
|
19
|
+
}
|
|
12
20
|
const identifiableBlocks = getAppBlocks(app.definition);
|
|
13
21
|
const blocksAssetsPaths = await getBlocksAssetsPaths({ identifiableBlocks, context: ctx });
|
|
14
22
|
ctx.body = `const blockAssets=${JSON.stringify(blocksAssetsPaths)};${serviceWorker}`;
|
package/server/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { type App, type AppMember, type AppMessages, type Asset, type BlockConfig, type BlockDefinition, type BlockManifest, type EmailActionDefinition, type Resource, type ResourceDefinition, type TeamMember, type Theme as ThemeType, type UserInfo } from '@appsemble/types';
|
|
4
4
|
import { type IdentifiableBlock, type Permission } from '@appsemble/utils';
|
|
5
5
|
import { type RawAxiosRequestConfig } from 'axios';
|
|
6
|
-
import { type DefaultContext as DefaultContextInterface, type DefaultState, type ParameterizedContext } from 'koa';
|
|
6
|
+
import { type Context, type DefaultContext as DefaultContextInterface, type DefaultState, type ParameterizedContext } from 'koa';
|
|
7
7
|
export interface UtilsUser {
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
@@ -19,6 +19,7 @@ declare module 'koa' {
|
|
|
19
19
|
body: any;
|
|
20
20
|
}
|
|
21
21
|
interface DefaultContext {
|
|
22
|
+
appPath: string;
|
|
22
23
|
appHost: string;
|
|
23
24
|
appsembleApp: App;
|
|
24
25
|
appBlocks: BlockManifest[];
|
|
@@ -130,6 +131,7 @@ export interface GetAppUserInfoParams {
|
|
|
130
131
|
app: App;
|
|
131
132
|
} | {};
|
|
132
133
|
user: UtilsUser;
|
|
134
|
+
ctx: Context;
|
|
133
135
|
}
|
|
134
136
|
export interface ExtendedTeam extends TeamMember {
|
|
135
137
|
size: number;
|
|
@@ -198,6 +200,7 @@ export interface VerifyResourceActionPermissionParams {
|
|
|
198
200
|
resourceType: string;
|
|
199
201
|
action: Action;
|
|
200
202
|
options: Options;
|
|
203
|
+
ctx: Context;
|
|
201
204
|
}
|
|
202
205
|
export interface ApplyAppServiceSecretsParams {
|
|
203
206
|
context: ParameterizedContext<DefaultState, DefaultContextInterface, any>;
|
package/tinyRouter.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { has } from '@appsemble/utils';
|
|
2
|
-
import { methodNotAllowed } from '@hapi/boom';
|
|
3
2
|
/**
|
|
4
3
|
* A tiny dynamic router middleware for GET requests.
|
|
5
4
|
*
|
|
@@ -23,7 +22,13 @@ export function tinyRouter(routes) {
|
|
|
23
22
|
let m = method.toLowerCase();
|
|
24
23
|
if (!has(result, m)) {
|
|
25
24
|
if (!has(result, 'any')) {
|
|
26
|
-
|
|
25
|
+
ctx.response.status = 405;
|
|
26
|
+
ctx.response.body = {
|
|
27
|
+
statusCode: 405,
|
|
28
|
+
error: 'Method Not Allowed',
|
|
29
|
+
message: 'Method not allowed',
|
|
30
|
+
};
|
|
31
|
+
ctx.throw();
|
|
27
32
|
}
|
|
28
33
|
m = 'any';
|
|
29
34
|
}
|
package/tinyRouter.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { noop } from '@appsemble/utils';
|
|
2
2
|
import { request, setTestApp } from 'axios-test-instance';
|
|
3
3
|
import Koa from 'koa';
|
|
4
|
+
import { errorMiddleware } from './index.js';
|
|
4
5
|
import { tinyRouter } from './tinyRouter.js';
|
|
5
6
|
let app;
|
|
6
7
|
let context;
|
|
@@ -24,10 +25,7 @@ it('should assign the match group to params', async () => {
|
|
|
24
25
|
expect(context.params).toStrictEqual({ foo: '1', bar: '2' });
|
|
25
26
|
});
|
|
26
27
|
it('should throw method not allowed if a URL is matched, but not for the given method', async () => {
|
|
27
|
-
|
|
28
|
-
app.on('error', (err) => {
|
|
29
|
-
error = err;
|
|
30
|
-
});
|
|
28
|
+
app.use(errorMiddleware());
|
|
31
29
|
app.use(tinyRouter([
|
|
32
30
|
{
|
|
33
31
|
route: '/',
|
|
@@ -35,8 +33,7 @@ it('should throw method not allowed if a URL is matched, but not for the given m
|
|
|
35
33
|
},
|
|
36
34
|
]));
|
|
37
35
|
await request.post('/');
|
|
38
|
-
expect(
|
|
39
|
-
expect(error.output.statusCode).toBe(405);
|
|
36
|
+
expect(context.status).toBe(405);
|
|
40
37
|
});
|
|
41
38
|
it('should fall back to the any handler if it exists', async () => {
|
|
42
39
|
const any = vi.fn();
|
package/middleware/boom.d.ts
DELETED
package/middleware/boom.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { SCIMError } from '../scim.js';
|
|
2
|
-
/**
|
|
3
|
-
* Koa middleware for handling Boom errors.
|
|
4
|
-
*
|
|
5
|
-
* @returns Koa middleware that converts Boom errors into an HTTP response.
|
|
6
|
-
*/
|
|
7
|
-
export function boomMiddleware() {
|
|
8
|
-
return async (ctx, next) => {
|
|
9
|
-
try {
|
|
10
|
-
await next();
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
if (error instanceof SCIMError) {
|
|
14
|
-
ctx.status = Number(error.status);
|
|
15
|
-
ctx.body = error;
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const err = error;
|
|
19
|
-
if (!err.isBoom) {
|
|
20
|
-
throw err;
|
|
21
|
-
}
|
|
22
|
-
const { output } = err;
|
|
23
|
-
ctx.status = output.statusCode;
|
|
24
|
-
ctx.body = output.payload;
|
|
25
|
-
if (err.data) {
|
|
26
|
-
ctx.body.data = err.data;
|
|
27
|
-
}
|
|
28
|
-
ctx.set(output.headers);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=boom.js.map
|
package/vitest.config.d.ts
DELETED
|
File without changes
|