@appsemble/node-utils 0.20.45 → 0.21.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/README.md +3 -3
- package/app.d.ts +17 -0
- package/app.js +49 -0
- package/csv.d.ts +2 -0
- package/csv.js +55 -0
- package/fs.d.ts +3 -3
- package/fs.test.js +5 -5
- package/getAppsembleMessages.d.ts +9 -0
- package/getAppsembleMessages.js +32 -0
- package/handleError.test.js +4 -4
- package/icon.d.ts +44 -0
- package/icon.js +69 -0
- package/index.d.ts +22 -1
- package/index.js +22 -1
- package/interceptors.test.js +5 -6
- package/jsonschema.d.ts +2 -0
- package/jsonschema.js +7 -0
- package/koa.d.ts +6 -0
- package/koa.js +27 -0
- package/mergeMessages.d.ts +9 -0
- package/mergeMessages.js +17 -0
- package/middleware/boom.d.ts +7 -0
- package/middleware/boom.js +32 -0
- package/middleware/boom.test.js +58 -0
- package/middleware/conditional.d.ts +2 -0
- package/middleware/conditional.js +9 -0
- package/middleware/frontend.d.ts +3 -0
- package/middleware/frontend.js +40 -0
- package/{loggerMiddleware.js → middleware/loggerMiddleware.js} +1 -1
- package/{loggerMiddleware.test.js → middleware/loggerMiddleware.test.js} +14 -15
- package/odata.d.ts +18 -0
- package/odata.js +45 -0
- package/organizationBlocklist.d.ts +4 -0
- package/organizationBlocklist.js +5 -0
- package/package.json +41 -4
- package/parsers.d.ts +3 -0
- package/parsers.js +18 -0
- package/readAsset.d.ts +13 -0
- package/readAsset.js +6 -0
- package/readFileOrString.d.ts +1 -1
- package/render.d.ts +21 -0
- package/render.js +46 -0
- package/resource.d.ts +43 -0
- package/resource.js +172 -0
- package/scim.d.ts +26 -0
- package/scim.js +32 -0
- package/server/controllers/action.d.ts +7 -0
- package/server/controllers/action.js +129 -0
- package/server/controllers/appMembers.d.ts +3 -0
- package/server/controllers/appMembers.js +19 -0
- package/server/controllers/appMessages.d.ts +3 -0
- package/server/controllers/appMessages.js +75 -0
- package/server/controllers/assets.d.ts +5 -0
- package/server/controllers/assets.js +74 -0
- package/server/controllers/index.d.ts +7 -0
- package/server/controllers/index.js +8 -0
- package/server/controllers/oauth2Provider.d.ts +3 -0
- package/server/controllers/oauth2Provider.js +7 -0
- package/server/controllers/resources.d.ts +8 -0
- package/server/controllers/resources.js +273 -0
- package/server/controllers/resources.test.d.ts +1 -0
- package/server/controllers/resources.test.js +552 -0
- package/server/controllers/teams.d.ts +3 -0
- package/server/controllers/teams.js +12 -0
- package/server/routes/appRouter/blockAssetHandler.d.ts +3 -0
- package/server/routes/appRouter/blockAssetHandler.js +13 -0
- package/server/routes/appRouter/blockCssHandler.d.ts +3 -0
- package/server/routes/appRouter/blockCssHandler.js +15 -0
- package/server/routes/appRouter/bulmaHandler.d.ts +3 -0
- package/server/routes/appRouter/bulmaHandler.js +116 -0
- package/server/routes/appRouter/cssHandler.d.ts +3 -0
- package/server/routes/appRouter/cssHandler.js +12 -0
- package/server/routes/appRouter/iconHandler.d.ts +3 -0
- package/server/routes/appRouter/iconHandler.js +23 -0
- package/server/routes/appRouter/index.d.ts +3 -0
- package/server/routes/appRouter/index.js +85 -0
- package/server/routes/appRouter/indexHandler.d.ts +5 -0
- package/server/routes/appRouter/indexHandler.js +69 -0
- package/server/routes/appRouter/manifestHandler.d.ts +3 -0
- package/server/routes/appRouter/manifestHandler.js +47 -0
- package/server/routes/appRouter/robotsHandler.d.ts +2 -0
- package/server/routes/appRouter/robotsHandler.js +6 -0
- package/server/routes/appRouter/screenshotHandler.d.ts +3 -0
- package/server/routes/appRouter/screenshotHandler.js +19 -0
- package/server/routes/appRouter/serviceWorkerHandler.d.ts +3 -0
- package/server/routes/appRouter/serviceWorkerHandler.js +18 -0
- package/server/routes/appRouter/staticHandler.d.ts +2 -0
- package/server/routes/appRouter/staticHandler.js +9 -0
- package/server/types.d.ts +356 -0
- package/server/types.js +2 -0
- package/testFixtures.d.ts +3 -3
- package/testFixtures.js +1 -1
- package/tinyRouter.d.ts +16 -0
- package/tinyRouter.js +34 -0
- package/tinyRouter.test.d.ts +1 -0
- package/tinyRouter.test.js +85 -0
- package/vitest.config.d.ts +2 -0
- package/vitest.setup.d.ts +1 -0
- package/{jest.setup.js → vitest.setup.js} +1 -1
- /package/{jest.setup.d.ts → middleware/boom.test.d.ts} +0 -0
- /package/{loggerMiddleware.d.ts → middleware/loggerMiddleware.d.ts} +0 -0
- /package/{loggerMiddleware.test.d.ts → middleware/loggerMiddleware.test.d.ts} +0 -0
package/scim.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SCIM protocol uses the HTTP response status codes defined in
|
|
3
|
+
* [Section 6 of [RFC7231]](https://datatracker.ietf.org/doc/html/rfc7231#section-6) to indicate
|
|
4
|
+
* operation success or failure. In addition to returning an HTTP response code, implementers
|
|
5
|
+
* **MUST** return the errors in the body of the response in a JSON format, using the attributes
|
|
6
|
+
* described below. Error responses are identified using the following "schema" URI:
|
|
7
|
+
* `"urn:ietf:params:scim:api:messages:2.0:Error"`.
|
|
8
|
+
*
|
|
9
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7644#section-3.12
|
|
10
|
+
*/
|
|
11
|
+
export declare class SCIMError extends Error {
|
|
12
|
+
schemas: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The HTTP status code (see
|
|
15
|
+
* [Section 6 of [RFC7231]](https://datatracker.ietf.org/doc/html/rfc7231#section-6)) expressed as
|
|
16
|
+
* a JSON string.
|
|
17
|
+
*/
|
|
18
|
+
status: string;
|
|
19
|
+
/**
|
|
20
|
+
* A detailed human-readable message.
|
|
21
|
+
*/
|
|
22
|
+
detail: string;
|
|
23
|
+
constructor(status: number, detail: string);
|
|
24
|
+
toJSON(): Pick<SCIMError, 'detail' | 'schemas' | 'status'>;
|
|
25
|
+
}
|
|
26
|
+
export declare function scimAssert(condition: unknown, status: number, detail: string): asserts condition;
|
package/scim.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SCIM protocol uses the HTTP response status codes defined in
|
|
3
|
+
* [Section 6 of [RFC7231]](https://datatracker.ietf.org/doc/html/rfc7231#section-6) to indicate
|
|
4
|
+
* operation success or failure. In addition to returning an HTTP response code, implementers
|
|
5
|
+
* **MUST** return the errors in the body of the response in a JSON format, using the attributes
|
|
6
|
+
* described below. Error responses are identified using the following "schema" URI:
|
|
7
|
+
* `"urn:ietf:params:scim:api:messages:2.0:Error"`.
|
|
8
|
+
*
|
|
9
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7644#section-3.12
|
|
10
|
+
*/
|
|
11
|
+
export class SCIMError extends Error {
|
|
12
|
+
constructor(status, detail) {
|
|
13
|
+
super(detail);
|
|
14
|
+
this.schemas = ['urn:ietf:params:scim:api:messages:2.0:Error'];
|
|
15
|
+
this.detail = detail;
|
|
16
|
+
this.name = 'SCIMError';
|
|
17
|
+
this.status = String(status);
|
|
18
|
+
}
|
|
19
|
+
toJSON() {
|
|
20
|
+
return {
|
|
21
|
+
detail: this.detail,
|
|
22
|
+
schemas: this.schemas,
|
|
23
|
+
status: this.status,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function scimAssert(condition, status, detail) {
|
|
28
|
+
if (!condition) {
|
|
29
|
+
throw new SCIMError(status, detail);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=scim.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Options } from '@appsemble/node-utils';
|
|
2
|
+
import { type Middleware } from 'koa';
|
|
3
|
+
export declare function createProxyDelete(options: Options): Middleware;
|
|
4
|
+
export declare function createProxyGet(options: Options): Middleware;
|
|
5
|
+
export declare function createProxyPatch(options: Options): Middleware;
|
|
6
|
+
export declare function createProxyPost(options: Options): Middleware;
|
|
7
|
+
export declare function createProxyPut(options: Options): Middleware;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getRemapperContext, logger } from '@appsemble/node-utils';
|
|
2
|
+
import { defaultLocale, formatRequestAction, remap } from '@appsemble/utils';
|
|
3
|
+
import { badGateway, badRequest, methodNotAllowed, notFound } from '@hapi/boom';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { get, pick } from 'lodash-es';
|
|
6
|
+
import pkg from '../../package.json' assert { type: 'json' };
|
|
7
|
+
/**
|
|
8
|
+
* These response headers are forwarded when proxying requests.
|
|
9
|
+
*/
|
|
10
|
+
const allowResponseHeaders = [
|
|
11
|
+
'content-encoding',
|
|
12
|
+
'content-length',
|
|
13
|
+
'content-type',
|
|
14
|
+
'transfer-encoding',
|
|
15
|
+
];
|
|
16
|
+
const supportedActions = ['email', 'notify', 'request'];
|
|
17
|
+
async function handleEmail(ctx, app, action, options) {
|
|
18
|
+
const { mailer, method, request: { body: data }, user, } = ctx;
|
|
19
|
+
if (method !== 'POST') {
|
|
20
|
+
throw methodNotAllowed('Method must be POST for email actions');
|
|
21
|
+
}
|
|
22
|
+
const { email, reloadUser } = options;
|
|
23
|
+
await reloadUser({ context: ctx });
|
|
24
|
+
await email({ action, data, mailer, user, options, context: ctx });
|
|
25
|
+
ctx.status = 204;
|
|
26
|
+
}
|
|
27
|
+
async function handleNotify(ctx, app, action, options) {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
const { request: { body: data }, user, } = ctx;
|
|
30
|
+
const { reloadUser, sendNotifications } = options;
|
|
31
|
+
await reloadUser({ context: ctx });
|
|
32
|
+
const remapperContext = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, user && {
|
|
33
|
+
sub: user.id,
|
|
34
|
+
name: user.name,
|
|
35
|
+
email: user.primaryEmail,
|
|
36
|
+
email_verified: Boolean((_b = (_a = user.EmailAuthorizations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.verified),
|
|
37
|
+
zoneinfo: user.timezone,
|
|
38
|
+
}, options, ctx);
|
|
39
|
+
const to = remap(action.to, data, remapperContext);
|
|
40
|
+
const title = remap(action.title, data, remapperContext);
|
|
41
|
+
const body = remap(action.body, data, remapperContext);
|
|
42
|
+
await sendNotifications({ app, to, title, body });
|
|
43
|
+
ctx.status = 204;
|
|
44
|
+
}
|
|
45
|
+
async function handleRequestProxy(ctx, app, action, useBody, options) {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const { method, query, request: { body }, user, } = ctx;
|
|
48
|
+
let data;
|
|
49
|
+
if (useBody) {
|
|
50
|
+
data = body;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
try {
|
|
54
|
+
data = JSON.parse(query.data);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw badRequest('data should be a JSON object');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const { applyAppServiceSecrets, reloadUser } = options;
|
|
61
|
+
await reloadUser({ context: ctx });
|
|
62
|
+
const remapperContext = await getRemapperContext(app, app.definition.defaultLanguage || defaultLocale, user && {
|
|
63
|
+
sub: user.id,
|
|
64
|
+
name: user.name,
|
|
65
|
+
email: user.primaryEmail,
|
|
66
|
+
email_verified: Boolean((_b = (_a = user.EmailAuthorizations) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.verified),
|
|
67
|
+
zoneinfo: user.timezone,
|
|
68
|
+
}, options, ctx);
|
|
69
|
+
let axiosConfig = formatRequestAction(action, data, (remapper, d) => remap(remapper, d, remapperContext), remapperContext.context);
|
|
70
|
+
axiosConfig = await applyAppServiceSecrets({ axiosConfig, context: ctx, app });
|
|
71
|
+
if (axiosConfig.method.toUpperCase() !== method) {
|
|
72
|
+
throw badRequest('Method does match the request action method');
|
|
73
|
+
}
|
|
74
|
+
if (useBody) {
|
|
75
|
+
axiosConfig.data = data;
|
|
76
|
+
}
|
|
77
|
+
axiosConfig.headers['user-agent'] = `AppsembleServer/${pkg.version}`;
|
|
78
|
+
axiosConfig.responseType = 'stream';
|
|
79
|
+
axiosConfig.validateStatus = () => true;
|
|
80
|
+
let response;
|
|
81
|
+
logger.verbose(`Forwarding request to ${axios.getUri(axiosConfig)}`);
|
|
82
|
+
try {
|
|
83
|
+
response = await axios(axiosConfig);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
logger.error(err);
|
|
87
|
+
throw badGateway();
|
|
88
|
+
}
|
|
89
|
+
ctx.status = response.status;
|
|
90
|
+
ctx.set(pick(response.headers, allowResponseHeaders));
|
|
91
|
+
ctx.body = response.data;
|
|
92
|
+
}
|
|
93
|
+
function createProxyHandler(useBody, options) {
|
|
94
|
+
return async (ctx) => {
|
|
95
|
+
const { pathParams: { appId, path }, } = ctx;
|
|
96
|
+
const app = await options.getApp({ context: ctx, query: { where: { id: appId } } });
|
|
97
|
+
if (!app) {
|
|
98
|
+
throw notFound('App not found');
|
|
99
|
+
}
|
|
100
|
+
const appAction = get(app.definition, path);
|
|
101
|
+
const action = supportedActions.find((act) => act === (appAction === null || appAction === void 0 ? void 0 : appAction.type));
|
|
102
|
+
switch (action) {
|
|
103
|
+
case 'email':
|
|
104
|
+
return handleEmail(ctx, app, appAction, options);
|
|
105
|
+
case 'notify':
|
|
106
|
+
return handleNotify(ctx, app, appAction, options);
|
|
107
|
+
case 'request':
|
|
108
|
+
return handleRequestProxy(ctx, app, appAction, useBody, options);
|
|
109
|
+
default:
|
|
110
|
+
throw badRequest('path does not point to a proxyable action');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function createProxyDelete(options) {
|
|
115
|
+
return createProxyHandler(false, options);
|
|
116
|
+
}
|
|
117
|
+
export function createProxyGet(options) {
|
|
118
|
+
return createProxyHandler(false, options);
|
|
119
|
+
}
|
|
120
|
+
export function createProxyPatch(options) {
|
|
121
|
+
return createProxyHandler(true, options);
|
|
122
|
+
}
|
|
123
|
+
export function createProxyPost(options) {
|
|
124
|
+
return createProxyHandler(true, options);
|
|
125
|
+
}
|
|
126
|
+
export function createProxyPut(options) {
|
|
127
|
+
return createProxyHandler(true, options);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { notFound } from '@hapi/boom';
|
|
2
|
+
export function createGetAppMember({ getApp, getAppMembers }) {
|
|
3
|
+
return async (ctx) => {
|
|
4
|
+
const { pathParams: { appId, memberId }, } = ctx;
|
|
5
|
+
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
6
|
+
if (!app) {
|
|
7
|
+
throw notFound('App not found');
|
|
8
|
+
}
|
|
9
|
+
if (app.definition.security === undefined) {
|
|
10
|
+
throw notFound('App does not have a security definition');
|
|
11
|
+
}
|
|
12
|
+
const appMembers = await getAppMembers({ context: ctx, app, memberId });
|
|
13
|
+
if (appMembers.length !== 1) {
|
|
14
|
+
throw notFound('App member not found');
|
|
15
|
+
}
|
|
16
|
+
ctx.body = appMembers[0];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=appMembers.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getAppsembleMessages, mergeMessages, } from '@appsemble/node-utils';
|
|
2
|
+
import { defaultLocale, extractAppMessages, normalizeBlockName, } from '@appsemble/utils';
|
|
3
|
+
import { badRequest, notFound } from '@hapi/boom';
|
|
4
|
+
import tags from 'language-tags';
|
|
5
|
+
export function createGetMessages({ getApp, getAppMessages, getBlockMessages, }) {
|
|
6
|
+
return async (ctx) => {
|
|
7
|
+
var _a, _b, _c, _d, _e;
|
|
8
|
+
const { pathParams: { appId, language }, query: { merge, override = 'true' }, } = ctx;
|
|
9
|
+
if (!tags.check(language)) {
|
|
10
|
+
throw badRequest(`Language “${language}” is invalid`);
|
|
11
|
+
}
|
|
12
|
+
const lang = language.toLowerCase();
|
|
13
|
+
const baseLanguage = tags(language)
|
|
14
|
+
.subtags()
|
|
15
|
+
.find((sub) => sub.type() === 'language');
|
|
16
|
+
const baseLang = baseLanguage && String(baseLanguage).toLowerCase();
|
|
17
|
+
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
18
|
+
if (!app) {
|
|
19
|
+
throw notFound('App not found');
|
|
20
|
+
}
|
|
21
|
+
const appMessages = await getAppMessages({ context: ctx, app, language });
|
|
22
|
+
const blockPrefixes = [];
|
|
23
|
+
const blockQuery = [];
|
|
24
|
+
const coreMessages = await getAppsembleMessages(lang, baseLang);
|
|
25
|
+
const messages = {
|
|
26
|
+
core: Object.fromEntries(Object.entries(coreMessages).filter(([key]) => key.startsWith('app') || key.startsWith('react-components') || key.startsWith('server'))),
|
|
27
|
+
blocks: {},
|
|
28
|
+
...extractAppMessages(app.definition, (block, prefix) => {
|
|
29
|
+
const blockName = normalizeBlockName(block.type);
|
|
30
|
+
const [org, name] = blockName.split('/');
|
|
31
|
+
blockQuery.push({ version: block.version, OrganizationId: org.slice(1), name });
|
|
32
|
+
blockPrefixes.push([blockName, prefix]);
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
const blockMessages = await getBlockMessages({ context: ctx, blockQuery, baseLang, lang });
|
|
36
|
+
if ((!appMessages.length || (merge && !appMessages.some((m) => m.language === lang))) &&
|
|
37
|
+
lang !== (app.definition.defaultLanguage || defaultLocale)) {
|
|
38
|
+
throw notFound(`Language “${language}” could not be found`);
|
|
39
|
+
}
|
|
40
|
+
const baseLanguageMessages = override === 'true' && appMessages.find((m) => m.language === baseLang);
|
|
41
|
+
const languageMessages = override === 'true' && appMessages.find((m) => m.language === lang);
|
|
42
|
+
for (const block of blockMessages) {
|
|
43
|
+
const { name } = block;
|
|
44
|
+
const defaultMessages = (_a = block.messages) === null || _a === void 0 ? void 0 : _a[defaultLocale];
|
|
45
|
+
const blockBaseLanguageMessages = baseLang && ((_b = block.messages) === null || _b === void 0 ? void 0 : _b[baseLang]);
|
|
46
|
+
const blockLanguageMessages = (_c = block.messages) === null || _c === void 0 ? void 0 : _c[language];
|
|
47
|
+
const blockVersionMessages = {
|
|
48
|
+
...defaultMessages,
|
|
49
|
+
...Object.fromEntries(Object.entries(blockBaseLanguageMessages !== null && blockBaseLanguageMessages !== void 0 ? blockBaseLanguageMessages : {}).filter(([, value]) => value)),
|
|
50
|
+
...Object.fromEntries(Object.entries(blockLanguageMessages !== null && blockLanguageMessages !== void 0 ? blockLanguageMessages : {}).filter(([, value]) => value)),
|
|
51
|
+
};
|
|
52
|
+
if (messages.blocks[name]) {
|
|
53
|
+
messages.blocks[name][block.version] = blockVersionMessages;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
messages.blocks[name] = {
|
|
57
|
+
[block.version]: blockVersionMessages,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (override !== 'true') {
|
|
61
|
+
const prefixed = blockPrefixes.filter(([b]) => b === name);
|
|
62
|
+
for (const [messageId, value] of Object.entries(blockVersionMessages)) {
|
|
63
|
+
for (const [, prefix] of prefixed) {
|
|
64
|
+
messages.app[`${prefix.join('.')}.${messageId}`] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
ctx.body = {
|
|
70
|
+
language: lang,
|
|
71
|
+
messages: mergeMessages(messages, (_d = baseLanguageMessages === null || baseLanguageMessages === void 0 ? void 0 : baseLanguageMessages.messages) !== null && _d !== void 0 ? _d : {}, (_e = languageMessages === null || languageMessages === void 0 ? void 0 : languageMessages.messages) !== null && _e !== void 0 ? _e : {}),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=appMessages.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Options } from '@appsemble/node-utils';
|
|
2
|
+
import { type Middleware } from 'koa';
|
|
3
|
+
export declare function createGetAssets({ checkRole, getApp, getAppAssets }: Options): Middleware;
|
|
4
|
+
export declare function createGetAssetById({ getApp, getAppAssets }: Options): Middleware;
|
|
5
|
+
export declare function createCreateAsset({ createAppAsset, getApp }: Options): Middleware;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Permission } from '@appsemble/utils';
|
|
2
|
+
import { notFound } from '@hapi/boom';
|
|
3
|
+
import { extension } from 'mime-types';
|
|
4
|
+
export function createGetAssets({ checkRole, getApp, getAppAssets }) {
|
|
5
|
+
return async (ctx) => {
|
|
6
|
+
const { pathParams: { appId }, queryParams: { $skip, $top }, } = ctx;
|
|
7
|
+
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
8
|
+
if (!app) {
|
|
9
|
+
throw notFound('App not found');
|
|
10
|
+
}
|
|
11
|
+
const findOptions = {
|
|
12
|
+
limit: $top,
|
|
13
|
+
offset: $skip,
|
|
14
|
+
where: { AppId: app.id },
|
|
15
|
+
};
|
|
16
|
+
await checkRole({ context: ctx, app, permissions: Permission.ReadAssets, findOptions });
|
|
17
|
+
ctx.body = await getAppAssets({ app, context: ctx });
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createGetAssetById({ getApp, getAppAssets }) {
|
|
21
|
+
return async (ctx) => {
|
|
22
|
+
const { pathParams: { appId, assetId }, } = ctx;
|
|
23
|
+
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
24
|
+
if (!app) {
|
|
25
|
+
throw notFound('App not found');
|
|
26
|
+
}
|
|
27
|
+
const assets = await getAppAssets({ app, context: ctx });
|
|
28
|
+
// Pick asset id over asset name
|
|
29
|
+
const asset = assets.find((a) => a.id === assetId) || assets.find((a) => a.name === assetId);
|
|
30
|
+
if (!asset) {
|
|
31
|
+
throw notFound('Asset not found');
|
|
32
|
+
}
|
|
33
|
+
if (assetId !== asset.id) {
|
|
34
|
+
// Redirect to asset using current asset ID
|
|
35
|
+
ctx.status = 302;
|
|
36
|
+
ctx.set('location', `/api/apps/${app.id}/assets/${asset.id}`);
|
|
37
|
+
ctx.type = null;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let { filename, mime } = asset;
|
|
41
|
+
if (!filename) {
|
|
42
|
+
filename = asset.id;
|
|
43
|
+
if (mime) {
|
|
44
|
+
const ext = extension(mime);
|
|
45
|
+
if (ext) {
|
|
46
|
+
filename += `.${ext}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
ctx.set('content-type', mime || 'application/octet-stream');
|
|
51
|
+
if (filename) {
|
|
52
|
+
ctx.set('content-disposition', `attachment; filename=${JSON.stringify(filename)}`);
|
|
53
|
+
}
|
|
54
|
+
ctx.set('Cache-Control', 'max-age=31536000,immutable');
|
|
55
|
+
ctx.body = asset.data;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function createCreateAsset({ createAppAsset, getApp }) {
|
|
59
|
+
return async (ctx) => {
|
|
60
|
+
const { pathParams: { appId }, request: { body: { file: { contents, filename, mime }, name, }, }, } = ctx;
|
|
61
|
+
const app = await getApp({ context: ctx, query: { where: { id: appId } } });
|
|
62
|
+
if (!app) {
|
|
63
|
+
throw notFound('App not found');
|
|
64
|
+
}
|
|
65
|
+
const asset = await createAppAsset({
|
|
66
|
+
app,
|
|
67
|
+
context: ctx,
|
|
68
|
+
payload: { filename, mime, name, data: contents },
|
|
69
|
+
});
|
|
70
|
+
ctx.status = 201;
|
|
71
|
+
ctx.body = { id: asset.id, mime, filename, name };
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './action.js';
|
|
2
|
+
export * from './appMessages.js';
|
|
3
|
+
export * from './assets.js';
|
|
4
|
+
export * from './resources.js';
|
|
5
|
+
export * from './appMembers.js';
|
|
6
|
+
export * from './teams.js';
|
|
7
|
+
export * from './oauth2Provider.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Options } from '@appsemble/node-utils';
|
|
2
|
+
import { type Middleware } from 'koa';
|
|
3
|
+
export declare function createQueryResources(options: Options): Middleware;
|
|
4
|
+
export declare function createCountResources(options: Options): Middleware;
|
|
5
|
+
export declare function createGetResourceById(options: Options): Middleware;
|
|
6
|
+
export declare function createCreateResource(options: Options): Middleware;
|
|
7
|
+
export declare function createUpdateResource(options: Options): Middleware;
|
|
8
|
+
export declare function createDeleteResource(options: Options): Middleware;
|