abmp-npm 2.0.76 → 2.0.78
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/package.json +8 -1
- package/pages/Home.js +27 -55
- package/pages/index.js +0 -1
- package/public/Utils/personalDetailsUtils.js +5 -1
- package/public/Utils/sharedUtils.js +32 -0
- package/public/consts.js +0 -1
- package/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +0 -19
- package/.claude/worktrees/fix-save-button-disable/.prettierignore +0 -7
- package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +0 -16
- package/.claude/worktrees/fix-save-button-disable/README.md +0 -1
- package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +0 -136
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +0 -124
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +0 -49
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +0 -194
- package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +0 -13
- package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +0 -251
- package/.claude/worktrees/fix-save-button-disable/backend/consts.js +0 -58
- package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +0 -67
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +0 -222
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +0 -39
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +0 -213
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +0 -41
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +0 -161
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +0 -151
- package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +0 -29
- package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +0 -58
- package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +0 -18
- package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +0 -52
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +0 -86
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +0 -3
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +0 -35
- package/.claude/worktrees/fix-save-button-disable/backend/index.js +0 -19
- package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +0 -158
- package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +0 -27
- package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +0 -8
- package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +0 -54
- package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +0 -159
- package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +0 -111
- package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +0 -88
- package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +0 -789
- package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +0 -54
- package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +0 -3
- package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +0 -191
- package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +0 -128
- package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +0 -124
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +0 -256
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +0 -160
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +0 -34
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +0 -78
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +0 -134
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +0 -10
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +0 -46
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +0 -251
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +0 -433
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +0 -606
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +0 -378
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +0 -174
- package/.claude/worktrees/fix-save-button-disable/backend/utils.js +0 -277
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +0 -159
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +0 -201
- package/.claude/worktrees/fix-save-button-disable/eslint.config.js +0 -120
- package/.claude/worktrees/fix-save-button-disable/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/package-lock.json +0 -12160
- package/.claude/worktrees/fix-save-button-disable/package.json +0 -60
- package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +0 -129
- package/.claude/worktrees/fix-save-button-disable/pages/Home.js +0 -779
- package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +0 -27
- package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +0 -29
- package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +0 -354
- package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +0 -39
- package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +0 -13
- package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +0 -46
- package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +0 -19
- package/.claude/worktrees/fix-save-button-disable/pages/index.js +0 -12
- package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +0 -2486
- package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +0 -798
- package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +0 -25
- package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +0 -235
- package/.claude/worktrees/fix-save-button-disable/public/consts.js +0 -113
- package/.claude/worktrees/fix-save-button-disable/public/index.js +0 -5
- package/.claude/worktrees/fix-save-button-disable/public/messages.js +0 -16
- package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +0 -43
- package/.husky/pre-commit +0 -19
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -16
- package/backend/__tests__/daily-pull-execution-check.test.js +0 -124
- package/backend/__tests__/ensure-unique-urls-in-batch.test.js +0 -129
- package/backend/__tests__/login-email-sync.test.js +0 -49
- package/backend/__tests__/transient-retry-and-bulk-lookup.test.js +0 -129
- package/backend/__tests__/url-uniqueness.test.js +0 -194
- package/dev-only-scripts/extract-duplicate-url-groups.js +0 -201
- package/dev-only-scripts/find-duplicate-ids.js +0 -159
- package/dev-only-scripts/find-duplicate-urls.js +0 -201
- package/eslint.config.js +0 -120
- package/pages/contactForLocation.js +0 -28
package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const { LOGIN_EMAIL_SYNC_STATUS } = require('../consts');
|
|
2
|
-
const { summarizeLoginEmailOutcomes } = require('../daily-pull/utils');
|
|
3
|
-
|
|
4
|
-
const { UPDATED, FAILED, SKIPPED } = LOGIN_EMAIL_SYNC_STATUS;
|
|
5
|
-
|
|
6
|
-
describe('summarizeLoginEmailOutcomes', () => {
|
|
7
|
-
test('collects only FAILED outcomes as failures and failed ids', () => {
|
|
8
|
-
const outcomes = [
|
|
9
|
-
{ memberId: 1, wixMemberId: 'w1', desiredEmail: 'a@x.com', status: UPDATED },
|
|
10
|
-
{
|
|
11
|
-
memberId: 2,
|
|
12
|
-
wixMemberId: 'w2',
|
|
13
|
-
desiredEmail: 'b@x.com',
|
|
14
|
-
status: FAILED,
|
|
15
|
-
error: 'duplicate',
|
|
16
|
-
},
|
|
17
|
-
{ memberId: 3, status: SKIPPED, desiredEmail: 'c@x.com' },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const { failedMemberIds, failures } = summarizeLoginEmailOutcomes(outcomes);
|
|
21
|
-
|
|
22
|
-
expect([...failedMemberIds]).toEqual([2]);
|
|
23
|
-
expect(failures).toEqual([
|
|
24
|
-
{ memberId: 2, wixMemberId: 'w2', desiredEmail: 'b@x.com', error: 'duplicate' },
|
|
25
|
-
]);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('UPDATED and SKIPPED never produce failures (CMS email is left to advance/no-op)', () => {
|
|
29
|
-
const outcomes = [
|
|
30
|
-
{ memberId: 1, status: UPDATED, desiredEmail: 'a@x.com' },
|
|
31
|
-
{ memberId: 2, status: SKIPPED, desiredEmail: 'b@x.com' },
|
|
32
|
-
];
|
|
33
|
-
const { failedMemberIds, failures } = summarizeLoginEmailOutcomes(outcomes);
|
|
34
|
-
expect(failedMemberIds.size).toBe(0);
|
|
35
|
-
expect(failures).toEqual([]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('defaults a missing error message', () => {
|
|
39
|
-
const { failures } = summarizeLoginEmailOutcomes([
|
|
40
|
-
{ memberId: 9, wixMemberId: 'w9', desiredEmail: 'z@x.com', status: FAILED },
|
|
41
|
-
]);
|
|
42
|
-
expect(failures[0].error).toBe('unknown error');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('handles empty / missing input', () => {
|
|
46
|
-
expect(summarizeLoginEmailOutcomes([]).failures).toEqual([]);
|
|
47
|
-
expect(summarizeLoginEmailOutcomes().failures).toEqual([]);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
jest.mock('../elevated-modules', () => ({
|
|
2
|
-
wixData: { query: jest.fn() },
|
|
3
|
-
}));
|
|
4
|
-
|
|
5
|
-
const { wixData } = require('../elevated-modules');
|
|
6
|
-
const { findMembersByIds } = require('../members-data-methods');
|
|
7
|
-
const { withTransientErrorRetry, isTransientNetworkError } = require('../utils');
|
|
8
|
-
|
|
9
|
-
const makeQueryResult = items => ({ items, hasNext: () => false });
|
|
10
|
-
|
|
11
|
-
const mockQueryReturning = find => {
|
|
12
|
-
const query = {
|
|
13
|
-
hasSome: jest.fn().mockReturnThis(),
|
|
14
|
-
limit: jest.fn().mockReturnThis(),
|
|
15
|
-
find,
|
|
16
|
-
};
|
|
17
|
-
wixData.query.mockReturnValue(query);
|
|
18
|
-
return query;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
describe('isTransientNetworkError', () => {
|
|
22
|
-
test('matches undici "fetch failed" and common network error codes', () => {
|
|
23
|
-
expect(isTransientNetworkError(new Error('fetch failed'))).toBe(true);
|
|
24
|
-
expect(isTransientNetworkError({ message: 'request failed', code: 'ECONNRESET' })).toBe(true);
|
|
25
|
-
expect(
|
|
26
|
-
isTransientNetworkError({ message: 'fetch failed', cause: { code: 'UND_ERR_SOCKET' } })
|
|
27
|
-
).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('does not match application errors', () => {
|
|
31
|
-
expect(isTransientNetworkError(new Error('Multiple members found with memberId 1'))).toBe(
|
|
32
|
-
false
|
|
33
|
-
);
|
|
34
|
-
expect(isTransientNetworkError(new Error('WDE0025: validation failed'))).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('withTransientErrorRetry', () => {
|
|
39
|
-
test('retries transient failures and resolves with the eventual result', async () => {
|
|
40
|
-
const operation = jest
|
|
41
|
-
.fn()
|
|
42
|
-
.mockRejectedValueOnce(new Error('fetch failed'))
|
|
43
|
-
.mockResolvedValueOnce('ok');
|
|
44
|
-
|
|
45
|
-
await expect(withTransientErrorRetry(operation, { baseDelayMs: 1 })).resolves.toBe('ok');
|
|
46
|
-
expect(operation).toHaveBeenCalledTimes(2);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('rethrows immediately on non-transient errors', async () => {
|
|
50
|
-
const operation = jest.fn().mockRejectedValue(new Error('validation failed'));
|
|
51
|
-
|
|
52
|
-
await expect(withTransientErrorRetry(operation, { baseDelayMs: 1 })).rejects.toThrow(
|
|
53
|
-
'validation failed'
|
|
54
|
-
);
|
|
55
|
-
expect(operation).toHaveBeenCalledTimes(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('gives up after the configured number of retries', async () => {
|
|
59
|
-
const operation = jest.fn().mockRejectedValue(new Error('fetch failed'));
|
|
60
|
-
|
|
61
|
-
await expect(
|
|
62
|
-
withTransientErrorRetry(operation, { retries: 2, baseDelayMs: 1 })
|
|
63
|
-
).rejects.toThrow('fetch failed');
|
|
64
|
-
expect(operation).toHaveBeenCalledTimes(3);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('findMembersByIds', () => {
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
wixData.query.mockReset();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('returns a map of String(memberId) to member record', async () => {
|
|
74
|
-
mockQueryReturning(
|
|
75
|
-
jest.fn().mockResolvedValue(
|
|
76
|
-
makeQueryResult([
|
|
77
|
-
{ _id: 'a', memberId: 1 },
|
|
78
|
-
{ _id: 'b', memberId: 2 },
|
|
79
|
-
])
|
|
80
|
-
)
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const membersById = await findMembersByIds([1, 2, 3]);
|
|
84
|
-
|
|
85
|
-
expect(membersById.get('1')).toEqual({ _id: 'a', memberId: 1 });
|
|
86
|
-
expect(membersById.get('2')).toEqual({ _id: 'b', memberId: 2 });
|
|
87
|
-
expect(membersById.has('3')).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('deduplicates input IDs and skips null/undefined before querying', async () => {
|
|
91
|
-
const query = mockQueryReturning(jest.fn().mockResolvedValue(makeQueryResult([])));
|
|
92
|
-
|
|
93
|
-
await findMembersByIds([1, 1, null, undefined, 2]);
|
|
94
|
-
|
|
95
|
-
expect(query.hasSome).toHaveBeenCalledWith('memberId', [1, 2]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('returns an empty map without querying when there are no IDs', async () => {
|
|
99
|
-
const membersById = await findMembersByIds([]);
|
|
100
|
-
|
|
101
|
-
expect(membersById.size).toBe(0);
|
|
102
|
-
expect(wixData.query).not.toHaveBeenCalled();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('chunks large ID lists into multiple queries', async () => {
|
|
106
|
-
const query = mockQueryReturning(jest.fn().mockResolvedValue(makeQueryResult([])));
|
|
107
|
-
const ids = Array.from({ length: 250 }, (_, i) => i + 1);
|
|
108
|
-
|
|
109
|
-
await findMembersByIds(ids);
|
|
110
|
-
|
|
111
|
-
expect(query.hasSome).toHaveBeenCalledTimes(3);
|
|
112
|
-
expect(query.hasSome.mock.calls.map(call => call[1].length)).toEqual([100, 100, 50]);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('throws when multiple records share the same memberId', async () => {
|
|
116
|
-
mockQueryReturning(
|
|
117
|
-
jest.fn().mockResolvedValue(
|
|
118
|
-
makeQueryResult([
|
|
119
|
-
{ _id: 'a', memberId: 1 },
|
|
120
|
-
{ _id: 'b', memberId: 1 },
|
|
121
|
-
])
|
|
122
|
-
)
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
await expect(findMembersByIds([1])).rejects.toThrow(
|
|
126
|
-
'Multiple members found with memberId(s): [1]'
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
const { incrementUrlCounter, extractBaseUrl } = require('../daily-pull/utils');
|
|
2
|
-
const {
|
|
3
|
-
normalizeUrlForComparison,
|
|
4
|
-
sortByUrlCounterDescending,
|
|
5
|
-
extractUrlCounter,
|
|
6
|
-
} = require('../utils');
|
|
7
|
-
|
|
8
|
-
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Simulates getMemberBySlug's normalized-comparison branch using
|
|
12
|
-
* the ACTUAL production sort comparator imported from utils.js.
|
|
13
|
-
*/
|
|
14
|
-
function simulateGetHighestMember(allMembers, slug) {
|
|
15
|
-
const matching = allMembers.filter(
|
|
16
|
-
m => m.url && normalizeUrlForComparison(m.url) === slug.toLowerCase()
|
|
17
|
-
);
|
|
18
|
-
matching.sort(sortByUrlCounterDescending);
|
|
19
|
-
return matching[0] || null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Simulates ensureUniqueUrl's counter-increment logic given the
|
|
24
|
-
* "highest" member returned by getMemberBySlug.
|
|
25
|
-
*/
|
|
26
|
-
function simulateEnsureUniqueUrl(baseSlug, highestMember) {
|
|
27
|
-
if (!highestMember || !highestMember.url) return baseSlug;
|
|
28
|
-
return incrementUrlCounter(highestMember?.url, baseSlug);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ─── Test data ───────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
function buildMembersInDb() {
|
|
34
|
-
return [
|
|
35
|
-
{ memberId: 1, url: 'firstNameLastName' },
|
|
36
|
-
{ memberId: 2, url: 'firstNameLastName-1' },
|
|
37
|
-
{ memberId: 3, url: 'firstNameLastName-2' },
|
|
38
|
-
{ memberId: 4, url: 'firstNameLastName-3' },
|
|
39
|
-
{ memberId: 5, url: 'firstNameLastName-4' },
|
|
40
|
-
{ memberId: 6, url: 'firstNameLastName-5' },
|
|
41
|
-
{ memberId: 7, url: 'firstNameLastName-6' },
|
|
42
|
-
{ memberId: 8, url: 'firstNameLastName-7' },
|
|
43
|
-
{ memberId: 9, url: 'firstNameLastName-8' },
|
|
44
|
-
{ memberId: 10, url: 'firstNameLastName-9' },
|
|
45
|
-
{ memberId: 11, url: 'firstNameLastName-10' },
|
|
46
|
-
{ memberId: 12, url: 'firstNameLastName-11' },
|
|
47
|
-
{ memberId: 13, url: 'firstNameLastName-12' },
|
|
48
|
-
];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function buildLargeCounterMembers() {
|
|
52
|
-
const members = [];
|
|
53
|
-
for (let i = 0; i <= 105; i++) {
|
|
54
|
-
members.push({ memberId: i, url: i === 0 ? 'testUser' : `testUser-${i}` });
|
|
55
|
-
}
|
|
56
|
-
return members;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ─── Tests ───────────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
describe('Production sort: getMemberBySlug must return the HIGHEST counter', () => {
|
|
62
|
-
test('should return -12 as the highest URL (not -9)', () => {
|
|
63
|
-
const members = buildMembersInDb();
|
|
64
|
-
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
65
|
-
|
|
66
|
-
expect(highest.url).toBe('firstNameLastName-12');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('should sort -12 above -9 in descending order', () => {
|
|
70
|
-
const members = buildMembersInDb();
|
|
71
|
-
const sorted = [...members]
|
|
72
|
-
.filter(m => normalizeUrlForComparison(m.url) === 'firstnamelastname')
|
|
73
|
-
.sort(sortByUrlCounterDescending);
|
|
74
|
-
const sortedUrls = sorted.map(m => m.url);
|
|
75
|
-
|
|
76
|
-
const indexOf9 = sortedUrls.indexOf('firstNameLastName-9');
|
|
77
|
-
const indexOf12 = sortedUrls.indexOf('firstNameLastName-12');
|
|
78
|
-
|
|
79
|
-
expect(indexOf12).toBeLessThan(indexOf9);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should handle large counters (100+) correctly', () => {
|
|
83
|
-
const members = buildLargeCounterMembers();
|
|
84
|
-
const highest = simulateGetHighestMember(members, 'testuser');
|
|
85
|
-
|
|
86
|
-
expect(highest.url).toBe('testUser-105');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('should handle hyphenated names (mary-jane) correctly', () => {
|
|
90
|
-
const members = [
|
|
91
|
-
{ memberId: 1, url: 'mary-jane' },
|
|
92
|
-
{ memberId: 2, url: 'mary-jane-1' },
|
|
93
|
-
{ memberId: 3, url: 'mary-jane-2' },
|
|
94
|
-
{ memberId: 4, url: 'mary-jane-10' },
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
const highest = simulateGetHighestMember(members, 'mary-jane');
|
|
98
|
-
expect(highest.url).toBe('mary-jane-10');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('Production sort: ensureUniqueUrl must generate a truly unique URL', () => {
|
|
103
|
-
test('should generate -13 (not -10) when DB has URLs up to -12', () => {
|
|
104
|
-
const members = buildMembersInDb();
|
|
105
|
-
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
106
|
-
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
107
|
-
|
|
108
|
-
expect(newUrl).toBe('firstNameLastName-13');
|
|
109
|
-
|
|
110
|
-
const existingUrls = members.map(m => m.url);
|
|
111
|
-
expect(existingUrls).not.toContain(newUrl);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('repeated daily pulls should produce sequential unique URLs', () => {
|
|
115
|
-
const members = buildMembersInDb();
|
|
116
|
-
const generatedUrls = [];
|
|
117
|
-
|
|
118
|
-
for (let day = 0; day < 5; day++) {
|
|
119
|
-
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
120
|
-
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
121
|
-
generatedUrls.push(newUrl);
|
|
122
|
-
members.push({ memberId: 100 + day, url: newUrl });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
expect(new Set(generatedUrls).size).toBe(5);
|
|
126
|
-
expect(generatedUrls).toEqual([
|
|
127
|
-
'firstNameLastName-13',
|
|
128
|
-
'firstNameLastName-14',
|
|
129
|
-
'firstNameLastName-15',
|
|
130
|
-
'firstNameLastName-16',
|
|
131
|
-
'firstNameLastName-17',
|
|
132
|
-
]);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('should NOT produce duplicates (the -10 bug)', () => {
|
|
136
|
-
const members = buildMembersInDb();
|
|
137
|
-
const highest = simulateGetHighestMember(members, 'firstnamelastname');
|
|
138
|
-
const newUrl = simulateEnsureUniqueUrl('firstNameLastName', highest);
|
|
139
|
-
|
|
140
|
-
expect(newUrl).not.toBe('firstNameLastName-10');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('Production sort: ensureUniqueUrlsInBatch single-member path', () => {
|
|
145
|
-
test('incrementUrlCounter should produce a unique URL from highest DB member', () => {
|
|
146
|
-
const members = buildMembersInDb();
|
|
147
|
-
const dbMember = simulateGetHighestMember(members, 'firstnamelastname');
|
|
148
|
-
const result = incrementUrlCounter(dbMember.url, 'firstNameLastName');
|
|
149
|
-
|
|
150
|
-
expect(result).toBe('firstNameLastName-13');
|
|
151
|
-
|
|
152
|
-
const existingUrls = members.map(m => m.url);
|
|
153
|
-
expect(existingUrls).not.toContain(result);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('Utility functions', () => {
|
|
158
|
-
test('normalizeUrlForComparison strips trailing counter', () => {
|
|
159
|
-
expect(normalizeUrlForComparison('firstNameLastName')).toBe('firstnamelastname');
|
|
160
|
-
expect(normalizeUrlForComparison('firstNameLastName-1')).toBe('firstnamelastname');
|
|
161
|
-
expect(normalizeUrlForComparison('firstNameLastName-9')).toBe('firstnamelastname');
|
|
162
|
-
expect(normalizeUrlForComparison('firstNameLastName-10')).toBe('firstnamelastname');
|
|
163
|
-
expect(normalizeUrlForComparison('firstNameLastName-100')).toBe('firstnamelastname');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('extractUrlCounter returns numeric counter', () => {
|
|
167
|
-
expect(extractUrlCounter('firstNameLastName')).toBe(-1);
|
|
168
|
-
expect(extractUrlCounter('firstNameLastName-1')).toBe(1);
|
|
169
|
-
expect(extractUrlCounter('firstNameLastName-9')).toBe(9);
|
|
170
|
-
expect(extractUrlCounter('firstNameLastName-10')).toBe(10);
|
|
171
|
-
expect(extractUrlCounter('firstNameLastName-100')).toBe(100);
|
|
172
|
-
expect(extractUrlCounter(null)).toBe(-1);
|
|
173
|
-
expect(extractUrlCounter('')).toBe(-1);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test('extractBaseUrl strips numeric counter', () => {
|
|
177
|
-
expect(extractBaseUrl('firstNameLastName-10')).toBe('firstNameLastName');
|
|
178
|
-
expect(extractBaseUrl('firstNameLastName-1')).toBe('firstNameLastName');
|
|
179
|
-
expect(extractBaseUrl('firstNameLastName')).toBe('firstNameLastName');
|
|
180
|
-
expect(extractBaseUrl('john-doe-3')).toBe('john-doe');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('incrementUrlCounter increments correctly', () => {
|
|
184
|
-
expect(incrementUrlCounter('firstNameLastName-9', 'firstNameLastName')).toBe(
|
|
185
|
-
'firstNameLastName-10'
|
|
186
|
-
);
|
|
187
|
-
expect(incrementUrlCounter('firstNameLastName-10', 'firstNameLastName')).toBe(
|
|
188
|
-
'firstNameLastName-11'
|
|
189
|
-
);
|
|
190
|
-
expect(incrementUrlCounter('firstNameLastName', 'firstNameLastName')).toBe(
|
|
191
|
-
'firstNameLastName-1'
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const { customTrigger } = require('@wix/automations');
|
|
2
|
-
const { auth } = require('@wix/essentials');
|
|
3
|
-
const triggerMethod = auth.elevate(customTrigger.runTrigger);
|
|
4
|
-
|
|
5
|
-
const triggerAutomation = async (triggerId, payload) =>
|
|
6
|
-
await triggerMethod({
|
|
7
|
-
triggerId,
|
|
8
|
-
payload,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
module.exports = {
|
|
12
|
-
triggerAutomation,
|
|
13
|
-
};
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
const geohash = require('ngeohash');
|
|
2
|
-
|
|
3
|
-
const { COLLECTIONS, MEMBERS_FIELDS } = require('../public/consts.js');
|
|
4
|
-
const { findMainAddress } = require('../public/Utils/sharedUtils.js');
|
|
5
|
-
const { calculateDistance, shuffleArray } = require('../public/Utils/sharedUtils.js');
|
|
6
|
-
|
|
7
|
-
const {
|
|
8
|
-
GEO_HASH_PRECISION,
|
|
9
|
-
MAX__MEMBERS_SEARCH_RESULTS,
|
|
10
|
-
WIX_QUERY_MAX_LIMIT,
|
|
11
|
-
MEMBERSHIPS_TYPES,
|
|
12
|
-
} = require('./consts.js');
|
|
13
|
-
const { wixData } = require('./elevated-modules');
|
|
14
|
-
|
|
15
|
-
function buildMembersSearchQuery(data) {
|
|
16
|
-
console.log('data: ', JSON.stringify(data));
|
|
17
|
-
const { filter, isSearchingNearby, includeStudents = false } = data;
|
|
18
|
-
const isUserLocationEnabled = filter.latitude !== 0 || filter.longitude !== 0;
|
|
19
|
-
filter.searchText = filter.searchText || '';
|
|
20
|
-
filter.stateSearch = filter.stateSearch || '';
|
|
21
|
-
filter.practiceAreasSearch = filter.practiceAreasSearch || '';
|
|
22
|
-
filter.practiceAreas = filter.practiceAreas || [];
|
|
23
|
-
filter.state = filter.state || [];
|
|
24
|
-
filter.citySearch = filter.citySearch || '';
|
|
25
|
-
filter.city = filter.city || [];
|
|
26
|
-
filter.latitude = filter.latitude || 0;
|
|
27
|
-
filter.longitude = filter.longitude || 0;
|
|
28
|
-
filter.postalcode = filter.postalcode || '';
|
|
29
|
-
return {
|
|
30
|
-
get: () => {
|
|
31
|
-
let query = wixData
|
|
32
|
-
.query(COLLECTIONS.MEMBERS_DATA)
|
|
33
|
-
.ne('optOut', true)
|
|
34
|
-
.ne('action', 'drop')
|
|
35
|
-
.ne('memberships.membertype', MEMBERSHIPS_TYPES.PAC_STAFF)
|
|
36
|
-
.eq('isVisible', true);
|
|
37
|
-
let filterConfig = [
|
|
38
|
-
{
|
|
39
|
-
filterKey: 'practiceAreas',
|
|
40
|
-
queryMethod: 'hasSome',
|
|
41
|
-
queryField: 'areasOfPractices',
|
|
42
|
-
condition: value => value && value.length > 0,
|
|
43
|
-
fallback: {
|
|
44
|
-
filterKey: 'practiceAreasSearch',
|
|
45
|
-
queryMethod: 'contains',
|
|
46
|
-
queryField: 'areasOfPractices',
|
|
47
|
-
condition: value => value && value.trim() !== '',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
filterKey: 'postalcode',
|
|
52
|
-
queryMethod: 'contains',
|
|
53
|
-
queryField: 'addresses.postalcode',
|
|
54
|
-
condition: value => value && value.trim() !== '',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
filterKey: 'state',
|
|
58
|
-
queryMethod: 'hasSome',
|
|
59
|
-
queryField: 'addresses.state',
|
|
60
|
-
condition: value => value && value.length > 0,
|
|
61
|
-
fallback: {
|
|
62
|
-
filterKey: 'stateSearch',
|
|
63
|
-
queryMethod: 'contains',
|
|
64
|
-
queryField: 'addresses.state',
|
|
65
|
-
condition: value => value && value.trim() !== '',
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
filterKey: 'city',
|
|
70
|
-
queryMethod: 'hasSome',
|
|
71
|
-
queryField: 'addresses.city',
|
|
72
|
-
condition: value => value && value.length > 0,
|
|
73
|
-
fallback: {
|
|
74
|
-
filterKey: 'citySearch',
|
|
75
|
-
queryMethod: 'contains',
|
|
76
|
-
queryField: 'addresses.city',
|
|
77
|
-
condition: value => value && value.trim() !== '',
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
//Ignore state, city and postal code when isSearchingNearby is true
|
|
82
|
-
if (isSearchingNearby) {
|
|
83
|
-
filterConfig = filterConfig.filter(
|
|
84
|
-
config => !['state', 'city', 'postalcode'].includes(config.filterKey)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
const applyFilterToQuery = (query, config, filter) => {
|
|
88
|
-
const filterValue = filter[config.filterKey];
|
|
89
|
-
if (config.condition(filterValue)) {
|
|
90
|
-
return query[config.queryMethod](config.queryField, filterValue);
|
|
91
|
-
} else if (config.fallback) {
|
|
92
|
-
return applyFilterToQuery(query, config.fallback, filter);
|
|
93
|
-
}
|
|
94
|
-
return query;
|
|
95
|
-
};
|
|
96
|
-
// Apply filters using the configuration
|
|
97
|
-
filterConfig.forEach(config => {
|
|
98
|
-
query = applyFilterToQuery(query, config, filter);
|
|
99
|
-
});
|
|
100
|
-
if (isUserLocationEnabled && isSearchingNearby) {
|
|
101
|
-
const userGeohash = geohash.encode(filter.latitude, filter.longitude, GEO_HASH_PRECISION);
|
|
102
|
-
const neighborGeohashes = geohash.neighbors(userGeohash);
|
|
103
|
-
const geohashList = [userGeohash, ...neighborGeohashes];
|
|
104
|
-
query = query.hasSome('locHash', geohashList);
|
|
105
|
-
}
|
|
106
|
-
if (filter.searchText.trim() !== '') {
|
|
107
|
-
query = query.contains('fullName', filter.searchText);
|
|
108
|
-
}
|
|
109
|
-
if (!includeStudents) {
|
|
110
|
-
query = query.ne('memberships.membertype', MEMBERSHIPS_TYPES.STUDENT);
|
|
111
|
-
}
|
|
112
|
-
return query;
|
|
113
|
-
},
|
|
114
|
-
run: async query => {
|
|
115
|
-
const baseQuery = query.ascending('firstName').fields(...Object.values(MEMBERS_FIELDS));
|
|
116
|
-
const getRandomSkip = totalCount => {
|
|
117
|
-
let randomSkip = 0;
|
|
118
|
-
if (totalCount > MAX__MEMBERS_SEARCH_RESULTS) {
|
|
119
|
-
const maxSkip = totalCount - MAX__MEMBERS_SEARCH_RESULTS;
|
|
120
|
-
randomSkip = Math.floor(Math.random() * (maxSkip + 1));
|
|
121
|
-
}
|
|
122
|
-
return randomSkip;
|
|
123
|
-
};
|
|
124
|
-
const getResult = async query => {
|
|
125
|
-
if (isSearchingNearby) {
|
|
126
|
-
return fetchAllItemsInParallel(baseQuery);
|
|
127
|
-
}
|
|
128
|
-
const totalCount = await query.count();
|
|
129
|
-
const randomSkip = getRandomSkip(totalCount);
|
|
130
|
-
const result = await query
|
|
131
|
-
.skip(randomSkip)
|
|
132
|
-
.limit(MAX__MEMBERS_SEARCH_RESULTS)
|
|
133
|
-
.find({ omitTotalCount: true });
|
|
134
|
-
|
|
135
|
-
// Shuffle the result items for additional randomization
|
|
136
|
-
return {
|
|
137
|
-
...result,
|
|
138
|
-
items: shuffleArray(result.items),
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const result = await getResult(baseQuery);
|
|
143
|
-
if (isUserLocationEnabled) {
|
|
144
|
-
const withDistances = result.items.map(item => ({
|
|
145
|
-
...item,
|
|
146
|
-
distance: calculateDistance(
|
|
147
|
-
{
|
|
148
|
-
latitude: filter.latitude,
|
|
149
|
-
longitude: filter.longitude,
|
|
150
|
-
},
|
|
151
|
-
findMainAddress(item.addressDisplayOption, item.addresses, {
|
|
152
|
-
requireValidCoordinates: true,
|
|
153
|
-
})
|
|
154
|
-
),
|
|
155
|
-
}));
|
|
156
|
-
const resultWithDistances = {
|
|
157
|
-
...result,
|
|
158
|
-
items: withDistances,
|
|
159
|
-
};
|
|
160
|
-
if (isSearchingNearby) {
|
|
161
|
-
return {
|
|
162
|
-
...resultWithDistances,
|
|
163
|
-
items: withDistances
|
|
164
|
-
.filter(item => item.distance !== null)
|
|
165
|
-
.sort((a, b) => a.distance - b.distance)
|
|
166
|
-
.slice(0, MAX__MEMBERS_SEARCH_RESULTS),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
return resultWithDistances;
|
|
170
|
-
}
|
|
171
|
-
return result;
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Generic parallel fetch function for large datasets
|
|
177
|
-
async function fetchAllItemsInParallel(query) {
|
|
178
|
-
const batchSize = WIX_QUERY_MAX_LIMIT;
|
|
179
|
-
const allItems = [];
|
|
180
|
-
|
|
181
|
-
const firstResult = await query.skip(0).limit(batchSize).find();
|
|
182
|
-
|
|
183
|
-
const totalBatches = firstResult.totalPages;
|
|
184
|
-
allItems.push(...firstResult.items);
|
|
185
|
-
|
|
186
|
-
if (totalBatches > 1) {
|
|
187
|
-
// Create parallel promises for all remaining batches
|
|
188
|
-
const batchPromises = [];
|
|
189
|
-
for (let i = 1; i < totalBatches; i++) {
|
|
190
|
-
const skip = i * batchSize;
|
|
191
|
-
const promise = query
|
|
192
|
-
.skip(skip)
|
|
193
|
-
.limit(batchSize)
|
|
194
|
-
.find()
|
|
195
|
-
.then(result => result.items);
|
|
196
|
-
batchPromises.push(promise);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Execute all batches in parallel
|
|
200
|
-
const batchResults = await Promise.all(batchPromises);
|
|
201
|
-
for (const items of batchResults) {
|
|
202
|
-
if (items.length > 0) {
|
|
203
|
-
allItems.push(...items);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
...firstResult,
|
|
210
|
-
items: allItems,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Get all interests from the database
|
|
216
|
-
* @returns {Promise<Array<string>>} Array of interest titles sorted alphabetically
|
|
217
|
-
*/
|
|
218
|
-
async function getInterestAll() {
|
|
219
|
-
try {
|
|
220
|
-
let res = await wixData.query(COLLECTIONS.INTERESTS).limit(1000).find();
|
|
221
|
-
|
|
222
|
-
let interests = res.items.map(x => x.title);
|
|
223
|
-
|
|
224
|
-
while (res.hasNext()) {
|
|
225
|
-
res = await res.next();
|
|
226
|
-
interests.push(...res.items.map(x => x.title));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Sort the interests alphabetically (case-insensitive)
|
|
230
|
-
interests = interests.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
231
|
-
|
|
232
|
-
return interests;
|
|
233
|
-
} catch (e) {
|
|
234
|
-
console.error('Error in getInterestAll:', e);
|
|
235
|
-
throw e;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async function clearCollection(collectionName) {
|
|
239
|
-
try {
|
|
240
|
-
await wixData.truncate(collectionName);
|
|
241
|
-
} catch (err) {
|
|
242
|
-
throw new Error(`Failed to clearCollection ${collectionName} with error: ${err.message}`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
module.exports = {
|
|
247
|
-
buildMembersSearchQuery,
|
|
248
|
-
fetchAllItemsInParallel,
|
|
249
|
-
getInterestAll,
|
|
250
|
-
clearCollection,
|
|
251
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
const PAC_API_URL = 'https://members.abmp.com/eweb/api/Wix';
|
|
2
|
-
const TEST_PAC_API_URL = 'https://members-test.abmp.com/eweb/api/Wix';
|
|
3
|
-
const BACKUP_API_URL = 'https://psdevteamenterpris.wixstudio.com/abmp-backup/_functions';
|
|
4
|
-
const SSO_TOKEN_AUTH_API_URL = 'https://members.professionalassistcorp.com/';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Valid configuration keys for getSiteConfigs function
|
|
8
|
-
* @readonly
|
|
9
|
-
* @enum {string}
|
|
10
|
-
*/
|
|
11
|
-
const CONFIG_KEYS = {
|
|
12
|
-
AUTOMATION_EMAIL_TRIGGER_ID: 'AUTOMATION_EMAIL_TRIGGER_ID',
|
|
13
|
-
SITE_ASSOCIATION: 'SITE_ASSOCIATION',
|
|
14
|
-
DEFAULT_PROFILE_SEO_DESCRIPTION: 'DEFAULT_PROFILE_SEO_DESCRIPTION',
|
|
15
|
-
INTERESTS_API_URL: 'INTERESTS_API_URL',
|
|
16
|
-
SITE_LOGO_URL: 'SITE_LOGO_URL',
|
|
17
|
-
MEMBERS_EXTERNAL_PORTAL_URL: 'MEMBERS_EXTERNAL_PORTAL_URL',
|
|
18
|
-
DEFAULT_PROFILE_IMAGE: 'DEFAULT_PROFILE_IMAGE',
|
|
19
|
-
QA_ALLOW_ANY_MEMBER: 'QA_ALLOW_ANY_MEMBER',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const MAX__MEMBERS_SEARCH_RESULTS = 120;
|
|
23
|
-
const WIX_QUERY_MAX_LIMIT = 1000;
|
|
24
|
-
|
|
25
|
-
const GEO_HASH_PRECISION = 3;
|
|
26
|
-
|
|
27
|
-
const COMPILED_FILTERS_FIELDS = {
|
|
28
|
-
COMPILED_STATE_LIST: 'COMPILED_STATE_LIST',
|
|
29
|
-
COMPILED_AREAS_OF_PRACTICES: 'COMPILED_AREAS_OF_PRACTICES',
|
|
30
|
-
COMPILED_STATE_CITY_MAP: 'COMPILED_STATE_CITY_MAP',
|
|
31
|
-
};
|
|
32
|
-
const MEMBERSHIPS_TYPES = {
|
|
33
|
-
STUDENT: 'Student',
|
|
34
|
-
PAC_STAFF: 'PAC STAFF',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Possible outcomes of attempting to change a Wix member's login email during the sync.
|
|
39
|
-
*/
|
|
40
|
-
const LOGIN_EMAIL_SYNC_STATUS = {
|
|
41
|
-
UPDATED: 'updated', // Wix login email successfully changed to the desired email
|
|
42
|
-
FAILED: 'failed', // change failed -> keep the CMS login email unchanged and report for manual handling
|
|
43
|
-
SKIPPED: 'skipped', // member has no wixMemberId, nothing to change
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
module.exports = {
|
|
47
|
-
CONFIG_KEYS,
|
|
48
|
-
MAX__MEMBERS_SEARCH_RESULTS,
|
|
49
|
-
WIX_QUERY_MAX_LIMIT,
|
|
50
|
-
GEO_HASH_PRECISION,
|
|
51
|
-
PAC_API_URL,
|
|
52
|
-
TEST_PAC_API_URL,
|
|
53
|
-
COMPILED_FILTERS_FIELDS,
|
|
54
|
-
MEMBERSHIPS_TYPES,
|
|
55
|
-
SSO_TOKEN_AUTH_API_URL,
|
|
56
|
-
BACKUP_API_URL,
|
|
57
|
-
LOGIN_EMAIL_SYNC_STATUS,
|
|
58
|
-
};
|