@unchainedshop/utils 1.1.2 → 1.1.7
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/director/BaseDiscountDirector.js +1 -1
- package/lib/director/BaseDiscountDirector.js.map +1 -1
- package/lib/locale-helpers.d.ts +3 -2
- package/license +274 -0
- package/package.json +7 -7
- package/src/buildSortOption.ts +16 -0
- package/src/db/build-db-indexes.ts +35 -0
- package/src/db/check-id.ts +5 -0
- package/src/db/generate-db-filter-by-id.ts +9 -0
- package/src/db/generate-db-mutations.ts +101 -0
- package/src/db/generate-db-object-id.ts +25 -0
- package/src/director/BaseAdapter.ts +8 -0
- package/src/director/BaseDirector.ts +36 -0
- package/src/director/BaseDiscountAdapter.ts +58 -0
- package/src/director/BaseDiscountDirector.ts +62 -0
- package/src/director/BasePricingAdapter.ts +41 -0
- package/src/director/BasePricingDirector.ts +106 -0
- package/src/director/BasePricingSheet.ts +78 -0
- package/src/find-localized-text.js +50 -0
- package/src/find-preserving-ids.ts +42 -0
- package/src/find-unused-slug.js +26 -0
- package/src/generate-random-hash.js +8 -0
- package/src/locale-helpers.js +41 -0
- package/src/object-invert.js +8 -0
- package/src/pipe-promises.js +2 -0
- package/src/random-value-hex.ts +8 -0
- package/src/schemas/AddressSchema.js +16 -0
- package/src/schemas/ContactSchema.js +9 -0
- package/src/schemas/UsersSchema.js +67 -0
- package/src/schemas/commonSchemaFields.js +32 -0
- package/src/slugify.js +10 -0
- package/src/utils-index.js +52 -0
- package/tests/generate-db-mutations.test.js +1 -1
- package/tests/utils-index.test.js +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { IDiscountAdapter, IDiscountDirector } from '@unchainedshop/types/discount';
|
|
2
|
+
import { log } from '@unchainedshop/logger';
|
|
3
|
+
import { BaseDirector } from './BaseDirector';
|
|
4
|
+
|
|
5
|
+
export const BaseDiscountDirector = (directorName: string): IDiscountDirector => {
|
|
6
|
+
const baseDirector = BaseDirector<IDiscountAdapter>(directorName, {
|
|
7
|
+
adapterSortKey: 'orderIndex',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...baseDirector,
|
|
12
|
+
|
|
13
|
+
actions: async (discountContext, requestContext) => {
|
|
14
|
+
const context = { ...discountContext, ...requestContext };
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
resolveDiscountKeyFromStaticCode: async (options) => {
|
|
18
|
+
if (!context.order) return null;
|
|
19
|
+
|
|
20
|
+
log(`DiscountDirector -> Find user discount for static code ${options?.code}`);
|
|
21
|
+
|
|
22
|
+
const discounts = await Promise.all(
|
|
23
|
+
baseDirector
|
|
24
|
+
.getAdapters()
|
|
25
|
+
.filter((Adapter) => Adapter.isManualAdditionAllowed(options?.code))
|
|
26
|
+
.map(async (Adapter) => {
|
|
27
|
+
const adapter = await Adapter.actions({ context });
|
|
28
|
+
return {
|
|
29
|
+
key: Adapter.key,
|
|
30
|
+
isValid: await adapter.isValidForCodeTriggering(options),
|
|
31
|
+
};
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return discounts.find(({ isValid }) => isValid === true)?.key;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async findSystemDiscounts() {
|
|
39
|
+
if (!context.order) return [];
|
|
40
|
+
const discounts = await Promise.all(
|
|
41
|
+
baseDirector.getAdapters().map(async (Adapter) => {
|
|
42
|
+
const adapter = await Adapter.actions({ context });
|
|
43
|
+
return {
|
|
44
|
+
key: Adapter.key,
|
|
45
|
+
isValid: await adapter.isValidForSystemTriggering(),
|
|
46
|
+
};
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const validDiscounts = discounts
|
|
51
|
+
.filter(({ isValid }) => isValid === true)
|
|
52
|
+
.map(({ key }) => key);
|
|
53
|
+
|
|
54
|
+
if (validDiscounts.length > 0) {
|
|
55
|
+
log(`DiscountDirector -> Found ${validDiscounts.length} system discounts`);
|
|
56
|
+
}
|
|
57
|
+
return validDiscounts;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BasePricingAdapterContext,
|
|
3
|
+
IPricingSheet,
|
|
4
|
+
IPricingAdapter,
|
|
5
|
+
PricingCalculation,
|
|
6
|
+
IPricingAdapterActions,
|
|
7
|
+
} from '@unchainedshop/types/pricing';
|
|
8
|
+
import { log, LogLevel } from '@unchainedshop/logger';
|
|
9
|
+
|
|
10
|
+
export const BasePricingAdapter = <
|
|
11
|
+
Context extends BasePricingAdapterContext,
|
|
12
|
+
Calculation extends PricingCalculation,
|
|
13
|
+
>(): IPricingAdapter<Context, Calculation, IPricingSheet<Calculation>> => ({
|
|
14
|
+
key: '',
|
|
15
|
+
label: '',
|
|
16
|
+
version: '',
|
|
17
|
+
orderIndex: 0,
|
|
18
|
+
|
|
19
|
+
isActivatedFor: () => {
|
|
20
|
+
return false;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
actions: (params) => {
|
|
24
|
+
const calculation = [];
|
|
25
|
+
const actions: IPricingAdapterActions<Calculation, Context> = {
|
|
26
|
+
calculate: async () => {
|
|
27
|
+
return [];
|
|
28
|
+
},
|
|
29
|
+
getCalculation: () => calculation,
|
|
30
|
+
getContext: () => params.context,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return actions as IPricingAdapterActions<Calculation, Context> & {
|
|
34
|
+
resultSheet: () => IPricingSheet<Calculation>;
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
log(message: string, { level = LogLevel.Debug, ...options } = {}) {
|
|
39
|
+
return log(message, { level, ...options });
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Discount } from '@unchainedshop/types/discount';
|
|
2
|
+
import {
|
|
3
|
+
BasePricingAdapterContext,
|
|
4
|
+
BasePricingContext,
|
|
5
|
+
IPricingDirector,
|
|
6
|
+
IPricingAdapter,
|
|
7
|
+
IPricingSheet,
|
|
8
|
+
PricingCalculation,
|
|
9
|
+
IPricingAdapterActions,
|
|
10
|
+
} from '@unchainedshop/types/pricing';
|
|
11
|
+
import { log, LogLevel } from '@unchainedshop/logger';
|
|
12
|
+
import { BaseDirector } from './BaseDirector';
|
|
13
|
+
|
|
14
|
+
export const BasePricingDirector = <
|
|
15
|
+
DirectorContext extends BasePricingContext,
|
|
16
|
+
AdapterContext extends BasePricingAdapterContext,
|
|
17
|
+
Calculation extends PricingCalculation,
|
|
18
|
+
PricingAdapter extends IPricingAdapter<AdapterContext, Calculation, IPricingSheet<Calculation>>,
|
|
19
|
+
>(
|
|
20
|
+
directorName: string,
|
|
21
|
+
): IPricingDirector<
|
|
22
|
+
DirectorContext,
|
|
23
|
+
Calculation,
|
|
24
|
+
AdapterContext,
|
|
25
|
+
IPricingSheet<Calculation>,
|
|
26
|
+
PricingAdapter
|
|
27
|
+
> => {
|
|
28
|
+
const baseDirector = BaseDirector<PricingAdapter>(directorName, {
|
|
29
|
+
adapterSortKey: 'orderIndex',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const director: IPricingDirector<
|
|
33
|
+
DirectorContext,
|
|
34
|
+
Calculation,
|
|
35
|
+
AdapterContext,
|
|
36
|
+
IPricingSheet<Calculation>,
|
|
37
|
+
PricingAdapter
|
|
38
|
+
> = {
|
|
39
|
+
...baseDirector,
|
|
40
|
+
buildPricingContext: async () => {
|
|
41
|
+
return {} as AdapterContext;
|
|
42
|
+
},
|
|
43
|
+
actions: async (pricingContext, requestContext, buildPricingContext) => {
|
|
44
|
+
const context = await buildPricingContext(pricingContext, requestContext);
|
|
45
|
+
|
|
46
|
+
let calculation: Array<Calculation> = [];
|
|
47
|
+
|
|
48
|
+
const actions: IPricingAdapterActions<Calculation, AdapterContext> = {
|
|
49
|
+
async calculate() {
|
|
50
|
+
const Adapters = baseDirector.getAdapters({
|
|
51
|
+
adapterFilter: (Adapter) => {
|
|
52
|
+
return Adapter.isActivatedFor(context);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
calculation = await Adapters.reduce(async (previousPromise, Adapter) => {
|
|
57
|
+
const resolvedCalculation = await previousPromise;
|
|
58
|
+
if (!resolvedCalculation) return null;
|
|
59
|
+
|
|
60
|
+
const discounts: Array<Discount> = await Promise.all(
|
|
61
|
+
context.discounts.map(async (discount) => ({
|
|
62
|
+
discountId: discount._id,
|
|
63
|
+
configuration: await context.modules.orders.discounts.configurationForPricingAdapterKey(
|
|
64
|
+
discount,
|
|
65
|
+
Adapter.key,
|
|
66
|
+
this.calculationSheet(),
|
|
67
|
+
context,
|
|
68
|
+
),
|
|
69
|
+
})),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const adapter = Adapter.actions({
|
|
74
|
+
context,
|
|
75
|
+
calculationSheet: this.calculationSheet(),
|
|
76
|
+
discounts: discounts.filter(({ configuration }) => configuration !== null),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const nextCalculationResult = await adapter.calculate();
|
|
80
|
+
if (!nextCalculationResult) return null;
|
|
81
|
+
calculation = resolvedCalculation.concat(nextCalculationResult);
|
|
82
|
+
return calculation;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
log(error, { level: LogLevel.Error });
|
|
85
|
+
}
|
|
86
|
+
return resolvedCalculation;
|
|
87
|
+
}, Promise.resolve([]));
|
|
88
|
+
|
|
89
|
+
return calculation;
|
|
90
|
+
},
|
|
91
|
+
getCalculation() {
|
|
92
|
+
return calculation;
|
|
93
|
+
},
|
|
94
|
+
getContext() {
|
|
95
|
+
return context;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return actions as IPricingAdapterActions<Calculation, AdapterContext> & {
|
|
100
|
+
calculationSheet: () => IPricingSheet<Calculation>;
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return director;
|
|
106
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PricingCalculation, IBasePricingSheet, PricingSheetParams } from '@unchainedshop/types/pricing';
|
|
2
|
+
|
|
3
|
+
export const BasePricingSheet = <Calculation extends PricingCalculation>(
|
|
4
|
+
params: PricingSheetParams<Calculation>,
|
|
5
|
+
): IBasePricingSheet<Calculation> => {
|
|
6
|
+
const calculation = params.calculation || [];
|
|
7
|
+
|
|
8
|
+
const pricingSheet: IBasePricingSheet<Calculation> = {
|
|
9
|
+
calculation,
|
|
10
|
+
currency: params.currency,
|
|
11
|
+
quantity: params.quantity,
|
|
12
|
+
|
|
13
|
+
getRawPricingSheet() {
|
|
14
|
+
return calculation;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
isValid() {
|
|
18
|
+
return calculation.length > 0;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
sum(filter) {
|
|
22
|
+
return this.filterBy(filter)
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.reduce((sum: number, calculationRow: Calculation) => sum + calculationRow.amount, 0);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
taxSum() {
|
|
28
|
+
return 0;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
gross() {
|
|
32
|
+
return this.sum();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
net() {
|
|
36
|
+
return this.gross() - this.taxSum();
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
total({ category, useNetPrice } = { useNetPrice: false }) {
|
|
40
|
+
if (!category) {
|
|
41
|
+
return {
|
|
42
|
+
amount: Math.round(useNetPrice ? this.net() : this.gross()),
|
|
43
|
+
currency: params.currency,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
amount: Math.round(this.sum({ category } as any)),
|
|
49
|
+
currency: params.currency,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
filterBy(filter) {
|
|
54
|
+
const filteredCalculation = Object.keys(filter || {}).reduce(
|
|
55
|
+
(oldCalculation, filterKey) =>
|
|
56
|
+
oldCalculation.filter(
|
|
57
|
+
(row: Calculation) =>
|
|
58
|
+
!!row && (filter[filterKey] === undefined || row[filterKey] === filter[filterKey]),
|
|
59
|
+
),
|
|
60
|
+
calculation,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return filteredCalculation;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
resetCalculation(calculationSheet) {
|
|
67
|
+
calculationSheet.filterBy().forEach(({ amount, ...row }: Calculation) => {
|
|
68
|
+
pricingSheet.calculation.push({
|
|
69
|
+
...row,
|
|
70
|
+
amount: amount * -1,
|
|
71
|
+
} as Calculation);
|
|
72
|
+
});
|
|
73
|
+
return pricingSheet.calculation;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return pricingSheet;
|
|
78
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import 'abort-controller/polyfill';
|
|
2
|
+
import LRU from 'lru-cache';
|
|
3
|
+
import { systemLocale } from './locale-helpers';
|
|
4
|
+
|
|
5
|
+
const { NODE_ENV } = process.env;
|
|
6
|
+
|
|
7
|
+
const ttl = NODE_ENV === 'production' ? 1000 * 30 : 0; // 30 seconds or 1 second
|
|
8
|
+
|
|
9
|
+
const textCache = new LRU({ max: 50000, ttl });
|
|
10
|
+
|
|
11
|
+
const extendSelectorWithLocale = (selector, locale) => {
|
|
12
|
+
const localeSelector = {
|
|
13
|
+
locale: { $in: [locale.normalized, locale.language] },
|
|
14
|
+
};
|
|
15
|
+
return { ...localeSelector, ...selector };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const findLocalizedText = async (collection, selector, locale) => {
|
|
19
|
+
const cacheKey = JSON.stringify({
|
|
20
|
+
n: collection._name, // eslint-disable-line
|
|
21
|
+
s: selector,
|
|
22
|
+
l: locale,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const cachedText = textCache.get(cacheKey);
|
|
26
|
+
|
|
27
|
+
if (cachedText) return cachedText;
|
|
28
|
+
|
|
29
|
+
const exactTranslation = await collection.findOne(extendSelectorWithLocale(selector, locale));
|
|
30
|
+
if (exactTranslation) {
|
|
31
|
+
textCache.set(cacheKey, exactTranslation);
|
|
32
|
+
return exactTranslation;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (systemLocale.normalized !== locale.normalized) {
|
|
36
|
+
const fallbackTranslation = await collection.findOne(
|
|
37
|
+
extendSelectorWithLocale(selector, systemLocale),
|
|
38
|
+
);
|
|
39
|
+
if (fallbackTranslation) {
|
|
40
|
+
textCache.set(cacheKey, fallbackTranslation);
|
|
41
|
+
return fallbackTranslation;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const foundText = await collection.findOne(selector, {});
|
|
46
|
+
textCache.set(cacheKey, foundText);
|
|
47
|
+
return foundText;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default findLocalizedText;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Collection, FindOptions, Query } from '@unchainedshop/types/common';
|
|
2
|
+
|
|
3
|
+
const { AMAZON_DOCUMENTDB_COMPAT_MODE } = process.env;
|
|
4
|
+
|
|
5
|
+
const sortByIndex = {
|
|
6
|
+
index: 1,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const sortBySequence = {
|
|
10
|
+
sequence: 1,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const defaultSort = AMAZON_DOCUMENTDB_COMPAT_MODE ? sortBySequence : sortByIndex;
|
|
14
|
+
|
|
15
|
+
export const findPreservingIds =
|
|
16
|
+
<T>(collection: Collection<T>) =>
|
|
17
|
+
async (selector: Query, ids: Array<string>, options?: FindOptions): Promise<Array<T>> => {
|
|
18
|
+
const { skip, limit, sort = defaultSort } = options || {};
|
|
19
|
+
const filteredSelector = {
|
|
20
|
+
...selector,
|
|
21
|
+
_id: { $in: ids },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const filteredPipeline = [
|
|
25
|
+
{
|
|
26
|
+
$match: filteredSelector,
|
|
27
|
+
},
|
|
28
|
+
typeof sort === 'object' &&
|
|
29
|
+
'index' in sort && {
|
|
30
|
+
$addFields: {
|
|
31
|
+
index: { $indexOfArray: [ids, '$_id'] },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
sort && { $sort: sort },
|
|
35
|
+
skip && { $skip: skip },
|
|
36
|
+
limit && { $limit: limit },
|
|
37
|
+
].filter(Boolean);
|
|
38
|
+
|
|
39
|
+
const aggregationPointer = collection.aggregate(filteredPipeline);
|
|
40
|
+
const items = await aggregationPointer.toArray();
|
|
41
|
+
return items as Array<T>;
|
|
42
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const DELIMITER = '-';
|
|
2
|
+
|
|
3
|
+
const addSuffixToSlug = (slug, index = 1, delimiter = DELIMITER) => {
|
|
4
|
+
return `${slug}${delimiter}${index}`;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const incrementSuffixedSlug = (slugIncludingSuffix, delimiter = DELIMITER) => {
|
|
8
|
+
const slugParts = slugIncludingSuffix.split(delimiter);
|
|
9
|
+
const suffixedIndex = parseInt(slugParts.pop(), 10);
|
|
10
|
+
const slugWithoutSuffix = slugParts.join(delimiter);
|
|
11
|
+
return addSuffixToSlug(slugWithoutSuffix, suffixedIndex + 1);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default (checkSlugIsUniqueFn, { slugify }) => {
|
|
15
|
+
const findUnusedSlug = async ({ title, existingSlug, newSlug }) => {
|
|
16
|
+
const slug = newSlug || existingSlug || `${slugify(title)}`;
|
|
17
|
+
if (!(await checkSlugIsUniqueFn(slug))) {
|
|
18
|
+
const isSlugAlreadySuffixed = !!newSlug;
|
|
19
|
+
return findUnusedSlug({
|
|
20
|
+
newSlug: isSlugAlreadySuffixed ? incrementSuffixedSlug(slug) : addSuffixToSlug(slug),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return slug;
|
|
24
|
+
};
|
|
25
|
+
return findUnusedSlug;
|
|
26
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Hashids from 'hashids/cjs';
|
|
2
|
+
|
|
3
|
+
const hashids = new Hashids('unchained', 6, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
|
|
4
|
+
|
|
5
|
+
export default () => {
|
|
6
|
+
const randomNumber = Math.floor(Math.random() * (999999999 - 1)) + 1;
|
|
7
|
+
return hashids.encode(randomNumber);
|
|
8
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Locales, Locale } from 'locale';
|
|
2
|
+
|
|
3
|
+
const { UNCHAINED_LANG = 'de', UNCHAINED_COUNTRY = 'CH' } = process.env;
|
|
4
|
+
|
|
5
|
+
export const systemLocale = new Locale(`${UNCHAINED_LANG}-${UNCHAINED_COUNTRY}`);
|
|
6
|
+
|
|
7
|
+
export const resolveBestSupported = (acceptLanguage, supportedLocales) => {
|
|
8
|
+
const acceptLocale = new Locales(acceptLanguage);
|
|
9
|
+
const bestLocale = acceptLocale.best(supportedLocales);
|
|
10
|
+
if (!bestLocale) return systemLocale;
|
|
11
|
+
return bestLocale;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const resolveBestCountry = (localeCountry, shopCountry, countries) => {
|
|
15
|
+
if (shopCountry) {
|
|
16
|
+
const resolvedCountry = countries.reduce((lastResolved, country) => {
|
|
17
|
+
if (shopCountry === country.isoCode) {
|
|
18
|
+
return country.isoCode;
|
|
19
|
+
}
|
|
20
|
+
return lastResolved;
|
|
21
|
+
}, null);
|
|
22
|
+
if (resolvedCountry) {
|
|
23
|
+
return resolvedCountry;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return localeCountry || systemLocale.country;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const resolveUserRemoteAddress = (req) => {
|
|
30
|
+
const remoteAddress =
|
|
31
|
+
req.headers['x-real-ip'] ||
|
|
32
|
+
req.headers['x-forwarded-for'] ||
|
|
33
|
+
req.connection.remoteAddress ||
|
|
34
|
+
req.socket.remoteAddress ||
|
|
35
|
+
req.connection.socket.remoteAddress;
|
|
36
|
+
|
|
37
|
+
const remotePort =
|
|
38
|
+
req.connection?.remotePort || req.socket?.remotePort || req.connection?.socket?.remotePort;
|
|
39
|
+
|
|
40
|
+
return { remoteAddress, remotePort };
|
|
41
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import SimpleSchema from 'simpl-schema';
|
|
2
|
+
|
|
3
|
+
export const AddressSchema = new SimpleSchema(
|
|
4
|
+
{
|
|
5
|
+
firstName: String,
|
|
6
|
+
lastName: String,
|
|
7
|
+
company: String,
|
|
8
|
+
addressLine: String,
|
|
9
|
+
addressLine2: String,
|
|
10
|
+
city: String,
|
|
11
|
+
postalCode: String,
|
|
12
|
+
regionCode: String,
|
|
13
|
+
countryCode: String,
|
|
14
|
+
},
|
|
15
|
+
{ requiredByDefault: false },
|
|
16
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import SimpleSchema from 'simpl-schema';
|
|
2
|
+
import { AddressSchema } from './AddressSchema';
|
|
3
|
+
import { timestampFields } from './commonSchemaFields';
|
|
4
|
+
|
|
5
|
+
const ProfileSchema = new SimpleSchema(
|
|
6
|
+
{
|
|
7
|
+
displayName: String,
|
|
8
|
+
birthday: Date,
|
|
9
|
+
phoneMobile: String,
|
|
10
|
+
gender: String,
|
|
11
|
+
address: AddressSchema,
|
|
12
|
+
},
|
|
13
|
+
{ requiredByDefault: false },
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const LastLoginSchema = new SimpleSchema(
|
|
17
|
+
{
|
|
18
|
+
timestamp: Date,
|
|
19
|
+
locale: String,
|
|
20
|
+
countryContext: String,
|
|
21
|
+
remoteAddress: String,
|
|
22
|
+
remotePort: String,
|
|
23
|
+
userAgent: String,
|
|
24
|
+
},
|
|
25
|
+
{ requiredByDefault: false },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const LastContactSchema = new SimpleSchema(
|
|
29
|
+
{
|
|
30
|
+
telNumber: String,
|
|
31
|
+
emailAddress: String,
|
|
32
|
+
},
|
|
33
|
+
{ requiredByDefault: false },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const UserSchema = new SimpleSchema(
|
|
37
|
+
{
|
|
38
|
+
emails: Array,
|
|
39
|
+
'emails.$': Object,
|
|
40
|
+
'emails.$.address': String,
|
|
41
|
+
'emails.$.verified': Boolean,
|
|
42
|
+
username: String,
|
|
43
|
+
lastLogin: LastLoginSchema,
|
|
44
|
+
profile: ProfileSchema,
|
|
45
|
+
lastBillingAddress: AddressSchema,
|
|
46
|
+
lastContact: LastContactSchema,
|
|
47
|
+
guest: Boolean,
|
|
48
|
+
initialPassword: Boolean,
|
|
49
|
+
tags: Array,
|
|
50
|
+
'tags.$': String,
|
|
51
|
+
avatarId: String,
|
|
52
|
+
meta: {
|
|
53
|
+
type: Object,
|
|
54
|
+
optional: true,
|
|
55
|
+
blackbox: true,
|
|
56
|
+
},
|
|
57
|
+
services: {
|
|
58
|
+
type: Object,
|
|
59
|
+
optional: true,
|
|
60
|
+
blackbox: true,
|
|
61
|
+
},
|
|
62
|
+
roles: Array,
|
|
63
|
+
'roles.$': String,
|
|
64
|
+
...timestampFields,
|
|
65
|
+
},
|
|
66
|
+
{ requiredByDefault: false },
|
|
67
|
+
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const contextFields = {
|
|
2
|
+
context: {
|
|
3
|
+
type: Object,
|
|
4
|
+
blackbox: true,
|
|
5
|
+
required: false,
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const logFields = {
|
|
10
|
+
log: Array,
|
|
11
|
+
'log.$': {
|
|
12
|
+
type: Object,
|
|
13
|
+
},
|
|
14
|
+
'log.$.date': {
|
|
15
|
+
type: Date,
|
|
16
|
+
},
|
|
17
|
+
'log.$.status': {
|
|
18
|
+
type: String,
|
|
19
|
+
},
|
|
20
|
+
'log.$.info': {
|
|
21
|
+
type: String,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const timestampFields = {
|
|
26
|
+
created: { type: Date, required: true },
|
|
27
|
+
createdBy: { type: String }, // Logically it is required but for backwards compatibility it's set to optional
|
|
28
|
+
updated: { type: Date },
|
|
29
|
+
updatedBy: { type: String },
|
|
30
|
+
deleted: { type: Date },
|
|
31
|
+
deletedBy: { type: String },
|
|
32
|
+
};
|
package/src/slugify.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function slugify(text) {
|
|
2
|
+
return text
|
|
3
|
+
.toString()
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/\s+/g, '-') // Replace spaces with -
|
|
6
|
+
.replace(/[^\w-]+/g, '') // Remove all non-word chars
|
|
7
|
+
.replace(/--+/g, '-') // Replace multiple - with single -
|
|
8
|
+
.replace(/^-+/, '') // Trim - from start of text
|
|
9
|
+
.replace(/-+$/, ''); // Trim - from end of text
|
|
10
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AddressSchema } from './schemas/AddressSchema';
|
|
2
|
+
import { ContactSchema } from './schemas/ContactSchema';
|
|
3
|
+
import { timestampFields, contextFields, logFields } from './schemas/commonSchemaFields';
|
|
4
|
+
import { UserSchema } from './schemas/UsersSchema';
|
|
5
|
+
|
|
6
|
+
export { default as findLocalizedText } from './find-localized-text';
|
|
7
|
+
export * from './locale-helpers';
|
|
8
|
+
export { default as objectInvert } from './object-invert';
|
|
9
|
+
export { default as findUnusedSlug } from './find-unused-slug';
|
|
10
|
+
export { default as slugify } from './slugify';
|
|
11
|
+
export { default as pipePromises } from './pipe-promises';
|
|
12
|
+
export { default as generateRandomHash } from './generate-random-hash';
|
|
13
|
+
export { default as randomValueHex } from './random-value-hex';
|
|
14
|
+
export { default as buildSortOptions } from './buildSortOption';
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Db utils
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export { checkId } from './db/check-id';
|
|
21
|
+
export { generateDbObjectId } from './db/generate-db-object-id';
|
|
22
|
+
export { generateDbFilterById } from './db/generate-db-filter-by-id';
|
|
23
|
+
export { generateDbMutations } from './db/generate-db-mutations';
|
|
24
|
+
export { buildDbIndexes } from './db/build-db-indexes';
|
|
25
|
+
export { findPreservingIds } from './find-preserving-ids';
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Schemas
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const Schemas = {
|
|
32
|
+
timestampFields,
|
|
33
|
+
contextFields,
|
|
34
|
+
logFields,
|
|
35
|
+
Address: AddressSchema,
|
|
36
|
+
Contact: ContactSchema,
|
|
37
|
+
User: UserSchema,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { Schemas };
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Director
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
export { BaseAdapter } from './director/BaseAdapter';
|
|
47
|
+
export { BaseDirector } from './director/BaseDirector';
|
|
48
|
+
export { BasePricingAdapter } from './director/BasePricingAdapter';
|
|
49
|
+
export { BasePricingDirector } from './director/BasePricingDirector';
|
|
50
|
+
export { BasePricingSheet } from './director/BasePricingSheet';
|
|
51
|
+
export { BaseDiscountAdapter } from './director/BaseDiscountAdapter';
|
|
52
|
+
export { BaseDiscountDirector } from './director/BaseDiscountDirector';
|