@unchainedshop/core-assortments 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.
@@ -0,0 +1,153 @@
1
+ import { AssortmentFilter, AssortmentsModule } from '@unchainedshop/types/assortments';
2
+ import { Collection, Query } from '@unchainedshop/types/common';
3
+ import { emit, registerEvents } from '@unchainedshop/events';
4
+ import { generateDbFilterById, generateDbObjectId } from '@unchainedshop/utils';
5
+
6
+ const ASSORTMENT_FILTER_EVENTS = [
7
+ 'ASSORTMENT_ADD_FILTER',
8
+ 'ASSORTMENT_REMOVE_FILTER',
9
+ 'ASSORTMENT_REORDER_FILTERS',
10
+ ];
11
+
12
+ export const configureAssortmentFiltersModule = ({
13
+ AssortmentFilters,
14
+ }: {
15
+ AssortmentFilters: Collection<AssortmentFilter>;
16
+ }): AssortmentsModule['filters'] => {
17
+ registerEvents(ASSORTMENT_FILTER_EVENTS);
18
+
19
+ return {
20
+ findFilter: async ({ assortmentFilterId }) => {
21
+ return AssortmentFilters.findOne(generateDbFilterById(assortmentFilterId), {});
22
+ },
23
+
24
+ findFilters: async ({ assortmentId }, options) => {
25
+ const filters = AssortmentFilters.find({ assortmentId }, options);
26
+ return filters.toArray();
27
+ },
28
+
29
+ findFilterIds: async ({ assortmentId }) => {
30
+ const filters = AssortmentFilters.find(
31
+ { assortmentId },
32
+ {
33
+ sort: { sortKey: 1 },
34
+ projection: { filterId: 1 },
35
+ },
36
+ ).map((filter) => filter.filterId);
37
+
38
+ return filters.toArray();
39
+ },
40
+ create: async (doc: AssortmentFilter, userId) => {
41
+ const { _id, assortmentId, filterId, sortKey, ...rest } = doc;
42
+
43
+ const selector = {
44
+ ...(_id ? generateDbFilterById(_id) : {}),
45
+ filterId,
46
+ assortmentId,
47
+ };
48
+
49
+ const $set: any = {
50
+ updated: new Date(),
51
+ updatedBy: userId,
52
+ ...rest,
53
+ };
54
+ const $setOnInsert: any = {
55
+ _id: _id || generateDbObjectId(),
56
+ filterId,
57
+ assortmentId,
58
+ created: new Date(),
59
+ createdBy: userId,
60
+ };
61
+
62
+ if (!doc.sortKey) {
63
+ // Get next sort key
64
+ const lastAssortmentFilter = (await AssortmentFilters.findOne(
65
+ { assortmentId },
66
+ { sort: { sortKey: -1 } },
67
+ )) || { sortKey: 0 };
68
+ $setOnInsert.sortKey = lastAssortmentFilter.sortKey + 1;
69
+ } else {
70
+ $set.sortKey = sortKey;
71
+ }
72
+
73
+ await AssortmentFilters.updateOne(
74
+ selector,
75
+ {
76
+ $set,
77
+ $setOnInsert,
78
+ },
79
+ { upsert: true },
80
+ );
81
+
82
+ const assortmentFilter = await AssortmentFilters.findOne(selector, {});
83
+
84
+ emit('ASSORTMENT_ADD_FILTER', { assortmentFilter });
85
+
86
+ return assortmentFilter;
87
+ },
88
+
89
+ delete: async (assortmentFilterId) => {
90
+ const selector: Query = generateDbFilterById(assortmentFilterId);
91
+
92
+ const assortmentFilter = await AssortmentFilters.findOne(selector, {
93
+ projection: { _id: 1 },
94
+ });
95
+
96
+ await AssortmentFilters.deleteOne(selector);
97
+
98
+ emit('ASSORTMENT_REMOVE_FILTER', {
99
+ assortmentFilterId: assortmentFilter._id,
100
+ });
101
+
102
+ return [assortmentFilter];
103
+ },
104
+
105
+ deleteMany: async (selector) => {
106
+ const assortmentFilters = await AssortmentFilters.find(selector, {
107
+ projection: { _id: 1 },
108
+ }).toArray();
109
+
110
+ await AssortmentFilters.deleteMany(selector);
111
+
112
+ assortmentFilters.forEach((assortmentFilter) => {
113
+ emit('ASSORTMENT_REMOVE_FILTER', {
114
+ assortmentFilterId: assortmentFilter._id,
115
+ });
116
+ });
117
+
118
+ return assortmentFilters;
119
+ },
120
+
121
+ // This action is specifically used for the bulk migration scripts in the platform package
122
+ update: async (assortmentFilterId, doc) => {
123
+ const selector = generateDbFilterById(assortmentFilterId);
124
+ const modifier = { $set: doc };
125
+ await AssortmentFilters.updateOne(selector, modifier);
126
+ return AssortmentFilters.findOne(selector, {});
127
+ },
128
+
129
+ updateManualOrder: async ({ sortKeys }, userId) => {
130
+ const changedAssortmentFilterIds = await Promise.all(
131
+ sortKeys.map(async ({ assortmentFilterId, sortKey }) => {
132
+ await AssortmentFilters.updateOne(generateDbFilterById(assortmentFilterId), {
133
+ $set: {
134
+ sortKey: sortKey + 1,
135
+ updated: new Date(),
136
+ updatedBy: userId,
137
+ },
138
+ });
139
+
140
+ return assortmentFilterId;
141
+ }),
142
+ );
143
+
144
+ const assortmentFilters = await AssortmentFilters.find({
145
+ _id: { $in: changedAssortmentFilterIds },
146
+ }).toArray();
147
+
148
+ emit('ASSORTMENT_REORDER_FILTERS', { assortmentFilters });
149
+
150
+ return assortmentFilters;
151
+ },
152
+ };
153
+ };
@@ -0,0 +1,212 @@
1
+ import { AssortmentLink, AssortmentsModule } from '@unchainedshop/types/assortments';
2
+ import { Collection } from '@unchainedshop/types/common';
3
+ import { emit, registerEvents } from '@unchainedshop/events';
4
+ import { generateDbFilterById, generateDbObjectId } from '@unchainedshop/utils';
5
+
6
+ const ASSORTMENT_LINK_EVENTS = [
7
+ 'ASSORTMENT_ADD_LINK',
8
+ 'ASSORTMENT_REMOVE_LINK',
9
+ 'ASSORTMENT_REORDER_LINKS',
10
+ ];
11
+
12
+ export const configureAssortmentLinksModule = ({
13
+ AssortmentLinks,
14
+ invalidateCache,
15
+ }: {
16
+ AssortmentLinks: Collection<AssortmentLink>;
17
+ invalidateCache: AssortmentsModule['invalidateCache'];
18
+ }): AssortmentsModule['links'] => {
19
+ registerEvents(ASSORTMENT_LINK_EVENTS);
20
+
21
+ return {
22
+ // Queries
23
+ findLink: async ({ assortmentLinkId, parentAssortmentId, childAssortmentId }) => {
24
+ return AssortmentLinks.findOne(
25
+ assortmentLinkId
26
+ ? generateDbFilterById(assortmentLinkId)
27
+ : { parentAssortmentId, childAssortmentId },
28
+ {},
29
+ );
30
+ },
31
+
32
+ findLinks: async ({ assortmentId, parentAssortmentId }, options) => {
33
+ const selector = parentAssortmentId
34
+ ? {
35
+ parentAssortmentId,
36
+ }
37
+ : {
38
+ $or: [{ parentAssortmentId: assortmentId }, { childAssortmentId: assortmentId }],
39
+ };
40
+
41
+ const links = AssortmentLinks.find(
42
+ selector,
43
+ options || {
44
+ sort: { sortKey: 1 },
45
+ },
46
+ );
47
+
48
+ return links.toArray();
49
+ },
50
+
51
+ // Mutations
52
+ create: async (doc, options, userId) => {
53
+ const { _id: assortmentLinkId, parentAssortmentId, childAssortmentId, sortKey, ...rest } = doc;
54
+
55
+ const selector = {
56
+ ...(assortmentLinkId ? generateDbFilterById(assortmentLinkId) : {}),
57
+ parentAssortmentId,
58
+ childAssortmentId,
59
+ };
60
+
61
+ const $set: any = {
62
+ updated: new Date(),
63
+ updatedBy: userId,
64
+ ...rest,
65
+ };
66
+ const $setOnInsert: any = {
67
+ _id: assortmentLinkId || generateDbObjectId(),
68
+ parentAssortmentId,
69
+ childAssortmentId,
70
+ created: new Date(),
71
+ createdBy: userId,
72
+ };
73
+
74
+ if (!sortKey) {
75
+ // Get next sort key
76
+ const lastAssortmentLink = (await AssortmentLinks.findOne(
77
+ { parentAssortmentId },
78
+ { sort: { sortKey: -1 } },
79
+ )) || { sortKey: 0 };
80
+ $setOnInsert.sortKey = lastAssortmentLink.sortKey + 1;
81
+ } else {
82
+ $set.sortKey = sortKey;
83
+ }
84
+
85
+ await AssortmentLinks.updateOne(
86
+ selector,
87
+ {
88
+ $set,
89
+ $setOnInsert,
90
+ },
91
+ {
92
+ upsert: true,
93
+ },
94
+ );
95
+
96
+ const assortmentLink = await AssortmentLinks.findOne(selector, {});
97
+
98
+ emit('ASSORTMENT_ADD_LINK', { assortmentLink });
99
+
100
+ if (!options.skipInvalidation) {
101
+ await invalidateCache({ assortmentIds: [parentAssortmentId] });
102
+ }
103
+
104
+ return assortmentLink;
105
+ },
106
+
107
+ // This action is specifically used for the bulk migration scripts in the platform package
108
+ update: async (assortmentLinkId, doc, options, userId) => {
109
+ const selector = generateDbFilterById(assortmentLinkId);
110
+ const modifier = {
111
+ $set: {
112
+ ...doc,
113
+ updated: new Date(),
114
+ updatedBy: userId,
115
+ },
116
+ };
117
+ await AssortmentLinks.updateOne(selector, modifier);
118
+
119
+ const assortmentLink = await AssortmentLinks.findOne(selector, {});
120
+ if (!options.skipInvalidation) {
121
+ await invalidateCache(
122
+ { assortmentIds: [assortmentLink.childAssortmentId] },
123
+ {
124
+ skipUpstreamTraversal: false,
125
+ },
126
+ );
127
+ }
128
+ return assortmentLink;
129
+ },
130
+
131
+ delete: async (assortmentLinkId, options) => {
132
+ const selector = generateDbFilterById(assortmentLinkId);
133
+
134
+ const assortmentLink = await AssortmentLinks.findOne(selector, {});
135
+
136
+ await AssortmentLinks.deleteOne(selector);
137
+
138
+ emit('ASSORTMENT_REMOVE_LINK', {
139
+ assortmentLinkId: assortmentLink._id,
140
+ });
141
+
142
+ if (!options.skipInvalidation) {
143
+ await invalidateCache(
144
+ {
145
+ assortmentIds: [assortmentLink.childAssortmentId, assortmentLink.parentAssortmentId],
146
+ },
147
+ { skipUpstreamTraversal: false },
148
+ );
149
+ }
150
+
151
+ return assortmentLink;
152
+ },
153
+
154
+ deleteMany: async (selector, options) => {
155
+ const assortmentLinks = await AssortmentLinks.find(selector, {}).toArray();
156
+
157
+ await AssortmentLinks.deleteMany(selector);
158
+ assortmentLinks.forEach((assortmentLink) => {
159
+ emit('ASSORTMENT_REMOVE_LINK', {
160
+ assortmentLinkId: assortmentLink._id,
161
+ });
162
+ });
163
+
164
+ if (!options.skipInvalidation && assortmentLinks.length) {
165
+ await invalidateCache(
166
+ {
167
+ assortmentIds: assortmentLinks.flatMap((link) => [
168
+ link.childAssortmentId,
169
+ link.parentAssortmentId,
170
+ ]),
171
+ },
172
+ { skipUpstreamTraversal: false },
173
+ );
174
+ }
175
+
176
+ return assortmentLinks;
177
+ },
178
+
179
+ updateManualOrder: async ({ sortKeys }, options, userId) => {
180
+ const changedAssortmentLinkIds = await Promise.all(
181
+ sortKeys.map(async ({ assortmentLinkId, sortKey }) => {
182
+ await AssortmentLinks.updateOne(generateDbFilterById(assortmentLinkId), {
183
+ $set: {
184
+ sortKey: sortKey + 1,
185
+ updated: new Date(),
186
+ updatedBy: userId,
187
+ },
188
+ });
189
+
190
+ return assortmentLinkId;
191
+ }),
192
+ );
193
+
194
+ const assortmentLinks = await AssortmentLinks.find({
195
+ _id: { $in: changedAssortmentLinkIds },
196
+ }).toArray();
197
+
198
+ if (!options.skipInvalidation && assortmentLinks.length) {
199
+ await invalidateCache(
200
+ { assortmentIds: assortmentLinks.map((link) => link.childAssortmentId) },
201
+ {
202
+ skipUpstreamTraversal: false,
203
+ },
204
+ );
205
+ }
206
+
207
+ emit('ASSORTMENT_REORDER_LINKS', { assortmentLinks });
208
+
209
+ return assortmentLinks;
210
+ },
211
+ };
212
+ };
@@ -0,0 +1,259 @@
1
+ import {
2
+ AssortmentMedia,
3
+ AssortmentMediaModule,
4
+ AssortmentMediaText,
5
+ } from '@unchainedshop/types/assortments.media';
6
+ import { ModuleInput, ModuleMutations, Query } from '@unchainedshop/types/common';
7
+ import { Locale } from 'locale';
8
+ import { emit, registerEvents } from '@unchainedshop/events';
9
+ import {
10
+ findLocalizedText,
11
+ generateDbFilterById,
12
+ generateDbMutations,
13
+ generateDbObjectId,
14
+ } from '@unchainedshop/utils';
15
+ import { FileDirector } from '@unchainedshop/file-upload';
16
+ import { AssortmentMediaCollection } from '../db/AssortmentMediasCollection';
17
+ import { AssortmentMediasSchema } from '../db/AssortmentMediasSchema';
18
+
19
+ const ASSORTMENT_MEDIA_EVENTS = [
20
+ 'ASSORTMENT_ADD_MEDIA',
21
+ 'ASSORTMENT_REMOVE_MEDIA',
22
+ 'ASSORTMENT_REORDER_MEDIA',
23
+ 'ASSORTMENT_UPDATE_MEDIA_TEXT',
24
+ ];
25
+
26
+ FileDirector.registerFileUploadCallback('assortment-media', async (file, { modules, userId }) => {
27
+ await modules.assortments.media.create(
28
+ {
29
+ assortmentId: file.meta.assortmentId,
30
+ mediaId: file._id,
31
+ },
32
+ file.updatedBy || file.createdBy || userId,
33
+ );
34
+ });
35
+
36
+ export const configureAssortmentMediaModule = async ({
37
+ db,
38
+ }: ModuleInput<Record<string, never>>): Promise<AssortmentMediaModule> => {
39
+ registerEvents(ASSORTMENT_MEDIA_EVENTS);
40
+
41
+ const { AssortmentMedias, AssortmentMediaTexts } = await AssortmentMediaCollection(db);
42
+
43
+ const mutations = generateDbMutations<AssortmentMedia>(
44
+ AssortmentMedias,
45
+ AssortmentMediasSchema,
46
+ ) as ModuleMutations<AssortmentMedia>;
47
+
48
+ const upsertLocalizedText: AssortmentMediaModule['texts']['upsertLocalizedText'] = async (
49
+ assortmentMediaId,
50
+ locale,
51
+ text,
52
+ userId,
53
+ ) => {
54
+ const selector = {
55
+ assortmentMediaId,
56
+ locale,
57
+ };
58
+
59
+ const updsertResult = await AssortmentMediaTexts.updateOne(
60
+ selector,
61
+ {
62
+ $set: {
63
+ updated: new Date(),
64
+ updatedBy: userId,
65
+ authorId: userId,
66
+ ...text,
67
+ },
68
+ $setOnInsert: {
69
+ _id: generateDbObjectId(),
70
+ created: new Date(),
71
+ createdBy: userId,
72
+ assortmentMediaId,
73
+ locale,
74
+ },
75
+ },
76
+ {
77
+ upsert: true,
78
+ },
79
+ );
80
+
81
+ return AssortmentMediaTexts.findOne(
82
+ updsertResult.upsertedId ? generateDbFilterById(updsertResult.upsertedId) : selector,
83
+ {},
84
+ );
85
+ };
86
+
87
+ return {
88
+ // Queries
89
+ findAssortmentMedia: async ({ assortmentMediaId }) => {
90
+ return AssortmentMedias.findOne(generateDbFilterById(assortmentMediaId), {});
91
+ },
92
+
93
+ findAssortmentMedias: async ({ assortmentId, tags, offset, limit }, options) => {
94
+ const selector: Query = assortmentId ? { assortmentId } : {};
95
+ if (tags && tags.length > 0) {
96
+ selector.tags = { $all: tags };
97
+ }
98
+
99
+ const mediaList = AssortmentMedias.find(selector, {
100
+ skip: offset,
101
+ limit,
102
+ sort: { sortKey: 1 },
103
+ ...options,
104
+ });
105
+
106
+ return mediaList.toArray();
107
+ },
108
+
109
+ // Mutations
110
+ create: async (doc: AssortmentMedia, userId) => {
111
+ let { sortKey } = doc;
112
+
113
+ if (!sortKey) {
114
+ // Get next sort key
115
+ const lastAssortmentMedia = (await AssortmentMedias.findOne(
116
+ {
117
+ assortmentId: doc.assortmentId,
118
+ },
119
+ {
120
+ sort: { sortKey: -1 },
121
+ },
122
+ )) || { sortKey: 0 };
123
+ sortKey = lastAssortmentMedia.sortKey + 1;
124
+ }
125
+
126
+ const assortmentMediaId = await mutations.create(
127
+ {
128
+ tags: [],
129
+ authorId: userId,
130
+ ...doc,
131
+ sortKey,
132
+ },
133
+ userId,
134
+ );
135
+
136
+ const assortmentMedia = await AssortmentMedias.findOne(
137
+ generateDbFilterById(assortmentMediaId),
138
+ {},
139
+ );
140
+
141
+ emit('ASSORTMENT_ADD_MEDIA', {
142
+ assortmentMedia,
143
+ });
144
+
145
+ return assortmentMedia;
146
+ },
147
+
148
+ delete: async (assortmentMediaId) => {
149
+ const selector = generateDbFilterById(assortmentMediaId);
150
+
151
+ const deletedResult = await AssortmentMedias.deleteOne(selector);
152
+
153
+ emit('ASSORTMENT_REMOVE_MEDIA', {
154
+ assortmentMediaId,
155
+ });
156
+
157
+ return deletedResult.deletedCount;
158
+ },
159
+
160
+ deleteMediaFiles: async ({ assortmentId, excludedAssortmentIds, excludedAssortmentMediaIds }) => {
161
+ const selector: Query = assortmentId ? { assortmentId } : {};
162
+
163
+ if (!assortmentId && excludedAssortmentIds) {
164
+ selector.assortmentId = { $nin: excludedAssortmentIds };
165
+ }
166
+
167
+ if (excludedAssortmentMediaIds) {
168
+ selector._id = { $nin: excludedAssortmentMediaIds };
169
+ }
170
+
171
+ const ids = await AssortmentMedias.find(selector, { projection: { _id: true } })
172
+ .map((m) => m._id)
173
+ .toArray();
174
+
175
+ const deletedResult = await AssortmentMedias.deleteMany(selector);
176
+
177
+ ids.forEach((assortmentMediaId) => {
178
+ emit('ASSORTMENT_REMOVE_MEDIA', {
179
+ assortmentMediaId,
180
+ });
181
+ });
182
+
183
+ return deletedResult.deletedCount;
184
+ },
185
+
186
+ // This action is specifically used for the bulk migration scripts in the platform package
187
+ update: async (assortmentMediaId, doc) => {
188
+ const selector = generateDbFilterById(assortmentMediaId);
189
+ const modifier = { $set: doc };
190
+ await AssortmentMedias.updateOne(selector, modifier);
191
+ return AssortmentMedias.findOne(selector, {});
192
+ },
193
+
194
+ updateManualOrder: async ({ sortKeys }, userId) => {
195
+ const changedAssortmentMediaIds = await Promise.all(
196
+ sortKeys.map(async ({ assortmentMediaId, sortKey }) => {
197
+ await AssortmentMedias.updateOne(generateDbFilterById(assortmentMediaId), {
198
+ $set: {
199
+ sortKey: sortKey + 1,
200
+ updated: new Date(),
201
+ updatedBy: userId,
202
+ },
203
+ });
204
+
205
+ return assortmentMediaId;
206
+ }),
207
+ );
208
+
209
+ const assortmentMedias = await AssortmentMedias.find({
210
+ _id: { $in: changedAssortmentMediaIds },
211
+ }).toArray();
212
+
213
+ emit('ASSORTMENT_REORDER_MEDIA', { assortmentMedias });
214
+
215
+ return assortmentMedias;
216
+ },
217
+
218
+ /*
219
+ * Assortment Media Texts
220
+ */
221
+
222
+ texts: {
223
+ // Queries
224
+ findMediaTexts: async ({ assortmentMediaId }) => {
225
+ return AssortmentMediaTexts.find({ assortmentMediaId }).toArray();
226
+ },
227
+
228
+ findLocalizedMediaText: async ({ assortmentMediaId, locale }) => {
229
+ const parsedLocale = new Locale(locale);
230
+
231
+ const text = await findLocalizedText<AssortmentMediaText>(
232
+ AssortmentMediaTexts,
233
+ { assortmentMediaId },
234
+ parsedLocale,
235
+ );
236
+
237
+ return text;
238
+ },
239
+
240
+ // Mutations
241
+ updateMediaTexts: async (assortmentMediaId, texts, userId) => {
242
+ const mediaTexts = await Promise.all(
243
+ texts.map(({ locale, ...text }) =>
244
+ upsertLocalizedText(assortmentMediaId, locale, text, userId),
245
+ ),
246
+ );
247
+
248
+ emit('ASSORTMENT_UPDATE_MEDIA_TEXT', {
249
+ assortmentMediaId,
250
+ mediaTexts,
251
+ });
252
+
253
+ return mediaTexts;
254
+ },
255
+
256
+ upsertLocalizedText,
257
+ },
258
+ };
259
+ };