abmp-npm 2.0.72 → 2.0.74
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/.claude/worktrees/fix-save-button-disable/.husky/pre-commit +19 -0
- package/.claude/worktrees/fix-save-button-disable/.prettierignore +7 -0
- package/.claude/worktrees/fix-save-button-disable/.prettierrc.json +16 -0
- package/.claude/worktrees/fix-save-button-disable/README.md +1 -0
- package/.claude/worktrees/fix-save-button-disable/backend/README-member-contact-flows.md +136 -0
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/daily-pull-execution-check.test.js +124 -0
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/ensure-unique-urls-in-batch.test.js +129 -0
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/login-email-sync.test.js +49 -0
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/transient-retry-and-bulk-lookup.test.js +129 -0
- package/.claude/worktrees/fix-save-button-disable/backend/__tests__/url-uniqueness.test.js +194 -0
- package/.claude/worktrees/fix-save-button-disable/backend/automations-methods.js +13 -0
- package/.claude/worktrees/fix-save-button-disable/backend/cms-data-methods.js +251 -0
- package/.claude/worktrees/fix-save-button-disable/backend/consts.js +58 -0
- package/.claude/worktrees/fix-save-button-disable/backend/contacts-methods.js +67 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/bulk-process-methods.js +222 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/consts.js +39 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/index.js +5 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/process-member-methods.js +213 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/schedule-methods.js +41 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/sync-to-cms-methods.js +161 -0
- package/.claude/worktrees/fix-save-button-disable/backend/daily-pull/utils.js +151 -0
- package/.claude/worktrees/fix-save-button-disable/backend/data-hooks.js +29 -0
- package/.claude/worktrees/fix-save-button-disable/backend/dev-only-methods.js +58 -0
- package/.claude/worktrees/fix-save-button-disable/backend/elevated-modules.js +18 -0
- package/.claude/worktrees/fix-save-button-disable/backend/forms-methods.js +52 -0
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/httpFunctions.js +86 -0
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/index.js +3 -0
- package/.claude/worktrees/fix-save-button-disable/backend/http-functions/interests.js +35 -0
- package/.claude/worktrees/fix-save-button-disable/backend/index.js +19 -0
- package/.claude/worktrees/fix-save-button-disable/backend/jobs.js +158 -0
- package/.claude/worktrees/fix-save-button-disable/backend/login/generate-member-session-token.js +27 -0
- package/.claude/worktrees/fix-save-button-disable/backend/login/index.js +8 -0
- package/.claude/worktrees/fix-save-button-disable/backend/login/qa-login-methods.js +54 -0
- package/.claude/worktrees/fix-save-button-disable/backend/login/sso-methods.js +159 -0
- package/.claude/worktrees/fix-save-button-disable/backend/member-contact-orchestration.js +111 -0
- package/.claude/worktrees/fix-save-button-disable/backend/members-area-methods.js +88 -0
- package/.claude/worktrees/fix-save-button-disable/backend/members-data-methods.js +789 -0
- package/.claude/worktrees/fix-save-button-disable/backend/pac-api-methods.js +54 -0
- package/.claude/worktrees/fix-save-button-disable/backend/routers/index.js +3 -0
- package/.claude/worktrees/fix-save-button-disable/backend/routers/methods.js +191 -0
- package/.claude/worktrees/fix-save-button-disable/backend/routers/utils.js +128 -0
- package/.claude/worktrees/fix-save-button-disable/backend/search-filters-methods.js +124 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-primary-methods.js +256 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/address-visibility-methods.js +160 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/consts.js +34 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/daily-pull-check-methods.js +78 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/email-normalize-methods.js +134 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/index.js +10 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/migration-methods.js +46 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-configs.js +251 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-helpers-methods.js +433 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/tasks-process-methods.js +606 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-migration-methods.js +378 -0
- package/.claude/worktrees/fix-save-button-disable/backend/tasks/url-space-fix-methods.js +174 -0
- package/.claude/worktrees/fix-save-button-disable/backend/utils.js +277 -0
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/extract-duplicate-url-groups.js +201 -0
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-ids.js +159 -0
- package/.claude/worktrees/fix-save-button-disable/dev-only-scripts/find-duplicate-urls.js +201 -0
- package/.claude/worktrees/fix-save-button-disable/eslint.config.js +120 -0
- package/.claude/worktrees/fix-save-button-disable/index.js +5 -0
- package/.claude/worktrees/fix-save-button-disable/package-lock.json +12160 -0
- package/.claude/worktrees/fix-save-button-disable/package.json +60 -0
- package/.claude/worktrees/fix-save-button-disable/pages/ContactUs.js +129 -0
- package/.claude/worktrees/fix-save-button-disable/pages/Home.js +779 -0
- package/.claude/worktrees/fix-save-button-disable/pages/LearnMore.js +27 -0
- package/.claude/worktrees/fix-save-button-disable/pages/LoadingPage.js +29 -0
- package/.claude/worktrees/fix-save-button-disable/pages/Profile.js +354 -0
- package/.claude/worktrees/fix-save-button-disable/pages/QAPage.js +39 -0
- package/.claude/worktrees/fix-save-button-disable/pages/SaveAlerts.js +13 -0
- package/.claude/worktrees/fix-save-button-disable/pages/SelectBannerImages.js +46 -0
- package/.claude/worktrees/fix-save-button-disable/pages/deleteConfirm.js +19 -0
- package/.claude/worktrees/fix-save-button-disable/pages/index.js +12 -0
- package/.claude/worktrees/fix-save-button-disable/pages/personalDetails.js +2486 -0
- package/.claude/worktrees/fix-save-button-disable/public/Utils/homePage.js +798 -0
- package/.claude/worktrees/fix-save-button-disable/public/Utils/personalDetailsUtils.js +25 -0
- package/.claude/worktrees/fix-save-button-disable/public/Utils/sharedUtils.js +235 -0
- package/.claude/worktrees/fix-save-button-disable/public/consts.js +113 -0
- package/.claude/worktrees/fix-save-button-disable/public/index.js +5 -0
- package/.claude/worktrees/fix-save-button-disable/public/messages.js +16 -0
- package/.claude/worktrees/fix-save-button-disable/public/sso-auth-methods.js +43 -0
- package/backend/jobs.js +19 -0
- package/backend/tasks/address-visibility-methods.js +160 -0
- package/backend/tasks/consts.js +2 -0
- package/backend/tasks/tasks-configs.js +18 -0
- package/package.json +1 -1
- package/pages/personalDetails.js +33 -34
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { CONFIG_KEYS } = require('../consts');
|
|
2
|
+
const { prepareMemberForQALogin, getQAUsers } = require('../members-data-methods');
|
|
3
|
+
const { getSecret, getSiteConfigs } = require('../utils');
|
|
4
|
+
|
|
5
|
+
const { generateMemberSessionToken } = require('./generate-member-session-token');
|
|
6
|
+
|
|
7
|
+
const validateQAUser = async userEmail => {
|
|
8
|
+
const qaUsers = await getQAUsers();
|
|
9
|
+
const matchingUserEmail = qaUsers.find(user => user.email === userEmail)?.email;
|
|
10
|
+
if (!matchingUserEmail) {
|
|
11
|
+
return { error: `Invalid user email: ${userEmail}` };
|
|
12
|
+
}
|
|
13
|
+
return { valid: true, email: matchingUserEmail };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Login a QA user
|
|
18
|
+
* @param {Object} params - The parameters for the login
|
|
19
|
+
* @param {string} params.userEmail - The email of the user to login
|
|
20
|
+
* @param {string} params.secret - The secret of the user to login
|
|
21
|
+
* @returns {Promise<Object>} The result of the login
|
|
22
|
+
*/
|
|
23
|
+
const loginQAMember = async ({ userEmail, secret }) => {
|
|
24
|
+
try {
|
|
25
|
+
const [qaSecret, allowAnyMember] = await Promise.all([
|
|
26
|
+
getSecret('ABMP_QA_SECRET'),
|
|
27
|
+
getSiteConfigs(CONFIG_KEYS.QA_ALLOW_ANY_MEMBER),
|
|
28
|
+
]);
|
|
29
|
+
if (secret !== qaSecret) {
|
|
30
|
+
return { success: false, error: 'Invalid secret' };
|
|
31
|
+
}
|
|
32
|
+
if (!allowAnyMember) {
|
|
33
|
+
const userValidation = await validateQAUser(userEmail);
|
|
34
|
+
if (userValidation.error) {
|
|
35
|
+
return { success: false, error: userValidation.error };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const memberData = await prepareMemberForQALogin(userEmail);
|
|
40
|
+
const token = await generateMemberSessionToken(memberData.email);
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
token,
|
|
44
|
+
memberCMSId: memberData._id,
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('QA login error:', error);
|
|
48
|
+
return { error: 'Failed to generate session token' };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
loginQAMember,
|
|
54
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const { createHmac } = require('crypto');
|
|
2
|
+
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const { decode } = require('jwt-js-decode');
|
|
5
|
+
|
|
6
|
+
const { CONFIG_KEYS, SSO_TOKEN_AUTH_API_URL } = require('../consts');
|
|
7
|
+
const { MEMBER_ACTIONS } = require('../daily-pull/consts');
|
|
8
|
+
const { getCurrentMember } = require('../members-area-methods');
|
|
9
|
+
const { getCMSMemberByWixMemberId, prepareMemberForSSOLogin } = require('../members-data-methods');
|
|
10
|
+
const {
|
|
11
|
+
formatDateToMonthYear,
|
|
12
|
+
getAddressDisplayOptions,
|
|
13
|
+
isStudent,
|
|
14
|
+
getSiteConfigs,
|
|
15
|
+
getSecret,
|
|
16
|
+
} = require('../utils');
|
|
17
|
+
|
|
18
|
+
const { generateMemberSessionToken } = require('./generate-member-session-token');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates member token and retrieves member data
|
|
22
|
+
* @param {string} memberIdInput - The member ID to validate
|
|
23
|
+
* @returns {Promise<{memberData: Object|null, isValid: boolean}>} Validation result with member data
|
|
24
|
+
*/
|
|
25
|
+
async function validateMemberToken(memberIdInput) {
|
|
26
|
+
const invalidTokenResponse = { memberData: null, isValid: false };
|
|
27
|
+
|
|
28
|
+
if (!memberIdInput) {
|
|
29
|
+
return invalidTokenResponse;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const member = await getCurrentMember();
|
|
34
|
+
if (!member || !member._id) {
|
|
35
|
+
console.log(
|
|
36
|
+
'member not found from members.getCurrentMember() for memberIdInput',
|
|
37
|
+
memberIdInput
|
|
38
|
+
);
|
|
39
|
+
return invalidTokenResponse;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [dbMember, siteConfigs] = await Promise.all([
|
|
43
|
+
getCMSMemberByWixMemberId(member._id),
|
|
44
|
+
getSiteConfigs(),
|
|
45
|
+
]);
|
|
46
|
+
const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
|
|
47
|
+
const membersExternalPortalUrl = siteConfigs[CONFIG_KEYS.MEMBERS_EXTERNAL_PORTAL_URL];
|
|
48
|
+
console.log('dbMember by wix member id is:', dbMember);
|
|
49
|
+
console.log('member._id', member._id);
|
|
50
|
+
|
|
51
|
+
if (!dbMember?._id) {
|
|
52
|
+
const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members? ] - There is no match in DB for currentMember: ${JSON.stringify(
|
|
53
|
+
{ memberIdInput, currentMemberId: member._id }
|
|
54
|
+
)}`;
|
|
55
|
+
console.error(errorMessage);
|
|
56
|
+
throw new Error('CORRUPTED_MEMBER_DATA');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${dbMember?._id}`);
|
|
60
|
+
|
|
61
|
+
const memberData = dbMember;
|
|
62
|
+
|
|
63
|
+
// Format membership dates
|
|
64
|
+
memberData.memberships = memberData.memberships.map(membership => ({
|
|
65
|
+
...membership,
|
|
66
|
+
membersince: formatDateToMonthYear(membership.membersince),
|
|
67
|
+
isSiteAssociation: membership.association === siteAssociation,
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
const savedMemberId = memberData?._id;
|
|
71
|
+
const isValid = savedMemberId === memberIdInput;
|
|
72
|
+
|
|
73
|
+
if (!savedMemberId || !isValid) {
|
|
74
|
+
return invalidTokenResponse;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if member is dropped
|
|
78
|
+
if (memberData.action === MEMBER_ACTIONS.DROP) {
|
|
79
|
+
return invalidTokenResponse;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add computed properties
|
|
83
|
+
memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
|
|
84
|
+
console.log('memberData', memberData);
|
|
85
|
+
memberData.isStudent = isStudent(memberData);
|
|
86
|
+
|
|
87
|
+
return { memberData, isValid, membersExternalPortalUrl };
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error in validateMemberToken:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function checkAndFetchSSO(token) {
|
|
94
|
+
const SSO_TOKEN_AUTH_API_KEY = await getSecret('SSO_TOKEN_AUTH_API_KEY');
|
|
95
|
+
const signature = createHmac('sha256', SSO_TOKEN_AUTH_API_KEY).update(token).digest('hex');
|
|
96
|
+
const professionalassistcorpUrl = `${SSO_TOKEN_AUTH_API_URL}/eweb/SSOToken.ashx?token=${token}&Partner=Wix&Signature=${signature}`;
|
|
97
|
+
try {
|
|
98
|
+
const httpResponse = await axios.get(professionalassistcorpUrl, {
|
|
99
|
+
transformResponse: [d => d],
|
|
100
|
+
validateStatus: () => true,
|
|
101
|
+
});
|
|
102
|
+
console.log('httpResponse status', httpResponse.status);
|
|
103
|
+
if (httpResponse.status < 200 || httpResponse.status >= 300) {
|
|
104
|
+
throw new Error('Fetch did not succeed with status: ' + httpResponse.status);
|
|
105
|
+
}
|
|
106
|
+
return httpResponse.data;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Error in checkAndFetchSSO', error);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Authenticate an SSO token
|
|
115
|
+
* @param {Object} params - The parameters for the authentication
|
|
116
|
+
* @param {string} params.token - The token to authenticate
|
|
117
|
+
* @returns {Promise<Object>} The result of the authentication
|
|
118
|
+
*/
|
|
119
|
+
const authenticateSSOToken = async ({ token }) => {
|
|
120
|
+
const responseToken = await checkAndFetchSSO(token);
|
|
121
|
+
const isValidToken = Boolean(
|
|
122
|
+
responseToken && typeof responseToken === 'string' && responseToken?.trim()
|
|
123
|
+
);
|
|
124
|
+
const toLogTokenData = {
|
|
125
|
+
isValidToken,
|
|
126
|
+
tokenData: responseToken
|
|
127
|
+
? {
|
|
128
|
+
length: responseToken.length,
|
|
129
|
+
preview: responseToken.substring(0, 50),
|
|
130
|
+
}
|
|
131
|
+
: 'No token',
|
|
132
|
+
};
|
|
133
|
+
console.log('checkAndFetchSSO responseToken data', JSON.stringify(toLogTokenData, null, 2));
|
|
134
|
+
if (isValidToken) {
|
|
135
|
+
const jwt = decode(responseToken);
|
|
136
|
+
const payload = jwt.payload;
|
|
137
|
+
const memberData = await prepareMemberForSSOLogin(payload);
|
|
138
|
+
console.log('memberDataCollectionId', memberData._id);
|
|
139
|
+
const sessionToken = await generateMemberSessionToken(memberData.email);
|
|
140
|
+
const authObj = {
|
|
141
|
+
type: 'success',
|
|
142
|
+
memberId: memberData._id,
|
|
143
|
+
sessionToken,
|
|
144
|
+
};
|
|
145
|
+
return authObj;
|
|
146
|
+
} else {
|
|
147
|
+
console.log('invalid Token responseToken is: ', responseToken);
|
|
148
|
+
return {
|
|
149
|
+
type: 'error',
|
|
150
|
+
memberId: '',
|
|
151
|
+
sessionToken: '',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
validateMemberToken,
|
|
158
|
+
authenticateSSOToken,
|
|
159
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates syncing between Wix Members and CRM Contacts.
|
|
3
|
+
* Returns member data to save; caller does a single updateMember to avoid double-write.
|
|
4
|
+
*/
|
|
5
|
+
const { emailsMatch } = require('../public/Utils/sharedUtils');
|
|
6
|
+
|
|
7
|
+
const { createSiteContact, updateContactInfo, deleteSiteContact } = require('./contacts-methods');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Updates contact email in CRM. Returns member patch { wixContactId } when member record
|
|
11
|
+
* must change; otherwise null. Caller merges and saves once.
|
|
12
|
+
*/
|
|
13
|
+
async function updateContactEmail(newContactEmail, existingMemberData) {
|
|
14
|
+
if (!newContactEmail) {
|
|
15
|
+
throw new Error('New email is required');
|
|
16
|
+
}
|
|
17
|
+
if (!existingMemberData || existingMemberData.wixContactId == null) {
|
|
18
|
+
throw new Error('Existing member data with wixContactId is required');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { wixContactId, wixMemberId, email: loginEmail } = existingMemberData;
|
|
22
|
+
const isSingleEntity = wixContactId === wixMemberId;
|
|
23
|
+
// Compare case-insensitively: Wix CRM enforces email uniqueness without regard to case,
|
|
24
|
+
// so a contact email that equals the login email (e.g. different casing) must collapse to
|
|
25
|
+
// a single entity rather than attempt a duplicate contact write.
|
|
26
|
+
const contactEmailDiffersFromLogin = !emailsMatch(loginEmail, newContactEmail);
|
|
27
|
+
|
|
28
|
+
if (!contactEmailDiffersFromLogin) {
|
|
29
|
+
if (isSingleEntity) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
await deleteSiteContact(wixContactId);
|
|
33
|
+
return { wixContactId: wixMemberId };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isSingleEntity) {
|
|
37
|
+
const newWixContactId = await createSiteContact({
|
|
38
|
+
firstName: existingMemberData.firstName,
|
|
39
|
+
lastName: existingMemberData.lastName,
|
|
40
|
+
contactFormEmail: newContactEmail,
|
|
41
|
+
});
|
|
42
|
+
return { wixContactId: newWixContactId };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await updateContactInfo(
|
|
46
|
+
wixContactId,
|
|
47
|
+
currentInfo => ({
|
|
48
|
+
...currentInfo,
|
|
49
|
+
emails: {
|
|
50
|
+
items: [{ email: newContactEmail, primary: true }],
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
'update contact email'
|
|
54
|
+
);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function updateContactNames({ wixContactId, firstName, lastName }) {
|
|
59
|
+
if (!firstName && !lastName) {
|
|
60
|
+
throw new Error('First name or last name is required');
|
|
61
|
+
}
|
|
62
|
+
const createNameUpdate = currentInfo => ({
|
|
63
|
+
...currentInfo,
|
|
64
|
+
name: {
|
|
65
|
+
first: firstName || currentInfo?.name?.first || '',
|
|
66
|
+
last: lastName || currentInfo?.name?.last || '',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return await updateContactInfo(wixContactId, createNameUpdate, 'update contact names');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Syncs contact with member (email, names). Contact CRUD only; returns member data to save.
|
|
74
|
+
* Caller must call updateMember once with the result.
|
|
75
|
+
*/
|
|
76
|
+
async function updateMemberContactInfo(data, existingMemberData) {
|
|
77
|
+
const { wixContactId } = existingMemberData;
|
|
78
|
+
if (!wixContactId) {
|
|
79
|
+
throw new Error('Wix Contact ID is required');
|
|
80
|
+
}
|
|
81
|
+
const updateConfig = [
|
|
82
|
+
{
|
|
83
|
+
fields: ['contactFormEmail'],
|
|
84
|
+
updater: updateContactEmail,
|
|
85
|
+
args: ([email]) => [email, existingMemberData],
|
|
86
|
+
returnsMemberPatch: true,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
fields: ['firstName', 'lastName'],
|
|
90
|
+
updater: updateContactNames,
|
|
91
|
+
args: ([firstName, lastName]) => [{ firstName, lastName, wixContactId }],
|
|
92
|
+
returnsMemberPatch: false,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const results = await Promise.all(
|
|
97
|
+
updateConfig.map(async ({ fields, updater, args, returnsMemberPatch }) => {
|
|
98
|
+
const existingValues = fields.map(field => existingMemberData[field]);
|
|
99
|
+
const newValues = fields.map(field => data[field]);
|
|
100
|
+
const hasChanged = existingValues.some((val, idx) => val !== newValues[idx]);
|
|
101
|
+
if (!hasChanged) return null;
|
|
102
|
+
const result = await updater(...args(newValues));
|
|
103
|
+
return returnsMemberPatch ? result : null;
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const memberPatch = results.reduce((acc, patch) => (patch ? { ...acc, ...patch } : acc), {});
|
|
108
|
+
return { ...data, ...memberPatch };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { updateMemberContactInfo };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const { auth } = require('@wix/essentials');
|
|
2
|
+
const { members, authentication } = require('@wix/members');
|
|
3
|
+
|
|
4
|
+
const { LOGIN_EMAIL_SYNC_STATUS } = require('./consts');
|
|
5
|
+
|
|
6
|
+
const elevatedCreateMember = auth.elevate(members.createMember);
|
|
7
|
+
const elevatedChangeLoginEmail = auth.elevate(authentication.changeLoginEmail);
|
|
8
|
+
|
|
9
|
+
const LOG = '[loginEmailSync]';
|
|
10
|
+
|
|
11
|
+
function prepareMemberData(partner) {
|
|
12
|
+
const options = {
|
|
13
|
+
member: {
|
|
14
|
+
loginEmail: partner.email,
|
|
15
|
+
contact: {
|
|
16
|
+
firstName: partner.firstName,
|
|
17
|
+
lastName: partner.lastName,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
async function createMemberFunction(member) {
|
|
24
|
+
const newMember = await elevatedCreateMember(member);
|
|
25
|
+
console.log('[createMemberFunction]newMember', JSON.stringify(newMember, null, 2));
|
|
26
|
+
return newMember._id;
|
|
27
|
+
}
|
|
28
|
+
const createSiteMember = async memberDetails => {
|
|
29
|
+
console.log('createSiteMember memberDetails', memberDetails);
|
|
30
|
+
try {
|
|
31
|
+
const options = prepareMemberData(memberDetails);
|
|
32
|
+
return await createMemberFunction(options);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(`Error in createSiteMember ${error.message}`);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getCurrentMember = async () => {
|
|
40
|
+
const member = await members.getCurrentMember();
|
|
41
|
+
return member.member;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Attempts to change a Wix member's login email to `member.email` and reports a structured
|
|
46
|
+
* outcome instead of swallowing failures. Never throws.
|
|
47
|
+
*
|
|
48
|
+
* Outcomes:
|
|
49
|
+
* - SKIPPED: member has no wixMemberId, nothing to change.
|
|
50
|
+
* - UPDATED: Wix login email changed successfully.
|
|
51
|
+
* - FAILED: change failed. The caller keeps the CMS login email unchanged (so it stays
|
|
52
|
+
* consistent with Wix) and reports the member in the task result for manual handling.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} member - Member with { memberId, wixMemberId, email }
|
|
55
|
+
* @returns {Promise<Object>} outcome
|
|
56
|
+
*/
|
|
57
|
+
async function updateWixMemberLoginEmail(member) {
|
|
58
|
+
const desiredEmail = member.email;
|
|
59
|
+
const base = { memberId: member.memberId, wixMemberId: member.wixMemberId, desiredEmail };
|
|
60
|
+
|
|
61
|
+
if (!member.wixMemberId) {
|
|
62
|
+
console.log(`${LOG} member ${member.memberId} has no wixMemberId - skipping`);
|
|
63
|
+
return { ...base, status: LOGIN_EMAIL_SYNC_STATUS.SKIPPED };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(
|
|
67
|
+
`${LOG} attempting login-email change for member ${member.memberId} (wixMemberId: ${member.wixMemberId}) -> ${desiredEmail}`
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const updatedWixMember = await elevatedChangeLoginEmail(member.wixMemberId, desiredEmail);
|
|
72
|
+
console.log(
|
|
73
|
+
`${LOG} ✅ updated member ${member.memberId} (wixMemberId: ${member.wixMemberId}) -> ${updatedWixMember.loginEmail}`
|
|
74
|
+
);
|
|
75
|
+
return { ...base, status: LOGIN_EMAIL_SYNC_STATUS.UPDATED };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(
|
|
78
|
+
`${LOG} ❌ login-email change failed for member ${member.memberId} (wixMemberId: ${member.wixMemberId}) -> ${desiredEmail}: ${error.message}`
|
|
79
|
+
);
|
|
80
|
+
return { ...base, status: LOGIN_EMAIL_SYNC_STATUS.FAILED, error: error.message };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
createSiteMember,
|
|
86
|
+
getCurrentMember,
|
|
87
|
+
updateWixMemberLoginEmail,
|
|
88
|
+
};
|