Skip to content

Commit 82402d7

Browse files
committed
Merge branch 'chore/migrate-to-active-campaign'
2 parents d6b9449 + dcb182a commit 82402d7

File tree

4 files changed

+314
-1
lines changed

4 files changed

+314
-1
lines changed

functions/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ACTIVE_CAMPAIGN_API_URL=123456demo.activehosted.com
2+
ACTIVE_CAMPAIGN_API_KEY=123abc-def-ghi
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
require('dotenv').config('../.env')
2+
const courseTags = require('./course_tags.json')
3+
4+
const API_URL = `https://${process.env.ACTIVE_CAMPAIGN_API_URL}.api-us1.com/api/3`
5+
const API_TOKEN = process.env.ACTIVE_CAMPAIGN_API_KEY
6+
7+
const headers = {
8+
Accept: 'application/json',
9+
'Content-Type': 'application/json',
10+
'Api-Token': API_TOKEN,
11+
}
12+
13+
async function makeRequest(endpoint, method = 'GET', body = null) {
14+
const options = {
15+
method,
16+
headers,
17+
...(body && { body: JSON.stringify(body) }),
18+
}
19+
20+
const response = await fetch(`${API_URL}${endpoint}`, options)
21+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
22+
return response.json()
23+
}
24+
25+
async function addContactToList(contactId, listId) {
26+
try {
27+
const response = await makeRequest('/contactLists', 'POST', {
28+
contactList: {
29+
list: listId,
30+
contact: contactId,
31+
status: 1,
32+
},
33+
})
34+
console.log(`Contact added to list ${listId} successfully`)
35+
return response
36+
} catch (error) {
37+
console.error(`Error adding contact to list ${listId}:`, error)
38+
throw error
39+
}
40+
}
41+
42+
async function searchContactByEmail(email) {
43+
try {
44+
const endpoint = `/contacts?search=${email}`
45+
const response = await makeRequest(endpoint)
46+
47+
if (response.contacts && response.contacts.length > 0) {
48+
return response.contacts[0].id
49+
}
50+
throw new Error('Contact not found')
51+
} catch (error) {
52+
console.error('Error searching contact:', error)
53+
throw error
54+
}
55+
}
56+
57+
exports.createActiveCampaignUser = async function (user) {
58+
try {
59+
// Create or update contact
60+
const { contact } = await makeRequest('/contacts', 'POST', {
61+
contact: {
62+
email: user.email,
63+
firstName: user.name || '',
64+
fieldValues: [
65+
{
66+
field: '2',
67+
value: user.photoUrl,
68+
},
69+
{
70+
field: '13',
71+
value: 'BUILD_PLATFORM_API',
72+
},
73+
],
74+
},
75+
})
76+
77+
await addContactToList(contact.id, 1) // Add to master list
78+
79+
console.log('User added to ActiveCampaign and lists successfully')
80+
return contact
81+
} catch (error) {
82+
console.error('Error in ActiveCampaign operations:', error)
83+
throw error
84+
}
85+
}
86+
87+
exports.fetchUSer = async function () {
88+
let userId = 2792
89+
console.log('Fetching user from ActiveCampaign...', userId)
90+
try {
91+
const contact = await makeRequest(`/contacts/${userId}`)
92+
console.log('User retrieved from ActiveCampaign successfully')
93+
return contact
94+
} catch (error) {
95+
console.error('Error fetching user from ActiveCampaign:', error)
96+
throw error
97+
}
98+
}
99+
100+
exports.fetchCustomFieldMeta = async function () {
101+
console.log('Fetching custom field metadata from ActiveCampaign...')
102+
try {
103+
const endpoint = '/tags'
104+
const params = '?limit=100'
105+
const response = await makeRequest(endpoint + params)
106+
console.log('Custom field metadata retrieved successfully')
107+
return response
108+
} catch (error) {
109+
console.error('Error fetching custom field metadata:', error)
110+
throw error
111+
}
112+
}
113+
114+
exports.updateUserLessonProgress = async function (user, lessonData, cohortData) {
115+
try {
116+
// First find the contact ID
117+
const contactId = await searchContactByEmail(user.email)
118+
119+
// Update the contact with new field values
120+
const contactData = {
121+
contact: {
122+
fieldValues: [
123+
{
124+
field: '9', // last_course field ID
125+
value: cohortData.course_id || '',
126+
},
127+
{
128+
field: '10', // cohort_name field ID
129+
value: cohortData.name || '',
130+
},
131+
{
132+
field: '15', // last_section field ID
133+
value: lessonData.section || '',
134+
},
135+
],
136+
},
137+
}
138+
139+
if (user.name) {
140+
contactData.contact.firstName = user.name
141+
}
142+
143+
const response = await makeRequest(`/contacts/${contactId}`, 'PUT', contactData)
144+
145+
return response
146+
} catch (error) {
147+
console.error('Error updating lesson progress in ActiveCampaign:', {
148+
error: error.message,
149+
userEmail: user.email,
150+
stack: error.stack,
151+
})
152+
throw error
153+
}
154+
}
155+
156+
exports.addCourseTagToUser = async function (email, courseId) {
157+
try {
158+
// Find contact ID by email
159+
const contactId = await searchContactByEmail(email)
160+
161+
// Get tag ID from the mapping
162+
const tagId = courseTags[courseId]
163+
if (!tagId) {
164+
throw new Error(`Tag ID not found for course: ${courseId}`)
165+
}
166+
167+
// Create tag and add to contact
168+
const response = await makeRequest('/contactTags', 'POST', {
169+
contactTag: {
170+
contact: contactId,
171+
tag: tagId,
172+
},
173+
})
174+
175+
console.log(`Tag ${courseId} (ID: ${tagId}) added to contact successfully`)
176+
return response
177+
} catch (error) {
178+
console.error('Error adding course tag in ActiveCampaign:', {
179+
error: error.message,
180+
userEmail: email,
181+
tagName,
182+
stack: error.stack,
183+
})
184+
throw error
185+
}
186+
}
187+
188+
exports.addTagToUserCertificate = async function (email, courseId) {
189+
try {
190+
const contactId = await searchContactByEmail(email)
191+
courseId
192+
193+
console.log('courseId');
194+
console.log(courseId);
195+
196+
197+
const tagId = courseTags[courseId]
198+
if (!tagId) {
199+
throw new Error(`Tag ID not found for course: ${courseId}`)
200+
}
201+
202+
const response = await makeRequest('/contactTags', 'POST', {
203+
contactTag: {
204+
contact: contactId,
205+
tag: tagId,
206+
},
207+
})
208+
209+
console.log(`Tag ${courseId} (ID: ${tagId}) added to contact successfully`)
210+
return response
211+
} catch (error) {
212+
console.error('Error adding certificate tag to user in ActiveCampaign:', {
213+
error: error.message,
214+
userEmail: email,
215+
stack: error.stack,
216+
})
217+
throw error
218+
}
219+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Solidity_And_Smart_Contracts": "7",
3+
"JS_DAO": "11",
4+
"Solana_And_Web3": "12",
5+
"Solana_NFTs": "13",
6+
"Solana_Pay_Store": "14",
7+
"Rust_State_Machine": "16",
8+
"NFT_Game": "20",
9+
"NFT_Collection": "25",
10+
"Graduado_Rust_State_Machine": "71"
11+
}

functions/index.js

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,23 @@ const { log_study_group } = require('./lib/log_study_group')
1313
const { db, firebase } = require('./lib/initDb')
1414
const { usersByStudyGroup, storeUsersPerStudyGroup } = require('./study_group_analytics')
1515
const { fetchAndStoreIssues } = require('./fetchKanban')
16+
const {
17+
createActiveCampaignUser,
18+
fetchCustomFieldMeta,
19+
updateUserLessonProgress,
20+
addCourseTagToUser,
21+
addTagToUserCertificate,
22+
} = require('./active_campaign/active_campaign.js')
1623

1724
exports.sendEmail = functions.https.onRequest(async (req, resp) => {
1825
const subject = req.query.subject || '🏕️ Seu primeiro Smart Contract na Ethereum'
1926
resp.send(await sendEmail(req.query.template, subject, req.query.to))
2027
})
2128

29+
exports.returnTrue = functions.https.onRequest(async (req, resp) => {
30+
return resp.send('true')
31+
})
32+
2233
async function docData(collection, doc_id) {
2334
return { ...(await db.collection(collection).doc(doc_id).get()).data(), id: doc_id }
2435
}
@@ -49,6 +60,14 @@ exports.onCohortSignup = functions.firestore
4960

5061
for (let cohortSnapshot of userNewCohorts) {
5162
const params = await emailParams(cohortSnapshot)
63+
64+
// Add course tag to Active Campaign
65+
try {
66+
await addCourseTagToUser(user.email, params.cohort.course_id)
67+
} catch (error) {
68+
console.error('Failed to add course tag:', error)
69+
}
70+
5271
//todo essas funções deveriam ser enfileiradas num pubsub para evitar falhas
5372
const emailRawData = {
5473
incoming_topic: 'cohort_signup',
@@ -108,6 +127,14 @@ async function issueCertificate(user_id, cohort) {
108127
console.log('callback params:')
109128
console.log(params)
110129
addDiscordRole(params.user?.discord?.id, GRADUATED_ROLE_ID)
130+
131+
// Add course certificate tag to Active Campaign
132+
try {
133+
addTagToUserCertificate(user.email, `Graduado_${cohort.course_id}`)
134+
} catch (error) {
135+
console.error('Failed to add course tag:', error)
136+
}
137+
111138
sendEmail(
112139
'nft_delivery.js',
113140
'👷👷‍♀️ WEB3DEV - NFT Certificate Sent: ' + params.course_title,
@@ -318,11 +345,19 @@ exports.router = functions.pubsub.topic('router-pubsub').onPublish(async (messag
318345

319346
exports.sendUserToMailchimpOnUserCreation = functions.pubsub
320347
.topic('user_created')
321-
.onPublish((message) => {
348+
.onPublish(async (message) => {
322349
const data = JSON.parse(Buffer.from(message.data, 'base64'))
350+
console.log('sending user to AC on user creation', data.user)
323351
return createUser(data.user)
324352
})
325353

354+
exports.sendUserToActiveCampaignOnUserCreation = functions.pubsub
355+
.topic('user_created')
356+
.onPublish(async (message) => {
357+
const data = JSON.parse(Buffer.from(message.data, 'base64'))
358+
return createActiveCampaignUser(data.user)
359+
})
360+
326361
exports.onCohortSignupMail = functions.pubsub.topic('cohort_signup').onPublish((message) => {
327362
const data = JSON.parse(Buffer.from(message.data, 'base64'))
328363
console.log(data)
@@ -410,3 +445,49 @@ exports.scheduledFetchAndStoreIssues = functions.pubsub
410445
return false
411446
}
412447
})
448+
449+
exports.fetchACCustomFieldsMeta = functions.https.onRequest(async (req, resp) => {
450+
try {
451+
const result = await fetchCustomFieldMeta()
452+
resp.json({ success: true, result })
453+
} catch (error) {
454+
console.error('Error creating test user:', error)
455+
resp.status(500).json({ success: false, error: error.message })
456+
}
457+
})
458+
459+
exports.onLessonCreated = functions.firestore
460+
.document('lessons_submissions/{lessonId}')
461+
.onCreate(async (snap, context) => {
462+
const lesson = snap.data()
463+
const userId = lesson.user_id
464+
465+
console.log('New lesson submission received:', {
466+
lessonId: context.params.lessonId,
467+
userId,
468+
cohortId: lesson.cohort_id,
469+
section: lesson.section,
470+
})
471+
472+
try {
473+
const userDoc = await db.collection('users').doc(userId).get()
474+
const userData = userDoc.data()
475+
476+
// Fetch cohort data
477+
const cohortRef = await db.collection('cohorts').doc(lesson.cohort_id).get()
478+
if (!cohortRef.exists) {
479+
throw new Error(`Cohort not found with ID: ${lesson.cohort_id}`)
480+
}
481+
const cohortData = cohortRef.data()
482+
483+
// Update Active Campaign with all the data
484+
await updateUserLessonProgress(userData, lesson, cohortData)
485+
} catch (error) {
486+
console.error('Error processing lesson submission:', {
487+
error: error.message,
488+
userId,
489+
lessonId: context.params.lessonId,
490+
stack: error.stack,
491+
})
492+
}
493+
})

0 commit comments

Comments
 (0)