cloudcommerce 0.0.40 → 0.0.43
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/CHANGELOG.md +24 -0
- package/package.json +8 -8
- package/packages/api/lib/index.d.ts +9 -3
- package/packages/api/lib/index.js +12 -2
- package/packages/api/lib/index.js.map +1 -1
- package/packages/api/lib/types.d.ts +12 -2
- package/packages/api/package.json +1 -1
- package/packages/api/src/index.ts +28 -5
- package/packages/api/src/types.ts +13 -1
- package/packages/apps/discounts/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/firebase/package.json +5 -4
- package/packages/modules/lib/firebase/ajv.js +33 -0
- package/packages/modules/lib/firebase/ajv.js.map +1 -0
- package/packages/modules/lib/firebase/call-app-module.js +70 -0
- package/packages/modules/lib/firebase/call-app-module.js.map +1 -0
- package/packages/modules/lib/firebase/checkout.js +1 -0
- package/packages/modules/lib/firebase/checkout.js.map +1 -0
- package/packages/modules/lib/firebase/handle-module.js +161 -0
- package/packages/modules/lib/firebase/handle-module.js.map +1 -0
- package/packages/modules/lib/firebase/proxy-apps.js +1 -0
- package/packages/modules/lib/firebase/proxy-apps.js.map +1 -0
- package/packages/modules/lib/firebase/serve-modules-api.js +57 -0
- package/packages/modules/lib/firebase/serve-modules-api.js.map +1 -0
- package/packages/modules/lib/firebase.js +10 -3
- package/packages/modules/lib/firebase.js.map +1 -1
- package/packages/modules/lib/index.js +11 -7
- package/packages/modules/lib/index.js.map +1 -1
- package/packages/modules/package.json +5 -2
- package/packages/modules/src/firebase/ajv.ts +38 -0
- package/packages/modules/src/firebase/call-app-module.ts +72 -0
- package/packages/modules/src/firebase/{.gitkeep → checkout.ts} +0 -0
- package/packages/modules/src/firebase/handle-module.ts +191 -0
- package/packages/modules/src/firebase/proxy-apps.ts +0 -0
- package/packages/modules/src/firebase/serve-modules-api.ts +66 -0
- package/packages/modules/src/firebase.ts +10 -3
- package/packages/modules/src/index.ts +11 -8
- package/packages/passport/package.json +2 -2
- package/packages/ssr/package.json +2 -2
- package/packages/storefront/package.json +2 -2
- package/packages/types/package.json +1 -1
- package/pnpm-lock.yaml +440 -168
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { schemas } from '../index.js';
|
|
2
|
+
import handleModule from './handle-module.js';
|
|
3
|
+
|
|
4
|
+
export default (req, res, apiAuth) => {
|
|
5
|
+
const { method } = req;
|
|
6
|
+
if (method !== 'POST' && method !== 'GET') {
|
|
7
|
+
return res.sendStatus(405);
|
|
8
|
+
}
|
|
9
|
+
if (method === 'POST'
|
|
10
|
+
&& (!req.body || typeof req.body !== 'object' || Array.isArray(req.body))) {
|
|
11
|
+
return res.sendStatus(400);
|
|
12
|
+
}
|
|
13
|
+
let { url } = req;
|
|
14
|
+
if (url.endsWith('.json')) {
|
|
15
|
+
url = url.slice(0, -5);
|
|
16
|
+
}
|
|
17
|
+
const modName = url.split('/')[1];
|
|
18
|
+
const sendSchema = (isResponseSchema = false) => {
|
|
19
|
+
return res.status(200)
|
|
20
|
+
.setHeader('Cache-Control', 'public, max-age=3600')
|
|
21
|
+
.send(schemas[modName][isResponseSchema ? 'response' : 'params']);
|
|
22
|
+
};
|
|
23
|
+
if (modName === '@checkout') {
|
|
24
|
+
if (url === '/@checkout') {
|
|
25
|
+
return res.status(200).send({
|
|
26
|
+
status: 200,
|
|
27
|
+
message: 'CHECKOUT',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (url === '/@checkout/schema') {
|
|
31
|
+
return sendSchema();
|
|
32
|
+
}
|
|
33
|
+
return res.sendStatus(404);
|
|
34
|
+
}
|
|
35
|
+
if (schemas[modName]) {
|
|
36
|
+
const { params: schema, response: responseSchema } = schemas[modName];
|
|
37
|
+
if (!schema.$schema) {
|
|
38
|
+
schema.$schema = 'http://json-schema.org/draft-06/schema#';
|
|
39
|
+
schema.title = `Module \`${modName}\`: Params model`;
|
|
40
|
+
}
|
|
41
|
+
if (!responseSchema.$schema) {
|
|
42
|
+
responseSchema.$schema = 'http://json-schema.org/draft-06/schema#';
|
|
43
|
+
responseSchema.title = `Module \`${modName}\`: App response model`;
|
|
44
|
+
}
|
|
45
|
+
if (url === `/${modName}/schema`) {
|
|
46
|
+
return sendSchema();
|
|
47
|
+
}
|
|
48
|
+
if (url === `/${modName}/response_schema`) {
|
|
49
|
+
return sendSchema(true);
|
|
50
|
+
}
|
|
51
|
+
if (url === `/${modName}`) {
|
|
52
|
+
return handleModule(modName, schema, responseSchema, req, res, apiAuth);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return res.sendStatus(404);
|
|
56
|
+
};
|
|
57
|
+
// # sourceMappingURL=serve-modules-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-modules-api.js","sourceRoot":"","sources":["../../src/firebase/serve-modules-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAE3C,eAAe,CACb,GAAY,EACZ,GAAa,EACb,OAAqD,EACrD,EAAE;IACF,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACvB,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,KAAK,EAAE;QACzC,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IACD,IACE,MAAM,KAAK,MAAM;WACd,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EACzE;QACA,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IAED,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IAClB,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACzB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KACxB;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,CAAC,gBAAgB,GAAG,KAAK,EAAE,EAAE;QAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;aACnB,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC;aAClD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,IAAI,OAAO,KAAK,WAAW,EAAE;QAC3B,IAAI,GAAG,KAAK,YAAY,EAAE;YACxB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;SACJ;QACD,IAAI,GAAG,KAAK,mBAAmB,EAAE;YAC/B,OAAO,UAAU,EAAE,CAAC;SACrB;QACD,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;KAC5B;IAED,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QACpB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;YACnB,MAAM,CAAC,OAAO,GAAG,yCAAyC,CAAC;YAC3D,MAAM,CAAC,KAAK,GAAG,YAAY,OAAO,kBAAkB,CAAC;SACtD;QACD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YAC3B,cAAc,CAAC,OAAO,GAAG,yCAAyC,CAAC;YACnE,cAAc,CAAC,KAAK,GAAG,YAAY,OAAO,wBAAwB,CAAC;SACpE;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,SAAS,EAAE;YAChC,OAAO,UAAU,EAAE,CAAC;SACrB;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,kBAAkB,EAAE;YACzC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;SACzB;QACD,IAAI,GAAG,KAAK,IAAI,OAAO,EAAE,EAAE;YACzB,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;SACzE;KACF;IACD,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC,CAAC"}
|
|
@@ -5,12 +5,19 @@ import { initializeApp } from 'firebase-admin/app';
|
|
|
5
5
|
// eslint-disable-next-line import/no-unresolved
|
|
6
6
|
import { onRequest } from 'firebase-functions/v2/https';
|
|
7
7
|
import config from '@cloudcommerce/firebase/lib/config';
|
|
8
|
+
import getEnv from '@cloudcommerce/firebase/lib/env';
|
|
9
|
+
import serveModulesApi from './firebase/serve-modules-api.js';
|
|
8
10
|
|
|
9
11
|
initializeApp();
|
|
10
|
-
const
|
|
12
|
+
const { httpsFunctionOptions } = config.get();
|
|
11
13
|
|
|
12
|
-
export const modulesApi = onRequest(
|
|
14
|
+
export const modulesApi = onRequest(httpsFunctionOptions, (req, res) => {
|
|
15
|
+
const { authenticationId, apiKey } = getEnv();
|
|
16
|
+
// Hide API key for security
|
|
13
17
|
process.env.ECOM_API_KEY = '***';
|
|
14
|
-
|
|
18
|
+
serveModulesApi(req, res, {
|
|
19
|
+
authenticationId,
|
|
20
|
+
apiKey,
|
|
21
|
+
});
|
|
15
22
|
});
|
|
16
23
|
// # sourceMappingURL=firebase.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firebase.js","sourceRoot":"","sources":["../src/firebase.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,gCAAgC,CAAC;AACxC,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,MAAM,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"firebase.js","sourceRoot":"","sources":["../src/firebase.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,gCAAgC,CAAC;AACxC,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,MAAM,MAAM,oCAAoC,CAAC;AACxD,OAAO,MAAM,MAAM,iCAAiC,CAAC;AACrD,OAAO,eAAe,MAAM,8BAA8B,CAAC;AAE3D,aAAa,EAAE,CAAC;AAChB,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACrE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9C,4BAA4B;IAC5B,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;IACjC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE;QACxB,gBAAgB;QAChB,MAAM;KACP,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -4,18 +4,22 @@ import * as listPayments from '../schemas/list_payments.cjs';
|
|
|
4
4
|
import * as createTransaction from '../schemas/create_transaction.cjs';
|
|
5
5
|
import * as checkout from '../schemas/@checkout.cjs';
|
|
6
6
|
|
|
7
|
+
const schemas = {
|
|
8
|
+
calculate_shipping: calculateShipping,
|
|
9
|
+
apply_discount: applyDiscount,
|
|
10
|
+
list_payments: listPayments,
|
|
11
|
+
create_transaction: createTransaction,
|
|
12
|
+
'@checkout': checkout,
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
export default {
|
|
8
16
|
calculateShipping,
|
|
9
17
|
applyDiscount,
|
|
10
18
|
listPayments,
|
|
11
19
|
createTransaction,
|
|
12
20
|
checkout,
|
|
13
|
-
schemas
|
|
14
|
-
calculate_shipping: calculateShipping,
|
|
15
|
-
apply_discount: applyDiscount,
|
|
16
|
-
list_payments: listPayments,
|
|
17
|
-
create_transaction: createTransaction,
|
|
18
|
-
'@checkout': checkout,
|
|
19
|
-
},
|
|
21
|
+
schemas,
|
|
20
22
|
};
|
|
23
|
+
|
|
24
|
+
export { schemas };
|
|
21
25
|
// # sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AAErD,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,iBAAiB,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AAErD,MAAM,OAAO,GAAG;IACd,kBAAkB,EAAE,iBAAiB;IACrC,cAAc,EAAE,aAAa;IAC7B,aAAa,EAAE,YAAY;IAC3B,kBAAkB,EAAE,iBAAiB;IACrC,WAAW,EAAE,QAAQ;CACtB,CAAC;AAEF,eAAe;IACb,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/modules",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.43",
|
|
5
5
|
"description": "E-Com Plus Cloud Commerce modules API",
|
|
6
6
|
"main": "lib/index.cjs",
|
|
7
7
|
"exports": {
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
"build:types": "sh scripts/build-types.sh"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@cloudcommerce/api": "workspace:*",
|
|
27
28
|
"@cloudcommerce/firebase": "workspace:*",
|
|
28
|
-
"
|
|
29
|
+
"ajv": "^8.11.0",
|
|
30
|
+
"axios": "^0.27.2",
|
|
31
|
+
"firebase-admin": "^11.0.1",
|
|
29
32
|
"firebase-functions": "^3.22.0",
|
|
30
33
|
"source-map-support": "^0.5.21"
|
|
31
34
|
},
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Response } from 'firebase-functions';
|
|
2
|
+
import Ajv, { Options, ErrorObject } from 'ajv';
|
|
3
|
+
|
|
4
|
+
const ajvOptions: Options = {
|
|
5
|
+
coerceTypes: false,
|
|
6
|
+
useDefaults: true,
|
|
7
|
+
removeAdditional: true,
|
|
8
|
+
// Explicitly set `allErrors` to false, when set to true a DoS attack is possible
|
|
9
|
+
allErrors: false,
|
|
10
|
+
multipleOfPrecision: 5,
|
|
11
|
+
allowMatchingProperties: true,
|
|
12
|
+
};
|
|
13
|
+
const ajv = new Ajv(ajvOptions);
|
|
14
|
+
|
|
15
|
+
const parseAjvErrors = (errors?: ErrorObject[] | null, ajvInstance = ajv) => {
|
|
16
|
+
return ajvInstance.errorsText(errors, { separator: '\n' });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const sendRequestError = (res: Response, modName: string, errors?: ErrorObject[] | null) => {
|
|
20
|
+
res.status(400).send({
|
|
21
|
+
status: 400,
|
|
22
|
+
error_code: 'MOD901',
|
|
23
|
+
message: 'Bad-formatted JSON body (POST) or URL params (GET), details in `user_message`',
|
|
24
|
+
user_message: {
|
|
25
|
+
en_us: ajv.errorsText(errors, { separator: '\n' }),
|
|
26
|
+
},
|
|
27
|
+
more_info: `/${modName}/schema`,
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default ajv;
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
ajv,
|
|
35
|
+
ajvOptions,
|
|
36
|
+
parseAjvErrors,
|
|
37
|
+
sendRequestError,
|
|
38
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { logger } from 'firebase-functions';
|
|
2
|
+
import axios, { AxiosResponse } from 'axios';
|
|
3
|
+
import config from '@cloudcommerce/firebase/lib/config';
|
|
4
|
+
|
|
5
|
+
// Blacklist urls to prevent consecultive errors
|
|
6
|
+
const blacklist = {};
|
|
7
|
+
|
|
8
|
+
export default async (url: string, data: any, isBigTimeout: boolean) => {
|
|
9
|
+
if (blacklist[url] > 2) {
|
|
10
|
+
logger.log(`> Skipping blacklisted ${url}`);
|
|
11
|
+
const err = new Error('Blacklited endpoint URL');
|
|
12
|
+
return Promise.reject(err);
|
|
13
|
+
}
|
|
14
|
+
const { storeId } = config.get();
|
|
15
|
+
|
|
16
|
+
const debug = (response: AxiosResponse) => {
|
|
17
|
+
const status = response ? response.status : 0;
|
|
18
|
+
if (!blacklist[url]) {
|
|
19
|
+
blacklist[url] = 1;
|
|
20
|
+
} else {
|
|
21
|
+
blacklist[url] += 1;
|
|
22
|
+
}
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
if (blacklist[url] > 1) {
|
|
25
|
+
blacklist[url] -= 1;
|
|
26
|
+
} else {
|
|
27
|
+
delete blacklist[url];
|
|
28
|
+
}
|
|
29
|
+
}, !status ? 180000 : 6000);
|
|
30
|
+
|
|
31
|
+
logger.info(`${url} : ${status}`);
|
|
32
|
+
if (status >= 400 && status < 500) {
|
|
33
|
+
const { data: resData } = response;
|
|
34
|
+
if (typeof resData === 'object' && resData !== null) {
|
|
35
|
+
const { error, message } = resData;
|
|
36
|
+
if (typeof error === 'string' && error.length && typeof message === 'string') {
|
|
37
|
+
logger.warn(JSON.stringify({ error, message }));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return axios({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
maxRedirects: 2,
|
|
46
|
+
responseType: 'json',
|
|
47
|
+
maxContentLength: 1000000, // 1MB
|
|
48
|
+
url,
|
|
49
|
+
data,
|
|
50
|
+
headers: {
|
|
51
|
+
'X-Store-ID': storeId.toString(),
|
|
52
|
+
},
|
|
53
|
+
// Wait 10s by default and 30s in specific cases
|
|
54
|
+
timeout: isBigTimeout ? 30000 : 10000,
|
|
55
|
+
})
|
|
56
|
+
.then((response) => {
|
|
57
|
+
debug(response);
|
|
58
|
+
return response.data;
|
|
59
|
+
})
|
|
60
|
+
.catch((err) => {
|
|
61
|
+
const { response } = err;
|
|
62
|
+
debug(response);
|
|
63
|
+
if (err.message || err.code) {
|
|
64
|
+
let msg = `Axios error ${err.code}: ${err.message}`;
|
|
65
|
+
if (data) {
|
|
66
|
+
msg += `\n\n${JSON.stringify(data)}`;
|
|
67
|
+
}
|
|
68
|
+
logger.warn(msg);
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
});
|
|
72
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { Request, Response } from 'firebase-functions';
|
|
2
|
+
import type { Applications } from '@cloudcommerce/types';
|
|
3
|
+
import { logger } from 'firebase-functions';
|
|
4
|
+
import Ajv, { ValidateFunction } from 'ajv';
|
|
5
|
+
import api, { ApiError, ApiConfig } from '@cloudcommerce/api';
|
|
6
|
+
import config from '@cloudcommerce/firebase/lib/config';
|
|
7
|
+
import {
|
|
8
|
+
ajv,
|
|
9
|
+
ajvOptions,
|
|
10
|
+
parseAjvErrors,
|
|
11
|
+
sendRequestError,
|
|
12
|
+
} from './ajv';
|
|
13
|
+
import callAppModule from './call-app-module';
|
|
14
|
+
|
|
15
|
+
const ajvAppsResponse = new Ajv({ ...ajvOptions, allErrors: true });
|
|
16
|
+
|
|
17
|
+
// Cache apps list and no params modules results
|
|
18
|
+
const appsCache = {};
|
|
19
|
+
const resultsCache = {};
|
|
20
|
+
|
|
21
|
+
async function runModule(
|
|
22
|
+
apiAuth: { authenticationId: string, apiKey: string },
|
|
23
|
+
params: { [key: string]: any },
|
|
24
|
+
res: Response,
|
|
25
|
+
modName: string,
|
|
26
|
+
validate: ValidateFunction,
|
|
27
|
+
responseValidate: ValidateFunction,
|
|
28
|
+
appId?: any,
|
|
29
|
+
) {
|
|
30
|
+
const respond = (result: any[]) => res.send({
|
|
31
|
+
result,
|
|
32
|
+
meta: params,
|
|
33
|
+
});
|
|
34
|
+
const { storeId } = config.get();
|
|
35
|
+
const isEmptyParams = (!params || !Object.keys(params).length);
|
|
36
|
+
if (!validate(params)) {
|
|
37
|
+
return sendRequestError(res, modName, validate.errors);
|
|
38
|
+
}
|
|
39
|
+
let canCache = true;
|
|
40
|
+
const cacheKey = `${storeId}:${modName}`;
|
|
41
|
+
const listAppsParams: ApiConfig['params'] = {
|
|
42
|
+
state: 'active',
|
|
43
|
+
[`modules.${modName}.enabled`]: true,
|
|
44
|
+
fields: `_id,app_id,version,data,hidden_data,modules.${modName}`,
|
|
45
|
+
};
|
|
46
|
+
if (
|
|
47
|
+
appId
|
|
48
|
+
&& (typeof appId === 'number' || (typeof appId === 'string' && /^\d+$/.test(appId)))
|
|
49
|
+
) {
|
|
50
|
+
canCache = false;
|
|
51
|
+
listAppsParams.app_id = appId;
|
|
52
|
+
listAppsParams.limit = 1;
|
|
53
|
+
}
|
|
54
|
+
let canCacheResults = false;
|
|
55
|
+
if (canCache && isEmptyParams) {
|
|
56
|
+
if (resultsCache[cacheKey]) {
|
|
57
|
+
return respond(resultsCache[cacheKey]);
|
|
58
|
+
}
|
|
59
|
+
canCacheResults = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let appsList: Applications[];
|
|
63
|
+
if (canCache && appsCache[cacheKey]) {
|
|
64
|
+
appsList = appsCache[cacheKey];
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
const { data } = await api.get('applications', {
|
|
68
|
+
...apiAuth,
|
|
69
|
+
params: listAppsParams,
|
|
70
|
+
});
|
|
71
|
+
appsList = data.result;
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
logger.error(err);
|
|
74
|
+
const error = err as ApiError;
|
|
75
|
+
return res.status(500).send({
|
|
76
|
+
status: 500,
|
|
77
|
+
error_code: 'MOD801',
|
|
78
|
+
message: `Store API returned status ${error.statusCode} trying to list apps`,
|
|
79
|
+
more_info: error.data?.user_message?.en_us,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(appsList)) {
|
|
85
|
+
if (!appsList.length) {
|
|
86
|
+
return respond([]);
|
|
87
|
+
}
|
|
88
|
+
if (canCache && !appsCache[cacheKey]) {
|
|
89
|
+
appsCache[cacheKey] = appsList;
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
appsCache[cacheKey] = null;
|
|
92
|
+
delete appsCache[cacheKey];
|
|
93
|
+
}, appsList.length ? 60000 : 3000);
|
|
94
|
+
}
|
|
95
|
+
const moduleReqs: Promise<any>[] = [];
|
|
96
|
+
for (let i = 0; i < appsList.length; i++) {
|
|
97
|
+
const application = appsList[i] as Applications & { modules: { [key: string]: any } };
|
|
98
|
+
if (!application.hidden_data) {
|
|
99
|
+
application.hidden_data = {};
|
|
100
|
+
}
|
|
101
|
+
if (!application.data) {
|
|
102
|
+
application.data = {};
|
|
103
|
+
}
|
|
104
|
+
const appModuleUrl = application.modules[modName].endpoint as string;
|
|
105
|
+
// Handle request with big timeout if proxying one app (by ID) only
|
|
106
|
+
const isBigTimeout = !!(appId);
|
|
107
|
+
const appModuleBody = {
|
|
108
|
+
module: modName,
|
|
109
|
+
params,
|
|
110
|
+
application,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const reqStartTime = Date.now();
|
|
114
|
+
moduleReqs.push(new Promise((resolve) => {
|
|
115
|
+
let response: any;
|
|
116
|
+
let isError = false;
|
|
117
|
+
let errorMessage: string | null = null;
|
|
118
|
+
callAppModule(appModuleUrl, appModuleBody, isBigTimeout)
|
|
119
|
+
.then((appResponse) => {
|
|
120
|
+
response = appResponse;
|
|
121
|
+
})
|
|
122
|
+
.catch((err: any) => {
|
|
123
|
+
response = null;
|
|
124
|
+
isError = true;
|
|
125
|
+
errorMessage = err.message;
|
|
126
|
+
})
|
|
127
|
+
.finally(() => {
|
|
128
|
+
const result = {
|
|
129
|
+
_id: application._id,
|
|
130
|
+
app_id: application.app_id,
|
|
131
|
+
took: Date.now() - reqStartTime,
|
|
132
|
+
version: application.version,
|
|
133
|
+
validated: false,
|
|
134
|
+
response_errors: null,
|
|
135
|
+
error: isError,
|
|
136
|
+
error_message: errorMessage,
|
|
137
|
+
response,
|
|
138
|
+
};
|
|
139
|
+
if (typeof response === 'object' && response !== null) {
|
|
140
|
+
result.validated = responseValidate(response);
|
|
141
|
+
if (!result.validated) {
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
result.response_errors = parseAjvErrors(
|
|
144
|
+
responseValidate.errors,
|
|
145
|
+
ajvAppsResponse,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
resolve(result);
|
|
150
|
+
});
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return Promise.all(moduleReqs).then((results) => {
|
|
155
|
+
if (!results.find(({ response }) => response)) {
|
|
156
|
+
res.status(409);
|
|
157
|
+
canCacheResults = false;
|
|
158
|
+
}
|
|
159
|
+
if (canCacheResults && !resultsCache[cacheKey]) {
|
|
160
|
+
resultsCache[cacheKey] = results;
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
resultsCache[cacheKey] = null;
|
|
163
|
+
delete resultsCache[cacheKey];
|
|
164
|
+
}, 60000);
|
|
165
|
+
}
|
|
166
|
+
return respond(results);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// Shoud never happen
|
|
170
|
+
return res.sendStatus(501);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default (
|
|
174
|
+
modName: string,
|
|
175
|
+
schema: { [key: string]: any },
|
|
176
|
+
responseSchema: { [key: string]: any },
|
|
177
|
+
req: Request,
|
|
178
|
+
res: Response,
|
|
179
|
+
apiAuth: { authenticationId: string, apiKey: string },
|
|
180
|
+
) => {
|
|
181
|
+
const validate = ajv.compile(schema);
|
|
182
|
+
const responseValidate = ajvAppsResponse.compile(responseSchema);
|
|
183
|
+
return {
|
|
184
|
+
GET() {
|
|
185
|
+
runModule(apiAuth, req.query, res, modName, validate, responseValidate);
|
|
186
|
+
},
|
|
187
|
+
POST() {
|
|
188
|
+
runModule(apiAuth, req.body, res, modName, validate, responseValidate, req.query.app_id);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Request, Response } from 'firebase-functions';
|
|
2
|
+
import { schemas } from '../index';
|
|
3
|
+
import handleModule from './handle-module';
|
|
4
|
+
|
|
5
|
+
export default (
|
|
6
|
+
req: Request,
|
|
7
|
+
res: Response,
|
|
8
|
+
apiAuth: { authenticationId: string, apiKey: string },
|
|
9
|
+
) => {
|
|
10
|
+
const { method } = req;
|
|
11
|
+
if (method !== 'POST' && method !== 'GET') {
|
|
12
|
+
return res.sendStatus(405);
|
|
13
|
+
}
|
|
14
|
+
if (
|
|
15
|
+
method === 'POST'
|
|
16
|
+
&& (!req.body || typeof req.body !== 'object' || Array.isArray(req.body))
|
|
17
|
+
) {
|
|
18
|
+
return res.sendStatus(400);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { url } = req;
|
|
22
|
+
if (url.endsWith('.json')) {
|
|
23
|
+
url = url.slice(0, -5);
|
|
24
|
+
}
|
|
25
|
+
const modName = url.split('/')[1];
|
|
26
|
+
const sendSchema = (isResponseSchema = false) => {
|
|
27
|
+
return res.status(200)
|
|
28
|
+
.setHeader('Cache-Control', 'public, max-age=3600')
|
|
29
|
+
.send(schemas[modName][isResponseSchema ? 'response' : 'params']);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (modName === '@checkout') {
|
|
33
|
+
if (url === '/@checkout') {
|
|
34
|
+
return res.status(200).send({
|
|
35
|
+
status: 200,
|
|
36
|
+
message: 'CHECKOUT',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (url === '/@checkout/schema') {
|
|
40
|
+
return sendSchema();
|
|
41
|
+
}
|
|
42
|
+
return res.sendStatus(404);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (schemas[modName]) {
|
|
46
|
+
const { params: schema, response: responseSchema } = schemas[modName];
|
|
47
|
+
if (!schema.$schema) {
|
|
48
|
+
schema.$schema = 'http://json-schema.org/draft-06/schema#';
|
|
49
|
+
schema.title = `Module \`${modName}\`: Params model`;
|
|
50
|
+
}
|
|
51
|
+
if (!responseSchema.$schema) {
|
|
52
|
+
responseSchema.$schema = 'http://json-schema.org/draft-06/schema#';
|
|
53
|
+
responseSchema.title = `Module \`${modName}\`: App response model`;
|
|
54
|
+
}
|
|
55
|
+
if (url === `/${modName}/schema`) {
|
|
56
|
+
return sendSchema();
|
|
57
|
+
}
|
|
58
|
+
if (url === `/${modName}/response_schema`) {
|
|
59
|
+
return sendSchema(true);
|
|
60
|
+
}
|
|
61
|
+
if (url === `/${modName}`) {
|
|
62
|
+
return handleModule(modName, schema, responseSchema, req, res, apiAuth);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return res.sendStatus(404);
|
|
66
|
+
};
|
|
@@ -6,11 +6,18 @@ import { initializeApp } from 'firebase-admin/app';
|
|
|
6
6
|
// eslint-disable-next-line import/no-unresolved
|
|
7
7
|
import { onRequest } from 'firebase-functions/v2/https';
|
|
8
8
|
import config from '@cloudcommerce/firebase/lib/config';
|
|
9
|
+
import getEnv from '@cloudcommerce/firebase/lib/env';
|
|
10
|
+
import serveModulesApi from './firebase/serve-modules-api';
|
|
9
11
|
|
|
10
12
|
initializeApp();
|
|
11
|
-
const
|
|
13
|
+
const { httpsFunctionOptions } = config.get();
|
|
12
14
|
|
|
13
|
-
export const modulesApi = onRequest(
|
|
15
|
+
export const modulesApi = onRequest(httpsFunctionOptions, (req, res) => {
|
|
16
|
+
const { authenticationId, apiKey } = getEnv();
|
|
17
|
+
// Hide API key for security
|
|
14
18
|
process.env.ECOM_API_KEY = '***';
|
|
15
|
-
|
|
19
|
+
serveModulesApi(req, res, {
|
|
20
|
+
authenticationId,
|
|
21
|
+
apiKey,
|
|
22
|
+
});
|
|
16
23
|
});
|
|
@@ -4,18 +4,21 @@ import * as listPayments from '../schemas/list_payments.cjs';
|
|
|
4
4
|
import * as createTransaction from '../schemas/create_transaction.cjs';
|
|
5
5
|
import * as checkout from '../schemas/@checkout.cjs';
|
|
6
6
|
|
|
7
|
+
const schemas = {
|
|
8
|
+
calculate_shipping: calculateShipping,
|
|
9
|
+
apply_discount: applyDiscount,
|
|
10
|
+
list_payments: listPayments,
|
|
11
|
+
create_transaction: createTransaction,
|
|
12
|
+
'@checkout': checkout,
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
export default {
|
|
8
16
|
calculateShipping,
|
|
9
17
|
applyDiscount,
|
|
10
18
|
listPayments,
|
|
11
19
|
createTransaction,
|
|
12
20
|
checkout,
|
|
13
|
-
|
|
14
|
-
schemas: {
|
|
15
|
-
calculate_shipping: calculateShipping,
|
|
16
|
-
apply_discount: applyDiscount,
|
|
17
|
-
list_payments: listPayments,
|
|
18
|
-
create_transaction: createTransaction,
|
|
19
|
-
'@checkout': checkout,
|
|
20
|
-
},
|
|
21
|
+
schemas,
|
|
21
22
|
};
|
|
23
|
+
|
|
24
|
+
export { schemas };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/passport",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.43",
|
|
5
5
|
"description": "E-Com Plus Cloud Commerce customers authentication (passport) API",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@cloudcommerce/api": "workspace:*",
|
|
27
27
|
"@cloudcommerce/firebase": "workspace:*",
|
|
28
|
-
"firebase-admin": "^11.0.
|
|
28
|
+
"firebase-admin": "^11.0.1",
|
|
29
29
|
"firebase-functions": "^3.22.0",
|
|
30
30
|
"source-map-support": "^0.5.21"
|
|
31
31
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/ssr",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.43",
|
|
5
5
|
"description": "E-Com Plus Cloud Commerce storefront SSR",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@cloudcommerce/api": "workspace:*",
|
|
27
27
|
"@cloudcommerce/firebase": "workspace:*",
|
|
28
28
|
"@cloudcommerce/storefront": "workspace:*",
|
|
29
|
-
"firebase-admin": "^11.0.
|
|
29
|
+
"firebase-admin": "^11.0.1",
|
|
30
30
|
"firebase-functions": "^3.22.0",
|
|
31
31
|
"source-map-support": "^0.5.21"
|
|
32
32
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/storefront",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.43",
|
|
5
5
|
"description": "E-Com Plus Cloud Commerce storefront with Astro",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"repository": {
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@cloudcommerce/api": "workspace:*",
|
|
23
|
-
"astro": "1.0.0-rc.
|
|
23
|
+
"astro": "1.0.0-rc.3"
|
|
24
24
|
}
|
|
25
25
|
}
|