-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
enhancing the emails for field activities (deploy and recall) with more specific content #3554
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
AIRQO_WEBSITE=https://airqo.net/ | ||
AIRQO_YOUTUBE= | ||
DB_NAME_STAGING= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Introduced the template .env file for open source contributions and new codebase setup |
||
FIREBASE_DATABASE_URL= | ||
GOOGLE_APPLICATION_CREDENTIALS= | ||
INSTANCE_ID= | ||
JWT_SECRET= | ||
KICKBOX_API_KEY= | ||
LOCAL_DB= | ||
MAILCHIMP_API_KEY= | ||
MAILCHIMP_LIST_ID= | ||
MAILCHIMP_MOBILE_API_KEY= | ||
MAILCHIMP_SERVER_PREFIX= | ||
MAIL_PASS= | ||
MAIL_USER= | ||
MONGO_DEV= | ||
MONGO_GCE_HOST= | ||
MONGO_GCE_PASSWORD= | ||
MONGO_GCE_PORT= | ||
MONGO_GCE_URI= | ||
MONGO_GCE_USERNAME= | ||
MONGO_STAGE= | ||
NODE_PATH= | ||
REQUEST_ACCESS_EMAILS= | ||
HARDWARE_AND_DS_EMAILS= | ||
PLATFORM_AND_DS_EMAILS= | ||
PLATFORM_EMAILS= | ||
COMMS_EMAILS= | ||
POLICY_EMAILS= | ||
CHAMPIONS_EMAILS= | ||
ASSISTANCE_EMAILS= | ||
RESEARCHERS_EMAILS= | ||
DEVELOPERS_EMAILS= | ||
PARTNERS_EMAILS= | ||
PASSWORD_REGEX= | ||
PLATFORM_DEV_BASE_URL= | ||
PLATFORM_STAGING_BASE_URL= | ||
SESSION_SECRET= | ||
MONGO_STAGE_URI= | ||
SUPPORT_EMAIL= | ||
MONGO_PROD_URI= | ||
MONGO_DEV_URI= | ||
FIREBASE_COLLECTION_USERS= | ||
FIREBASE_COLLECTION_KYA= | ||
FIREBASE_COLLECTION_ANALYTICS= | ||
FIREBASE_COLLECTION_NOTIFICATIONS= | ||
FIREBASE_COLLECTION_FAVORITE_PLACES= | ||
PRODUCTS_DEV_EMAIL= | ||
DEFAULT_LIMIT= | ||
SLACK_TOKEN= | ||
SLACK_CHANNEL= | ||
SLACK_USERNAME= | ||
PROD_DEFAULT_NETWORK= | ||
STAGE_DEFAULT_NETWORK= | ||
DEV_DEFAULT_NETWORK= | ||
PROD_DEFAULT_NETWORK_ROLE= | ||
STAGE_DEFAULT_NETWORK_ROLE= | ||
DEV_DEFAULT_NETWORK_ROLE= | ||
MOBILE_APP_USERS_TOPIC= | ||
DEPLOY_TOPIC= | ||
RECALL_TOPIC= | ||
DEFAULT_TENANT= | ||
UNIQUE_CONSUMER_GROUP= | ||
UNIQUE_PRODUCER_GROUP= | ||
NEW_MOBILE_APP_USER_TOPIC= | ||
KAFKA_BOOTSTRAP_SERVERS_DEV= | ||
KAFKA_CLIENT_ID_DEV= | ||
KAFKA_CLIENT_GROUP_DEV= | ||
KAFKA_BOOTSTRAP_SERVERS_STAGE= | ||
KAFKA_CLIENT_ID_STAGE= | ||
KAFKA_CLIENT_GROUP_STAGE= | ||
KAFKA_BOOTSTRAP_SERVERS_PROD= | ||
KAFKA_CLIENT_ID_PROD= | ||
KAFKA_CLIENT_GROUP_PROD= | ||
GOOGLE_CLIENT_ID= | ||
GOOGLE_CLIENT_SECRET= | ||
SUPER_ADMIN_PERMISSIONS= | ||
TENANTS= | ||
NETWORKS= | ||
GMAIL_VERIFICATION_FAILURE_REDIRECT= | ||
GMAIL_VERIFICATION_SUCCESS_REDIRECT= | ||
FIREBASE_API_KEY= | ||
FIREBASE_AUTH_DOMAIN= | ||
FIREBASE_PROJECT_ID= | ||
FIREBASE_TYPE= | ||
FIREBASE_PRIVATE_KEY_ID= | ||
FIREBASE_PRIVATE_KEY= | ||
FIREBASE_CLIENT_EMAIL= | ||
FIREBASE_CLIENT_ID= | ||
FIREBASE_AUTH_URI= | ||
FIREBASE_TOKEN_URI= | ||
FIREBASE_AUTH_PROVIDER_X509_CERT_URL= | ||
FIREBASE_CLIENT_X509_CERT_URL= | ||
FIREBASE_UNIVERSE_DOMAIN= | ||
FIREBASE_AUTHORIZATION_URL= | ||
AIRQO_ANALYTICS_GMAIL= | ||
AIRQO_ANALYTICS_GMAIL_PASS= | ||
MOBILE_APP_PACKAGE_NAME= | ||
MOBILE_APP_DYNAMIC_LINK_DOMAIN= | ||
DEV_REDIS_SERVER= | ||
DEV_REDIS_PORT= | ||
PROD_REDIS_SERVER= | ||
PROD_REDIS_PORT= | ||
STAGE_REDIS_SERVER= | ||
STAGE_REDIS_PORT= | ||
STAGE_DEFAULT_GROUP= | ||
DEV_DEFAULT_GROUP= | ||
PROD_DEFAULT_GROUP= | ||
ANALYTICS_PRODUCTION_BASE_URL= | ||
ANALYTICS_STAGING_BASE_URL= | ||
ANALYTICS_DEV_BASE_URL= | ||
STAGE_DEFAULT_GROUP_ROLE= | ||
PROD_DEFAULT_GROUP_ROLE= | ||
DEV_DEFAULT_GROUP_ROLE= | ||
STAGE_DEFAULT_AIRQLOUD= | ||
PROD_DEFAULT_AIRQLOUD= | ||
DEV_DEFAULT_AIRQLOUD= | ||
STAGE_DEFAULT_GRID= | ||
PROD_DEFAULT_GRID= | ||
DEV_DEFAULT_GRID= | ||
STAGE_DEFAULT_COHORT= | ||
PROD_DEFAULT_COHORT= | ||
DEV_DEFAULT_COHORT= |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,14 +11,56 @@ | |||||||||||||||||||||||||||||||||
const { jsonrepair } = require("jsonrepair"); | ||||||||||||||||||||||||||||||||||
const BlacklistedIPRangeModel = require("@models/BlacklistedIPRange"); | ||||||||||||||||||||||||||||||||||
const BlacklistedIPModel = require("@models/BlacklistedIP"); | ||||||||||||||||||||||||||||||||||
const UserModel = require("@models/User"); | ||||||||||||||||||||||||||||||||||
const stringify = require("@utils/stringify"); | ||||||||||||||||||||||||||||||||||
const rangeCheck = require("ip-range-check"); | ||||||||||||||||||||||||||||||||||
const asyncRetry = require("async-retry"); | ||||||||||||||||||||||||||||||||||
const mongoose = require("mongoose"); | ||||||||||||||||||||||||||||||||||
const isEmpty = require("is-empty"); | ||||||||||||||||||||||||||||||||||
const ObjectId = mongoose.Types.ObjectId; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const userSchema = Joi.object({ | ||||||||||||||||||||||||||||||||||
email: Joi.string().email().empty("").required(), | ||||||||||||||||||||||||||||||||||
}).unknown(true); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const extractDeviceDetails = (updatedDevice) => { | ||||||||||||||||||||||||||||||||||
const { | ||||||||||||||||||||||||||||||||||
_id, | ||||||||||||||||||||||||||||||||||
status, | ||||||||||||||||||||||||||||||||||
category, | ||||||||||||||||||||||||||||||||||
isActive, | ||||||||||||||||||||||||||||||||||
long_name, | ||||||||||||||||||||||||||||||||||
device_number, | ||||||||||||||||||||||||||||||||||
name, | ||||||||||||||||||||||||||||||||||
deployment_date, | ||||||||||||||||||||||||||||||||||
latitude, | ||||||||||||||||||||||||||||||||||
longitude, | ||||||||||||||||||||||||||||||||||
mountType, | ||||||||||||||||||||||||||||||||||
powerType, | ||||||||||||||||||||||||||||||||||
} = updatedDevice; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||
status, | ||||||||||||||||||||||||||||||||||
category, | ||||||||||||||||||||||||||||||||||
_id, | ||||||||||||||||||||||||||||||||||
isActive, | ||||||||||||||||||||||||||||||||||
long_name, | ||||||||||||||||||||||||||||||||||
device_number, | ||||||||||||||||||||||||||||||||||
name, | ||||||||||||||||||||||||||||||||||
deployment_date, | ||||||||||||||||||||||||||||||||||
latitude, | ||||||||||||||||||||||||||||||||||
longitude, | ||||||||||||||||||||||||||||||||||
mountType, | ||||||||||||||||||||||||||||||||||
powerType, | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const extractActivityDetails = (createdActivity) => { | ||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||
...createdActivity, // Spread operator to include all properties directly | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
Comment on lines
+26
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unit tests are missing for new utility functions The utility functions Writing unit tests for these functions will improve code reliability and prevent future regressions. Would you like assistance in creating these tests or opening a GitHub issue to track this task? 🧰 Tools🪛 GitHub Check: codecov/patch
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. create a Github issue for this, please. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Baalmart, I've created a GitHub issue to track adding unit tests for 🔗 Analysis chain🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Create a GitHub issue to track adding unit tests for the utility functions.
gh issue create \
--title "Add unit tests for `extractDeviceDetails` and `extractActivityDetails`" \
--body "The utility functions \`extractDeviceDetails\` and \`extractActivityDetails\` added in PR [#3554](https://github.com/airqo-platform/AirQo-api/pull/3554) lack unit tests. Writing unit tests for these functions will improve code reliability and prevent future regressions.
See the discussion at [this comment](https://github.com/airqo-platform/AirQo-api/pull/3554#discussion_r1787818825).
Requested by @Baalmart." \
--assignee Baalmart
Length of output: 774 |
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const operationForNewMobileAppUser = async (messageData) => { | ||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
logger.info( | ||||||||||||||||||||||||||||||||||
|
@@ -177,7 +219,106 @@ | |||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const operationFunction2 = async (messageData) => {}; | ||||||||||||||||||||||||||||||||||
const emailsForDeployedDevices = async (messageData) => { | ||||||||||||||||||||||||||||||||||
let parsedData; | ||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
parsedData = JSON.parse(messageData); | ||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||
logger.error("Invalid JSON format in messageData."); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
const { createdActivity, updatedDevice, user_id } = parsedData; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// Validate input data | ||||||||||||||||||||||||||||||||||
if (!createdActivity || !updatedDevice || !user_id) { | ||||||||||||||||||||||||||||||||||
logger.error("Invalid input data: Missing required fields."); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (!ObjectId.isValid(user_id)) { | ||||||||||||||||||||||||||||||||||
logger.error(`Invalid user_id format: ${user_id}`); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
const user = await UserModel("airqo") | ||||||||||||||||||||||||||||||||||
.findOne({ _id: ObjectId(user_id) }, "email _id firstName lastName") | ||||||||||||||||||||||||||||||||||
.lean(); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// Check if user exists | ||||||||||||||||||||||||||||||||||
if (!user) { | ||||||||||||||||||||||||||||||||||
logger.error(`User not found for user_id: ${user_id}`); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const emailResponse = await mailer.fieldActivity({ | ||||||||||||||||||||||||||||||||||
email: user.email, | ||||||||||||||||||||||||||||||||||
firstName: user.firstName, | ||||||||||||||||||||||||||||||||||
lastName: user.lastName, | ||||||||||||||||||||||||||||||||||
deviceDetails: extractDeviceDetails(updatedDevice), | ||||||||||||||||||||||||||||||||||
activityDetails: extractActivityDetails(createdActivity), | ||||||||||||||||||||||||||||||||||
activityType: "deploy", | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// Handle email response | ||||||||||||||||||||||||||||||||||
if (emailResponse && emailResponse.success === false) { | ||||||||||||||||||||||||||||||||||
logger.error(`🐛 Internal Server Error -- ${stringify(emailResponse)}`); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||
logger.error(`🐛 Internal Server Error -- ${error.message}`); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const emailsForRecalledDevices = async (messageData) => { | ||||||||||||||||||||||||||||||||||
// Parse the message and validate input data | ||||||||||||||||||||||||||||||||||
let parsedData; | ||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
parsedData = JSON.parse(messageData); | ||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||
logger.error("Invalid JSON format in messageData."); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const { createdActivity, updatedDevice, user_id } = parsedData; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (!createdActivity || !updatedDevice || !user_id) { | ||||||||||||||||||||||||||||||||||
logger.error("Invalid input data: Missing required fields."); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Comment on lines
+282
to
+287
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate Similar to Add a validation step: if (!createdActivity || !updatedDevice || !user_id) {
logger.error("Invalid input data: Missing required fields.");
return;
}
+ if (!ObjectId.isValid(user_id)) {
+ logger.error(`Invalid user_id format: ${user_id}`);
+ return;
+ } 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: codecov/patch
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All cool, this is taken care of by the producing functionality, so the consumer does not have to worry about this input validation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Baalmart, understood. Thank you for the clarification that input validation for (_/) ✏️ Learnings added
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (!ObjectId.isValid(user_id)) { | ||||||||||||||||||||||||||||||||||
logger.error(`Invalid user_id format: ${user_id}`); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
const user = await UserModel("airqo") | ||||||||||||||||||||||||||||||||||
.findOne({ _id: ObjectId(user_id) }, "email _id firstName lastName") | ||||||||||||||||||||||||||||||||||
.lean(); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// Check if user exists | ||||||||||||||||||||||||||||||||||
if (!user) { | ||||||||||||||||||||||||||||||||||
logger.error(`User not found for user_id: ${user_id}`); | ||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const emailResponse = await mailer.fieldActivity({ | ||||||||||||||||||||||||||||||||||
email: user.email, | ||||||||||||||||||||||||||||||||||
firstName: user.firstName, | ||||||||||||||||||||||||||||||||||
lastName: user.lastName, | ||||||||||||||||||||||||||||||||||
deviceDetails: extractDeviceDetails(updatedDevice), | ||||||||||||||||||||||||||||||||||
activityDetails: extractActivityDetails(createdActivity), | ||||||||||||||||||||||||||||||||||
activityType: "recall", | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// Handle email response | ||||||||||||||||||||||||||||||||||
if (emailResponse && emailResponse.success === false) { | ||||||||||||||||||||||||||||||||||
logger.error(`🐛 Internal Server Error -- ${stringify(emailResponse)}`); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||
logger.error(`🐛 Internal Server Error -- ${error.message}`); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const kafkaConsumer = async () => { | ||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||
|
@@ -196,7 +337,8 @@ | |||||||||||||||||||||||||||||||||
const topicOperations = { | ||||||||||||||||||||||||||||||||||
// ["new-mobile-app-user-topic"]: operationForNewMobileAppUser, | ||||||||||||||||||||||||||||||||||
["ip-address"]: operationForBlacklistedIPs, | ||||||||||||||||||||||||||||||||||
// topic2: operationFunction2, | ||||||||||||||||||||||||||||||||||
["deploy-topic"]: emailsForDeployedDevices, | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added functions to send emails when device is deployed or recalled |
||||||||||||||||||||||||||||||||||
["recall-topic"]: emailsForRecalledDevices, | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
await consumer.connect(); | ||||||||||||||||||||||||||||||||||
// Subscribe to all topics in the mapping | ||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,8 @@ const envs = { | |
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, | ||
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, | ||
MOBILE_APP_USERS_TOPIC: process.env.MOBILE_APP_USERS_TOPIC, | ||
DEPLOY_TOPIC: process.env.DEPLOY_TOPIC, | ||
RECALL_TOPIC: process.env.RECALL_TOPIC, | ||
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification DEPLOY_TOPIC and RECALL_TOPIC are declared but not used elsewhere. It appears that the newly added environment variables 🔗 Analysis chainNew Kafka topics added for deployment and recallment processes. The addition of A few points to consider:
To ensure these new topics are being utilized correctly, let's verify their usage in the codebase: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify the usage of new Kafka topics in the codebase
# Search for DEPLOY_TOPIC usage
echo "Searching for DEPLOY_TOPIC usage:"
rg --type js "DEPLOY_TOPIC" -C 3
echo "\nSearching for RECALL_TOPIC usage:"
rg --type js "RECALL_TOPIC" -C 3
Length of output: 1535 |
||
UNIQUE_CONSUMER_GROUP: process.env.UNIQUE_CONSUMER_GROUP, | ||
UNIQUE_PRODUCER_GROUP: process.env.UNIQUE_PRODUCER_GROUP, | ||
NEW_MOBILE_APP_USER_TOPIC: process.env.NEW_MOBILE_APP_USER_TOPIC, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
introducing new Kafka topics for sending emails based on field activities