@unchainedshop/plugins 4.6.1 → 4.6.2
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/lib/delivery/post/adapter.d.ts +2 -0
- package/lib/delivery/post/adapter.js +22 -0
- package/lib/delivery/post/index.d.ts +4 -0
- package/lib/delivery/post/index.js +10 -0
- package/lib/delivery/send-message/adapter.d.ts +2 -0
- package/lib/delivery/send-message/adapter.js +39 -0
- package/lib/delivery/send-message/index.d.ts +4 -0
- package/lib/delivery/send-message/index.js +10 -0
- package/lib/delivery/stores/adapter.d.ts +2 -0
- package/lib/delivery/stores/adapter.js +42 -0
- package/lib/delivery/stores/index.d.ts +4 -0
- package/lib/delivery/stores/index.js +10 -0
- package/lib/enrollments/licensed/adapter.d.ts +6 -0
- package/lib/enrollments/licensed/adapter.js +51 -0
- package/lib/enrollments/licensed/index.d.ts +4 -0
- package/lib/enrollments/licensed/index.js +10 -0
- package/lib/files/gridfs/api.d.ts +7 -0
- package/lib/files/gridfs/api.js +163 -0
- package/lib/files/minio/adapter.d.ts +4 -0
- package/lib/files/minio/adapter.js +160 -0
- package/lib/files/minio/api.d.ts +2 -0
- package/lib/files/minio/api.js +41 -0
- package/lib/files/temp-upload/api.d.ts +6 -0
- package/lib/files/temp-upload/api.js +49 -0
- package/lib/files/temp-upload/index.d.ts +3 -0
- package/lib/files/temp-upload/index.js +15 -0
- package/lib/filters/local-search/adapter.d.ts +2 -0
- package/lib/filters/local-search/adapter.js +56 -0
- package/lib/filters/local-search/index.d.ts +4 -0
- package/lib/filters/local-search/index.js +16 -0
- package/lib/filters/strict-equal/adapter.d.ts +2 -0
- package/lib/filters/strict-equal/adapter.js +23 -0
- package/lib/filters/strict-equal/index.d.ts +4 -0
- package/lib/filters/strict-equal/index.js +10 -0
- package/lib/payment/apple-iap/api.d.ts +2 -0
- package/lib/payment/apple-iap/api.js +119 -0
- package/lib/payment/cryptopay/adapter.d.ts +2 -0
- package/lib/payment/cryptopay/adapter.js +157 -0
- package/lib/payment/cryptopay/api.d.ts +2 -0
- package/lib/payment/cryptopay/api.js +23 -0
- package/lib/payment/datatrans-v2/adapter.d.ts +2 -0
- package/lib/payment/datatrans-v2/adapter.js +406 -0
- package/lib/payment/datatrans-v2/api.d.ts +2 -0
- package/lib/payment/datatrans-v2/api.js +108 -0
- package/lib/payment/invoice/adapter.d.ts +2 -0
- package/lib/payment/invoice/adapter.js +26 -0
- package/lib/payment/invoice/index.d.ts +4 -0
- package/lib/payment/invoice/index.js +10 -0
- package/lib/payment/invoice-prepaid/adapter.d.ts +2 -0
- package/lib/payment/invoice-prepaid/adapter.js +29 -0
- package/lib/payment/invoice-prepaid/index.d.ts +4 -0
- package/lib/payment/invoice-prepaid/index.js +10 -0
- package/lib/payment/payrexx/adapter.d.ts +2 -0
- package/lib/payment/payrexx/adapter.js +161 -0
- package/lib/payment/payrexx/api.d.ts +2 -0
- package/lib/payment/payrexx/api.js +97 -0
- package/lib/payment/postfinance-checkout/adapter.d.ts +2 -0
- package/lib/payment/postfinance-checkout/adapter.js +189 -0
- package/lib/payment/postfinance-checkout/webhook.d.ts +2 -0
- package/lib/payment/postfinance-checkout/webhook.js +48 -0
- package/lib/payment/saferpay/webhook.d.ts +2 -0
- package/lib/payment/saferpay/webhook.js +64 -0
- package/lib/payment/stripe/adapter.d.ts +2 -0
- package/lib/payment/stripe/adapter.js +121 -0
- package/lib/payment/stripe/api.d.ts +6 -0
- package/lib/payment/stripe/api.js +165 -0
- package/lib/pricing/delivery-swiss-tax/adapter.d.ts +3 -0
- package/lib/pricing/delivery-swiss-tax/adapter.js +62 -0
- package/lib/pricing/delivery-swiss-tax/index.d.ts +4 -0
- package/lib/pricing/delivery-swiss-tax/index.js +10 -0
- package/lib/pricing/discount-100-off/adapter.d.ts +3 -0
- package/lib/pricing/discount-100-off/adapter.js +30 -0
- package/lib/pricing/discount-100-off/index.d.ts +4 -0
- package/lib/pricing/discount-100-off/index.js +10 -0
- package/lib/pricing/discount-half-price-manual/adapter.d.ts +3 -0
- package/lib/pricing/discount-half-price-manual/adapter.js +30 -0
- package/lib/pricing/discount-half-price-manual/index.d.ts +4 -0
- package/lib/pricing/discount-half-price-manual/index.js +10 -0
- package/lib/pricing/free-delivery/adapter.d.ts +3 -0
- package/lib/pricing/free-delivery/adapter.js +27 -0
- package/lib/pricing/free-delivery/index.d.ts +4 -0
- package/lib/pricing/free-delivery/index.js +10 -0
- package/lib/pricing/free-payment/adapter.d.ts +3 -0
- package/lib/pricing/free-payment/adapter.js +27 -0
- package/lib/pricing/free-payment/index.d.ts +4 -0
- package/lib/pricing/free-payment/index.js +10 -0
- package/lib/pricing/order-delivery/adapter.d.ts +3 -0
- package/lib/pricing/order-delivery/adapter.js +33 -0
- package/lib/pricing/order-delivery/index.d.ts +4 -0
- package/lib/pricing/order-delivery/index.js +10 -0
- package/lib/pricing/order-discount/adapter.d.ts +3 -0
- package/lib/pricing/order-discount/adapter.js +74 -0
- package/lib/pricing/order-discount/index.d.ts +4 -0
- package/lib/pricing/order-discount/index.js +10 -0
- package/lib/pricing/order-items/adapter.d.ts +3 -0
- package/lib/pricing/order-items/adapter.js +43 -0
- package/lib/pricing/order-items/index.d.ts +4 -0
- package/lib/pricing/order-items/index.js +10 -0
- package/lib/pricing/order-payment/adapter.d.ts +3 -0
- package/lib/pricing/order-payment/adapter.js +33 -0
- package/lib/pricing/order-payment/index.d.ts +4 -0
- package/lib/pricing/order-payment/index.js +10 -0
- package/lib/pricing/product-catalog-price/adapter.d.ts +3 -0
- package/lib/pricing/product-catalog-price/adapter.js +60 -0
- package/lib/pricing/product-catalog-price/index.d.ts +4 -0
- package/lib/pricing/product-catalog-price/index.js +10 -0
- package/lib/pricing/product-discount/adapter.d.ts +3 -0
- package/lib/pricing/product-discount/adapter.js +69 -0
- package/lib/pricing/product-discount/index.d.ts +4 -0
- package/lib/pricing/product-discount/index.js +10 -0
- package/lib/pricing/product-price-rateconversion/adapter.d.ts +3 -0
- package/lib/pricing/product-price-rateconversion/adapter.js +53 -0
- package/lib/pricing/product-price-rateconversion/index.d.ts +4 -0
- package/lib/pricing/product-price-rateconversion/index.js +10 -0
- package/lib/pricing/product-swiss-tax/adapter.d.ts +3 -0
- package/lib/pricing/product-swiss-tax/adapter.js +76 -0
- package/lib/pricing/product-swiss-tax/index.d.ts +4 -0
- package/lib/pricing/product-swiss-tax/index.js +10 -0
- package/lib/quotations/manual/adapter.d.ts +2 -0
- package/lib/quotations/manual/adapter.js +19 -0
- package/lib/quotations/manual/index.d.ts +4 -0
- package/lib/quotations/manual/index.js +10 -0
- package/lib/warehousing/erc-metadata/api.d.ts +12 -0
- package/lib/warehousing/erc-metadata/api.js +43 -0
- package/lib/warehousing/erc-metadata/index.d.ts +3 -0
- package/lib/warehousing/erc-metadata/index.js +15 -0
- package/lib/warehousing/eth-minter/adapter.d.ts +2 -0
- package/lib/warehousing/eth-minter/adapter.js +119 -0
- package/lib/warehousing/eth-minter/index.d.ts +4 -0
- package/lib/warehousing/eth-minter/index.js +10 -0
- package/lib/warehousing/store/adapter.d.ts +2 -0
- package/lib/warehousing/store/adapter.js +33 -0
- package/lib/warehousing/store/index.d.ts +4 -0
- package/lib/warehousing/store/index.js +10 -0
- package/lib/worker/budgetsms/adapter.d.ts +22 -0
- package/lib/worker/budgetsms/adapter.js +169 -0
- package/lib/worker/budgetsms/index.d.ts +4 -0
- package/lib/worker/budgetsms/index.js +10 -0
- package/lib/worker/bulk-export/adapter.d.ts +3 -0
- package/lib/worker/bulk-export/adapter.js +64 -0
- package/lib/worker/bulk-export/index.d.ts +4 -0
- package/lib/worker/bulk-export/index.js +10 -0
- package/lib/worker/bulk-import/adapter.d.ts +3 -0
- package/lib/worker/bulk-import/adapter.js +65 -0
- package/lib/worker/bulk-import/api.d.ts +7 -0
- package/lib/worker/bulk-import/api.js +53 -0
- package/lib/worker/bulk-import/index.d.ts +4 -0
- package/lib/worker/bulk-import/index.js +18 -0
- package/lib/worker/bulkgate/adapter.d.ts +12 -0
- package/lib/worker/bulkgate/adapter.js +91 -0
- package/lib/worker/bulkgate/index.d.ts +4 -0
- package/lib/worker/bulkgate/index.js +10 -0
- package/lib/worker/email/adapter.d.ts +9 -0
- package/lib/worker/email/adapter.js +150 -0
- package/lib/worker/email/index.d.ts +4 -0
- package/lib/worker/email/index.js +10 -0
- package/lib/worker/enrollment-order-generator/adapter.d.ts +4 -0
- package/lib/worker/enrollment-order-generator/adapter.js +77 -0
- package/lib/worker/enrollment-order-generator/index.d.ts +4 -0
- package/lib/worker/enrollment-order-generator/index.js +13 -0
- package/lib/worker/error-notifications/adapter.d.ts +10 -0
- package/lib/worker/error-notifications/adapter.js +62 -0
- package/lib/worker/error-notifications/index.d.ts +4 -0
- package/lib/worker/error-notifications/index.js +13 -0
- package/lib/worker/export-token/adapter.d.ts +4 -0
- package/lib/worker/export-token/adapter.js +25 -0
- package/lib/worker/export-token/index.d.ts +4 -0
- package/lib/worker/export-token/index.js +13 -0
- package/lib/worker/external/adapter.d.ts +3 -0
- package/lib/worker/external/adapter.js +13 -0
- package/lib/worker/external/index.d.ts +4 -0
- package/lib/worker/external/index.js +10 -0
- package/lib/worker/heartbeat/adapter.d.ts +8 -0
- package/lib/worker/heartbeat/adapter.js +26 -0
- package/lib/worker/heartbeat/index.d.ts +4 -0
- package/lib/worker/heartbeat/index.js +10 -0
- package/lib/worker/http-request/adapter.d.ts +8 -0
- package/lib/worker/http-request/adapter.js +55 -0
- package/lib/worker/http-request/index.d.ts +4 -0
- package/lib/worker/http-request/index.js +10 -0
- package/lib/worker/message/adapter.d.ts +11 -0
- package/lib/worker/message/adapter.js +42 -0
- package/lib/worker/message/index.d.ts +4 -0
- package/lib/worker/message/index.js +10 -0
- package/lib/worker/push-notification/adapter.d.ts +9 -0
- package/lib/worker/push-notification/adapter.js +87 -0
- package/lib/worker/push-notification/index.d.ts +4 -0
- package/lib/worker/push-notification/index.js +10 -0
- package/lib/worker/twilio/adapter.d.ts +8 -0
- package/lib/worker/twilio/adapter.js +59 -0
- package/lib/worker/twilio/index.d.ts +4 -0
- package/lib/worker/twilio/index.js +10 -0
- package/lib/worker/update-coinbase-rates/adapter.d.ts +4 -0
- package/lib/worker/update-coinbase-rates/adapter.js +61 -0
- package/lib/worker/update-coinbase-rates/index.d.ts +4 -0
- package/lib/worker/update-coinbase-rates/index.js +13 -0
- package/lib/worker/update-ecb-rates/adapter.d.ts +4 -0
- package/lib/worker/update-ecb-rates/adapter.js +94 -0
- package/lib/worker/update-ecb-rates/index.d.ts +4 -0
- package/lib/worker/update-ecb-rates/index.js +13 -0
- package/lib/worker/update-token-ownership/adapter.d.ts +5 -0
- package/lib/worker/update-token-ownership/adapter.js +58 -0
- package/lib/worker/update-token-ownership/index.d.ts +4 -0
- package/lib/worker/update-token-ownership/index.js +13 -0
- package/lib/worker/zombie-killer/adapter.d.ts +13 -0
- package/lib/worker/zombie-killer/adapter.js +90 -0
- package/lib/worker/zombie-killer/index.d.ts +4 -0
- package/lib/worker/zombie-killer/index.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { Roles } from '@unchainedshop/roles';
|
|
3
|
+
const logger = createLogger('unchained:bulk-import');
|
|
4
|
+
export async function bulkImportHandler(request, context) {
|
|
5
|
+
try {
|
|
6
|
+
const hasPermission = await Roles.userHasPermission(context, 'bulkImport', []);
|
|
7
|
+
if (!hasPermission) {
|
|
8
|
+
return Response.json({ error: 'Permission denied', message: 'User does not have bulkImport permission' }, { status: 403 });
|
|
9
|
+
}
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
const input = {
|
|
12
|
+
createShouldUpsertIfIDExists: url.searchParams.get('createShouldUpsertIfIDExists') === 'true',
|
|
13
|
+
updateShouldUpsertIfIDNotExists: url.searchParams.get('updateShouldUpsertIfIDNotExists') === 'true',
|
|
14
|
+
skipCacheInvalidation: url.searchParams.get('skipCacheInvalidation') === 'true',
|
|
15
|
+
remoteAddress: context.remoteAddress,
|
|
16
|
+
};
|
|
17
|
+
const date = new Date().toISOString();
|
|
18
|
+
const stream = context.rawRequest;
|
|
19
|
+
if (!stream) {
|
|
20
|
+
return Response.json({ error: 'No request stream available' }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
const file = await context.services.files.uploadFileFromStream({
|
|
23
|
+
directoryName: 'bulk-import-streams',
|
|
24
|
+
rawFile: Promise.resolve({
|
|
25
|
+
filename: `${date}.json`,
|
|
26
|
+
createReadStream: () => stream,
|
|
27
|
+
}),
|
|
28
|
+
chunkSizeBytes: 1024 * 1024 * 5,
|
|
29
|
+
meta: {},
|
|
30
|
+
});
|
|
31
|
+
const validationStream = await context.services.files.createDownloadStream({
|
|
32
|
+
fileId: file._id,
|
|
33
|
+
});
|
|
34
|
+
if (!validationStream) {
|
|
35
|
+
throw new Error('Could not create download stream from uploaded file');
|
|
36
|
+
}
|
|
37
|
+
await context.bulkImporter.validateEventStream(validationStream);
|
|
38
|
+
input.payloadId = file._id;
|
|
39
|
+
input.payloadSize = file.size;
|
|
40
|
+
const purgedInput = Object.fromEntries(Object.entries(input).filter(([, value]) => Boolean(value)));
|
|
41
|
+
const work = await context.modules.worker.addWork({
|
|
42
|
+
type: 'BULK_IMPORT',
|
|
43
|
+
input: purgedInput,
|
|
44
|
+
retries: 0,
|
|
45
|
+
priority: 10,
|
|
46
|
+
});
|
|
47
|
+
return Response.json(work, { status: 200 });
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
logger.error(e);
|
|
51
|
+
return Response.json({ name: e.name, code: e.code, message: e.message }, { status: 503 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BulkImportWorker } from "./adapter.js";
|
|
2
|
+
import { bulkImportHandler } from "./api.js";
|
|
3
|
+
const { BULK_IMPORT_API_PATH = '/bulk-import' } = process.env;
|
|
4
|
+
export const BulkImportPlugin = {
|
|
5
|
+
key: 'shop.unchained.worker-plugin.bulk-import',
|
|
6
|
+
label: 'Bulk Import Worker Plugin',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
adapters: [BulkImportWorker],
|
|
9
|
+
routes: [
|
|
10
|
+
{
|
|
11
|
+
path: BULK_IMPORT_API_PATH,
|
|
12
|
+
method: 'POST',
|
|
13
|
+
handler: bulkImportHandler,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
export default BulkImportPlugin;
|
|
18
|
+
export { BulkImportWorker } from "./adapter.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IWorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
export declare const BulkGateWorker: IWorkerAdapter<{
|
|
3
|
+
from?: string;
|
|
4
|
+
to?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
unicode?: boolean;
|
|
7
|
+
country?: string;
|
|
8
|
+
schedule?: string | number;
|
|
9
|
+
promotional?: boolean;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}, any>;
|
|
12
|
+
export default BulkGateWorker;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
const { BULKGATE_APPLICATION_ID, BULKGATE_APPLICATION_TOKEN } = process.env;
|
|
3
|
+
export const BulkGateWorker = {
|
|
4
|
+
...WorkerAdapter,
|
|
5
|
+
key: 'shop.unchained.worker-plugin.bulkgate',
|
|
6
|
+
label: 'Send SMS through BulkGate',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'BULKGATE',
|
|
9
|
+
doWork: async ({ from, to, text, unicode = false, country, schedule, promotional = false, ...params }) => {
|
|
10
|
+
try {
|
|
11
|
+
const url = promotional
|
|
12
|
+
? 'https://portal.bulkgate.com/api/1.0/simple/promotional'
|
|
13
|
+
: 'https://portal.bulkgate.com/api/1.0/simple/transactional';
|
|
14
|
+
const requestBody = {
|
|
15
|
+
application_id: BULKGATE_APPLICATION_ID,
|
|
16
|
+
application_token: BULKGATE_APPLICATION_TOKEN,
|
|
17
|
+
number: to,
|
|
18
|
+
text: text || '',
|
|
19
|
+
unicode: unicode,
|
|
20
|
+
sender_id: from ? 'gText' : 'gSystem',
|
|
21
|
+
sender_id_value: from || '',
|
|
22
|
+
...(country && { country }),
|
|
23
|
+
...(schedule && { schedule }),
|
|
24
|
+
...params,
|
|
25
|
+
};
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'Cache-Control': 'no-cache',
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(requestBody),
|
|
33
|
+
});
|
|
34
|
+
const responseData = await response.json();
|
|
35
|
+
if (responseData.error || responseData.type) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
error: {
|
|
39
|
+
name: 'BULKGATE_ERROR',
|
|
40
|
+
message: responseData.error ||
|
|
41
|
+
`Failed to send ${promotional ? 'promotional' : 'transactional'} SMS`,
|
|
42
|
+
type: responseData.type || null,
|
|
43
|
+
code: responseData.code || response.status,
|
|
44
|
+
detail: responseData.detail || null,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (responseData.data) {
|
|
49
|
+
if (!promotional && responseData.data.status === 'accepted') {
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
result: {
|
|
53
|
+
sms_id: responseData.data.sms_id,
|
|
54
|
+
price: responseData.data.price,
|
|
55
|
+
credit: responseData.data.credit,
|
|
56
|
+
number: responseData.data.number,
|
|
57
|
+
status: responseData.data.status,
|
|
58
|
+
},
|
|
59
|
+
error: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (promotional || responseData.data.status) {
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
result: responseData.data,
|
|
66
|
+
error: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: {
|
|
73
|
+
name: 'BULKGATE_ERROR',
|
|
74
|
+
message: 'Unexpected response format from BulkGate API',
|
|
75
|
+
response: responseData,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: {
|
|
83
|
+
name: err.name,
|
|
84
|
+
message: err.message,
|
|
85
|
+
stack: err.stack,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
export default BulkGateWorker;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {} from '@unchainedshop/core';
|
|
2
|
+
import { BulkGateWorker } from "./adapter.js";
|
|
3
|
+
export const BulkGatePlugin = {
|
|
4
|
+
key: 'shop.unchained.worker-plugin.bulkgate',
|
|
5
|
+
label: 'BulkGate SMS Worker Plugin',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
adapters: [BulkGateWorker],
|
|
8
|
+
};
|
|
9
|
+
export default BulkGatePlugin;
|
|
10
|
+
export { BulkGateWorker } from "./adapter.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type IWorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
export declare const checkEmailInterceptionEnabled: () => boolean;
|
|
3
|
+
export declare const EmailWorkerPlugin: IWorkerAdapter<{
|
|
4
|
+
from?: string;
|
|
5
|
+
to?: string;
|
|
6
|
+
subject?: string;
|
|
7
|
+
[x: string]: any;
|
|
8
|
+
}, any>;
|
|
9
|
+
export default EmailWorkerPlugin;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { writeFile, mkdtemp } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
7
|
+
const logger = createLogger('unchained:worker:email');
|
|
8
|
+
export const checkEmailInterceptionEnabled = () => {
|
|
9
|
+
return process.env.NODE_ENV !== 'production' && !process.env.UNCHAINED_DISABLE_EMAIL_INTERCEPTION;
|
|
10
|
+
};
|
|
11
|
+
const escapeHtml = (str) => {
|
|
12
|
+
if (!str)
|
|
13
|
+
return '';
|
|
14
|
+
return str
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"')
|
|
19
|
+
.replace(/'/g, ''');
|
|
20
|
+
};
|
|
21
|
+
const buildLink = async ({ filename, content, href, contentType, encoding, path }) => {
|
|
22
|
+
const safeFilename = escapeHtml(filename);
|
|
23
|
+
if (path) {
|
|
24
|
+
const safePath = escapeHtml(path.startsWith('/') ? path : `${process.cwd()}/${path}`);
|
|
25
|
+
return `<a href="file:/${safePath}">${safeFilename}</a>`;
|
|
26
|
+
}
|
|
27
|
+
if (href) {
|
|
28
|
+
const safeHref = escapeHtml(href);
|
|
29
|
+
return `<a href="${safeHref}">${safeFilename}</a>`;
|
|
30
|
+
}
|
|
31
|
+
if (content && encoding === 'base64') {
|
|
32
|
+
const safeContentType = escapeHtml(contentType);
|
|
33
|
+
return `<a target="_blank" href="${`data:${safeContentType};base64,${content}`}">${safeFilename}</a>`;
|
|
34
|
+
}
|
|
35
|
+
return '';
|
|
36
|
+
};
|
|
37
|
+
let nodemailer;
|
|
38
|
+
try {
|
|
39
|
+
const nodemailerModule = await import('nodemailer');
|
|
40
|
+
nodemailer = nodemailerModule.default;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
logger.warn(`optional peer npm package 'nodemailer' not installed, emails can't be sent`);
|
|
44
|
+
}
|
|
45
|
+
const openInBrowser = async (options) => {
|
|
46
|
+
const command = {
|
|
47
|
+
darwin: 'open',
|
|
48
|
+
win32: 'explorer.exe',
|
|
49
|
+
linux: 'xdg-open',
|
|
50
|
+
}[process.platform];
|
|
51
|
+
if (!command) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const messageBody = options.html || escapeHtml(options.text)?.replace(/(\r\n|\n|\r)/gm, '<br/>');
|
|
55
|
+
const attachmentLinks = await Promise.all((options.attachments || []).map(buildLink));
|
|
56
|
+
const content = `
|
|
57
|
+
<!DOCTYPE html>
|
|
58
|
+
<html lang="en" xmlns="https://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
59
|
+
<head>
|
|
60
|
+
<meta charset="UTF-8"/>
|
|
61
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
<b>From: </b>${escapeHtml(options.from)}<br/>
|
|
65
|
+
<b>To: </b>${escapeHtml(options.to)}<br/>
|
|
66
|
+
<b>Cc: </b>${escapeHtml(options.cc)}<br/>
|
|
67
|
+
<b>Bcc: </b>${escapeHtml(options.bcc)}<br/>
|
|
68
|
+
<b>Reply-To: </b>${escapeHtml(options.replyTo)}<br/>
|
|
69
|
+
<br/>
|
|
70
|
+
<b>subject: </b>${escapeHtml(options.subject)}<br/>
|
|
71
|
+
<b>attachments: </b>${attachmentLinks.join(', ')}<br/>
|
|
72
|
+
<hr/>
|
|
73
|
+
${messageBody}
|
|
74
|
+
</body>
|
|
75
|
+
</html>`;
|
|
76
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'unchained-email-'));
|
|
77
|
+
const tempFile = join(tempDir, 'email-preview.html');
|
|
78
|
+
await writeFile(tempFile, content, 'utf8');
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const child = spawn(command, [tempFile], {
|
|
81
|
+
detached: true,
|
|
82
|
+
stdio: 'ignore',
|
|
83
|
+
});
|
|
84
|
+
child.unref();
|
|
85
|
+
resolve(true);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
export const EmailWorkerPlugin = {
|
|
89
|
+
...WorkerAdapter,
|
|
90
|
+
key: 'shop.unchained.worker-plugin.email',
|
|
91
|
+
label: 'Send a Mail through Nodemailer',
|
|
92
|
+
version: '1.0.0',
|
|
93
|
+
type: 'EMAIL',
|
|
94
|
+
doWork: async ({ from, to, subject, ...rest }) => {
|
|
95
|
+
if (!to) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: {
|
|
99
|
+
name: 'RECIPIENT_REQUIRED',
|
|
100
|
+
message: 'EMAIL requires a to',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const sendMailOptions = {
|
|
106
|
+
from,
|
|
107
|
+
to,
|
|
108
|
+
subject,
|
|
109
|
+
...rest,
|
|
110
|
+
};
|
|
111
|
+
if (checkEmailInterceptionEnabled()) {
|
|
112
|
+
const opened = await openInBrowser(sendMailOptions);
|
|
113
|
+
return {
|
|
114
|
+
success: opened,
|
|
115
|
+
result: opened ? { intercepted: true } : undefined,
|
|
116
|
+
error: !opened ? { message: "Interception failed due to missing package 'open'" } : undefined,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (!process.env.MAIL_URL) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
error: { name: 'NO_MAIL_URL_SET', message: 'MAIL_URL is not set' },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (!nodemailer) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: {
|
|
129
|
+
name: 'NODEMAILER_NOT_INSTALLED',
|
|
130
|
+
message: 'npm dependency nodemailer is not installed, please install it to use email features',
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const transporter = nodemailer.createTransport(process.env.MAIL_URL);
|
|
135
|
+
const result = await transporter.sendMail(sendMailOptions);
|
|
136
|
+
return { success: true, result };
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: {
|
|
142
|
+
name: err.name,
|
|
143
|
+
message: err.message,
|
|
144
|
+
stack: err.stack,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
export default EmailWorkerPlugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {} from '@unchainedshop/core';
|
|
2
|
+
import { EmailWorkerPlugin as EmailWorker } from "./adapter.js";
|
|
3
|
+
export const EmailPlugin = {
|
|
4
|
+
key: 'shop.unchained.worker-plugin.email',
|
|
5
|
+
label: 'Email Worker Plugin',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
adapters: [EmailWorker],
|
|
8
|
+
};
|
|
9
|
+
export default EmailPlugin;
|
|
10
|
+
export { EmailWorkerPlugin, checkEmailInterceptionEnabled } from "./adapter.js";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { enrollmentsSettings, EnrollmentStatus } from '@unchainedshop/core-enrollments';
|
|
2
|
+
import { EnrollmentDirector, WorkerDirector, WorkerAdapter, } from '@unchainedshop/core';
|
|
3
|
+
export const GenerateOrderWorker = {
|
|
4
|
+
...WorkerAdapter,
|
|
5
|
+
key: 'shop.unchained.worker-plugin.generate-enrollment-orders',
|
|
6
|
+
label: 'Generates new Orders from Enrollments',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
type: 'ENROLLMENT_ORDER_GENERATOR',
|
|
9
|
+
doWork: async (input, unchainedAPI) => {
|
|
10
|
+
const { modules, services } = unchainedAPI;
|
|
11
|
+
const enrollments = await modules.enrollments.findEnrollments({
|
|
12
|
+
status: [EnrollmentStatus.ACTIVE, EnrollmentStatus.PAUSED],
|
|
13
|
+
});
|
|
14
|
+
const errors = (await Promise.all(enrollments.map(async (enrollment) => {
|
|
15
|
+
try {
|
|
16
|
+
const product = await unchainedAPI.modules.products.findProduct({
|
|
17
|
+
productId: enrollment.productId,
|
|
18
|
+
});
|
|
19
|
+
const director = await EnrollmentDirector.actions({ enrollment, product: product }, unchainedAPI);
|
|
20
|
+
const period = await director.nextPeriod();
|
|
21
|
+
if (period) {
|
|
22
|
+
if (period.isTrial) {
|
|
23
|
+
await modules.enrollments.addEnrollmentPeriod(enrollment._id, {
|
|
24
|
+
...period,
|
|
25
|
+
});
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const configuration = await director.configurationForOrder({
|
|
29
|
+
period,
|
|
30
|
+
});
|
|
31
|
+
if (configuration) {
|
|
32
|
+
const order = await services.enrollments.generateOrderFromEnrollment(enrollment, configuration);
|
|
33
|
+
if (order) {
|
|
34
|
+
await modules.enrollments.addEnrollmentPeriod(enrollment._id, {
|
|
35
|
+
...period,
|
|
36
|
+
orderId: order._id,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return {
|
|
44
|
+
name: e.name,
|
|
45
|
+
message: e.message,
|
|
46
|
+
stack: e.stack,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}))).filter(Boolean);
|
|
51
|
+
if (errors.length) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: {
|
|
55
|
+
name: 'SOME_ENROLLMENTS_COULD_NOT_PROCESS',
|
|
56
|
+
message: 'Some errors have been reported during order generation',
|
|
57
|
+
logs: errors,
|
|
58
|
+
},
|
|
59
|
+
result: {},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
result: input,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const configureGenerateOrderAutoscheduling = () => {
|
|
69
|
+
if (enrollmentsSettings.autoSchedulingSchedule) {
|
|
70
|
+
WorkerDirector.configureAutoscheduling({
|
|
71
|
+
type: GenerateOrderWorker.type,
|
|
72
|
+
schedule: enrollmentsSettings.autoSchedulingSchedule,
|
|
73
|
+
retries: 5,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export default GenerateOrderWorker;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {} from '@unchainedshop/core';
|
|
2
|
+
import { GenerateOrderWorker, configureGenerateOrderAutoscheduling } from "./adapter.js";
|
|
3
|
+
export const EnrollmentOrderGeneratorPlugin = {
|
|
4
|
+
key: 'shop.unchained.worker.enrollment-order-generator',
|
|
5
|
+
label: 'Enrollment Order Generator Worker Plugin',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
adapters: [GenerateOrderWorker],
|
|
8
|
+
onRegister: () => {
|
|
9
|
+
configureGenerateOrderAutoscheduling();
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default EnrollmentOrderGeneratorPlugin;
|
|
13
|
+
export { GenerateOrderWorker, configureGenerateOrderAutoscheduling } from "./adapter.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type IWorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
interface Arg {
|
|
3
|
+
secondsPassed?: number;
|
|
4
|
+
}
|
|
5
|
+
interface Result {
|
|
6
|
+
forked?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const ErrorNotifications: IWorkerAdapter<Arg, Result>;
|
|
9
|
+
export default ErrorNotifications;
|
|
10
|
+
export declare const configureErrorNotificationsAutoscheduling: () => void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { WorkStatus } from '@unchainedshop/core-worker';
|
|
2
|
+
import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
|
|
3
|
+
const everyDayAtFourInTheNight = schedule.parse.cron('0 3 * * *');
|
|
4
|
+
export const ErrorNotifications = {
|
|
5
|
+
...WorkerAdapter,
|
|
6
|
+
key: 'shop.unchained.worker.error-notifications',
|
|
7
|
+
label: 'Send a daily report about errors that failed permanently/unrecoverable',
|
|
8
|
+
version: '1.0',
|
|
9
|
+
type: 'ERROR_NOTIFICATIONS',
|
|
10
|
+
doWork: async (args, context, workId) => {
|
|
11
|
+
const work = (await context.modules.worker.findWork({ workId }));
|
|
12
|
+
const secondsPassed = args?.secondsPassed || 60;
|
|
13
|
+
const to = new Date();
|
|
14
|
+
const from = new Date(new Date(work.scheduled).getTime() - secondsPassed * 1000);
|
|
15
|
+
try {
|
|
16
|
+
const workItems = (await context.modules.worker.findWorkQueue({
|
|
17
|
+
scheduled: {
|
|
18
|
+
end: to,
|
|
19
|
+
start: from,
|
|
20
|
+
},
|
|
21
|
+
types: undefined,
|
|
22
|
+
status: [WorkStatus.FAILED],
|
|
23
|
+
})).filter((workItem) => workItem.type !== ErrorNotifications.type && workItem.retries === 0);
|
|
24
|
+
if (!workItems.length) {
|
|
25
|
+
return { success: true, result: {} };
|
|
26
|
+
}
|
|
27
|
+
const message = await context.modules.worker.addWork({
|
|
28
|
+
type: 'MESSAGE',
|
|
29
|
+
retries: 0,
|
|
30
|
+
input: {
|
|
31
|
+
workItems,
|
|
32
|
+
template: 'ERROR_REPORT',
|
|
33
|
+
},
|
|
34
|
+
originalWorkId: workId,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
result: { forked: message._id },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: {
|
|
45
|
+
name: err.name,
|
|
46
|
+
message: err.message,
|
|
47
|
+
stack: err.stack,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export default ErrorNotifications;
|
|
54
|
+
const ONE_DAY_IN_SECONDS = 24 * 60 * 60;
|
|
55
|
+
export const configureErrorNotificationsAutoscheduling = () => {
|
|
56
|
+
WorkerDirector.configureAutoscheduling({
|
|
57
|
+
type: ErrorNotifications.type,
|
|
58
|
+
schedule: everyDayAtFourInTheNight,
|
|
59
|
+
input: async () => ({ secondsPassed: ONE_DAY_IN_SECONDS }),
|
|
60
|
+
retries: 0,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {} from '@unchainedshop/core';
|
|
2
|
+
import { ErrorNotifications, configureErrorNotificationsAutoscheduling } from "./adapter.js";
|
|
3
|
+
export const ErrorNotificationsPlugin = {
|
|
4
|
+
key: 'shop.unchained.worker.error-notifications',
|
|
5
|
+
label: 'Error Notifications Worker Plugin',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
adapters: [ErrorNotifications],
|
|
8
|
+
onRegister: () => {
|
|
9
|
+
configureErrorNotificationsAutoscheduling();
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default ErrorNotificationsPlugin;
|
|
13
|
+
export * from "./adapter.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WorkerEventTypes } from '@unchainedshop/core-worker';
|
|
2
|
+
import { WorkerAdapter } from '@unchainedshop/core';
|
|
3
|
+
import { subscribe } from '@unchainedshop/events';
|
|
4
|
+
export const ExportTokenWorker = {
|
|
5
|
+
...WorkerAdapter,
|
|
6
|
+
key: 'shop.unchained.worker-plugin.export-token',
|
|
7
|
+
label: 'External worker to hold the state of the minting/export process of tokens',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
type: 'EXPORT_TOKEN',
|
|
10
|
+
external: true,
|
|
11
|
+
async doWork() {
|
|
12
|
+
throw new Error('Cannot do work for external workers');
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export const configureExportToken = (unchainedAPI) => {
|
|
16
|
+
subscribe(WorkerEventTypes.FINISHED, async ({ payload: work }) => {
|
|
17
|
+
if (work.type === 'EXPORT_TOKEN' && work.success) {
|
|
18
|
+
await unchainedAPI.modules.warehousing.updateTokenOwnership({
|
|
19
|
+
tokenId: work.input.token._id,
|
|
20
|
+
walletAddress: work.input.recipientWalletAddress,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export default ExportTokenWorker;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {} from '@unchainedshop/core';
|
|
2
|
+
import { ExportTokenWorker, configureExportToken } from "./adapter.js";
|
|
3
|
+
export const ExportTokenPlugin = {
|
|
4
|
+
key: 'shop.unchained.worker.export-token',
|
|
5
|
+
label: 'Export Token Worker Plugin',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
adapters: [ExportTokenWorker],
|
|
8
|
+
onRegister: (unchainedAPI) => {
|
|
9
|
+
configureExportToken(unchainedAPI);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default ExportTokenPlugin;
|
|
13
|
+
export { ExportTokenWorker, configureExportToken } from "./adapter.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
export const ExternalWorkerPlugin = {
|
|
3
|
+
...WorkerAdapter,
|
|
4
|
+
key: 'shop.unchained.worker-plugin.external',
|
|
5
|
+
label: 'External plugin as a placeholder for workers who interact with the system only via GraphQL',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
type: 'EXTERNAL',
|
|
8
|
+
external: true,
|
|
9
|
+
async doWork() {
|
|
10
|
+
throw new Error('Cannot do work for external workers');
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default ExternalWorkerPlugin;
|