@appsemble/node-utils 0.22.10 → 0.23.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/AppsembleError.test.js +1 -0
- package/README.md +3 -3
- package/basicAuth.test.js +1 -0
- package/formData.test.js +1 -0
- package/fs.test.js +1 -0
- package/handleError.test.js +1 -0
- package/interceptors.test.js +1 -0
- package/jsonschema.js +2 -10
- package/koa.d.ts +63 -0
- package/koa.js +74 -0
- package/logger.test.js +1 -0
- package/middleware/error.test.js +4 -14
- package/middleware/loggerMiddleware.test.js +1 -0
- package/package.json +4 -4
- package/readFileOrString.test.js +1 -0
- package/resource.js +4 -21
- package/server/controllers/action.js +8 -52
- package/server/controllers/appMembers.js +4 -27
- package/server/controllers/appMessages.js +5 -24
- package/server/controllers/assets.js +5 -36
- package/server/controllers/resources.js +6 -43
- package/server/controllers/resources.test.js +1 -0
- package/server/controllers/teams.js +2 -9
- package/server/routes/appRouter/blockAssetHandler.js +2 -9
- package/server/routes/appRouter/blockCssHandler.js +2 -9
- package/server/routes/appRouter/cssHandler.js +2 -9
- package/server/routes/appRouter/iconHandler.js +1 -1
- package/server/routes/appRouter/manifestHandler.js +2 -9
- package/server/routes/appRouter/screenshotHandler.js +3 -18
- package/server/routes/appRouter/serviceWorkerHandler.js +2 -9
- package/server/types.d.ts +8 -5
- package/tinyRouter.js +2 -7
- package/tinyRouter.test.js +1 -0
package/AppsembleError.test.js
CHANGED
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.23.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.23.0/LICENSE.md) ©
|
|
30
30
|
[Appsemble](https://appsemble.com)
|
package/basicAuth.test.js
CHANGED
package/formData.test.js
CHANGED
package/fs.test.js
CHANGED
package/handleError.test.js
CHANGED
package/interceptors.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import MockAdapter from 'axios-mock-adapter';
|
|
3
3
|
import { highlight } from 'cli-highlight';
|
|
4
4
|
import FormData from 'form-data';
|
|
5
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
6
|
import { configureAxios, formData, logger, requestLogger, responseLogger } from './index.js';
|
|
6
7
|
function h(content) {
|
|
7
8
|
return highlight(content, { language: 'http' });
|
package/jsonschema.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
|
+
import { throwKoaError } from './koa.js';
|
|
1
2
|
export function handleValidatorResult(ctx, result, msg = 'JSON schema validation failed') {
|
|
2
3
|
if (!result.valid) {
|
|
3
4
|
if (ctx.response === undefined) {
|
|
4
5
|
throw new Error(msg, { cause: result.errors });
|
|
5
6
|
}
|
|
6
|
-
ctx.
|
|
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();
|
|
7
|
+
throwKoaError(ctx, 400, msg, { errors: result.errors });
|
|
16
8
|
}
|
|
17
9
|
}
|
|
18
10
|
//# sourceMappingURL=jsonschema.js.map
|
package/koa.d.ts
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
|
+
import { type Context } from 'koa';
|
|
1
2
|
import { type Plugin } from 'koas-core';
|
|
2
3
|
declare function serializer(): Plugin;
|
|
3
4
|
declare function bodyParser(): Plugin;
|
|
4
5
|
export { bodyParser, serializer };
|
|
5
6
|
export * from 'koas-parameters';
|
|
6
7
|
export * from 'koas-security';
|
|
8
|
+
declare const errorStatusMap: {
|
|
9
|
+
readonly 400: "Bad Request";
|
|
10
|
+
readonly 401: "Unauthorized";
|
|
11
|
+
readonly 402: "Payment Required";
|
|
12
|
+
readonly 403: "Forbidden";
|
|
13
|
+
readonly 404: "Not Found";
|
|
14
|
+
readonly 405: "Method Not Allowed";
|
|
15
|
+
readonly 406: "Not Acceptable";
|
|
16
|
+
readonly 407: "Proxy Authentication Required";
|
|
17
|
+
readonly 408: "Request Timeout";
|
|
18
|
+
readonly 409: "Conflict";
|
|
19
|
+
readonly 410: "Gone";
|
|
20
|
+
readonly 411: "Length Required";
|
|
21
|
+
readonly 412: "Precondition Failed";
|
|
22
|
+
readonly 413: "Payload Too Large";
|
|
23
|
+
readonly 414: "Uri Too Long";
|
|
24
|
+
readonly 415: "Unsupported Media Type";
|
|
25
|
+
readonly 416: "Range Not Satisfiable";
|
|
26
|
+
readonly 417: "Expectation Failed";
|
|
27
|
+
readonly 418: "Im A Teapot";
|
|
28
|
+
readonly 421: "Misdirected Request";
|
|
29
|
+
readonly 422: "Unprocessable Entity";
|
|
30
|
+
readonly 423: "Locked";
|
|
31
|
+
readonly 424: "Failed Dependency";
|
|
32
|
+
readonly 425: "Too Early";
|
|
33
|
+
readonly 426: "Upgrade Required";
|
|
34
|
+
readonly 428: "Precondition Required";
|
|
35
|
+
readonly 429: "Too Many Requests";
|
|
36
|
+
readonly 431: "Request Header Fields Too Large";
|
|
37
|
+
readonly 451: "Unavailable For Legal Reasons";
|
|
38
|
+
readonly 500: "Internal Server Error";
|
|
39
|
+
readonly 501: "Not Implemented";
|
|
40
|
+
readonly 502: "Bad Gateway";
|
|
41
|
+
readonly 503: "Service Unavailable";
|
|
42
|
+
readonly 504: "Gateway Timeout";
|
|
43
|
+
readonly 505: "Http Version Not Supported";
|
|
44
|
+
readonly 506: "Variant Also Negotiates";
|
|
45
|
+
readonly 507: "Insufficient Storage";
|
|
46
|
+
readonly 508: "Loop Detected";
|
|
47
|
+
readonly 510: "Not Extended";
|
|
48
|
+
readonly 511: "Network Authentication Required";
|
|
49
|
+
};
|
|
50
|
+
type HttpErrorCodes = keyof typeof errorStatusMap;
|
|
51
|
+
/**
|
|
52
|
+
* Throw standard Koa error response.
|
|
53
|
+
*
|
|
54
|
+
* @param ctx The Koa context used to throw the error response.
|
|
55
|
+
* @param status The HTTP status code to use.
|
|
56
|
+
* @param message The message for in the response body.
|
|
57
|
+
* @param data Optional data to include.
|
|
58
|
+
*/
|
|
59
|
+
export declare function throwKoaError(ctx: Context, status: HttpErrorCodes, message: string, data?: Record<string, any>): void;
|
|
60
|
+
/**
|
|
61
|
+
* Throw standard Koa error response when condition is met.
|
|
62
|
+
*
|
|
63
|
+
* @param condition The condition to check.
|
|
64
|
+
* @param ctx The Koa context used to throw the error response.
|
|
65
|
+
* @param status The HTTP status code to use.
|
|
66
|
+
* @param message The message for in the response body.
|
|
67
|
+
* @param data Optional data to include.
|
|
68
|
+
*/
|
|
69
|
+
export declare function assertKoaError(condition: boolean, ctx: Context, status: HttpErrorCodes, message: string, data?: Record<string, any>): void;
|
package/koa.js
CHANGED
|
@@ -24,4 +24,78 @@ function bodyParser() {
|
|
|
24
24
|
export { bodyParser, serializer };
|
|
25
25
|
export * from 'koas-parameters';
|
|
26
26
|
export * from 'koas-security';
|
|
27
|
+
const errorStatusMap = {
|
|
28
|
+
400: 'Bad Request',
|
|
29
|
+
401: 'Unauthorized',
|
|
30
|
+
402: 'Payment Required',
|
|
31
|
+
403: 'Forbidden',
|
|
32
|
+
404: 'Not Found',
|
|
33
|
+
405: 'Method Not Allowed',
|
|
34
|
+
406: 'Not Acceptable',
|
|
35
|
+
407: 'Proxy Authentication Required',
|
|
36
|
+
408: 'Request Timeout',
|
|
37
|
+
409: 'Conflict',
|
|
38
|
+
410: 'Gone',
|
|
39
|
+
411: 'Length Required',
|
|
40
|
+
412: 'Precondition Failed',
|
|
41
|
+
413: 'Payload Too Large',
|
|
42
|
+
414: 'Uri Too Long',
|
|
43
|
+
415: 'Unsupported Media Type',
|
|
44
|
+
416: 'Range Not Satisfiable',
|
|
45
|
+
417: 'Expectation Failed',
|
|
46
|
+
418: 'Im A Teapot',
|
|
47
|
+
421: 'Misdirected Request',
|
|
48
|
+
422: 'Unprocessable Entity',
|
|
49
|
+
423: 'Locked',
|
|
50
|
+
424: 'Failed Dependency',
|
|
51
|
+
425: 'Too Early',
|
|
52
|
+
426: 'Upgrade Required',
|
|
53
|
+
428: 'Precondition Required',
|
|
54
|
+
429: 'Too Many Requests',
|
|
55
|
+
431: 'Request Header Fields Too Large',
|
|
56
|
+
451: 'Unavailable For Legal Reasons',
|
|
57
|
+
500: 'Internal Server Error',
|
|
58
|
+
501: 'Not Implemented',
|
|
59
|
+
502: 'Bad Gateway',
|
|
60
|
+
503: 'Service Unavailable',
|
|
61
|
+
504: 'Gateway Timeout',
|
|
62
|
+
505: 'Http Version Not Supported',
|
|
63
|
+
506: 'Variant Also Negotiates',
|
|
64
|
+
507: 'Insufficient Storage',
|
|
65
|
+
508: 'Loop Detected',
|
|
66
|
+
510: 'Not Extended',
|
|
67
|
+
511: 'Network Authentication Required',
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Throw standard Koa error response.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx The Koa context used to throw the error response.
|
|
73
|
+
* @param status The HTTP status code to use.
|
|
74
|
+
* @param message The message for in the response body.
|
|
75
|
+
* @param data Optional data to include.
|
|
76
|
+
*/
|
|
77
|
+
export function throwKoaError(ctx, status, message, data) {
|
|
78
|
+
ctx.response.status = status;
|
|
79
|
+
ctx.response.body = {
|
|
80
|
+
error: errorStatusMap[status],
|
|
81
|
+
message,
|
|
82
|
+
statusCode: status,
|
|
83
|
+
data,
|
|
84
|
+
};
|
|
85
|
+
ctx.throw();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Throw standard Koa error response when condition is met.
|
|
89
|
+
*
|
|
90
|
+
* @param condition The condition to check.
|
|
91
|
+
* @param ctx The Koa context used to throw the error response.
|
|
92
|
+
* @param status The HTTP status code to use.
|
|
93
|
+
* @param message The message for in the response body.
|
|
94
|
+
* @param data Optional data to include.
|
|
95
|
+
*/
|
|
96
|
+
export function assertKoaError(condition, ctx, status, message, data) {
|
|
97
|
+
if (condition) {
|
|
98
|
+
throwKoaError(ctx, status, message, data);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
27
101
|
//# sourceMappingURL=koa.js.map
|
package/logger.test.js
CHANGED
package/middleware/error.test.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { request, setTestApp } from 'axios-test-instance';
|
|
2
2
|
import Koa from 'koa';
|
|
3
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
4
|
import { errorMiddleware } from './error.js';
|
|
5
|
+
import { throwKoaError } from '../koa.js';
|
|
4
6
|
describe('errorMiddleware', () => {
|
|
5
7
|
let app;
|
|
6
8
|
beforeEach(async () => {
|
|
@@ -10,13 +12,7 @@ describe('errorMiddleware', () => {
|
|
|
10
12
|
});
|
|
11
13
|
it('should catch Koa errors', async () => {
|
|
12
14
|
app.use((ctx) => {
|
|
13
|
-
ctx
|
|
14
|
-
ctx.response.body = {
|
|
15
|
-
statusCode: 404,
|
|
16
|
-
error: 'Not Found',
|
|
17
|
-
message: 'Error not found',
|
|
18
|
-
};
|
|
19
|
-
ctx.throw();
|
|
15
|
+
throwKoaError(ctx, 404, 'Error not found');
|
|
20
16
|
});
|
|
21
17
|
const response = await request.get('/');
|
|
22
18
|
expect(response).toMatchObject({
|
|
@@ -44,13 +40,7 @@ describe('errorMiddleware', () => {
|
|
|
44
40
|
it('should set Koa headers correctly', async () => {
|
|
45
41
|
app.use((ctx) => {
|
|
46
42
|
ctx.set('www-authenticate', 'Basic realm="Access to test data", charset="UTF-8"');
|
|
47
|
-
ctx
|
|
48
|
-
ctx.response.body = {
|
|
49
|
-
error: 'Unauthorized',
|
|
50
|
-
message: 'Not authorized!',
|
|
51
|
-
statusCode: 401,
|
|
52
|
-
};
|
|
53
|
-
ctx.throw();
|
|
43
|
+
throwKoaError(ctx, 401, 'Not authorized!');
|
|
54
44
|
});
|
|
55
45
|
const response = await request.get('/');
|
|
56
46
|
expect(response).toMatchObject({
|
|
@@ -2,6 +2,7 @@ import { logger, loggerMiddleware } from '@appsemble/node-utils';
|
|
|
2
2
|
import { request, setTestApp } from 'axios-test-instance';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import Koa from 'koa';
|
|
5
|
+
import { beforeEach, expect, it, vi } from 'vitest';
|
|
5
6
|
class TestError extends Error {
|
|
6
7
|
constructor(message) {
|
|
7
8
|
super(message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/node-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "NodeJS utilities used by Appsemble internally.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"test": "vitest"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@appsemble/types": "0.
|
|
42
|
-
"@appsemble/utils": "0.
|
|
41
|
+
"@appsemble/types": "0.23.0",
|
|
42
|
+
"@appsemble/utils": "0.23.0",
|
|
43
43
|
"@formatjs/fast-memoize": "^2.0.0",
|
|
44
44
|
"@fortawesome/fontawesome-free": "^6.0.0",
|
|
45
45
|
"@koa/cors": "^4.0.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"bulma-checkradio": "2.1.1",
|
|
51
51
|
"bulma-slider": "2.0.4",
|
|
52
52
|
"bulma-switch": "^2.0.0",
|
|
53
|
-
"chalk": "^
|
|
53
|
+
"chalk": "^5.0.0",
|
|
54
54
|
"cli-highlight": "^2.0.0",
|
|
55
55
|
"csv-parse": "^5.0.0",
|
|
56
56
|
"date-fns": "^2.0.0",
|
package/readFileOrString.test.js
CHANGED
package/resource.js
CHANGED
|
@@ -4,6 +4,7 @@ import { addMilliseconds, isPast, parseISO } from 'date-fns';
|
|
|
4
4
|
import { ValidationError, Validator } from 'jsonschema';
|
|
5
5
|
import parseDuration from 'parse-duration';
|
|
6
6
|
import { preProcessCSV } from './csv.js';
|
|
7
|
+
import { throwKoaError } from './koa.js';
|
|
7
8
|
function stripResource({ $author, $created, $editor, $updated, ...data }) {
|
|
8
9
|
return data;
|
|
9
10
|
}
|
|
@@ -25,13 +26,7 @@ export function getResourceDefinition(app, resourceType, ctx, view) {
|
|
|
25
26
|
throw new Error('App not found');
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
ctx
|
|
29
|
-
ctx.response.body = {
|
|
30
|
-
statusCode: 404,
|
|
31
|
-
error: 'Not Found',
|
|
32
|
-
message: 'App not found',
|
|
33
|
-
};
|
|
34
|
-
ctx.throw();
|
|
29
|
+
throwKoaError(ctx, 404, 'App not found');
|
|
35
30
|
}
|
|
36
31
|
}
|
|
37
32
|
const definition = (_a = app.definition.resources) === null || _a === void 0 ? void 0 : _a[resourceType];
|
|
@@ -40,13 +35,7 @@ export function getResourceDefinition(app, resourceType, ctx, view) {
|
|
|
40
35
|
throw new Error(`App does not have resources called ${resourceType}`);
|
|
41
36
|
}
|
|
42
37
|
else {
|
|
43
|
-
ctx
|
|
44
|
-
ctx.response.body = {
|
|
45
|
-
statusCode: 404,
|
|
46
|
-
error: 'Not Found',
|
|
47
|
-
message: `App does not have resources called ${resourceType}`,
|
|
48
|
-
};
|
|
49
|
-
ctx.throw();
|
|
38
|
+
throwKoaError(ctx, 404, `App does not have resources called ${resourceType}`);
|
|
50
39
|
}
|
|
51
40
|
}
|
|
52
41
|
if (view && !definition.views[view]) {
|
|
@@ -54,13 +43,7 @@ export function getResourceDefinition(app, resourceType, ctx, view) {
|
|
|
54
43
|
throw new Error(`View ${view} does not exist for resource type ${resourceType}`);
|
|
55
44
|
}
|
|
56
45
|
else {
|
|
57
|
-
ctx
|
|
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();
|
|
46
|
+
throwKoaError(ctx, 404, `View ${view} does not exist for resource type ${resourceType}`);
|
|
64
47
|
}
|
|
65
48
|
}
|
|
66
49
|
return definition;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getRemapperContext, logger, version } from '@appsemble/node-utils';
|
|
1
|
+
import { assertKoaError, getRemapperContext, logger, throwKoaError, version, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, formatRequestAction, remap } from '@appsemble/utils';
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
import { get, pick } from 'lodash-es';
|
|
@@ -16,13 +16,7 @@ const supportedActions = ['email', 'notify', 'request'];
|
|
|
16
16
|
async function handleEmail(ctx, app, action, options) {
|
|
17
17
|
const { mailer, method, request: { body: data }, user, } = ctx;
|
|
18
18
|
if (method !== 'POST') {
|
|
19
|
-
ctx
|
|
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();
|
|
19
|
+
throwKoaError(ctx, 405, 'Method must be POST for email actions');
|
|
26
20
|
}
|
|
27
21
|
const { email, reloadUser } = options;
|
|
28
22
|
await reloadUser({ context: ctx });
|
|
@@ -31,13 +25,7 @@ async function handleEmail(ctx, app, action, options) {
|
|
|
31
25
|
}
|
|
32
26
|
catch (error) {
|
|
33
27
|
if (error instanceof EmailQuotaExceededError) {
|
|
34
|
-
ctx
|
|
35
|
-
ctx.response.body = {
|
|
36
|
-
statusCode: 429,
|
|
37
|
-
error: 'Too Many Requests',
|
|
38
|
-
message: error.message,
|
|
39
|
-
};
|
|
40
|
-
ctx.throw();
|
|
28
|
+
throwKoaError(ctx, 429, error.message);
|
|
41
29
|
}
|
|
42
30
|
ctx.throw(error);
|
|
43
31
|
}
|
|
@@ -73,13 +61,7 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
73
61
|
data = JSON.parse(query.data);
|
|
74
62
|
}
|
|
75
63
|
catch {
|
|
76
|
-
ctx
|
|
77
|
-
ctx.response.body = {
|
|
78
|
-
status: 400,
|
|
79
|
-
message: 'data should be a JSON object.',
|
|
80
|
-
error: 'Bad Request',
|
|
81
|
-
};
|
|
82
|
-
ctx.throw();
|
|
64
|
+
throwKoaError(ctx, 400, 'data should be a JSON object.');
|
|
83
65
|
}
|
|
84
66
|
}
|
|
85
67
|
const { applyAppServiceSecrets, reloadUser } = options;
|
|
@@ -94,13 +76,7 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
94
76
|
let axiosConfig = formatRequestAction(action, data, (remapper, d) => remap(remapper, d, remapperContext), remapperContext.context);
|
|
95
77
|
axiosConfig = await applyAppServiceSecrets({ axiosConfig, context: ctx, app });
|
|
96
78
|
if (axiosConfig.method.toUpperCase() !== method) {
|
|
97
|
-
ctx
|
|
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();
|
|
79
|
+
throwKoaError(ctx, 400, 'Method does not match the request action method');
|
|
104
80
|
}
|
|
105
81
|
if (useBody) {
|
|
106
82
|
axiosConfig.data = data;
|
|
@@ -116,13 +92,7 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
116
92
|
}
|
|
117
93
|
catch (err) {
|
|
118
94
|
logger.error(err);
|
|
119
|
-
ctx
|
|
120
|
-
ctx.response.body = {
|
|
121
|
-
statusCode: 502,
|
|
122
|
-
error: 'Bad Gateway',
|
|
123
|
-
message: 'Bad Gateway',
|
|
124
|
-
};
|
|
125
|
-
ctx.throw(502);
|
|
95
|
+
throwKoaError(ctx, 502, 'Bad Gateway');
|
|
126
96
|
}
|
|
127
97
|
ctx.status = response.status;
|
|
128
98
|
ctx.set(pick(response.headers, allowResponseHeaders));
|
|
@@ -132,15 +102,7 @@ function createProxyHandler(useBody, options) {
|
|
|
132
102
|
return async (ctx) => {
|
|
133
103
|
const { pathParams: { appId, path }, } = ctx;
|
|
134
104
|
const app = await options.getApp({ context: ctx, query: { where: { id: appId } } });
|
|
135
|
-
|
|
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();
|
|
143
|
-
}
|
|
105
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
144
106
|
const appAction = get(app.definition, path);
|
|
145
107
|
const action = supportedActions.find((act) => act === (appAction === null || appAction === void 0 ? void 0 : appAction.type));
|
|
146
108
|
switch (action) {
|
|
@@ -151,13 +113,7 @@ function createProxyHandler(useBody, options) {
|
|
|
151
113
|
case 'request':
|
|
152
114
|
return handleRequestProxy(ctx, app, appAction, useBody, options);
|
|
153
115
|
default:
|
|
154
|
-
ctx
|
|
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();
|
|
116
|
+
throwKoaError(ctx, 400, 'path does not point to a proxyable action');
|
|
161
117
|
}
|
|
162
118
|
};
|
|
163
119
|
}
|
|
@@ -1,35 +1,12 @@
|
|
|
1
|
+
import { assertKoaError } from '../../koa.js';
|
|
1
2
|
export function createGetAppMember({ getApp, getAppMembers }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const { pathParams: { appId, memberId }, } = ctx;
|
|
4
5
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ctx.response.body = {
|
|
8
|
-
statusCode: 404,
|
|
9
|
-
error: 'Not Found',
|
|
10
|
-
message: 'App not found',
|
|
11
|
-
};
|
|
12
|
-
ctx.throw();
|
|
13
|
-
}
|
|
14
|
-
if (app.definition.security === undefined) {
|
|
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();
|
|
22
|
-
}
|
|
6
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
7
|
+
assertKoaError(app.definition.security === undefined, ctx, 404, 'App does not have a security definition');
|
|
23
8
|
const appMembers = await getAppMembers({ context: ctx, app, memberId });
|
|
24
|
-
|
|
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();
|
|
32
|
-
}
|
|
9
|
+
assertKoaError(appMembers.length !== 1, ctx, 404, 'App member not found');
|
|
33
10
|
ctx.body = appMembers[0];
|
|
34
11
|
};
|
|
35
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAppsembleMessages, mergeMessages, } from '@appsemble/node-utils';
|
|
1
|
+
import { assertKoaError, getAppsembleMessages, mergeMessages, throwKoaError, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, extractAppMessages, normalizeBlockName, } from '@appsemble/utils';
|
|
3
3
|
import tags from 'language-tags';
|
|
4
4
|
export function createGetMessages({ getApp, getAppMessages, getBlockMessages, }) {
|
|
@@ -6,12 +6,7 @@ export function createGetMessages({ getApp, getAppMessages, getBlockMessages, })
|
|
|
6
6
|
var _a, _b, _c, _d, _e;
|
|
7
7
|
const { pathParams: { appId, language }, query: { merge, override = 'true' }, } = ctx;
|
|
8
8
|
if (!tags.check(language)) {
|
|
9
|
-
ctx
|
|
10
|
-
ctx.response.body = {
|
|
11
|
-
error: 'Bad Request',
|
|
12
|
-
message: '`Language “${language}” is invalid`',
|
|
13
|
-
};
|
|
14
|
-
ctx.throw();
|
|
9
|
+
throwKoaError(ctx, 400, '`Language “${language}” is invalid`');
|
|
15
10
|
}
|
|
16
11
|
const lang = language.toLowerCase();
|
|
17
12
|
const baseLanguage = tags(language)
|
|
@@ -19,18 +14,10 @@ export function createGetMessages({ getApp, getAppMessages, getBlockMessages, })
|
|
|
19
14
|
.find((sub) => sub.type() === 'language');
|
|
20
15
|
const baseLang = baseLanguage && String(baseLanguage).toLowerCase();
|
|
21
16
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
22
|
-
|
|
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();
|
|
30
|
-
}
|
|
31
|
-
const appMessages = await getAppMessages({ context: ctx, app, language });
|
|
17
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
32
18
|
const blockPrefixes = [];
|
|
33
19
|
const blockQuery = [];
|
|
20
|
+
const appMessages = await getAppMessages({ context: ctx, app, language });
|
|
34
21
|
const coreMessages = await getAppsembleMessages(lang, baseLang);
|
|
35
22
|
const messages = {
|
|
36
23
|
core: Object.fromEntries(Object.entries(coreMessages).filter(([key]) => key.startsWith('app') || key.startsWith('react-components') || key.startsWith('server'))),
|
|
@@ -45,13 +32,7 @@ export function createGetMessages({ getApp, getAppMessages, getBlockMessages, })
|
|
|
45
32
|
const blockMessages = await getBlockMessages({ context: ctx, blockQuery, baseLang, lang });
|
|
46
33
|
if ((!appMessages.length || (merge && !appMessages.some((m) => m.language === lang))) &&
|
|
47
34
|
lang !== (app.definition.defaultLanguage || defaultLocale)) {
|
|
48
|
-
ctx
|
|
49
|
-
ctx.response.body = {
|
|
50
|
-
statusCode: 404,
|
|
51
|
-
message: `Language “${language}” could not be found`,
|
|
52
|
-
error: 'Not Found',
|
|
53
|
-
};
|
|
54
|
-
ctx.throw();
|
|
35
|
+
throwKoaError(ctx, 404, `Language “${language}” could not be found`);
|
|
55
36
|
}
|
|
56
37
|
const baseLanguageMessages = override === 'true' && appMessages.find((m) => m.language === baseLang);
|
|
57
38
|
const languageMessages = override === 'true' && appMessages.find((m) => m.language === lang);
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
import { Permission } from '@appsemble/utils';
|
|
2
3
|
import { extension } from 'mime-types';
|
|
3
4
|
export function createGetAssets({ checkRole, getApp, getAppAssets }) {
|
|
4
5
|
return async (ctx) => {
|
|
5
6
|
const { pathParams: { appId }, queryParams: { $skip, $top }, } = ctx;
|
|
6
7
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
7
|
-
|
|
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();
|
|
15
|
-
}
|
|
8
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
16
9
|
const findOptions = {
|
|
17
10
|
limit: $top,
|
|
18
11
|
offset: $skip,
|
|
@@ -26,27 +19,11 @@ export function createGetAssetById({ getApp, getAppAssets }) {
|
|
|
26
19
|
return async (ctx) => {
|
|
27
20
|
const { pathParams: { appId, assetId }, } = ctx;
|
|
28
21
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
29
|
-
|
|
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();
|
|
37
|
-
}
|
|
22
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
38
23
|
const assets = await getAppAssets({ app, context: ctx });
|
|
39
24
|
// Pick asset id over asset name
|
|
40
25
|
const asset = assets.find((a) => a.id === assetId) || assets.find((a) => a.name === assetId);
|
|
41
|
-
|
|
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();
|
|
49
|
-
}
|
|
26
|
+
assertKoaError(!asset, ctx, 404, 'Asset not found');
|
|
50
27
|
if (assetId !== asset.id) {
|
|
51
28
|
// Redirect to asset using current asset ID
|
|
52
29
|
ctx.status = 302;
|
|
@@ -76,15 +53,7 @@ export function createCreateAsset({ createAppAsset, getApp }) {
|
|
|
76
53
|
return async (ctx) => {
|
|
77
54
|
const { pathParams: { appId }, request: { body: { file: { contents, filename, mime }, name, }, }, } = ctx;
|
|
78
55
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
79
|
-
|
|
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();
|
|
87
|
-
}
|
|
56
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
88
57
|
const asset = await createAppAsset({
|
|
89
58
|
app,
|
|
90
59
|
context: ctx,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getRemapperContext, getResourceDefinition, logger, processResourceBody, } from '@appsemble/node-utils';
|
|
1
|
+
import { assertKoaError, getRemapperContext, getResourceDefinition, logger, processResourceBody, throwKoaError, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, remap } from '@appsemble/utils';
|
|
3
3
|
function generateQuery(ctx, { parseQuery }) {
|
|
4
4
|
try {
|
|
@@ -6,23 +6,10 @@ function generateQuery(ctx, { parseQuery }) {
|
|
|
6
6
|
}
|
|
7
7
|
catch (error) {
|
|
8
8
|
if (error instanceof Error) {
|
|
9
|
-
ctx.
|
|
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();
|
|
9
|
+
throwKoaError(ctx, 400, 'Unable to process this query', { syntaxError: error.message });
|
|
17
10
|
}
|
|
18
11
|
logger.error(error);
|
|
19
|
-
ctx
|
|
20
|
-
ctx.response.body = {
|
|
21
|
-
statusCode: 500,
|
|
22
|
-
error: 'Internal Server Error',
|
|
23
|
-
message: 'Unable to process this query',
|
|
24
|
-
};
|
|
25
|
-
ctx.throw();
|
|
12
|
+
throwKoaError(ctx, 400, 'Unable to process this query');
|
|
26
13
|
}
|
|
27
14
|
}
|
|
28
15
|
export function createQueryResources(options) {
|
|
@@ -149,15 +136,7 @@ export function createGetResourceById(options) {
|
|
|
149
136
|
context: ctx,
|
|
150
137
|
findOptions,
|
|
151
138
|
});
|
|
152
|
-
|
|
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();
|
|
160
|
-
}
|
|
139
|
+
assertKoaError(!resource, ctx, 404, 'Resource not found');
|
|
161
140
|
if (view) {
|
|
162
141
|
const context = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, user && {
|
|
163
142
|
sub: user.id,
|
|
@@ -229,15 +208,7 @@ export function createUpdateResource(options) {
|
|
|
229
208
|
context: ctx,
|
|
230
209
|
findOptions,
|
|
231
210
|
});
|
|
232
|
-
|
|
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();
|
|
240
|
-
}
|
|
211
|
+
assertKoaError(!oldResource, ctx, 404, 'Resource not found');
|
|
241
212
|
const appAssets = await getAppAssets({ context: ctx, app });
|
|
242
213
|
const [processedBody, preparedAssets, deletedAssetIds] = processResourceBody(ctx, resourceDefinition, appAssets.filter((asset) => asset.resourceId === resourceId).map((asset) => asset.id), oldResource.expires);
|
|
243
214
|
const resources = Array.isArray(processedBody) ? processedBody : [processedBody];
|
|
@@ -285,15 +256,7 @@ export function createDeleteResource(options) {
|
|
|
285
256
|
context: ctx,
|
|
286
257
|
findOptions,
|
|
287
258
|
});
|
|
288
|
-
|
|
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();
|
|
296
|
-
}
|
|
259
|
+
assertKoaError(!resource, ctx, 404, 'Resource not found');
|
|
297
260
|
await deleteAppResource({
|
|
298
261
|
app,
|
|
299
262
|
context: ctx,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defaultLocale, remap } from '@appsemble/utils';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import { createCountResources, createCreateResource, createGetResourceById, createQueryResources, } from './resources.js';
|
|
3
4
|
import { getRemapperContext } from '../../app.js';
|
|
4
5
|
import { getResourceDefinition } from '../../resource.js';
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
+
import { assertKoaError } from '../../koa.js';
|
|
1
2
|
export function createGetTeams({ getApp, getAppTeams }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const { pathParams: { appId }, user, } = ctx;
|
|
4
5
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
5
|
-
|
|
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();
|
|
13
|
-
}
|
|
6
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
14
7
|
ctx.body = await getAppTeams({ context: ctx, app, user });
|
|
15
8
|
};
|
|
16
9
|
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
export function createBlockAssetHandler({ getBlockAsset }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const { params: { filename, name, version }, } = ctx;
|
|
4
5
|
const blockAsset = await getBlockAsset({ filename, name, version, context: ctx });
|
|
5
|
-
|
|
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();
|
|
13
|
-
}
|
|
6
|
+
assertKoaError(!blockAsset, ctx, 404, 'Block asset not found');
|
|
14
7
|
ctx.body = blockAsset.content;
|
|
15
8
|
ctx.type = blockAsset.mime;
|
|
16
9
|
};
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
export function createBlockCssHandler({ getApp, getAppBlockStyles }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const { params: { name }, } = ctx;
|
|
4
5
|
const app = await getApp({ context: ctx });
|
|
5
|
-
|
|
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();
|
|
13
|
-
}
|
|
6
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
14
7
|
const appBlockStyles = await getAppBlockStyles({ app, name, context: ctx });
|
|
15
8
|
const [style] = appBlockStyles;
|
|
16
9
|
ctx.body = style ? style.style : '';
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
export function createCssHandler(type, { getAppStyles }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const app = await getAppStyles({ context: ctx, query: { attributes: [type], raw: true } });
|
|
4
|
-
|
|
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();
|
|
12
|
-
}
|
|
5
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
13
6
|
ctx.body = app[type];
|
|
14
7
|
ctx.type = 'css';
|
|
15
8
|
};
|
|
@@ -10,7 +10,7 @@ export function createIconHandler({ getApp, getAppIcon, getDbUpdated }) {
|
|
|
10
10
|
const dbUpdated = app ? await getDbUpdated({ app, maskable, context: ctx }) : false;
|
|
11
11
|
const appIcon = app ? await getAppIcon({ app, context: ctx }) : null;
|
|
12
12
|
await serveIcon(ctx, {
|
|
13
|
-
background: maskable ? app.iconBackground || '#ffffff' : undefined,
|
|
13
|
+
background: maskable ? (app === null || app === void 0 ? void 0 : app.iconBackground) || '#ffffff' : undefined,
|
|
14
14
|
cache: dbUpdated ? isEqual(parseISO(updated), dbUpdated) : false,
|
|
15
15
|
fallback: 'mobile-alt-solid.png',
|
|
16
16
|
height: size && Number.parseInt(size),
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
import { baseTheme, normalize } from '@appsemble/utils';
|
|
2
3
|
import { extension } from 'mime-types';
|
|
3
4
|
const iconSizes = [48, 144, 192, 512];
|
|
4
5
|
export function createManifestHandler({ getApp, getAppScreenshots }) {
|
|
5
6
|
return async (ctx) => {
|
|
6
7
|
const app = await getApp({ context: ctx });
|
|
7
|
-
|
|
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();
|
|
15
|
-
}
|
|
8
|
+
assertKoaError(!app, ctx, 404, 'Block asset not found');
|
|
16
9
|
const { defaultPage, description, name, theme = baseTheme } = app.definition;
|
|
17
10
|
const { themeColor = '#ffffff', splashColor = themeColor } = theme;
|
|
18
11
|
const appScreenshots = await getAppScreenshots({ app, context: ctx });
|
|
@@ -1,27 +1,12 @@
|
|
|
1
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
1
2
|
export function createScreenshotHandler({ getApp, getAppScreenshots }) {
|
|
2
3
|
return async (ctx) => {
|
|
3
4
|
const { id } = ctx.params;
|
|
4
5
|
const app = await getApp({ context: ctx });
|
|
5
|
-
|
|
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();
|
|
13
|
-
}
|
|
6
|
+
assertKoaError(!app, ctx, 404, 'App not found');
|
|
14
7
|
const appScreenshots = await getAppScreenshots({ app, context: ctx });
|
|
15
8
|
const appScreenshot = appScreenshots.find((screenshot) => screenshot.id === Number.parseInt(id));
|
|
16
|
-
|
|
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();
|
|
24
|
-
}
|
|
9
|
+
assertKoaError(!appScreenshot, ctx, 404, 'Screenshot not found');
|
|
25
10
|
const { mime, screenshot } = appScreenshot;
|
|
26
11
|
ctx.type = mime;
|
|
27
12
|
ctx.body = screenshot;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { assertKoaError } from '@appsemble/node-utils';
|
|
2
3
|
import { getAppBlocks } from '@appsemble/utils';
|
|
3
4
|
export function createServiceWorkerHandler({ getApp, getBlocksAssetsPaths }) {
|
|
4
5
|
return async (ctx) => {
|
|
@@ -8,15 +9,7 @@ export function createServiceWorkerHandler({ getApp, getBlocksAssetsPaths }) {
|
|
|
8
9
|
? readFile(new URL('../../../../../dist/app/service-worker.js', import.meta.url), 'utf8')
|
|
9
10
|
: ctx.fs.promises.readFile(filename, 'utf8'));
|
|
10
11
|
const app = await getApp({ context: ctx });
|
|
11
|
-
|
|
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
|
+
assertKoaError(!app, ctx, 404, 'App does not exist.');
|
|
20
13
|
const identifiableBlocks = getAppBlocks(app.definition);
|
|
21
14
|
const blocksAssetsPaths = await getBlocksAssetsPaths({ identifiableBlocks, context: ctx });
|
|
22
15
|
ctx.body = `const blockAssets=${JSON.stringify(blocksAssetsPaths)};${serviceWorker}`;
|
package/server/types.d.ts
CHANGED
|
@@ -76,6 +76,8 @@ declare module 'koas-parameters' {
|
|
|
76
76
|
token: string;
|
|
77
77
|
appServiceId: number;
|
|
78
78
|
userId: string;
|
|
79
|
+
trainingBlockId: number;
|
|
80
|
+
trainingId: number;
|
|
79
81
|
}
|
|
80
82
|
interface QueryParams {
|
|
81
83
|
domains: string[];
|
|
@@ -89,6 +91,7 @@ declare module 'koas-parameters' {
|
|
|
89
91
|
count: number;
|
|
90
92
|
startIndex: number;
|
|
91
93
|
view: string;
|
|
94
|
+
resources: boolean;
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
export type Operator = 'and' | 'not' | 'or';
|
|
@@ -193,8 +196,8 @@ export interface GetCspParams {
|
|
|
193
196
|
settingsHash: string;
|
|
194
197
|
nonce: string;
|
|
195
198
|
}
|
|
196
|
-
export type
|
|
197
|
-
export type Action =
|
|
199
|
+
export type ResourceAction = 'create' | 'delete' | 'update';
|
|
200
|
+
export type Action = ResourceAction | 'count' | 'get' | 'patch' | 'query';
|
|
198
201
|
export interface VerifyResourceActionPermissionParams {
|
|
199
202
|
context: ParameterizedContext<DefaultState, DefaultContextInterface, any>;
|
|
200
203
|
app: App;
|
|
@@ -238,7 +241,7 @@ export interface CreateAppResourcesWithAssetsParams extends GetAppSubEntityParam
|
|
|
238
241
|
resources: Record<string, unknown>[];
|
|
239
242
|
preparedAssets: PreparedAsset[];
|
|
240
243
|
resourceType: string;
|
|
241
|
-
action:
|
|
244
|
+
action: ResourceAction;
|
|
242
245
|
options: Options;
|
|
243
246
|
}
|
|
244
247
|
export interface UpdateAppResourceParams extends GetAppSubEntityParams {
|
|
@@ -249,13 +252,13 @@ export interface UpdateAppResourceParams extends GetAppSubEntityParams {
|
|
|
249
252
|
deletedAssetIds: string[];
|
|
250
253
|
type: string;
|
|
251
254
|
options: Options;
|
|
252
|
-
action:
|
|
255
|
+
action: ResourceAction;
|
|
253
256
|
}
|
|
254
257
|
export interface DeleteAppResourceParams extends GetAppSubEntityParams {
|
|
255
258
|
id: number | string;
|
|
256
259
|
type: string;
|
|
257
260
|
whereOptions?: WhereOptions;
|
|
258
|
-
action:
|
|
261
|
+
action: ResourceAction;
|
|
259
262
|
options: Options;
|
|
260
263
|
}
|
|
261
264
|
export interface CreateAppAssetParams extends GetAppSubEntityParams {
|
package/tinyRouter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { has } from '@appsemble/utils';
|
|
2
|
+
import { throwKoaError } from './koa.js';
|
|
2
3
|
/**
|
|
3
4
|
* A tiny dynamic router middleware for GET requests.
|
|
4
5
|
*
|
|
@@ -22,13 +23,7 @@ export function tinyRouter(routes) {
|
|
|
22
23
|
let m = method.toLowerCase();
|
|
23
24
|
if (!has(result, m)) {
|
|
24
25
|
if (!has(result, 'any')) {
|
|
25
|
-
ctx
|
|
26
|
-
ctx.response.body = {
|
|
27
|
-
statusCode: 405,
|
|
28
|
-
error: 'Method Not Allowed',
|
|
29
|
-
message: 'Method not allowed',
|
|
30
|
-
};
|
|
31
|
-
ctx.throw();
|
|
26
|
+
throwKoaError(ctx, 405, 'Method not allowed');
|
|
32
27
|
}
|
|
33
28
|
m = 'any';
|
|
34
29
|
}
|
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 { beforeEach, expect, it, vi } from 'vitest';
|
|
4
5
|
import { errorMiddleware } from './index.js';
|
|
5
6
|
import { tinyRouter } from './tinyRouter.js';
|
|
6
7
|
let app;
|