mirror of
https://bitbucket.org/tagfer_team/tagfer-server.git
synced 2025-12-25 03:37:38 +00:00
[Performance] Suggest Profiles, Find Users By Phone and Endpoint Names
This commit is contained in:
parent
0b432b99f7
commit
1006554f73
@ -1,5 +1,4 @@
|
||||
const AuthHandlers = require('../ups/auth/handlers');
|
||||
const UserHandlers = require('../ups/users/handlers');
|
||||
const ProfileHandlers = require('../ups/profiles/handlers');
|
||||
|
||||
/**
|
||||
@ -16,18 +15,16 @@ function router(app) {
|
||||
app.post('/auth/phone/code', AuthHandlers.sendPhoneCode);
|
||||
app.post('/auth/phone/verify', AuthHandlers.verifyPhoneCode);
|
||||
app.post('/auth/signin', AuthHandlers.signin);
|
||||
app.put('/auth/signup', AuthHandlers.signup);
|
||||
app.post('/auth/signout', AuthHandlers.signout);
|
||||
app.post('/auth/passwordReset', AuthHandlers.sendPasswordResetEmail);
|
||||
|
||||
// Users Endpoints
|
||||
app.post('/users/by/phone', UserHandlers.findUsersByPhone);
|
||||
app.get('/users/suggest', UserHandlers.suggestUsers);
|
||||
app.post('/auth/findUsers/byPhone', AuthHandlers.findUsersByPhoneNumber);
|
||||
app.put('/auth/signup', AuthHandlers.signup);
|
||||
|
||||
// Profile Endpoints
|
||||
app.post('/profiles/:profileNumber', ProfileHandlers.updateUserProfile);
|
||||
app.get('/profiles/:profileNumber', ProfileHandlers.getUserProfile);
|
||||
app.post('/profiles/me/:profileNumber', ProfileHandlers.updateUserProfile);
|
||||
app.get('/profiles/me/:profileNumber', ProfileHandlers.getUserProfile);
|
||||
app.put('/profiles/uploadImage/:profileNumber', ProfileHandlers.updateUserProfileImage);
|
||||
app.get('/profiles/suggest', ProfileHandlers.suggestNProfiles);
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
@ -61,6 +61,29 @@ function createNewUser(user) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters contacts into three batches:
|
||||
* 1. Contacts that are in the tagfer network.
|
||||
* 2. Contacts that are out of the network.
|
||||
* 3. Contacts that failed to be processed.
|
||||
* @param {Array} phoneNumbers
|
||||
* @returns {Object} groups of inNetwork, outNetwork, failed
|
||||
*/
|
||||
async function getUsersByPhoneNumber(phoneNumbers) {
|
||||
const promises = [];
|
||||
const inNetwork = [];
|
||||
const outNetwork = [];
|
||||
const failed = [];
|
||||
|
||||
for (let i = 0; i < phoneNumbers.length; i++) {
|
||||
if (phoneNumbers[i]) promises.push(_getTagferIdByPhoneNumber(phoneNumbers[i],inNetwork, outNetwork, failed));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return { inNetwork, outNetwork, failed };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a password reset email
|
||||
* @param {email} email
|
||||
@ -182,6 +205,12 @@ function _doesKeyValueExist({email, tagferId, phoneNumber}) {
|
||||
});
|
||||
}
|
||||
|
||||
function _getTagferIdByPhoneNumber(phoneNumber, inNetwork, outNetwork, failed) {
|
||||
return admin.database().ref(`mapper/phoneNumbers/${phoneNumber}`).once('value')
|
||||
.then( data => data.exists() ? inNetwork.push(data.val()) : outNetwork.push(phoneNumber))
|
||||
.catch( () => failed.push(phoneNumber));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doesEmailExist,
|
||||
doesTagferIdExist,
|
||||
@ -194,17 +223,6 @@ module.exports = {
|
||||
createNewSessionId,
|
||||
getSession,
|
||||
deleteSession,
|
||||
resetPassword
|
||||
};
|
||||
|
||||
// function signinWithTagferId1(tagferId, password, callback) {
|
||||
// const path = `users/${tagferId}/hash`;
|
||||
|
||||
// db.ref(path).once('value').then( data => {
|
||||
// if(!data.exists()) {
|
||||
// callback({error: errors.AUTH_USER_NOT_FOUND});
|
||||
// }
|
||||
// const hash = data.val();
|
||||
// bcrypt.compare(password, hash).then(result => callback({result}));
|
||||
// });
|
||||
// }
|
||||
resetPassword,
|
||||
getUsersByPhoneNumber
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
const phone = require('phone');
|
||||
|
||||
const authDao = require('./dao');
|
||||
const profileDao = require('./../profiles/dao');
|
||||
const utils = require('../utils/utils');
|
||||
@ -156,6 +158,24 @@ async function signup(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: auth/findUsers/byPhone
|
||||
* Seperates the phone numbers into batches in/out of Tagfer network, receiver should use this information to either
|
||||
* add people who already in network, or send invites or repeat calls for failed batches.
|
||||
* @param {Object} req {phoneNumbers: Array<PhoneNumber>}
|
||||
* @param {Object} res {inNetwork : Array<PhoneNumber>, outNetwork: Array<PhoneNumber>, failed: Array<PhoneNumber> }
|
||||
*/
|
||||
async function findUsersByPhoneNumber(req, res) {
|
||||
const phoneNumbers = req.body.phoneNumbers;
|
||||
const verifier = () => phoneNumbers && Array.isArray(phoneNumbers);
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batches = await authDao.getUsersByPhoneNumber(phoneNumbers.map((number) => phone(number)[0]));
|
||||
res.json(batches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: auth/passwordReset
|
||||
* Send a reset password link to the email
|
||||
@ -200,6 +220,7 @@ function getTwitterUsername(req, res) {
|
||||
module.exports = {
|
||||
doesAttributeExist,
|
||||
doesSessionExist,
|
||||
findUsersByPhoneNumber,
|
||||
sendPhoneCode,
|
||||
verifyPhoneCode,
|
||||
signin,
|
||||
|
||||
@ -54,9 +54,51 @@ async function updateProfileImage(profileImageData, profileNumber, tagferId) {
|
||||
}
|
||||
}
|
||||
|
||||
var lastRetrievedProfile = undefined;
|
||||
/**
|
||||
* Returns a list of user profiles,that work as 'suggested contacts'. It works in a cyclic manner, starting from the top
|
||||
* node of the children of the profiles it fetches N profiles and saves the last retrieved profile. The next call will
|
||||
* start from the last retrieved profile and fetch the next N profiles. If the number of profiles fetched is ever less
|
||||
* than N, we make a recursive call to start at the top of the tree again and fetch the X remaining profiles.
|
||||
*
|
||||
* @param {Number} N number of profiles to retrieve
|
||||
*/
|
||||
function suggestNProfiles(N) {
|
||||
let promise = null;
|
||||
|
||||
if (lastRetrievedProfile) {
|
||||
promise = database.ref('profiles').orderByKey().startAt(lastRetrievedProfile).limitToFirst(N).once('value');
|
||||
} else {
|
||||
promise = database.ref('profiles').limitToFirst(N).once('value');
|
||||
}
|
||||
|
||||
return promise.then(data => {
|
||||
const list = new Array(data.numChildren());
|
||||
let index = 0;
|
||||
|
||||
data.forEach(profile => { list[index++] = _toLiteProfile(profile.val().profile1, profile.key); });
|
||||
if (index > 0) {
|
||||
lastRetrievedProfile = list[index-1].tagferId;
|
||||
}
|
||||
|
||||
if (data.hasChildren && N !== data.numChildren()) {
|
||||
lastRetrievedProfile = undefined;
|
||||
return suggestNProfiles(N - index).then(list2 => list2.concat(list));
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
function _toLiteProfile(profile, tagferId) {
|
||||
const { fullName, jobTitle, companyName, photoURL } = profile;
|
||||
return { tagferId, fullName, jobTitle, companyName, photoURL };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateProfile,
|
||||
createInitialProfiles,
|
||||
updateProfileImage,
|
||||
getProfile
|
||||
getProfile,
|
||||
suggestNProfiles
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ const authDao = require('../auth/dao');
|
||||
const utils = require('../utils/utils');
|
||||
const errors = require('../../config/errors');
|
||||
const http = require('../../config/http');
|
||||
const appConfig = require('../../config/app.json');
|
||||
|
||||
// Handlers
|
||||
/**
|
||||
@ -23,7 +24,7 @@ async function updateUserProfile(req, res) {
|
||||
const tagferId = authDao.getSession(sessionId).tagferId;
|
||||
profileDao.updateProfile(profileObj, profileNumber, tagferId).then(() => {
|
||||
res.status(http.CREATED).json({});
|
||||
}).catch((error) => {
|
||||
}).catch(() => {
|
||||
res.status(http.INTERNAL_SERVER_ERROR).json({ error: errors.APP_FIREBASE_DATABASE_ERROR });
|
||||
});
|
||||
} catch (error) {
|
||||
@ -73,8 +74,26 @@ async function getUserProfile(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: profiles/suggest
|
||||
* @param {Object} req {}
|
||||
* @param {Object} res { profiles: LiteProfileObject }
|
||||
*
|
||||
* LiteProfileObject = { tagferId, fullName, photoURL, jobTitle, companyName }
|
||||
*/
|
||||
function suggestNProfiles(req, res) {
|
||||
if (!utils.isAppSecretValid(req,res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
profileDao.suggestNProfiles(appConfig.suggestedUsersCount)
|
||||
.then(profiles => res.json({ profiles }))
|
||||
.catch(() => res.json({ error: errors.APP_FIREBASE_DATABASE_ERROR }));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateUserProfile,
|
||||
updateUserProfileImage,
|
||||
getUserProfile
|
||||
getUserProfile,
|
||||
suggestNProfiles
|
||||
};
|
||||
@ -1,60 +0,0 @@
|
||||
const admin = require('firebase-admin');
|
||||
|
||||
const errors = require('../../config/errors');
|
||||
|
||||
/**
|
||||
* Filters contacts into three batches:
|
||||
* 1. Contacts that are in the tagfer network.
|
||||
* 2. Contacts that are out of the network.
|
||||
* 3. Contacts that failed to be processed.
|
||||
* @param {Array} contacts
|
||||
* @returns {Object} groups inNetwork, outNetwork, failedBatch
|
||||
*/
|
||||
async function filterContactsIntoBatches(contacts) {
|
||||
const promises = [];
|
||||
const inNetworkBatch = [];
|
||||
const outNetworkBatch = [];
|
||||
const failedBatch = [];
|
||||
|
||||
const addToBatch = (contact) => contact.inNetwork? inNetworkBatch.push(contact.tagferId) : contact.outNetwork? outNetworkBatch.push(contact.phoneNumber) : failedBatch.push(contact.phoneNumber);
|
||||
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
if (contacts[i]) {
|
||||
promises.push(_getContactStatus(contacts[i]));
|
||||
}
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
for(let i = 0; i < results.length; i++) {
|
||||
if (results[i])
|
||||
addToBatch(results[i]);
|
||||
}
|
||||
|
||||
return {inNetworkBatch, outNetworkBatch, failedBatch};
|
||||
}
|
||||
|
||||
var pageToken = undefined;
|
||||
/**
|
||||
* Returns a list of N users. Used to suggest contacts.
|
||||
* @param {Number} N is the number of contacts you want to get
|
||||
*/
|
||||
function getNextNUsers(N) {
|
||||
return admin.auth().listUsers(N, pageToken).then( result => {
|
||||
pageToken = !result.pageToken? undefined : result.pageToken;
|
||||
return result.users.length === 0? getNextNUsers(N): result.users;
|
||||
}).catch( error => { throw { error: error.code }; });
|
||||
}
|
||||
|
||||
function _getContactStatus(phoneNumber) {
|
||||
return admin.auth().getUserByPhoneNumber(phoneNumber).then( user => {
|
||||
return {inNetwork: true, tagferId: user.uid};
|
||||
}).catch( error => {
|
||||
return error.code === errors.AUTH_USER_NOT_FOUND ? { outNetwork: true, phoneNumber } : { error: error.code, phoneNumber};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterContactsIntoBatches,
|
||||
getNextNUsers
|
||||
};
|
||||
@ -1,46 +0,0 @@
|
||||
const phone = require('phone');
|
||||
|
||||
const dao = require('./dao');
|
||||
const utils = require('../utils/utils');
|
||||
const appConfig = require('../../config/app.json');
|
||||
|
||||
/**
|
||||
* Endpoint: users/by/phone
|
||||
* Seperates the phone numbers into batches in/out of Tagfer network, receiver should use this information to either
|
||||
* add people who already in network, or send invites or repeat calls for failed batches.
|
||||
* @param {Object} req {contacts: Array<PhoneNumber>}
|
||||
* @param {Object} res {inNetworkBatch : Array<PhoneNumber>, outNetworkBatch: Array<PhoneNumber>, failedBatch: Array<PhoneNumber> }
|
||||
*/
|
||||
async function findUsersByPhone(req, res) {
|
||||
const contacts = req.body.contacts;
|
||||
const verifier = () => contacts && Array.isArray(contacts);
|
||||
if (!utils.isAppSecretValid(req,res) || !utils.isBodyValid(verifier, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batches = await dao.filterContactsIntoBatches(contacts.map((number) => phone(number)[0]));
|
||||
res.json(batches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint: users/suggest
|
||||
* @param {Object} req {}
|
||||
* @param {Object} res { users: UserObject }
|
||||
*
|
||||
* UserObject = { tagferId: String, displayName: String, photoURL: String }
|
||||
*/
|
||||
function suggestUsers(req, res) {
|
||||
if (!utils.isAppSecretValid(req,res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dao.getNextNUsers(appConfig.suggestedUsersCount).then( listUsers => {
|
||||
const users = listUsers.map( user => ({ tagferId: user.uid, displayName: user.displayName, photoURL: user.photoURL }) );
|
||||
res.json({ users });
|
||||
}).catch(error => res.json({ error }) );
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findUsersByPhone,
|
||||
suggestUsers
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user