@appsemble/node-utils 0.30.14-test.5 → 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.
Files changed (89) hide show
  1. package/README.md +3 -3
  2. package/app.js +8 -0
  3. package/assets.d.ts +16 -0
  4. package/assets.js +65 -0
  5. package/authentication.d.ts +1 -1
  6. package/container/helpers.d.ts +1 -1
  7. package/container/helpers.js +1 -0
  8. package/container/logs.js +8 -5
  9. package/container/operations.js +22 -14
  10. package/container/scale.js +3 -2
  11. package/container/specs.js +1 -1
  12. package/fs.js +4 -0
  13. package/getAppsembleMessages.js +2 -0
  14. package/getMessagesUtil.js +5 -4
  15. package/getValidTrainings.d.ts +8 -0
  16. package/getValidTrainings.js +47 -0
  17. package/icon.js +5 -0
  18. package/index.d.ts +5 -1
  19. package/index.js +5 -1
  20. package/interceptors.js +1 -1
  21. package/koa.d.ts +13 -1
  22. package/koa.js +17 -1
  23. package/logger.js +5 -3
  24. package/middleware/loggerMiddleware.js +1 -0
  25. package/odata.js +2 -1
  26. package/package.json +22 -14
  27. package/parsers.d.ts +1 -0
  28. package/parsers.js +130 -0
  29. package/render.js +1 -1
  30. package/resource.d.ts +18 -3
  31. package/resource.js +50 -10
  32. package/s3.d.ts +18 -0
  33. package/s3.js +131 -0
  34. package/server/controllers/common/apps/assets/createGetAppAssetByIdController.js +4 -3
  35. package/server/controllers/common/apps/groups/createGetAppGroupsController.js +2 -2
  36. package/server/controllers/common/apps/messages/createGetAppMessagesController.js +2 -0
  37. package/server/controllers/common/apps/resources/createCreateAppResourceController.js +16 -2
  38. package/server/controllers/common/apps/resources/createDeleteAppResourceController.js +2 -2
  39. package/server/controllers/common/apps/resources/createGetAppResourceByIdController.js +3 -3
  40. package/server/controllers/common/apps/resources/createQueryAppResourcesController.js +1 -1
  41. package/server/controllers/common/apps/resources/createUpdateAppResourceController.js +6 -3
  42. package/server/controllers/common/apps/variables/createGetAppVariablesController.js +2 -2
  43. package/server/controllers/main/apps/assets/createCreateAppAssetController.js +4 -4
  44. package/server/controllers/main/apps/resources/createDeleteAppSeedResourcesController.js +2 -2
  45. package/server/routes/appRouter/blockAssetHandler.js +5 -3
  46. package/server/routes/appRouter/blockCssHandler.js +5 -3
  47. package/server/routes/appRouter/bulmaHandler.js +1 -0
  48. package/server/routes/appRouter/cssHandler.js +2 -2
  49. package/server/routes/appRouter/iconHandler.js +4 -1
  50. package/server/routes/appRouter/indexHandler.js +3 -1
  51. package/server/routes/appRouter/manifestHandler.js +2 -2
  52. package/server/routes/appRouter/readmeHandler.js +4 -3
  53. package/server/routes/appRouter/robotsHandler.js +2 -2
  54. package/server/routes/appRouter/screenshotHandler.js +4 -3
  55. package/server/routes/appRouter/serviceWorkerHandler.js +2 -2
  56. package/server/types.d.ts +22 -7
  57. package/server/types.js +7 -1
  58. package/server/utils/actions.js +16 -10
  59. package/server/utils/resources.js +4 -1
  60. package/tinyRouter.js +3 -3
  61. package/uploads.d.ts +2 -0
  62. package/uploads.js +27 -0
  63. package/versions.d.ts +3 -0
  64. package/versions.js +7 -0
  65. package/AppsembleError.test.d.ts +0 -1
  66. package/AppsembleError.test.js +0 -7
  67. package/basicAuth.test.d.ts +0 -1
  68. package/basicAuth.test.js +0 -7
  69. package/formData.test.d.ts +0 -1
  70. package/formData.test.js +0 -93
  71. package/fs.test.d.ts +0 -1
  72. package/fs.test.js +0 -75
  73. package/handleError.test.d.ts +0 -1
  74. package/handleError.test.js +0 -33
  75. package/interceptors.test.d.ts +0 -1
  76. package/interceptors.test.js +0 -83
  77. package/logger.test.d.ts +0 -1
  78. package/logger.test.js +0 -37
  79. package/middleware/error.test.d.ts +0 -1
  80. package/middleware/error.test.js +0 -59
  81. package/middleware/loggerMiddleware.test.d.ts +0 -1
  82. package/middleware/loggerMiddleware.test.js +0 -120
  83. package/readFileOrString.test.d.ts +0 -1
  84. package/readFileOrString.test.js +0 -12
  85. package/server/controllers/common/apps/resources/resources.test.d.ts +0 -1
  86. package/server/controllers/common/apps/resources/resources.test.js +0 -580
  87. package/tinyRouter.test.d.ts +0 -1
  88. package/tinyRouter.test.js +0 -83
  89. 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, appAssets.map((appAsset) => ({ id: appAsset.id, name: appAsset.name })));
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!resource, ctx, 404, 'Resource not found');
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 { assertKoaError, getRemapperContext, getResourceDefinition, } from '@appsemble/node-utils';
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
- assertKoaError(!resource, ctx, 404, 'Resource not found');
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 { assertKoaError, getResourceDefinition, processResourceBody, } from '@appsemble/node-utils';
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
- assertKoaError(!oldResource, ctx, 404, 'Resource not found');
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, appAssets.map((asset) => ({ id: asset.id, name: asset.name })));
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 { assertKoaError } from '../../../../../koa.js';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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 { assertKoaError } from '../../../../../koa.js';
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: { contents, filename, mime }, name, }, }, } = ctx;
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
- assertKoaError(!app, ctx, 404, 'App not found');
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, data: contents },
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 { assertKoaError } from '../../../../../koa.js';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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 { assertKoaError } from '@appsemble/node-utils';
1
+ import { assertKoaCondition } from '@appsemble/node-utils';
2
2
  export function createBlockAssetHandler({ getBlockAsset }) {
3
3
  return async (ctx) => {
4
- const { params: { filename, name, version }, } = ctx;
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
- assertKoaError(!blockAsset, ctx, 404, 'Block asset not found');
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 { assertKoaError } from '@appsemble/node-utils';
1
+ import { assertKoaCondition } from '@appsemble/node-utils';
2
2
  export function createBlockCssHandler({ getApp, getAppBlockStyles }) {
3
3
  return async (ctx) => {
4
- const { params: { name }, } = ctx;
4
+ const {
5
+ // @ts-expect-error Messed up
6
+ params: { name }, } = ctx;
5
7
  const app = await getApp({ context: ctx });
6
- assertKoaError(!app, ctx, 404, 'App not found');
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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 { params: { size = 128 }, query: { maskable = false, updated }, } = ctx;
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.cjs';
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'Block asset not found');
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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
- assertKoaError(!appReadme, ctx, 404, 'Readme not found');
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 { assertKoaError, logger } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'App not found');
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
- assertKoaError(!appScreenshot, ctx, 404, 'Screenshot not found');
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 { assertKoaError } from '@appsemble/node-utils';
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
- assertKoaError(!app, ctx, 404, 'App does not exist.');
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
- data: Buffer;
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
- data: Buffer;
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
- data: Buffer;
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
@@ -1,2 +1,8 @@
1
- export {};
1
+ export class TempFile {
2
+ constructor({ filename, mime, path }) {
3
+ this.filename = filename;
4
+ this.mime = mime;
5
+ this.path = path;
6
+ }
7
+ }
2
8
  //# sourceMappingURL=types.js.map
@@ -1,4 +1,4 @@
1
- import { assertKoaError, createFormData, EmailQuotaExceededError, getContainerNamespace, getRemapperContext, logger, parseServiceUrl, scaleDeployment, setLastRequestAnnotation, throwKoaError, version, waitForPodReadiness, } from '@appsemble/node-utils';
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
- if (axiosConfig.method.toUpperCase() !== method) {
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 urlPattern = /^http:\/\/(([\da-z-]+).){2}svc.cluster.local/;
122
+ const containerUrlPattern = /^http:\/\/(([\da-z-]+).){2}svc.cluster.local/;
122
123
  // Restricting access to only the containers defined by the app
123
- if (urlPattern.test(String(proxyUrl))) {
124
- axiosConfig.url = axiosConfig.url.replace(axiosConfig.url.split('.')[1], getContainerNamespace());
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 (urlPattern.test(String(proxyUrl))) {
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 ?? undefined);
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
- assertKoaError(!app, ctx, 404, 'App not found');
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 } : null;
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
@@ -0,0 +1,2 @@
1
+ export declare function uploadToBuffer(path: string): Promise<Buffer>;
2
+ export declare function removeUploads(paths: string[]): Promise<void>;
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
@@ -0,0 +1,3 @@
1
+ export declare const version: string;
2
+ export declare const faVersion: string;
3
+ export declare const bulmaVersion: string;