@unchainedshop/plugins 1.1.3

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 (76) hide show
  1. package/lib/events/node-event-emitter.d.ts +1 -0
  2. package/lib/events/node-event-emitter.js +16 -0
  3. package/lib/events/node-event-emitter.js.map +1 -0
  4. package/lib/events/redis.d.ts +1 -0
  5. package/lib/events/redis.js +24 -0
  6. package/lib/events/redis.js.map +1 -0
  7. package/lib/files/gridfs/gridfs-adapter.d.ts +2 -0
  8. package/lib/files/gridfs/gridfs-adapter.js +106 -0
  9. package/lib/files/gridfs/gridfs-adapter.js.map +1 -0
  10. package/lib/files/gridfs/gridfs-webhook.d.ts +1 -0
  11. package/lib/files/gridfs/gridfs-webhook.js +54 -0
  12. package/lib/files/gridfs/gridfs-webhook.js.map +1 -0
  13. package/lib/files/gridfs/index.d.ts +8 -0
  14. package/lib/files/gridfs/index.js +21 -0
  15. package/lib/files/gridfs/index.js.map +1 -0
  16. package/lib/files/gridfs/promisePipe.d.ts +2 -0
  17. package/lib/files/gridfs/promisePipe.js +21 -0
  18. package/lib/files/gridfs/promisePipe.js.map +1 -0
  19. package/lib/files/gridfs/sign.d.ts +2 -0
  20. package/lib/files/gridfs/sign.js +12 -0
  21. package/lib/files/gridfs/sign.js.map +1 -0
  22. package/lib/files/minio/minio-adapter.d.ts +4 -0
  23. package/lib/files/minio/minio-adapter.js +166 -0
  24. package/lib/files/minio/minio-adapter.js.map +1 -0
  25. package/lib/files/minio/minio-webhook.d.ts +1 -0
  26. package/lib/files/minio/minio-webhook.js +45 -0
  27. package/lib/files/minio/minio-webhook.js.map +1 -0
  28. package/lib/worker/BulkImportWorker.d.ts +2 -0
  29. package/lib/worker/BulkImportWorker.js +91 -0
  30. package/lib/worker/BulkImportWorker.js.map +1 -0
  31. package/lib/worker/GenerateOrderWorker.d.ts +1 -0
  32. package/lib/worker/GenerateOrderWorker.js +110 -0
  33. package/lib/worker/GenerateOrderWorker.js.map +1 -0
  34. package/lib/worker/MessageWorker.d.ts +10 -0
  35. package/lib/worker/MessageWorker.js +45 -0
  36. package/lib/worker/MessageWorker.js.map +1 -0
  37. package/lib/worker/ZombieKillerWorker.d.ts +7 -0
  38. package/lib/worker/ZombieKillerWorker.js +88 -0
  39. package/lib/worker/ZombieKillerWorker.js.map +1 -0
  40. package/lib/worker/email.d.ts +1 -0
  41. package/lib/worker/email.js +45 -0
  42. package/lib/worker/email.js.map +1 -0
  43. package/lib/worker/external.d.ts +2 -0
  44. package/lib/worker/external.js +13 -0
  45. package/lib/worker/external.js.map +1 -0
  46. package/lib/worker/heartbeat.d.ts +1 -0
  47. package/lib/worker/heartbeat.js +32 -0
  48. package/lib/worker/heartbeat.js.map +1 -0
  49. package/lib/worker/http-request.d.ts +8 -0
  50. package/lib/worker/http-request.js +61 -0
  51. package/lib/worker/http-request.js.map +1 -0
  52. package/lib/worker/sms.d.ts +1 -0
  53. package/lib/worker/sms.js +69 -0
  54. package/lib/worker/sms.js.map +1 -0
  55. package/package.json +44 -0
  56. package/readme.md +1 -0
  57. package/src/events/node-event-emitter.ts +20 -0
  58. package/src/events/redis.ts +30 -0
  59. package/src/files/gridfs/gridfs-adapter.ts +136 -0
  60. package/src/files/gridfs/gridfs-webhook.js +60 -0
  61. package/src/files/gridfs/index.js +21 -0
  62. package/src/files/gridfs/promisePipe.js +19 -0
  63. package/src/files/gridfs/sign.js +16 -0
  64. package/src/files/minio/minio-adapter.ts +215 -0
  65. package/src/files/minio/minio-webhook.js +51 -0
  66. package/src/worker/BulkImportWorker.ts +117 -0
  67. package/src/worker/GenerateOrderWorker.ts +156 -0
  68. package/src/worker/MessageWorker.ts +62 -0
  69. package/src/worker/ZombieKillerWorker.ts +114 -0
  70. package/src/worker/email.ts +60 -0
  71. package/src/worker/external.ts +17 -0
  72. package/src/worker/heartbeat.ts +45 -0
  73. package/src/worker/http-request.ts +75 -0
  74. package/src/worker/sms.ts +85 -0
  75. package/tsconfig.build.json +26 -0
  76. package/tsconfig.json +8 -0
@@ -0,0 +1,51 @@
1
+ import { useMiddlewareWithCurrentContext } from 'meteor/unchained:api';
2
+ import { log, LogLevel } from '@unchainedshop/logger';
3
+ import bodyParser from 'body-parser';
4
+
5
+ const { MINIO_WEBHOOK_AUTH_TOKEN } = process.env;
6
+
7
+ const isAuthorized = ({ authorization = '' }) => {
8
+ const [type, token] = authorization.split(' ');
9
+ return type === 'Bearer' && token === MINIO_WEBHOOK_AUTH_TOKEN;
10
+ };
11
+
12
+ useMiddlewareWithCurrentContext(
13
+ '/minio/',
14
+ bodyParser.json({
15
+ strict: false,
16
+ }),
17
+ );
18
+
19
+ useMiddlewareWithCurrentContext('/minio/', async (req, res) => {
20
+ try {
21
+ if (req.method === 'POST' && req.body) {
22
+ const { headers } = req;
23
+ const { Records = [], EventName } = req.body;
24
+ if (EventName === 's3:ObjectCreated:Put' && isAuthorized(headers)) {
25
+ const [{ s3 }] = Records;
26
+ const { object } = s3;
27
+ const { size, contentType: type } = object;
28
+ const [fileId] = object.key.split('.');
29
+ const { services } = req.unchainedContext;
30
+ await services.files.linkFile({ fileId, type, size }, req.unchainedContext);
31
+ res.writeHead(200);
32
+ res.end();
33
+ return;
34
+ }
35
+ }
36
+ res.writeHead(404);
37
+ res.end();
38
+ return;
39
+ } catch (e) {
40
+ log(e.message, { level: LogLevel.Error });
41
+ res.writeHead(503);
42
+ res.end(JSON.stringify(e));
43
+ }
44
+ });
45
+
46
+ /*
47
+
48
+ curl -X PUT -T /Users/pozylon/Desktop/Bildschirmfoto\ 2022-02-17\ um\ 16.47.22.png http://localhost:4010/gridfs/3e0cd78ac07f0028890ce36c391057dd0c5fb46f4c147fb73e54bd8a7ffee389/user-avatars/avatar.png?e=1645883865878&s=e7466b30039593e5790e23cab92878e0144289c3710344a55b78c9a916e2f78c
49
+ curl -X PUT -T /Users/pozylon/Desktop/Bildschirmfoto\ 2022-02-17\ um\ 16.47.22.png https://minio.dev.shared.ucc.dev/unchained-test-bucket/user-avatars/9aedd551499b2850e9483b07cd4c8cff7ad1d710f49933c183861bf40183bc5a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=unchained-test%2F20220225%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220225T140957Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=22807026db1210370170cd5cf49bfbb9e86010425c9d1834a56e049faf39b368
50
+
51
+ */
@@ -0,0 +1,117 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import { WorkerDirector } from '@unchainedshop/core-worker';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ import { BaseAdapter } from '@unchainedshop/utils';
5
+ import fs from 'fs';
6
+ import JSONStream from 'JSONStream';
7
+ import { EventIterator } from 'event-iterator';
8
+
9
+ const logger = createLogger('unchained:platform:bulk-import');
10
+
11
+ const streamPayloadToBulkImporter = async (bulkImporter, createReadStream) => {
12
+ logger.profile(`parseAsync`, { level: 'verbose' });
13
+
14
+ const eventIterator = new EventIterator(
15
+ (queue) => {
16
+ const readStream = createReadStream();
17
+ const jsonStream = JSONStream.parse('events.*'); // rows, ANYTHING, doc
18
+ jsonStream.on('data', queue.push);
19
+ jsonStream.on('close', queue.stop);
20
+ jsonStream.on('error', queue.fail);
21
+
22
+ queue.on('highWater', () => readStream.pause());
23
+ queue.on('lowWater', () => readStream.resume());
24
+
25
+ readStream.pipe(jsonStream);
26
+ return () => {
27
+ jsonStream.removeListener('data', queue.push);
28
+ jsonStream.removeListener('close', queue.stop);
29
+ jsonStream.removeListener('error', queue.fail);
30
+ jsonStream.destroy();
31
+ };
32
+ },
33
+ { highWaterMark: 100, lowWaterMark: 5 },
34
+ );
35
+
36
+ for await (const event of eventIterator) { // eslint-disable-line
37
+ await bulkImporter.prepare(event);
38
+ }
39
+
40
+ logger.profile(`parseAsync`, { level: 'verbose' });
41
+ };
42
+
43
+ export const BulkImportWorker: IWorkerAdapter<any, Record<string, unknown>> = {
44
+ ...BaseAdapter,
45
+
46
+ key: 'shop.unchained.worker-plugin.bulk-import',
47
+ label: 'Bulk Import',
48
+ version: '1.0',
49
+ type: 'BULK_IMPORT',
50
+
51
+ doWork: async (rawPayload, context) => {
52
+ try {
53
+ const {
54
+ createShouldUpsertIfIDExists = false,
55
+ skipCacheInvalidation = false,
56
+ authorId = 'root',
57
+ } = rawPayload;
58
+
59
+ const bulkImporter = context.bulkImporter.createBulkImporter(
60
+ {
61
+ logger,
62
+ authorId,
63
+ createShouldUpsertIfIDExists,
64
+ skipCacheInvalidation,
65
+ },
66
+ context,
67
+ );
68
+
69
+ if (rawPayload.payloadFilePath) {
70
+ // stream payload from file system
71
+ await streamPayloadToBulkImporter(bulkImporter, () =>
72
+ fs.createReadStream(rawPayload.payloadFilePath),
73
+ );
74
+ } else if (rawPayload.payloadId) {
75
+ // stream payload from gridfs
76
+ await streamPayloadToBulkImporter(bulkImporter, () =>
77
+ context.bulkImporter.BulkImportPayloads.openDownloadStream(rawPayload.payloadId),
78
+ );
79
+ } else {
80
+ const { events } = rawPayload;
81
+ if (!events?.length) throw new Error('No events submitted');
82
+
83
+ await events.reduce(async (currentEventPromise, nextEvent) => {
84
+ await currentEventPromise;
85
+ return bulkImporter.prepare(nextEvent);
86
+ }, Promise.resolve());
87
+ }
88
+
89
+ const [result, error] = await bulkImporter.execute();
90
+ await bulkImporter.invalidateCaches();
91
+
92
+ if (error) {
93
+ return {
94
+ success: false,
95
+ result,
96
+ error,
97
+ };
98
+ }
99
+ return {
100
+ success: true,
101
+ result,
102
+ };
103
+ } catch (err) {
104
+ logger.error(err.message, err);
105
+ return {
106
+ success: false,
107
+ error: {
108
+ name: err.name,
109
+ message: err.message,
110
+ stack: err.stack,
111
+ },
112
+ };
113
+ }
114
+ },
115
+ };
116
+
117
+ WorkerDirector.registerAdapter(BulkImportWorker);
@@ -0,0 +1,156 @@
1
+ import { Context } from '@unchainedshop/types/api';
2
+ import { Enrollment } from '@unchainedshop/types/enrollments';
3
+ import { OrderPosition } from '@unchainedshop/types/orders.positions';
4
+ import { Product } from '@unchainedshop/types/products';
5
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
6
+ import {
7
+ EnrollmentDirector,
8
+ enrollmentsSettings,
9
+ EnrollmentStatus,
10
+ } from 'meteor/unchained:core-enrollments';
11
+ import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core-worker';
12
+
13
+ const generateOrder = async (
14
+ enrollment: Enrollment,
15
+ params: {
16
+ orderProducts: Array<{ orderPosition: OrderPosition; product: Product }>;
17
+ orderContext?: any;
18
+ } & { [x: string]: any },
19
+ requestContext: Context,
20
+ ) => {
21
+ if (!enrollment.payment || !enrollment.delivery) return null;
22
+
23
+ const { modules } = requestContext;
24
+ const { orderProducts, orderContext, ...configuration } = params;
25
+ let order = await modules.orders.create(
26
+ {
27
+ currency: enrollment.currencyCode,
28
+ countryCode: enrollment.countryCode,
29
+ contact: enrollment.contact,
30
+ billingAddress: enrollment.billingAddress,
31
+ originEnrollmentId: enrollment._id,
32
+ ...configuration,
33
+ },
34
+ enrollment.userId,
35
+ );
36
+ const orderId = order._id;
37
+
38
+ if (orderProducts) {
39
+ await Promise.all(
40
+ orderProducts.map(({ orderPosition, product }) =>
41
+ modules.orders.positions.addProductItem(orderPosition, { order, product }, requestContext),
42
+ ),
43
+ );
44
+ } else {
45
+ const product = await modules.products.findProduct({
46
+ productId: enrollment.productId,
47
+ });
48
+ await modules.orders.positions.addProductItem(
49
+ { quantity: 1 },
50
+ {
51
+ order,
52
+ product,
53
+ },
54
+ requestContext,
55
+ );
56
+ }
57
+
58
+ const { paymentProviderId, context: paymentContext } = enrollment.payment;
59
+
60
+ if (paymentProviderId) {
61
+ await modules.orders.setPaymentProvider(orderId, paymentProviderId, requestContext);
62
+ }
63
+ const { deliveryProviderId, context: deliveryContext } = enrollment.delivery;
64
+ if (deliveryProviderId) {
65
+ await modules.orders.setDeliveryProvider(orderId, deliveryProviderId, requestContext);
66
+ }
67
+
68
+ order = await modules.orders.checkout(
69
+ order._id,
70
+ {
71
+ paymentContext,
72
+ deliveryContext,
73
+ orderContext,
74
+ },
75
+ requestContext,
76
+ );
77
+
78
+ return order;
79
+ };
80
+
81
+ const GenerateOrderWorker: IWorkerAdapter<any, any> = {
82
+ ...WorkerAdapter,
83
+
84
+ key: 'shop.unchained.worker-plugin.generate-enrollment-orders',
85
+ label: 'Generates new Orders from Enrollments',
86
+ version: '1.0',
87
+ type: 'ENROLLMENT_ORDER_GENERATOR',
88
+
89
+ doWork: async (input, requestContext) => {
90
+ const { modules } = requestContext;
91
+
92
+ const enrollments = await modules.enrollments.findEnrollments({
93
+ status: [EnrollmentStatus.ACTIVE, EnrollmentStatus.PAUSED],
94
+ });
95
+
96
+ const errors = (
97
+ await Promise.all(
98
+ enrollments.map(async (enrollment) => {
99
+ try {
100
+ const director = await EnrollmentDirector.actions({ enrollment }, requestContext);
101
+ const period = await director.nextPeriod();
102
+ if (period) {
103
+ const configuration = await director.configurationForOrder({
104
+ ...input,
105
+ period,
106
+ });
107
+ if (configuration) {
108
+ const order = await generateOrder(enrollment, configuration, requestContext);
109
+ if (order) {
110
+ await modules.enrollments.addEnrollmentPeriod(enrollment._id, {
111
+ ...period,
112
+ orderId: order._id,
113
+ });
114
+ }
115
+ }
116
+ }
117
+ } catch (e) {
118
+ return {
119
+ name: e.name,
120
+ message: e.message,
121
+ stack: e.stack,
122
+ };
123
+ }
124
+ return null;
125
+ }),
126
+ )
127
+ ).filter(Boolean);
128
+
129
+ if (errors.length) {
130
+ return {
131
+ success: false,
132
+ error: {
133
+ name: 'SOME_ENROLLMENTS_COULD_NOT_PROCESS',
134
+ message: 'Some errors have been reported during order generation',
135
+ logs: errors,
136
+ },
137
+ result: {},
138
+ };
139
+ }
140
+ return {
141
+ success: true,
142
+ result: input,
143
+ };
144
+ },
145
+ };
146
+
147
+ WorkerDirector.registerAdapter(GenerateOrderWorker);
148
+
149
+ export const configureGenerateOrderAutoscheduling = () => {
150
+ if (enrollmentsSettings.autoSchedulingSchedule) {
151
+ WorkerDirector.configureAutoscheduling(GenerateOrderWorker, {
152
+ schedule: enrollmentsSettings.autoSchedulingSchedule,
153
+ input: enrollmentsSettings.autoSchedulingInput,
154
+ });
155
+ }
156
+ };
@@ -0,0 +1,62 @@
1
+ import { IWorkerAdapter, Work } from '@unchainedshop/types/worker';
2
+ import { MessagingDirector } from '@unchainedshop/core-messaging';
3
+ import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core-worker';
4
+ import { createLogger } from '@unchainedshop/logger';
5
+
6
+ export const messagingLogger = createLogger('unchained:core-messaging');
7
+
8
+ export const MessageWorker: IWorkerAdapter<
9
+ { template: string; _id?: string; [x: string]: any },
10
+ { info?: string; forked?: Array<Work> }
11
+ > = {
12
+ ...WorkerAdapter,
13
+
14
+ key: 'shop.unchained.worker-plugin.message',
15
+ label: 'Send Message by combining payload with a template and start concrete jobs',
16
+ version: '1.0',
17
+ type: 'MESSAGE',
18
+
19
+ doWork: async ({ template, ...payload }, requestContext, workId) => {
20
+ try {
21
+ const templateResolver = MessagingDirector.getTemplate(template);
22
+
23
+ const workConfigurations = await templateResolver(
24
+ {
25
+ template,
26
+ ...payload,
27
+ },
28
+ requestContext,
29
+ );
30
+
31
+ if (workConfigurations.length > 0) {
32
+ const forked = await Promise.all(
33
+ workConfigurations.map(async (workConfiguration: any) => {
34
+ const work = await requestContext.modules.worker.addWork(
35
+ {
36
+ ...workConfiguration,
37
+ originalWorkId: workId,
38
+ },
39
+ requestContext.userId,
40
+ );
41
+ delete work.input;
42
+ return work;
43
+ }),
44
+ );
45
+ return { success: true, result: { forked } };
46
+ }
47
+ return { success: true, result: { info: 'Skipped Message' } };
48
+ } catch (err) {
49
+ messagingLogger.warn(err.stack);
50
+ return {
51
+ success: false,
52
+ error: {
53
+ name: err.name,
54
+ message: err.message,
55
+ stack: err.stack,
56
+ },
57
+ };
58
+ }
59
+ },
60
+ };
61
+
62
+ WorkerDirector.registerAdapter(MessageWorker);
@@ -0,0 +1,114 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import { WorkerDirector } from '@unchainedshop/core-worker';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ import { BaseAdapter } from '@unchainedshop/utils';
5
+
6
+ const logger = createLogger('unchained:platform:zombie-killer');
7
+
8
+ const findId = { projection: { _id: 1 } };
9
+ const findFileId = { projection: { mediaId: 1 } };
10
+ const mapId = (a: any) => a._id;
11
+
12
+ export const ZombieKillerWorker: IWorkerAdapter<
13
+ never,
14
+ {
15
+ deletedProductMediaCount: number;
16
+ deletedAssortmentMediaCount: number;
17
+ deletedFilesCount: number;
18
+ }
19
+ > = {
20
+ ...BaseAdapter,
21
+
22
+ key: 'shop.unchained.worker-plugin.zombie-killer',
23
+ label: 'Zombie Killer',
24
+ version: '1.0',
25
+ type: 'ZOMBIE_KILLER',
26
+
27
+ doWork: async (_, unchainedAPI) => {
28
+ const { modules, services } = unchainedAPI;
29
+
30
+ try {
31
+ const error = false;
32
+
33
+ // Remove unreferenced product media objects
34
+ const products = await modules.products.findProducts({}, findId);
35
+ const deletedProductMediaCount = await modules.products.media.deleteMediaFiles({
36
+ excludedProdcutIds: products.map(mapId),
37
+ });
38
+
39
+ // Remove unreferenced assortment media objects
40
+ const assortments = await modules.assortments.findAssortments({}, findId);
41
+ const deletedAssortmentMediaCount = await modules.assortments.media.deleteMediaFiles({
42
+ excludedAssortmentIds: assortments.map(mapId),
43
+ });
44
+
45
+ // Remove unreferenced files
46
+ const fileIdsToRemove = [];
47
+ const productMedia = await modules.products.media.findProductMedias({}, findFileId);
48
+ const assortmentMedia = await modules.assortments.media.findAssortmentMedias({}, findFileId);
49
+
50
+ const filesWithProductId = await modules.files.findFilesByMetaData({
51
+ meta: {
52
+ productId: { $exists: true },
53
+ },
54
+ });
55
+ const filesWithAssortmentId = await modules.files.findFilesByMetaData({
56
+ meta: {
57
+ assortmentId: { $exists: true },
58
+ },
59
+ });
60
+
61
+ filesWithProductId
62
+ .concat(filesWithAssortmentId)
63
+ .filter((file) => {
64
+ const fileInProductMedia = productMedia.some((media) => media.mediaId === file._id);
65
+ const fileInAssortmentMedia = assortmentMedia.some((media) => media.mediaId === file._id);
66
+ return !fileInProductMedia && !fileInAssortmentMedia;
67
+ })
68
+ .forEach((m) => fileIdsToRemove.push(m._id));
69
+
70
+ const deletedFilesCount =
71
+ fileIdsToRemove.length > 0
72
+ ? await services.files.removeFiles(
73
+ {
74
+ fileIds: fileIdsToRemove,
75
+ },
76
+ unchainedAPI,
77
+ )
78
+ : 0;
79
+
80
+ // Return delete count
81
+ const result = {
82
+ deletedProductMediaCount,
83
+ deletedAssortmentMediaCount,
84
+ deletedFilesCount,
85
+ };
86
+
87
+ if (error) {
88
+ return {
89
+ success: false,
90
+ result,
91
+ error,
92
+ };
93
+ }
94
+ return {
95
+ success: true,
96
+ result,
97
+ };
98
+ } catch (err) {
99
+ logger.error(err.message, err);
100
+ return {
101
+ success: false,
102
+ error: {
103
+ name: err.name,
104
+ message: err.message,
105
+ stack: err.stack,
106
+ },
107
+ };
108
+ }
109
+ },
110
+ };
111
+
112
+ export default ZombieKillerWorker;
113
+
114
+ WorkerDirector.registerAdapter(ZombieKillerWorker);
@@ -0,0 +1,60 @@
1
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
2
+ import { createLogger } from '@unchainedshop/logger';
3
+ import { Email } from 'meteor/email';
4
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
5
+
6
+ const logger = createLogger('unchained:plugins:worker:email');
7
+
8
+ const EmailWorkerPlugin: IWorkerAdapter<
9
+ {
10
+ from?: string;
11
+ to?: string;
12
+ subject?: string;
13
+ [x: string]: any;
14
+ },
15
+ void
16
+ > = {
17
+ ...WorkerAdapter,
18
+
19
+ key: 'shop.unchained.worker-plugin.email',
20
+ label: 'Send a Mail through Meteor Mailer',
21
+ version: '1.0',
22
+
23
+ type: 'EMAIL',
24
+
25
+ doWork: async ({ from, to, subject, ...rest }) => {
26
+ logger.debug(`${EmailWorkerPlugin.key} -> doWork: ${from} -> ${to} (${subject})`);
27
+
28
+ if (!to) {
29
+ return {
30
+ success: false,
31
+ error: {
32
+ name: 'RECIPIENT_REQUIRED',
33
+ message: 'EMAIL requires a to',
34
+ },
35
+ };
36
+ }
37
+
38
+ try {
39
+ // https://docs.meteor.com/api/email.html#Email-send
40
+ const result = Email.send({
41
+ from,
42
+ to,
43
+ subject,
44
+ ...rest,
45
+ });
46
+ return { success: true, result };
47
+ } catch (err) {
48
+ return {
49
+ success: false,
50
+ error: {
51
+ name: err.name,
52
+ message: err.message,
53
+ stack: err.stack,
54
+ },
55
+ };
56
+ }
57
+ },
58
+ };
59
+
60
+ WorkerDirector.registerAdapter(EmailWorkerPlugin);
@@ -0,0 +1,17 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
3
+
4
+ export const ExternalWorkerPlugin: IWorkerAdapter<void, void> = {
5
+ ...WorkerAdapter,
6
+
7
+ key: 'shop.unchained.worker-plugin.external',
8
+ label: 'External plugin as a placeholder for workers who interact with the system only via GraphQL',
9
+ version: '1.0',
10
+ type: 'EXTERNAL',
11
+
12
+ async doWork() {
13
+ throw new Error('Cannot do work for external workers');
14
+ },
15
+ };
16
+
17
+ WorkerDirector.registerAdapter(ExternalWorkerPlugin);
@@ -0,0 +1,45 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
3
+
4
+ const wait = async (time: number) => {
5
+ return new Promise((resolve) => {
6
+ setTimeout(() => {
7
+ resolve(true);
8
+ }, time);
9
+ });
10
+ };
11
+
12
+ type Arg = {
13
+ wait?: number;
14
+ fails?: boolean;
15
+ };
16
+
17
+ type Result = Arg;
18
+
19
+ const Heartbeat: IWorkerAdapter<Arg, Result> = {
20
+ ...WorkerAdapter,
21
+
22
+ key: 'shop.unchained.worker-plugin.heartbeat',
23
+ label: 'Heartbeat plugin to check if workers are working',
24
+ version: '1.0',
25
+
26
+ type: 'HEARTBEAT',
27
+
28
+ doWork: async (input: Arg): Promise<{ success: boolean; result: Result }> => {
29
+ if (input?.wait) {
30
+ await wait(input.wait);
31
+ }
32
+ if (input?.fails) {
33
+ return {
34
+ success: false,
35
+ result: input,
36
+ };
37
+ }
38
+ return {
39
+ success: true,
40
+ result: input,
41
+ };
42
+ },
43
+ };
44
+
45
+ WorkerDirector.registerAdapter(Heartbeat);
@@ -0,0 +1,75 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import fetch from 'isomorphic-unfetch';
3
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
4
+ import { log, LogLevel } from '@unchainedshop/logger';
5
+
6
+ const postFetch = async (url, { data, headers }) => {
7
+ return fetch(url, {
8
+ method: 'POST',
9
+ body: JSON.stringify(data),
10
+ headers: { 'Content-Type': 'application/json', ...headers },
11
+ });
12
+ };
13
+
14
+ const nonPostFetch = async (url, { headers, method = 'GET' }) => {
15
+ return fetch(url, {
16
+ method,
17
+ headers,
18
+ });
19
+ };
20
+
21
+ const HttpRequestWorkerPlugin: IWorkerAdapter<
22
+ { url?: string; data?: any; headers?: any; method: 'POST' | 'GET' },
23
+ any
24
+ > = {
25
+ ...WorkerAdapter,
26
+
27
+ key: 'shop.unchained.worker-plugin.http-request',
28
+ label: 'Request a resource via http request. 200 = success',
29
+ version: '1.0',
30
+ type: 'HTTP_REQUEST',
31
+
32
+ async doWork({ url, data = {}, headers, method = 'POST' } = { method: 'POST' }) {
33
+ log(`${this.key} -> doWork: ${method} ${url} ${data}`, {
34
+ level: LogLevel.Debug,
35
+ });
36
+
37
+ if (!url) {
38
+ return {
39
+ success: false,
40
+ error: {
41
+ name: 'URL_REQUIRED',
42
+ message: 'HTTP_REQUEST requires an url',
43
+ },
44
+ };
45
+ }
46
+
47
+ try {
48
+ const normalizedFetch = method.toUpperCase() === 'POST' ? postFetch : nonPostFetch;
49
+ const res = await normalizedFetch(url, { data, headers, method });
50
+
51
+ const contentType = res.headers.get('content-type');
52
+ const isJson = contentType && contentType.includes('application/json');
53
+
54
+ const result = isJson ? await res.json() : { text: await res.text() };
55
+
56
+ if (res.status === 200 && res.ok) {
57
+ return { success: true, result };
58
+ }
59
+ return { success: false, result };
60
+ } catch (err) {
61
+ return {
62
+ success: false,
63
+ error: {
64
+ name: err.name,
65
+ message: err.message,
66
+ stack: err.stack,
67
+ },
68
+ };
69
+ }
70
+ },
71
+ };
72
+
73
+ WorkerDirector.registerAdapter(HttpRequestWorkerPlugin);
74
+
75
+ export default HttpRequestWorkerPlugin;