@appsemble/node-utils 0.30.14-test.6 → 0.32.1-test.14
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/README.md +3 -3
- package/app.js +8 -0
- package/assets.d.ts +16 -0
- package/assets.js +65 -0
- package/authentication.d.ts +1 -1
- package/container/helpers.d.ts +1 -1
- package/container/helpers.js +1 -0
- package/container/logs.js +8 -5
- package/container/operations.js +22 -14
- package/container/scale.js +3 -2
- package/container/specs.js +1 -1
- package/fs.js +4 -0
- package/getAppsembleMessages.js +2 -0
- package/getMessagesUtil.js +5 -4
- package/getValidTrainings.d.ts +8 -0
- package/getValidTrainings.js +47 -0
- package/icon.js +5 -0
- package/index.d.ts +5 -1
- package/index.js +5 -1
- package/interceptors.js +1 -1
- package/koa.d.ts +13 -1
- package/koa.js +17 -1
- package/logger.js +5 -3
- package/middleware/loggerMiddleware.js +1 -0
- package/odata.js +2 -1
- package/package.json +22 -14
- package/parsers.d.ts +1 -0
- package/parsers.js +130 -0
- package/render.js +1 -1
- package/resource.d.ts +18 -3
- package/resource.js +50 -10
- package/s3.d.ts +18 -0
- package/s3.js +131 -0
- package/server/controllers/common/apps/assets/createGetAppAssetByIdController.js +4 -3
- package/server/controllers/common/apps/groups/createGetAppGroupsController.js +2 -2
- package/server/controllers/common/apps/messages/createGetAppMessagesController.js +2 -0
- package/server/controllers/common/apps/resources/createCreateAppResourceController.js +16 -2
- package/server/controllers/common/apps/resources/createDeleteAppResourceController.js +2 -2
- package/server/controllers/common/apps/resources/createGetAppResourceByIdController.js +3 -3
- package/server/controllers/common/apps/resources/createQueryAppResourcesController.js +1 -1
- package/server/controllers/common/apps/resources/createUpdateAppResourceController.js +6 -3
- package/server/controllers/common/apps/variables/createGetAppVariablesController.js +2 -2
- package/server/controllers/main/apps/assets/createCreateAppAssetController.js +4 -4
- package/server/controllers/main/apps/resources/createDeleteAppSeedResourcesController.js +2 -2
- package/server/routes/appRouter/blockAssetHandler.js +5 -3
- package/server/routes/appRouter/blockCssHandler.js +5 -3
- package/server/routes/appRouter/bulmaHandler.js +1 -0
- package/server/routes/appRouter/cssHandler.js +2 -2
- package/server/routes/appRouter/iconHandler.js +4 -1
- package/server/routes/appRouter/indexHandler.js +3 -1
- package/server/routes/appRouter/manifestHandler.js +2 -2
- package/server/routes/appRouter/readmeHandler.js +4 -3
- package/server/routes/appRouter/robotsHandler.js +2 -2
- package/server/routes/appRouter/screenshotHandler.js +4 -3
- package/server/routes/appRouter/serviceWorkerHandler.js +2 -2
- package/server/types.d.ts +22 -7
- package/server/types.js +7 -1
- package/server/utils/actions.js +16 -10
- package/server/utils/resources.js +4 -1
- package/tinyRouter.js +3 -3
- package/uploads.d.ts +2 -0
- package/uploads.js +27 -0
- package/versions.d.ts +3 -0
- package/versions.js +7 -0
- package/AppsembleError.test.d.ts +0 -1
- package/AppsembleError.test.js +0 -7
- package/basicAuth.test.d.ts +0 -1
- package/basicAuth.test.js +0 -7
- package/formData.test.d.ts +0 -1
- package/formData.test.js +0 -93
- package/fs.test.d.ts +0 -1
- package/fs.test.js +0 -75
- package/handleError.test.d.ts +0 -1
- package/handleError.test.js +0 -33
- package/interceptors.test.d.ts +0 -1
- package/interceptors.test.js +0 -83
- package/logger.test.d.ts +0 -1
- package/logger.test.js +0 -37
- package/middleware/error.test.d.ts +0 -1
- package/middleware/error.test.js +0 -59
- package/middleware/loggerMiddleware.test.d.ts +0 -1
- package/middleware/loggerMiddleware.test.js +0 -120
- package/readFileOrString.test.d.ts +0 -1
- package/readFileOrString.test.js +0 -12
- package/server/controllers/common/apps/resources/resources.test.d.ts +0 -1
- package/server/controllers/common/apps/resources/resources.test.js +0 -580
- package/tinyRouter.test.d.ts +0 -1
- package/tinyRouter.test.js +0 -83
- package/versions.cjs +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
-
import { getResourceDefinition, processResourceBody } from '../../../../../index.js';
|
|
3
|
+
import { getResourceDefinition, processResourceBody, uploadAssets, } from '../../../../../index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Create a controller for resource creation.
|
|
6
6
|
*
|
|
@@ -26,12 +26,16 @@ export function createCreateAppResourceController(options) {
|
|
|
26
26
|
});
|
|
27
27
|
const resourceDefinition = getResourceDefinition(app.definition, resourceType, ctx);
|
|
28
28
|
const appAssets = await getAppAssets({ app, context: ctx });
|
|
29
|
-
const [processedBody, preparedAssets] = processResourceBody(ctx, resourceDefinition, undefined, undefined,
|
|
29
|
+
const [processedBody, preparedAssets] = processResourceBody(ctx, resourceDefinition, undefined, undefined,
|
|
30
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
31
|
+
// (strictNullChecks)
|
|
32
|
+
appAssets.map((appAsset) => ({ id: appAsset.id, name: appAsset.name })));
|
|
30
33
|
if (Array.isArray(processedBody) && !processedBody.length) {
|
|
31
34
|
ctx.body = [];
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
const resources = Array.isArray(processedBody) ? processedBody : [processedBody];
|
|
38
|
+
const assetsToUpload = [];
|
|
35
39
|
if (!(ctx.client && 'app' in ctx.client) && query?.seed === 'true') {
|
|
36
40
|
const preparedSeedAssets = structuredClone(preparedAssets);
|
|
37
41
|
const preparedSeedResources = resources.map((resource) => {
|
|
@@ -49,6 +53,7 @@ export function createCreateAppResourceController(options) {
|
|
|
49
53
|
});
|
|
50
54
|
for (const preparedSeedAsset of preparedSeedAssets) {
|
|
51
55
|
const index = resources.findIndex(({ $clonable: clonable, $ephemeral: ephemeral, $seed: unused, ...cleanResource }) => {
|
|
56
|
+
// @ts-expect-error Messed up
|
|
52
57
|
const { $clonable, $ephemeral, $seed, ...cleanAssetResource } = preparedSeedAsset.resource;
|
|
53
58
|
return isDeepStrictEqual(cleanResource, cleanAssetResource);
|
|
54
59
|
});
|
|
@@ -67,11 +72,16 @@ export function createCreateAppResourceController(options) {
|
|
|
67
72
|
preparedAssets: preparedSeedAssets,
|
|
68
73
|
resourceType,
|
|
69
74
|
options,
|
|
75
|
+
positioning: resourceDefinition.positioning,
|
|
70
76
|
});
|
|
71
77
|
if (!app.demoMode) {
|
|
72
78
|
ctx.body = Array.isArray(processedBody) ? createdSeedResources : createdSeedResources[0];
|
|
79
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
80
|
+
// (strictNullChecks)
|
|
81
|
+
await uploadAssets(app.id, preparedSeedAssets);
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
84
|
+
assetsToUpload.push(...preparedSeedAssets);
|
|
75
85
|
}
|
|
76
86
|
const createdResources = await createAppResourcesWithAssets({
|
|
77
87
|
app,
|
|
@@ -87,6 +97,10 @@ export function createCreateAppResourceController(options) {
|
|
|
87
97
|
resourceType,
|
|
88
98
|
options,
|
|
89
99
|
});
|
|
100
|
+
assetsToUpload.push(...preparedAssets);
|
|
101
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
102
|
+
// (strictNullChecks)
|
|
103
|
+
await uploadAssets(app.id, assetsToUpload);
|
|
90
104
|
ctx.body = Array.isArray(processedBody) ? createdResources : createdResources[0];
|
|
91
105
|
};
|
|
92
106
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createDeleteAppResourceController(options) {
|
|
3
3
|
return async (ctx) => {
|
|
4
4
|
const { pathParams: { appId, resourceId, resourceType }, queryParams: { selectedGroupId }, user: authSubject, } = ctx;
|
|
@@ -21,7 +21,7 @@ export function createDeleteAppResourceController(options) {
|
|
|
21
21
|
context: ctx,
|
|
22
22
|
findOptions,
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
assertKoaCondition(resource != null, ctx, 404, 'Resource not found');
|
|
25
25
|
await checkAppPermissions({
|
|
26
26
|
context: ctx,
|
|
27
27
|
permissions: [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition, getRemapperContext, getResourceDefinition, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, remap } from '@appsemble/utils';
|
|
3
3
|
export function createGetAppResourceByIdController(options) {
|
|
4
4
|
return async (ctx) => {
|
|
@@ -22,7 +22,7 @@ export function createGetAppResourceByIdController(options) {
|
|
|
22
22
|
context: ctx,
|
|
23
23
|
findOptions,
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
assertKoaCondition(resource != null, ctx, 404, 'Resource not found');
|
|
26
26
|
await checkAppPermissions({
|
|
27
27
|
context: ctx,
|
|
28
28
|
permissions: [
|
|
@@ -38,7 +38,7 @@ export function createGetAppResourceByIdController(options) {
|
|
|
38
38
|
if (view) {
|
|
39
39
|
const context = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, options, ctx);
|
|
40
40
|
const resourceDefinition = getResourceDefinition(app.definition, resourceType, ctx, view);
|
|
41
|
-
ctx.body = remap(resourceDefinition.views[view].remap, resource, context);
|
|
41
|
+
ctx.body = remap(resourceDefinition.views?.[view].remap ?? null, resource, context);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
ctx.body = resource;
|
|
@@ -48,7 +48,7 @@ export function createQueryAppResourcesController(options) {
|
|
|
48
48
|
});
|
|
49
49
|
if (view) {
|
|
50
50
|
const context = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, options, ctx);
|
|
51
|
-
ctx.body = resources.map((resource) => remap(resourceDefinition.views[view].remap, resource, context));
|
|
51
|
+
ctx.body = resources.map((resource) => remap(resourceDefinition.views?.[view].remap ?? null, resource, context));
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
ctx.body = resources;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition, getResourceDefinition, processResourceBody, } from '@appsemble/node-utils';
|
|
2
2
|
export function createUpdateAppResourceController(options) {
|
|
3
3
|
return async (ctx) => {
|
|
4
4
|
const { pathParams: { appId, resourceId, resourceType }, queryParams: { selectedGroupId }, user: authSubject, } = ctx;
|
|
@@ -21,7 +21,7 @@ export function createUpdateAppResourceController(options) {
|
|
|
21
21
|
context: ctx,
|
|
22
22
|
findOptions,
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
assertKoaCondition(oldResource != null, ctx, 404, 'Resource not found');
|
|
25
25
|
await checkAppPermissions({
|
|
26
26
|
context: ctx,
|
|
27
27
|
permissions: [
|
|
@@ -34,7 +34,10 @@ export function createUpdateAppResourceController(options) {
|
|
|
34
34
|
});
|
|
35
35
|
const appAssets = await getAppAssets({ context: ctx, app });
|
|
36
36
|
const resourceDefinition = getResourceDefinition(app.definition, resourceType, ctx);
|
|
37
|
-
const [processedBody, preparedAssets, deletedAssetIds] = processResourceBody(ctx, resourceDefinition, appAssets.filter((asset) => asset.resourceId === resourceId).map((asset) => asset.id), oldResource.expires,
|
|
37
|
+
const [processedBody, preparedAssets, deletedAssetIds] = processResourceBody(ctx, resourceDefinition, appAssets.filter((asset) => asset.resourceId === resourceId).map((asset) => asset.id), oldResource.expires,
|
|
38
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
39
|
+
// (strictNullChecks)
|
|
40
|
+
appAssets.map((asset) => ({ id: asset.id, name: asset.name })));
|
|
38
41
|
const resources = Array.isArray(processedBody) ? processedBody : [processedBody];
|
|
39
42
|
ctx.body = await updateAppResource({
|
|
40
43
|
app,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '../../../../../koa.js';
|
|
2
2
|
export function createGetAppVariablesController({ getApp, getAppVariables }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
4
|
const { pathParams: { appId }, } = ctx;
|
|
5
5
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
6
|
-
|
|
6
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
7
7
|
ctx.body = await getAppVariables({
|
|
8
8
|
context: ctx,
|
|
9
9
|
app,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '../../../../../koa.js';
|
|
2
2
|
export function createCreateAppAssetController({ createAppAsset, getApp }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
|
-
const { pathParams: { appId }, request: { body: { file: {
|
|
4
|
+
const { pathParams: { appId }, request: { body: { file: { filename, mime, path }, name, }, }, } = ctx;
|
|
5
5
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
6
|
-
|
|
6
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
7
7
|
const asset = await createAppAsset({
|
|
8
8
|
app,
|
|
9
9
|
context: ctx,
|
|
10
|
-
payload: { filename, mime, name,
|
|
10
|
+
payload: { filename, mime, name, path },
|
|
11
11
|
});
|
|
12
12
|
ctx.status = 201;
|
|
13
13
|
ctx.body = { id: asset.id, mime, filename, name };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '../../../../../koa.js';
|
|
2
2
|
import { deleteResourcesRecursively } from '../../../../utils/resources.js';
|
|
3
3
|
export function createDeleteAppSeedResourcesController(options) {
|
|
4
4
|
return async (ctx) => {
|
|
5
5
|
const { pathParams: { appId }, } = ctx;
|
|
6
6
|
const { getApp } = options;
|
|
7
7
|
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
8
|
-
|
|
8
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
9
9
|
for (const resourceType of Object.keys(app.definition.resources ?? {})) {
|
|
10
10
|
await deleteResourcesRecursively(resourceType, app, options, ctx);
|
|
11
11
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createBlockAssetHandler({ getBlockAsset }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
// @ts-expect-error Messed up
|
|
6
|
+
params: { filename, name, version }, } = ctx;
|
|
5
7
|
const blockAsset = await getBlockAsset({ filename, name, version, context: ctx });
|
|
6
|
-
|
|
8
|
+
assertKoaCondition(blockAsset != null, ctx, 404, 'Block asset not found');
|
|
7
9
|
ctx.body = blockAsset.content;
|
|
8
10
|
ctx.type = blockAsset.mime;
|
|
9
11
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createBlockCssHandler({ getApp, getAppBlockStyles }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
// @ts-expect-error Messed up
|
|
6
|
+
params: { name }, } = ctx;
|
|
5
7
|
const app = await getApp({ context: ctx });
|
|
6
|
-
|
|
8
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
7
9
|
const appBlockStyles = await getAppBlockStyles({ app, name, context: ctx });
|
|
8
10
|
const [style] = appBlockStyles;
|
|
9
11
|
ctx.body = style ? style.style : '';
|
|
@@ -85,6 +85,7 @@ export function createBulmaHandler({ createTheme, getTheme }) {
|
|
|
85
85
|
themeColor: (getQueryParameter(query.themeColor) || baseTheme.themeColor).toLowerCase(),
|
|
86
86
|
splashColor: (getQueryParameter(query.splashColor) || baseTheme.splashColor).toLowerCase(),
|
|
87
87
|
fontFamily: getQueryParameter(query.fontFamily) || baseTheme.font.family,
|
|
88
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
88
89
|
fontSource: getQueryParameter(query.fontSource) || baseTheme.font.source,
|
|
89
90
|
};
|
|
90
91
|
const result = await getTheme({ theme });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createCssHandler(type, { getAppStyles }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
4
|
const app = await getAppStyles({ context: ctx, query: { attributes: [type], raw: true } });
|
|
5
|
-
|
|
5
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
6
6
|
ctx.body = app[type];
|
|
7
7
|
ctx.type = 'css';
|
|
8
8
|
};
|
|
@@ -2,7 +2,9 @@ import { serveIcon } from '@appsemble/node-utils';
|
|
|
2
2
|
import { isEqual, parseISO } from 'date-fns';
|
|
3
3
|
export function createIconHandler({ getApp, getAppIcon, getDbUpdated }) {
|
|
4
4
|
return async (ctx) => {
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
// @ts-expect-error Messed up
|
|
7
|
+
params: { size = 128 }, query: { maskable = false, updated }, } = ctx;
|
|
6
8
|
const app = await getApp({
|
|
7
9
|
context: ctx,
|
|
8
10
|
query: { where: { ...(updated ? { updated } : {}) } },
|
|
@@ -14,6 +16,7 @@ export function createIconHandler({ getApp, getAppIcon, getDbUpdated }) {
|
|
|
14
16
|
cache: dbUpdated ? isEqual(parseISO(updated), dbUpdated) : false,
|
|
15
17
|
fallback: 'mobile-alt-solid.png',
|
|
16
18
|
height: size && Number.parseInt(size),
|
|
19
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
17
20
|
icon: appIcon,
|
|
18
21
|
maskable: Boolean(maskable),
|
|
19
22
|
width: size && Number.parseInt(size),
|
|
@@ -2,7 +2,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
2
2
|
import { createThemeURL, defaultLocale, getAppBlocks, mergeThemes } from '@appsemble/utils';
|
|
3
3
|
import { organizationBlocklist } from '../../../organizationBlocklist.js';
|
|
4
4
|
import { makeCSP, render } from '../../../render.js';
|
|
5
|
-
import { bulmaVersion, faVersion } from '../../../versions.
|
|
5
|
+
import { bulmaVersion, faVersion } from '../../../versions.js';
|
|
6
6
|
export const bulmaURL = `/bulma/${bulmaVersion}/bulma.min.css`;
|
|
7
7
|
export const faURL = `/fa/${faVersion}/css/all.min.css`;
|
|
8
8
|
export function createIndexHandler({ createSettings, getApp, getAppDetails, getAppMessages, getAppUrl, getCsp, getHost, }) {
|
|
@@ -56,6 +56,8 @@ export function createIndexHandler({ createSettings, getApp, getAppDetails, getA
|
|
|
56
56
|
host,
|
|
57
57
|
locale: defaultLanguage,
|
|
58
58
|
locales: languages.filter((lang) => lang !== defaultLanguage),
|
|
59
|
+
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
60
|
+
// (strictNullChecks)
|
|
59
61
|
bulmaURL: createThemeURL(mergeThemes(app.definition.theme)),
|
|
60
62
|
faURL,
|
|
61
63
|
nonce,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
import { baseTheme, normalize } from '@appsemble/utils';
|
|
3
3
|
import { extension } from 'mime-types';
|
|
4
4
|
const iconSizes = [48, 144, 192, 512];
|
|
5
5
|
export function createManifestHandler({ getApp, getAppScreenshots }) {
|
|
6
6
|
return async (ctx) => {
|
|
7
7
|
const app = await getApp({ context: ctx });
|
|
8
|
-
|
|
8
|
+
assertKoaCondition(app != null, ctx, 404, 'Block asset not found');
|
|
9
9
|
const { defaultPage, description, name, theme = baseTheme } = app.definition;
|
|
10
10
|
const { themeColor = '#ffffff', splashColor = themeColor } = theme;
|
|
11
11
|
const appScreenshots = await getAppScreenshots({ app, context: ctx });
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createReadmeHandler({ getApp, getAppReadmes }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
|
+
// @ts-expect-error Messed up
|
|
4
5
|
const { id } = ctx.params;
|
|
5
6
|
const app = await getApp({ context: ctx });
|
|
6
|
-
|
|
7
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
7
8
|
const appReadmes = await getAppReadmes({ app, context: ctx });
|
|
8
9
|
const appReadme = appReadmes.find((readme) => readme.id === Number.parseInt(id));
|
|
9
|
-
|
|
10
|
+
assertKoaCondition(appReadme != null, ctx, 404, 'Readme not found');
|
|
10
11
|
const { file } = appReadme;
|
|
11
12
|
ctx.type = 'text/markdown';
|
|
12
13
|
ctx.body = file;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition, logger } from '@appsemble/node-utils';
|
|
2
2
|
export function createRobotsHandler({ getApp }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
4
|
const app = await getApp({ context: ctx });
|
|
5
|
-
|
|
5
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
6
6
|
switch (app.visibility) {
|
|
7
7
|
case 'public':
|
|
8
8
|
ctx.body = 'User-agent: *\nAllow: *\n';
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
2
2
|
export function createScreenshotHandler({ getApp, getAppScreenshots }) {
|
|
3
3
|
return async (ctx) => {
|
|
4
|
+
// @ts-expect-error Messed up
|
|
4
5
|
const { id } = ctx.params;
|
|
5
6
|
const app = await getApp({ context: ctx });
|
|
6
|
-
|
|
7
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
7
8
|
const appScreenshots = await getAppScreenshots({ app, context: ctx });
|
|
8
9
|
const appScreenshot = appScreenshots.find((screenshot) => screenshot.id === Number.parseInt(id));
|
|
9
|
-
|
|
10
|
+
assertKoaCondition(appScreenshot != null, ctx, 404, 'Screenshot not found');
|
|
10
11
|
const { mime, screenshot } = appScreenshot;
|
|
11
12
|
ctx.type = mime;
|
|
12
13
|
ctx.body = screenshot;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { assertKoaCondition } from '@appsemble/node-utils';
|
|
3
3
|
import { getAppBlocks } from '@appsemble/utils';
|
|
4
4
|
export function createServiceWorkerHandler({ getApp, getBlocksAssetsPaths }) {
|
|
5
5
|
return async (ctx) => {
|
|
@@ -9,7 +9,7 @@ export function createServiceWorkerHandler({ getApp, getBlocksAssetsPaths }) {
|
|
|
9
9
|
? readFile(new URL('../../../../../dist/app/service-worker.js', import.meta.url), 'utf8')
|
|
10
10
|
: ctx.fs.promises.readFile(filename, 'utf8'));
|
|
11
11
|
const app = await getApp({ context: ctx });
|
|
12
|
-
|
|
12
|
+
assertKoaCondition(app != null, ctx, 404, 'App does not exist.');
|
|
13
13
|
const identifiableBlocks = getAppBlocks(app.definition);
|
|
14
14
|
const blocksAssetsPaths = await getBlocksAssetsPaths({ identifiableBlocks, context: ctx });
|
|
15
15
|
ctx.body = `const blockAssets=${JSON.stringify(blocksAssetsPaths)};${serviceWorker}`;
|
package/server/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Readable } from 'node:stream';
|
|
1
2
|
import { type App, type AppConfigEntry, type AppMemberInfo, type AppMessages, type Asset, type BlockDefinition, type BlockManifest, type ControllerDefinition, type CustomAppPermission, type EmailActionDefinition, type Group, type OrganizationPermission, type Resource, type ResourceDefinition, type Theme as ThemeType } from '@appsemble/types';
|
|
2
3
|
import { type IdentifiableBlock } from '@appsemble/utils';
|
|
3
4
|
import { type RawAxiosRequestConfig } from 'axios';
|
|
@@ -36,6 +37,7 @@ declare module 'koas-security' {
|
|
|
36
37
|
scope: string;
|
|
37
38
|
};
|
|
38
39
|
studio: {};
|
|
40
|
+
webhook: {};
|
|
39
41
|
}
|
|
40
42
|
interface Users {
|
|
41
43
|
app: AuthSubject;
|
|
@@ -43,6 +45,7 @@ declare module 'koas-security' {
|
|
|
43
45
|
cli: AuthSubject;
|
|
44
46
|
scim: AuthSubject;
|
|
45
47
|
studio: AuthSubject;
|
|
48
|
+
webhook: AuthSubject;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
declare module 'koas-parameters' {
|
|
@@ -77,9 +80,10 @@ declare module 'koas-parameters' {
|
|
|
77
80
|
appVariableId: number;
|
|
78
81
|
userId: string;
|
|
79
82
|
memberEmail: string;
|
|
80
|
-
trainingBlockId: number;
|
|
81
83
|
trainingId: number;
|
|
82
84
|
container: string;
|
|
85
|
+
webhookName: string;
|
|
86
|
+
webhookSecretId: string;
|
|
83
87
|
}
|
|
84
88
|
interface QueryParams {
|
|
85
89
|
domains: string[];
|
|
@@ -256,7 +260,7 @@ export interface GetAppResourcesParams extends GetAppSubEntityParams {
|
|
|
256
260
|
type: string;
|
|
257
261
|
}
|
|
258
262
|
export interface PreparedAsset extends Pick<Asset, 'filename' | 'id' | 'mime'> {
|
|
259
|
-
|
|
263
|
+
path: string;
|
|
260
264
|
resource?: Record<string, unknown>;
|
|
261
265
|
}
|
|
262
266
|
export interface CreateAppResourcesWithAssetsParams extends GetAppSubEntityParams {
|
|
@@ -265,6 +269,7 @@ export interface CreateAppResourcesWithAssetsParams extends GetAppSubEntityParam
|
|
|
265
269
|
resourceType: string;
|
|
266
270
|
options: Options;
|
|
267
271
|
groupId?: number;
|
|
272
|
+
positioning?: boolean;
|
|
268
273
|
}
|
|
269
274
|
export interface UpdateAppResourceParams extends GetAppSubEntityParams {
|
|
270
275
|
id: number | string;
|
|
@@ -286,7 +291,7 @@ export interface CreateAppAssetParams extends GetAppSubEntityParams {
|
|
|
286
291
|
filename: string;
|
|
287
292
|
mime: string;
|
|
288
293
|
name: string;
|
|
289
|
-
|
|
294
|
+
path: string;
|
|
290
295
|
};
|
|
291
296
|
seed?: boolean;
|
|
292
297
|
}
|
|
@@ -330,7 +335,7 @@ export interface AppBlockStyle {
|
|
|
330
335
|
style: string;
|
|
331
336
|
}
|
|
332
337
|
export interface AppAsset extends Asset {
|
|
333
|
-
|
|
338
|
+
stream: Readable;
|
|
334
339
|
}
|
|
335
340
|
export interface ProjectAsset {
|
|
336
341
|
filename: string;
|
|
@@ -349,8 +354,8 @@ export interface ParsedQuery {
|
|
|
349
354
|
}
|
|
350
355
|
export type ContentSecurityPolicy = Record<string, (string | false)[]>;
|
|
351
356
|
export interface Options {
|
|
352
|
-
getCurrentAppMember: (params: GetCurrentAppMemberParams) => Promise<AppMemberInfo>;
|
|
353
|
-
getCurrentAppMemberGroups: (params: GetCurrentAppMemberGroupsParams) => Promise<Group[]>;
|
|
357
|
+
getCurrentAppMember: (params: GetCurrentAppMemberParams) => Promise<AppMemberInfo | null>;
|
|
358
|
+
getCurrentAppMemberGroups: (params: GetCurrentAppMemberGroupsParams) => Promise<Group[] | null>;
|
|
354
359
|
getApp: (params: GetAppParams) => Promise<App>;
|
|
355
360
|
getAppDetails: (params: GetAppParams) => Promise<AppDetails>;
|
|
356
361
|
getAppMessages: (params: GetAppMessagesParams) => Promise<AppMessages[]>;
|
|
@@ -377,7 +382,7 @@ export interface Options {
|
|
|
377
382
|
checkAppPermissions: (params: CheckAppPermissionsParams) => Promise<void>;
|
|
378
383
|
reloadUser: (params: ReloadUserParams) => Promise<Record<string, any>>;
|
|
379
384
|
parseQuery: (params: ParseQueryParams) => ParsedQuery;
|
|
380
|
-
getAppResource: (params: GetAppResourceParams) => Promise<Resource>;
|
|
385
|
+
getAppResource: (params: GetAppResourceParams) => Promise<Resource | null>;
|
|
381
386
|
getAppResources: (params: GetAppResourcesParams) => Promise<Resource[]>;
|
|
382
387
|
createAppResourcesWithAssets: (params: CreateAppResourcesWithAssetsParams) => Promise<Resource[]>;
|
|
383
388
|
updateAppResource: (params: UpdateAppResourceParams) => Promise<Resource | null>;
|
|
@@ -390,4 +395,14 @@ export interface Options {
|
|
|
390
395
|
sendNotifications: (params: SendNotificationsParams) => Promise<void>;
|
|
391
396
|
getAppVariables: (params: GetAppVariablesParams) => Promise<AppConfigEntry[]>;
|
|
392
397
|
}
|
|
398
|
+
export declare class TempFile {
|
|
399
|
+
readonly filename: string;
|
|
400
|
+
readonly mime: string;
|
|
401
|
+
readonly path: string;
|
|
402
|
+
constructor({ filename, mime, path }: {
|
|
403
|
+
filename: string;
|
|
404
|
+
mime: string;
|
|
405
|
+
path: string;
|
|
406
|
+
});
|
|
407
|
+
}
|
|
393
408
|
export {};
|
package/server/types.js
CHANGED
package/server/utils/actions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertKoaCondition, createFormData, EmailQuotaExceededError, getContainerNamespace, getRemapperContext, logger, parseServiceUrl, scaleDeployment, setLastRequestAnnotation, throwKoaError, version, waitForPodReadiness, } from '@appsemble/node-utils';
|
|
2
2
|
import { defaultLocale, remap } from '@appsemble/utils';
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
import { get, mapValues, pick } from 'lodash-es';
|
|
@@ -82,7 +82,7 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
82
82
|
// TODO
|
|
83
83
|
const { applyAppServiceSecrets } = options;
|
|
84
84
|
const remapperContext = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, options, ctx);
|
|
85
|
-
const proxyUrl = new URL(String(remap(action.url, data, remapperContext)));
|
|
85
|
+
const proxyUrl = new URL(String(remap(action.url ?? null, data, remapperContext)));
|
|
86
86
|
if (/\/api\/apps\/\d+\/actions\/.*/.test(proxyUrl.pathname) && ctx.URL.host === proxyUrl.host) {
|
|
87
87
|
throwKoaError(ctx, 400, 'Not allowed to make direct requests to the Appsemble action controller using this action.');
|
|
88
88
|
}
|
|
@@ -103,7 +103,8 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
103
103
|
headers: {},
|
|
104
104
|
};
|
|
105
105
|
axiosConfig = await applyAppServiceSecrets({ axiosConfig, context: ctx, app });
|
|
106
|
-
|
|
106
|
+
axiosConfig.headers ?? (axiosConfig.headers = {});
|
|
107
|
+
if (axiosConfig.method?.toUpperCase() !== method) {
|
|
107
108
|
throwKoaError(ctx, 400, 'Method does not match the request action method');
|
|
108
109
|
}
|
|
109
110
|
if (useBody) {
|
|
@@ -118,16 +119,18 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
118
119
|
axiosConfig.validateStatus = () => true;
|
|
119
120
|
axiosConfig.decompress = false;
|
|
120
121
|
let response;
|
|
121
|
-
const
|
|
122
|
+
const containerUrlPattern = /^http:\/\/(([\da-z-]+).){2}svc.cluster.local/;
|
|
122
123
|
// Restricting access to only the containers defined by the app
|
|
123
|
-
if (
|
|
124
|
-
axiosConfig.url = axiosConfig.url
|
|
124
|
+
if (containerUrlPattern.test(String(proxyUrl))) {
|
|
125
|
+
axiosConfig.url = axiosConfig.url?.replace(axiosConfig.url.split('.')[1], getContainerNamespace());
|
|
125
126
|
const { appId } = parseServiceUrl(String(proxyUrl));
|
|
126
127
|
if (appId !== String(app.id)) {
|
|
127
128
|
throwKoaError(ctx, 403, 'Forbidden');
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
logger.verbose(`Forwarding request to ${axios.getUri(axiosConfig)}`);
|
|
132
|
+
logger.verbose('Axios Config:');
|
|
133
|
+
logger.verbose(axiosConfig);
|
|
131
134
|
try {
|
|
132
135
|
response = await axios(axiosConfig);
|
|
133
136
|
logger.verbose(response);
|
|
@@ -135,15 +138,16 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
135
138
|
catch (err) {
|
|
136
139
|
// If request is sent to a companion container and fails
|
|
137
140
|
// Try to start it anew and retry the request
|
|
138
|
-
if (
|
|
141
|
+
if (containerUrlPattern.test(String(proxyUrl))) {
|
|
139
142
|
const { deploymentName, namespace } = parseServiceUrl(String(proxyUrl));
|
|
140
143
|
try {
|
|
141
144
|
await scaleDeployment(namespace, deploymentName, 1);
|
|
142
|
-
await waitForPodReadiness(namespace, deploymentName, process.env.POD_READINESS_TIMEOUT
|
|
145
|
+
await waitForPodReadiness(namespace, deploymentName, Number.isNaN(Number(process.env.POD_READINESS_TIMEOUT))
|
|
146
|
+
? undefined
|
|
147
|
+
: Number(process.env.POD_READINESS_TIMEOUT));
|
|
143
148
|
response = await axios(axiosConfig);
|
|
144
149
|
}
|
|
145
150
|
catch {
|
|
146
|
-
logger.error(err);
|
|
147
151
|
throwKoaError(ctx, 502, 'Bad Gateway');
|
|
148
152
|
}
|
|
149
153
|
finally {
|
|
@@ -152,6 +156,8 @@ async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
|
152
156
|
}
|
|
153
157
|
else {
|
|
154
158
|
logger.error(err);
|
|
159
|
+
logger.verbose('Response:');
|
|
160
|
+
logger.verbose(response);
|
|
155
161
|
throwKoaError(ctx, 502, 'Bad Gateway');
|
|
156
162
|
}
|
|
157
163
|
}
|
|
@@ -163,7 +169,7 @@ export function createProxyHandler(useBody, options) {
|
|
|
163
169
|
return async (ctx) => {
|
|
164
170
|
const { pathParams: { appId, path }, } = ctx;
|
|
165
171
|
const app = await options.getApp({ context: ctx, query: { where: { id: appId } } });
|
|
166
|
-
|
|
172
|
+
assertKoaCondition(app != null, ctx, 404, 'App not found');
|
|
167
173
|
const appAction = get(app.definition, path);
|
|
168
174
|
const action = supportedActions.find((act) => act === appAction?.type);
|
|
169
175
|
switch (action) {
|
|
@@ -3,8 +3,11 @@ import { logger } from '../../logger.js';
|
|
|
3
3
|
export function generateResourceQuery(ctx, { parseQuery }, resourceDefinition) {
|
|
4
4
|
try {
|
|
5
5
|
return parseQuery({
|
|
6
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
6
7
|
$filter: ctx.queryParams.$filter,
|
|
8
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
7
9
|
$orderby: ctx.queryParams.$orderby,
|
|
10
|
+
// @ts-expect-error 2322 null is not assignable to type (strictNullChecks)
|
|
8
11
|
resourceDefinition,
|
|
9
12
|
});
|
|
10
13
|
}
|
|
@@ -18,7 +21,7 @@ export function generateResourceQuery(ctx, { parseQuery }, resourceDefinition) {
|
|
|
18
21
|
}
|
|
19
22
|
export async function deleteResourcesRecursively(type, app, options, context) {
|
|
20
23
|
const { deleteAppResource, getAppResources } = options;
|
|
21
|
-
const referencingResources = Object.entries(app.definition.resources).filter(([, resourceDefinition]) => Object.values(resourceDefinition.references ?? {}).find((resourceReference) => resourceReference.resource === type));
|
|
24
|
+
const referencingResources = Object.entries(app.definition.resources ?? {}).filter(([, resourceDefinition]) => Object.values(resourceDefinition.references ?? {}).find((resourceReference) => resourceReference.resource === type));
|
|
22
25
|
for (const [referencingResourceType] of referencingResources) {
|
|
23
26
|
await deleteResourcesRecursively(referencingResourceType, app, options, context);
|
|
24
27
|
}
|
package/tinyRouter.js
CHANGED
|
@@ -14,7 +14,7 @@ export function tinyRouter(routes) {
|
|
|
14
14
|
if (typeof route === 'string') {
|
|
15
15
|
return path === route;
|
|
16
16
|
}
|
|
17
|
-
match = path.match(route);
|
|
17
|
+
match = path.match(route) ?? undefined;
|
|
18
18
|
return match;
|
|
19
19
|
});
|
|
20
20
|
if (!result) {
|
|
@@ -27,8 +27,8 @@ export function tinyRouter(routes) {
|
|
|
27
27
|
}
|
|
28
28
|
m = 'any';
|
|
29
29
|
}
|
|
30
|
-
ctx.params = match?.groups ? { ...match.groups } :
|
|
31
|
-
return result[m](ctx, next);
|
|
30
|
+
ctx.params = match?.groups ? { ...match.groups } : undefined;
|
|
31
|
+
return result[m]?.(ctx, next);
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
//# sourceMappingURL=tinyRouter.js.map
|
package/uploads.d.ts
ADDED
package/uploads.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { unlink } from 'node:fs/promises';
|
|
3
|
+
import { streamToBuffer } from 'memfs/lib/node/util.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
// XXX: a bit severe
|
|
6
|
+
// @ts-expect-error 2366 - Function lacks ending return statement and return type does not include 'undefined'
|
|
7
|
+
export async function uploadToBuffer(path) {
|
|
8
|
+
try {
|
|
9
|
+
const buffer = await streamToBuffer(await createReadStream(path));
|
|
10
|
+
await unlink(path);
|
|
11
|
+
return buffer;
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
logger.error(error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function removeUploads(paths) {
|
|
18
|
+
for (const path of paths) {
|
|
19
|
+
try {
|
|
20
|
+
await unlink(path);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
logger.error(error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=uploads.js.map
|
package/versions.d.ts
ADDED