@unchainedshop/plugins 4.8.14 → 4.8.15

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.
@@ -10,7 +10,7 @@ else {
10
10
  try {
11
11
  const { default: Stripe } = await import('stripe');
12
12
  stripe = new Stripe(STRIPE_SECRET, {
13
- apiVersion: '2026-04-22.dahlia',
13
+ apiVersion: '2026-05-27.dahlia',
14
14
  });
15
15
  }
16
16
  catch {
@@ -14,6 +14,7 @@ import '../enrollments/licensed.ts';
14
14
  import '../events/node-event-emitter.ts';
15
15
  import '../worker/bulk-import.ts';
16
16
  import '../worker/zombie-killer.ts';
17
+ import '../worker/gc-guests.ts';
17
18
  import '../worker/message.ts';
18
19
  import '../worker/external.ts';
19
20
  import '../worker/http-request.ts';
@@ -14,6 +14,7 @@ import "../enrollments/licensed.js";
14
14
  import "../events/node-event-emitter.js";
15
15
  import "../worker/bulk-import.js";
16
16
  import "../worker/zombie-killer.js";
17
+ import "../worker/gc-guests.js";
17
18
  import "../worker/message.js";
18
19
  import "../worker/external.js";
19
20
  import "../worker/http-request.js";
@@ -0,0 +1,8 @@
1
+ import { type IWorkerAdapter } from '@unchainedshop/core';
2
+ export declare const GCGuestsWorker: IWorkerAdapter<{
3
+ guestUserMaxAgeInDays?: number;
4
+ }, {
5
+ scannedGuestCount: number;
6
+ deletedGuestCount: number;
7
+ }>;
8
+ export default GCGuestsWorker;
@@ -0,0 +1,60 @@
1
+ import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
2
+ import { userSettings } from '@unchainedshop/core-users';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ const logger = createLogger('unchained:worker:gc-guests');
5
+ const ONE_DAY_IN_MILLISECONDS = 86400000;
6
+ const everyDayAtHalfPastTwo = schedule.parse.cron('30 2 * * *');
7
+ export const GCGuestsWorker = {
8
+ ...WorkerAdapter,
9
+ key: 'shop.unchained.worker-plugin.gc-guests',
10
+ label: 'Garbage Collect Guests',
11
+ version: '1.0.0',
12
+ type: 'GC_GUESTS',
13
+ doWork: async ({ guestUserMaxAgeInDays } = {}, unchainedAPI) => {
14
+ const { modules, services } = unchainedAPI;
15
+ try {
16
+ const maxAgeInDays = guestUserMaxAgeInDays ?? userSettings.guestUserMaxAgeInDays;
17
+ const before = new Date(Date.now() - maxAgeInDays * ONE_DAY_IN_MILLISECONDS);
18
+ const candidateIds = await modules.users.findGuestUserIds({ before });
19
+ const recentCarts = candidateIds.length
20
+ ? await modules.orders.findCarts({ userIds: candidateIds }, { projection: { userId: 1, updated: 1 } })
21
+ : [];
22
+ const userIdsWithRecentCart = new Set(recentCarts.filter((cart) => cart.updated && cart.updated >= before).map((c) => c.userId));
23
+ const userIdsToDelete = candidateIds.filter((id) => !userIdsWithRecentCart.has(id));
24
+ let deletedGuestCount = 0;
25
+ await Array.fromAsync(userIdsToDelete, async (userId) => {
26
+ try {
27
+ await services.users.deleteUser({ userId });
28
+ deletedGuestCount += 1;
29
+ }
30
+ catch (err) {
31
+ logger.warn(`Failed to garbage-collect guest ${userId}: ${err.message}`);
32
+ }
33
+ });
34
+ return {
35
+ success: true,
36
+ result: {
37
+ scannedGuestCount: candidateIds.length,
38
+ deletedGuestCount,
39
+ },
40
+ };
41
+ }
42
+ catch (err) {
43
+ return {
44
+ success: false,
45
+ error: {
46
+ name: err.name,
47
+ message: err.message,
48
+ stack: err.stack,
49
+ },
50
+ };
51
+ }
52
+ },
53
+ };
54
+ export default GCGuestsWorker;
55
+ WorkerDirector.registerAdapter(GCGuestsWorker);
56
+ WorkerDirector.configureAutoscheduling({
57
+ type: GCGuestsWorker.type,
58
+ schedule: everyDayAtHalfPastTwo,
59
+ retries: 2,
60
+ });
@@ -9,5 +9,6 @@ export declare const ZombieKillerWorker: IWorkerAdapter<{
9
9
  deletedProductTextsCount: number;
10
10
  deletedProductVariationsCount: number;
11
11
  deletedAssortmentTextsCount: number;
12
+ deletedCartsCount: number;
12
13
  }>;
13
14
  export default ZombieKillerWorker;
@@ -1,12 +1,14 @@
1
- import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
1
+ import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
2
+ const everyDayAtTwo = schedule.parse.cron('0 2 * * *');
2
3
  export const ZombieKillerWorker = {
3
4
  ...WorkerAdapter,
4
5
  key: 'shop.unchained.worker-plugin.zombie-killer',
5
6
  label: 'Zombie Killer',
6
7
  version: '1.0.0',
7
8
  type: 'ZOMBIE_KILLER',
8
- doWork: async ({ bulkImportMaxAgeInDays } = { bulkImportMaxAgeInDays: 5 }, unchainedAPI) => {
9
+ doWork: async (input, unchainedAPI) => {
9
10
  const { modules, services } = unchainedAPI;
11
+ const { bulkImportMaxAgeInDays = 5 } = input || {};
10
12
  try {
11
13
  const error = false;
12
14
  const filters = await modules.filters.findFilters({ includeInactive: true }, { projection: { _id: 1 } });
@@ -38,7 +40,9 @@ export const ZombieKillerWorker = {
38
40
  const productMedia = await modules.products.media.findProductMedias({}, { projection: { mediaId: 1 } });
39
41
  const assortmentMedia = await modules.assortments.media.findAssortmentMedias({}, { projection: { mediaId: 1 } });
40
42
  const allFileIdsLinked = [...productMedia, ...assortmentMedia].map((l) => l?.mediaId);
41
- const allFileIdsRelevant = (await modules.files.findFiles({ paths: ['product-media', 'assortment-media'] }, { projection: { _id: 1 } })).map((a) => a._id);
43
+ const allFileIdsRelevant = (await modules.files.findFiles({ paths: ['product-media', 'assortment-media'] }, { projection: { _id: 1, expires: 1 } }))
44
+ .filter((file) => !file.expires)
45
+ .map((a) => a._id);
42
46
  const fileIdsToRemove = allFileIdsRelevant.filter((fileId) => {
43
47
  return !allFileIdsLinked.includes(fileId);
44
48
  }) || [];
@@ -54,6 +58,17 @@ export const ZombieKillerWorker = {
54
58
  fileIds: fileIdsToRemove,
55
59
  })
56
60
  : 0;
61
+ const cartUserIds = (await modules.orders.findCartUserIds()).filter(Boolean);
62
+ const existingUserIds = new Set(cartUserIds.length ? await modules.users.findExistingUserIds({ userIds: cartUserIds }) : []);
63
+ const deadUserIds = cartUserIds.filter((userId) => !existingUserIds.has(userId));
64
+ const deadCarts = deadUserIds.length
65
+ ? await modules.orders.findCarts({ userIds: deadUserIds }, { projection: { _id: 1 } })
66
+ : [];
67
+ let deletedCartsCount = 0;
68
+ await Array.fromAsync(deadCarts, async (cart) => {
69
+ await services.orders.deleteCart(cart._id);
70
+ deletedCartsCount += 1;
71
+ });
57
72
  const result = {
58
73
  deletedFilterTextsCount,
59
74
  deletedProductMediaCount,
@@ -62,6 +77,7 @@ export const ZombieKillerWorker = {
62
77
  deletedAssortmentMediaCount,
63
78
  deletedAssortmentTextsCount,
64
79
  deletedFilesCount,
80
+ deletedCartsCount,
65
81
  };
66
82
  if (error) {
67
83
  return {
@@ -89,3 +105,8 @@ export const ZombieKillerWorker = {
89
105
  };
90
106
  export default ZombieKillerWorker;
91
107
  WorkerDirector.registerAdapter(ZombieKillerWorker);
108
+ WorkerDirector.configureAutoscheduling({
109
+ type: ZombieKillerWorker.type,
110
+ schedule: everyDayAtTwo,
111
+ retries: 2,
112
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unchainedshop/plugins",
3
3
  "description": "Official plugin collection for the Unchained Engine with payment, delivery, and pricing adapters",
4
- "version": "4.8.14",
4
+ "version": "4.8.15",
5
5
  "main": "lib/plugins-index.js",
6
6
  "types": "lib/plugins-index.d.ts",
7
7
  "exports": {
@@ -43,6 +43,7 @@
43
43
  "@unchainedshop/core-orders": "^4.8.12",
44
44
  "@unchainedshop/core-payment": "^4.8.12",
45
45
  "@unchainedshop/core-products": "^4.8.12",
46
+ "@unchainedshop/core-users": "^4.8.12",
46
47
  "@unchainedshop/core-warehousing": "^4.8.12",
47
48
  "@unchainedshop/core-worker": "^4.8.12",
48
49
  "@unchainedshop/events": "^4.8.12",