Skip to content
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

Jatin get profile images from website #1116

Open
wants to merge 10 commits into
base: development
Choose a base branch
from
364 changes: 316 additions & 48 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
JatinAgrawal94 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@sentry/integrations": "^7.110.0",
"@sentry/node": "^7.56.0",
"async-exit-hook": "^2.0.1",
"axios": "^1.7.7",
"babel-plugin-module-resolver": "^5.0.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
Expand All @@ -77,6 +78,7 @@
"nodemailer": "^6.4.16",
"redis": "^4.2.0",
"sanitize-html": "^2.13.0",
"sharp": "^0.33.5",
"supertest": "^6.3.4",
"uuid": "^3.4.0",
"ws": "^8.17.1"
Expand Down
31 changes: 29 additions & 2 deletions src/controllers/userProfileController.js
Original file line number Diff line number Diff line change
Expand Up @@ -881,13 +881,11 @@ const userProfileController = function (UserProfile, Project) {

const getUserById = function (req, res) {
const userid = req.params.userId;

// if (cache.getCache(`user-${userid}`)) {
// const getData = JSON.parse(cache.getCache(`user-${userid}`));
// res.status(200).send(getData);
// return;
// }

UserProfile.findById(userid, '-password -refreshTokens -lastModifiedDate -__v')
.populate([
{
Expand Down Expand Up @@ -1838,6 +1836,33 @@ const userProfileController = function (UserProfile, Project) {
.send({ message: 'Encountered an error to get all team codes, please try again!' });
}
};

const removeProfileImage = async (req,res) =>{
try{
var user_id=req.body.user_id
await UserProfile.updateOne({_id:user_id},{$unset:{profilePic:""}})
cache.removeCache(`user-${user_id}`);
return res.status(200).send({message:'Image Removed'})
}catch(err){
console.log(err)
return res.status(404).send({message:"Error Removing Image"})
}
}
const updateProfileImageFromWebsite = async (req,res) =>{
try{
var user=req.body
await UserProfile.updateOne({_id:user.user_id},
{
$set: { profilePic : user.selectedImage.nitro_src},
$unset: { suggestedProfilePics: "" }
})
cache.removeCache(`user-${user.user_id}`);
return res.status(200).send({message:"Profile Updated"})
}catch(err){
console.log(err)
return res.status(404).send({message:"Profile Update Failed"})
}
}

const getUserByAutocomplete = (req, res) => {
const { searchText } = req.params;
Expand Down Expand Up @@ -1919,6 +1944,8 @@ const userProfileController = function (UserProfile, Project) {
getProjectsByPerson,
getAllTeamCode,
getAllTeamCodeHelper,
removeProfileImage,
updateProfileImageFromWebsite,
getUserByAutocomplete,
getUserProfileBasicInfo,
updateUserInformation,
Expand Down
4 changes: 2 additions & 2 deletions src/cronjobs/userProfileJobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const userProfileJobs = () => {
async () => {
const SUNDAY = 0; // will change back to 0 after fix
if (moment().tz('America/Los_Angeles').day() === SUNDAY) {
console.log('Running Cron Jobs');
await userhelper.getProfileImagesFromWebsite();
await userhelper.assignBlueSquareForTimeNotMet();
await userhelper.applyMissedHourForCoreTeam();
await userhelper.emailWeeklySummariesForAllUsers();
Expand All @@ -36,7 +36,7 @@ const userProfileJobs = () => {
false,
'America/Los_Angeles',
);

allUserProfileJobs.start();
dailyUserDeactivateJobs.start();
};
Expand Down
151 changes: 151 additions & 0 deletions src/helpers/userHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const timeOffRequest = require('../models/timeOffRequest');
const notificationService = require('../services/notificationService');
const { NEW_USER_BLUE_SQUARE_NOTIFICATION_MESSAGE } = require('../constants/message');
const timeUtils = require('../utilities/timeUtils');
const fs = require('fs');
const cheerio = require('cheerio');
const axios=require('axios');
const sharp = require("sharp");

const userHelper = function () {
// Update format to "MMM-DD-YY" from "YYYY-MMM-DD" (Confirmed with Jae)
JatinAgrawal94 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -2246,6 +2250,152 @@ const userHelper = function () {
}
};

function searchForTermsInFields(data, term1, term2) {
const lowerCaseTerm1 = term1.toLowerCase();
const lowerCaseTerm2 = term2.toLowerCase();

let bothTermsMatches = [];
let term2Matches = [];

// Check if the current data is an array
if (Array.isArray(data)) {
data.forEach(item => {
const bothTermsFound = searchForBothTerms(item, lowerCaseTerm1, lowerCaseTerm2);
const term2OnlyFound = searchForTerm2(item, lowerCaseTerm2);

if (bothTermsFound) {
bothTermsMatches.push(item); // If both terms are found, store the item
} else if (term2OnlyFound) {
term2Matches.push(item); // If only term2 is found, store the item
}
});

// If matches for both terms are found, return them, else return term2 matches
if (bothTermsMatches.length > 0) {
return bothTermsMatches;
} else if (term2Matches.length > 0) {
return term2Matches;
} else {
return []; // No match found, return empty array
}
}

// Recursion case for nested objects
if (typeof data === 'object' && data !== null) {
const result = Object.keys(data).some(key => {
if (typeof data[key] === 'object') {
return searchForTermsInFields(data[key], lowerCaseTerm1, lowerCaseTerm2);
}
});
return result ? data : null;
}
return [];
}

// Helper function to check if both terms are in the string
function searchForBothTerms(data, term1, term2) {
if (typeof data === 'object' && data !== null) {
const fieldsToCheck = ['src', 'alt', 'title','nitro_src'];
return Object.keys(data).some(key => {
if (fieldsToCheck.includes(key)) {
const stringValue = String(data[key]).toLowerCase();
return stringValue.includes(term1) && stringValue.includes(term2); // Check if both terms are in the string
}
return false;
});
}
return false;
}

// Helper function to check if only term2 is in the string
function searchForTerm2(data, term2) {
if (typeof data === 'object' && data !== null) {
const fieldsToCheck = ['src', 'alt', 'title','nitro_src'];
return Object.keys(data).some(key => {
if (fieldsToCheck.includes(key)) {
const stringValue = String(data[key]).toLowerCase();
return stringValue.includes(term2); // Check if only term2 is in the string
}
return false;
});
}
return false;
}

async function imageUrlToPngBase64(url) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding image size limit or image compression to ensure large size images won't slow down the server significantly.

try {
// Fetch the image as a buffer
const response = await axios.get(url, { responseType: "arraybuffer" });

if (response.status !== 200) {
throw new Error(`Failed to fetch the image: ${response.statusText}`);
}

const imageBuffer = Buffer.from(response.data);

// Convert the image to PNG format using sharp
const pngBuffer = await sharp(imageBuffer).png().toBuffer();

// Convert the PNG buffer to a base64 string
const base64Png = pngBuffer.toString("base64");

return `data:image/png;base64,${base64Png}`;;
} catch (error) {
console.error(`An error occurred: ${error.message}`);
return null;
}
}

const getProfileImagesFromWebsite= async () => {
try {
// Fetch the webpage
const response = await axios.get("https://www.onecommunityglobal.org/team");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding timeout and retry logic. Without timeout and retry logic, if the network requests fail or hang, it could cause the application to become unresponsive.

const htmlText = response.data;
// Load HTML into Cheerio
const $ = cheerio.load(htmlText);
// Select all <img> elements and extract properties
const imgData = [];
$('img').each((i, img) => {
imgData.push({
src: $(img).attr('src'),
alt: $(img).attr('alt'),
title: $(img).attr('title'),
nitro_src: $(img).attr('nitro-lazy-src')
});
});
var users=await userProfile.find({'isActive':true},"firstName lastName email profilePic suggestedProfilePics")

users.map(async(u)=>{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your current implementation using map with async callbacks. I think this might lead to race conditions, since it doesn't wait for all promises to resolve. I would suggest using Promise.all to properly handle multiple asynchronous operations.

if(u.profilePic==undefined || u.profilePic==null || u.profilePic==""){
var result=searchForTermsInFields(imgData,u.firstName,u.lastName)
try {
if(result.length==1){
if(result[0].nitro_src!==undefined){
await userProfile.updateOne({_id:u._id},
{$set:{
"profilePic":result[0].nitro_src
}});
}else{
// when nitro is undefined, at that time src is a 403 link then I need to convert it into a base64 link.
let image=await imageUrlToPngBase64(result[0].src)
await userProfile.updateOne({_id:u._id},
{$set:{
"profilePic":image
}});
}
}else if(result.length>1){
await userProfile.updateOne({_id:u._id},{$set:{"suggestedProfilePics":result}});
}
} catch (error) {
console.log(error);
}
}
})
} catch (error) {
console.error('An error occurred:', error);
}
}

return {
changeBadgeCount,
getUserName,
Expand All @@ -2266,6 +2416,7 @@ const userHelper = function () {
getTangibleHoursReportedThisWeekByUserId,
deleteExpiredTokens,
deleteOldTimeOffRequests,
getProfileImagesFromWebsite
};
};

Expand Down
4 changes: 4 additions & 0 deletions src/models/userProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ const userProfileSchema = new Schema({
},
],
profilePic: { type: String },
suggestedProfilePics:{
type:[mongoose.Schema.Types.Mixed],
default:[]
},
infringements: [
{
date: { type: String, required: true },
Expand Down
3 changes: 3 additions & 0 deletions src/routes/userProfileRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ const routes = function (userProfile, project) {
userProfileRouter.route('/userProfile/projects/:name').get(controller.getProjectsByPerson);

userProfileRouter.route('/userProfile/teamCode/list').get(controller.getAllTeamCode);

userProfileRouter.route('/userProfile/profileImage/remove').put(controller.removeProfileImage);
JatinAgrawal94 marked this conversation as resolved.
Show resolved Hide resolved
userProfileRouter.route('/userProfile/profileImage/imagefromwebsite').put(controller.updateProfileImageFromWebsite);
JatinAgrawal94 marked this conversation as resolved.
Show resolved Hide resolved

userProfileRouter
.route('/userProfile/autocomplete/:searchText')
Expand Down
2 changes: 0 additions & 2 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* eslint-disable quotes */
require('dotenv').load();

const { app, logger } = require('./app');
const websockets = require('./websockets').default;

require('./startup/db')();
require('./cronjobs/userProfileJobs')();

Expand Down
Loading