@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.
- package/lib/events/node-event-emitter.d.ts +1 -0
- package/lib/events/node-event-emitter.js +16 -0
- package/lib/events/node-event-emitter.js.map +1 -0
- package/lib/events/redis.d.ts +1 -0
- package/lib/events/redis.js +24 -0
- package/lib/events/redis.js.map +1 -0
- package/lib/files/gridfs/gridfs-adapter.d.ts +2 -0
- package/lib/files/gridfs/gridfs-adapter.js +106 -0
- package/lib/files/gridfs/gridfs-adapter.js.map +1 -0
- package/lib/files/gridfs/gridfs-webhook.d.ts +1 -0
- package/lib/files/gridfs/gridfs-webhook.js +54 -0
- package/lib/files/gridfs/gridfs-webhook.js.map +1 -0
- package/lib/files/gridfs/index.d.ts +8 -0
- package/lib/files/gridfs/index.js +21 -0
- package/lib/files/gridfs/index.js.map +1 -0
- package/lib/files/gridfs/promisePipe.d.ts +2 -0
- package/lib/files/gridfs/promisePipe.js +21 -0
- package/lib/files/gridfs/promisePipe.js.map +1 -0
- package/lib/files/gridfs/sign.d.ts +2 -0
- package/lib/files/gridfs/sign.js +12 -0
- package/lib/files/gridfs/sign.js.map +1 -0
- package/lib/files/minio/minio-adapter.d.ts +4 -0
- package/lib/files/minio/minio-adapter.js +166 -0
- package/lib/files/minio/minio-adapter.js.map +1 -0
- package/lib/files/minio/minio-webhook.d.ts +1 -0
- package/lib/files/minio/minio-webhook.js +45 -0
- package/lib/files/minio/minio-webhook.js.map +1 -0
- package/lib/worker/BulkImportWorker.d.ts +2 -0
- package/lib/worker/BulkImportWorker.js +91 -0
- package/lib/worker/BulkImportWorker.js.map +1 -0
- package/lib/worker/GenerateOrderWorker.d.ts +1 -0
- package/lib/worker/GenerateOrderWorker.js +110 -0
- package/lib/worker/GenerateOrderWorker.js.map +1 -0
- package/lib/worker/MessageWorker.d.ts +10 -0
- package/lib/worker/MessageWorker.js +45 -0
- package/lib/worker/MessageWorker.js.map +1 -0
- package/lib/worker/ZombieKillerWorker.d.ts +7 -0
- package/lib/worker/ZombieKillerWorker.js +88 -0
- package/lib/worker/ZombieKillerWorker.js.map +1 -0
- package/lib/worker/email.d.ts +1 -0
- package/lib/worker/email.js +45 -0
- package/lib/worker/email.js.map +1 -0
- package/lib/worker/external.d.ts +2 -0
- package/lib/worker/external.js +13 -0
- package/lib/worker/external.js.map +1 -0
- package/lib/worker/heartbeat.d.ts +1 -0
- package/lib/worker/heartbeat.js +32 -0
- package/lib/worker/heartbeat.js.map +1 -0
- package/lib/worker/http-request.d.ts +8 -0
- package/lib/worker/http-request.js +61 -0
- package/lib/worker/http-request.js.map +1 -0
- package/lib/worker/sms.d.ts +1 -0
- package/lib/worker/sms.js +69 -0
- package/lib/worker/sms.js.map +1 -0
- package/package.json +44 -0
- package/readme.md +1 -0
- package/src/events/node-event-emitter.ts +20 -0
- package/src/events/redis.ts +30 -0
- package/src/files/gridfs/gridfs-adapter.ts +136 -0
- package/src/files/gridfs/gridfs-webhook.js +60 -0
- package/src/files/gridfs/index.js +21 -0
- package/src/files/gridfs/promisePipe.js +19 -0
- package/src/files/gridfs/sign.js +16 -0
- package/src/files/minio/minio-adapter.ts +215 -0
- package/src/files/minio/minio-webhook.js +51 -0
- package/src/worker/BulkImportWorker.ts +117 -0
- package/src/worker/GenerateOrderWorker.ts +156 -0
- package/src/worker/MessageWorker.ts +62 -0
- package/src/worker/ZombieKillerWorker.ts +114 -0
- package/src/worker/email.ts +60 -0
- package/src/worker/external.ts +17 -0
- package/src/worker/heartbeat.ts +45 -0
- package/src/worker/http-request.ts +75 -0
- package/src/worker/sms.ts +85 -0
- package/tsconfig.build.json +26 -0
- 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;
|