Skip to content

Commit

Permalink
avniproject/avni-webapp#1214 - replaced phone number validation and f…
Browse files Browse the repository at this point in the history
…ormatting logic with PhoneNumberUtil that uses google library.
  • Loading branch information
petmongrels committed May 22, 2024
1 parent 255aa68 commit b846c62
Show file tree
Hide file tree
Showing 23 changed files with 197 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.avni.messaging.domain;

public class Constants {
public final static int NO_OF_DIGITS_IN_INDIAN_MOBILE_NO = 10;
public final static String PHONE_NUMBER_PATTERN = "^[0-9]{10}";
public final static String DURATION_PATTERN = "^P(?!$)(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+S)?)?$";
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.avni.messaging.domain.exception.GlificNotConfiguredException;
import org.avni.messaging.external.GlificRestClient;
import org.avni.messaging.service.PhoneNumberNotAvailableOrIncorrectException;
import org.avni.server.util.PhoneNumberUtil;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.ParameterizedTypeReference;
Expand All @@ -17,15 +18,11 @@
import java.util.Collections;
import java.util.List;

import static org.avni.messaging.domain.Constants.NO_OF_DIGITS_IN_INDIAN_MOBILE_NO;

@Repository
@Lazy //for better startup performance
public class GlificContactRepository extends AbstractGlificRepository {
public static final String GLIFIC_CONTACT_FOR_PHONE_NUMBER = "glificContactForPhoneNumber";
public static final String INDIA_ISD_CODE = "91";
public static final String PHONE_NUMBER = "${phoneNumber}";
public static final String RECEIVER_NAME = "${receiverName}";
public static final String RECEIVER_ID = "${receiverId}";
public static final String FULL_NAME = "${fullName}";
public static final String GROUP_ID = "${groupId}";
Expand All @@ -41,7 +38,6 @@ public class GlificContactRepository extends AbstractGlificRepository {
private final String GET_CONTACT_GROUP_CONTACT_COUNT_JSON;
private final String GET_CONTACT_GROUP_JSON;
private final String GET_ALL_MSGS_JSON;
private final String GET_ALL_GROUP_CONVERSATION_MSGS_JSON;
private final String ADD_CONTACTS_IN_GROUP_JSON;
private final String REMOVE_CONTACTS_IN_GROUP_JSON;
private final String ADD_CONTACT_GROUP_JSON;
Expand All @@ -58,7 +54,6 @@ public GlificContactRepository(GlificRestClient glificRestClient) {
GET_CONTACT_GROUP_CONTACT_COUNT_JSON = getJson("getContactGroupContactCount");
GET_CONTACT_GROUP_JSON = getJson("getContactGroup");
GET_ALL_MSGS_JSON = getJson("getAllMessages");
GET_ALL_GROUP_CONVERSATION_MSGS_JSON = getJson("searchConversationMessages");
ADD_CONTACTS_IN_GROUP_JSON = getJson("updateContactsInGroup");
REMOVE_CONTACTS_IN_GROUP_JSON = getJson("removeContactsInGroup");
ADD_CONTACT_GROUP_JSON = getJson("addContactGroup");
Expand All @@ -67,7 +62,7 @@ public GlificContactRepository(GlificRestClient glificRestClient) {
}

public String getOrCreateContact(String phoneNumber, String fullName) throws PhoneNumberNotAvailableOrIncorrectException, GlificNotConfiguredException {
if (phoneNumber == null || phoneNumber.length() < NO_OF_DIGITS_IN_INDIAN_MOBILE_NO) {
if (!PhoneNumberUtil.isValidPhoneNumber(phoneNumber)) {
throw new PhoneNumberNotAvailableOrIncorrectException();
}

Expand All @@ -77,20 +72,16 @@ public String getOrCreateContact(String phoneNumber, String fullName) throws Pho
glificContacts.getContacts().get(0).getId();
}

private String formatPhoneNumberToGlificFormat(String phoneNumber) {
return INDIA_ISD_CODE + phoneNumber.substring(phoneNumber.length() - NO_OF_DIGITS_IN_INDIAN_MOBILE_NO);
}

private String createContact(String phoneNumber, String fullName) throws GlificNotConfiguredException {
String message = OPTIN_CONTACT_JSON.replace(PHONE_NUMBER, formatPhoneNumberToGlificFormat(phoneNumber))
String message = OPTIN_CONTACT_JSON.replace(PHONE_NUMBER, PhoneNumberUtil.getPhoneNumberInGlificFormat(phoneNumber))
.replace(FULL_NAME, fullName);
GlificOptinContactResponse glificOptinContactResponse = glificRestClient.callAPI(message, new ParameterizedTypeReference<GlificResponse<GlificOptinContactResponse>>() {
});
return glificOptinContactResponse.getOptinContact().getContact().getId();
}

private GlificGetContactsResponse getContact(String phoneNumber) throws GlificNotConfiguredException {
String message = GET_CONTACT_JSON.replace(PHONE_NUMBER, formatPhoneNumberToGlificFormat(phoneNumber));
String message = GET_CONTACT_JSON.replace(PHONE_NUMBER, PhoneNumberUtil.getPhoneNumberInGlificFormat(phoneNumber));
return glificRestClient.callAPI(message, new ParameterizedTypeReference<GlificResponse<GlificGetContactsResponse>>() {
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.avni.server.service.IndividualService;
import org.avni.server.service.UserService;
import org.avni.server.util.A;
import org.avni.server.util.PhoneNumberUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -143,6 +144,7 @@ private void logAndNotifyError(GlificContactGroupContactsResponse.GlificContactG
}

private Optional<String> findNameOfTheContact(GlificContactGroupContactsResponse.GlificContactGroupContacts contactGroupContact) {
PhoneNumberUtil.getStandardFormatPhoneNumber(contactGroupContact.getPhone());
Optional<String> name = Stream.<Supplier<Optional<String>>>of(
() -> userService.findByPhoneNumber(contactGroupContact.getPhone()).map(User::getName),
() -> individualService.findByPhoneNumber(contactGroupContact.getPhone()).map(Individual::getFirstName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

import java.util.Optional;

import static org.avni.messaging.domain.Constants.NO_OF_DIGITS_IN_INDIAN_MOBILE_NO;

@Service
public class MessageReceiverService {

Expand Down Expand Up @@ -47,7 +45,7 @@ public MessageReceiver saveReceiverIfRequired(ReceiverType receiverType, Long en
}

public MessageReceiver saveReceiverIfRequired(ReceiverType receiverType, String receiverId) {
if(receiverType != ReceiverType.Group) return saveReceiverIfRequired(receiverType, new Long(receiverId));
if (receiverType != ReceiverType.Group) return saveReceiverIfRequired(receiverType, new Long(receiverId));
Optional<MessageReceiver> messageReceiverOptional = messageReceiverRepository.findByReceiverTypeAndExternalId(receiverType, receiverId);
return messageReceiverOptional.orElseGet(() -> {
MessageReceiver messageReceiver = new MessageReceiver(receiverType, receiverId);
Expand All @@ -62,12 +60,11 @@ public MessageReceiver ensureExternalIdPresent(MessageReceiver messageReceiver)
}

String phoneNumber = null, fullName = null;
if(messageReceiver.getReceiverType() == ReceiverType.Subject){
if (messageReceiver.getReceiverType() == ReceiverType.Subject) {
Individual individual = individualService.findById(messageReceiver.getReceiverId());
phoneNumber = individualService.findPhoneNumber(individual);
fullName = individual.getFullName();
}
else if (messageReceiver.getReceiverType() == ReceiverType.User){
} else if (messageReceiver.getReceiverType() == ReceiverType.User) {
User user = userService.findById(messageReceiver.getReceiverId()).get();
phoneNumber = user.getPhoneNumber();
fullName = user.getName();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
package org.avni.messaging.service;

public class PhoneNumberNotAvailableOrIncorrectException extends Exception {
public PhoneNumberNotAvailableOrIncorrectException() {
}

public PhoneNumberNotAvailableOrIncorrectException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,6 @@ Page<User> findByOrganisationIdAndIsVoidedFalse(@Param("organisationId") Long or

List<User> findByCatchmentAndIsVoidedFalse(Catchment catchment);

default Optional<User> findUserWithMatchingPropertyValue(String propertyName, String value) {
Specification<User> specification = (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
cb.like(root.get(propertyName), "%" + value + "%");

List<User> users = findAll(specification);
return users.isEmpty() ? Optional.empty() : Optional.of(users.get(0));
}

default User getUser(String userId) {
User user = null;
if (RequestUtils.isValidUUID(userId)) {
Expand All @@ -96,6 +88,8 @@ default User getUser(String userId) {
return user;
}

Optional<User> findByPhoneNumber(String phoneNumber);

@Query(value = "select (count(p.id) > 0) as exists from group_privilege\n" +
" join privilege p on group_privilege.privilege_id = p.id\n" +
" join groups on group_privilege.group_id = groups.id and groups.is_voided = false\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ public void setName(String name) {
public String getRegion() {
return region;
}

public void setRegion(String region) {
this.region = region;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.avni.server.service.LocationService;
import org.avni.server.service.ObservationService;
import org.avni.server.service.S3Service;
import org.avni.server.util.PhoneNumberUtil;
import org.avni.server.util.S;
import org.avni.server.web.request.ObservationRequest;
import org.slf4j.Logger;
Expand All @@ -37,20 +38,18 @@
import java.util.stream.Stream;

import static java.lang.String.format;
import static org.avni.messaging.domain.Constants.PHONE_NUMBER_PATTERN;

@Component
public class ObservationCreator {

private static Logger logger = LoggerFactory.getLogger(ObservationCreator.class);
private AddressLevelTypeRepository addressLevelTypeRepository;
private ConceptRepository conceptRepository;
private FormRepository formRepository;
private ObservationService observationService;
private S3Service s3Service;
private IndividualService individualService;
private LocationService locationService;
private FormElementRepository formElementRepository;
private final AddressLevelTypeRepository addressLevelTypeRepository;
private final ConceptRepository conceptRepository;
private final FormRepository formRepository;
private final ObservationService observationService;
private final S3Service s3Service;
private final IndividualService individualService;
private final LocationService locationService;
private final FormElementRepository formElementRepository;

@Autowired
public ObservationCreator(AddressLevelTypeRepository addressLevelTypeRepository,
Expand Down Expand Up @@ -274,11 +273,11 @@ private Object getMediaObservationValue(String answerValue, List<String> errorMs

private Map<String, Object> toPhoneNumberFormat(String phoneNumber, List<String> errorMsgs, String conceptName) {
Map<String, Object> phoneNumberObs = new HashMap<>();
if (!phoneNumber.matches(PHONE_NUMBER_PATTERN)) {
errorMsgs.add(format("Invalid %s provided %s. Please provide 10 digit number.", conceptName, phoneNumber));
if (!PhoneNumberUtil.isValidPhoneNumber(phoneNumber)) {
errorMsgs.add(format("Invalid %s provided %s. Please provide valid phone number.", conceptName, phoneNumber));
return null;
}
phoneNumberObs.put("phoneNumber", phoneNumber);
phoneNumberObs.put("phoneNumber", PhoneNumberUtil.getDomesticPhoneNumber(phoneNumber));
phoneNumberObs.put("verified", false);
return phoneNumberObs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ private void write(Row row) throws Exception {
}
User.validateEmail(email);
user.setEmail(email);
if (!userService.isValidPhoneNumber(phoneNumber)) {
throw new ValidationException(String.format("Phone number is invalid or empty - '%s'.", phoneNumber));
}
user.setPhoneNumber(phoneNumber);
userService.setPhoneNumber(phoneNumber, user);
user.setName(nameOfUser);
if (!isNewUser) resetSyncService.recordSyncAttributeValueChangeForUser(user, catchment.getId(), syncSettings);
user.setCatchment(catchment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.avni.server.util.BugsnagReporter;
import org.avni.server.util.DateTimeUtil;
import org.avni.server.util.ObjectMapperSingleton;
import org.avni.server.util.PhoneNumberUtil;
import org.avni.server.web.request.ObservationRequest;
import org.avni.server.web.request.rules.RulesContractWrapper.Decision;
import org.avni.server.web.validation.ValidationException;
Expand All @@ -34,7 +35,6 @@
import java.util.stream.Collectors;

import static org.avni.messaging.domain.Constants.DURATION_PATTERN;
import static org.avni.messaging.domain.Constants.PHONE_NUMBER_PATTERN;

@Service("EnhancedValidationService")
@ConditionalOnProperty(value = "avni.enhancedValidation.enabled", havingValue = "true")
Expand Down Expand Up @@ -240,7 +240,7 @@ private String validateAnswer(Concept question, FormElement formElement, Object
case PhoneNumber:
try {
PhoneNumberObservationValue phoneNumber = objectMapper.convertValue(value, PhoneNumberObservationValue.class);
if (!phoneNumber.getPhoneNumber().matches(PHONE_NUMBER_PATTERN)) {
if (PhoneNumberUtil.isValidPhoneNumber(phoneNumber.getPhoneNumber())) {
return formatErrorMessage(question, value);
}
} catch (ClassCastException | IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.avni.server.service.accessControl.AccessControlService;
import org.avni.server.util.BadRequestError;
import org.avni.server.util.ObjectMapperSingleton;
import org.avni.server.util.PhoneNumberUtil;
import org.avni.server.util.S;
import org.avni.server.web.request.*;
import org.avni.server.web.request.api.RequestUtils;
Expand All @@ -38,9 +39,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.avni.messaging.domain.Constants.NO_OF_DIGITS_IN_INDIAN_MOBILE_NO;


@Service
public class IndividualService implements ScopeAwareService<Individual> {
public static final String PHONE_NUMBER_FOR_SUBJECT_ID = "phoneNumberForSubjectId";
Expand Down Expand Up @@ -461,7 +459,7 @@ public Optional<Individual> findByPhoneNumber(String phoneNumber) {
if (!phoneNumberConcept.isPresent()) {
phoneNumberConcept = conceptService.findContactNumberConcept();
}
phoneNumber = phoneNumber.substring(phoneNumber.length() - NO_OF_DIGITS_IN_INDIAN_MOBILE_NO);
phoneNumber = PhoneNumberUtil.getDomesticPhoneNumber(phoneNumber);
return phoneNumberConcept.isPresent()
? individualRepository.findByConceptWithMatchingPattern(phoneNumberConcept.get(), "%" + phoneNumber)
: Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package org.avni.server.service;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.avni.server.application.Subject;
import org.avni.server.dao.*;
import org.avni.server.domain.*;
import org.avni.server.framework.security.UserContextHolder;
import org.avni.server.service.exception.GroupNotFoundException;
import org.avni.server.util.PhoneNumberUtil;
import org.avni.server.web.validation.ValidationException;
import org.bouncycastle.util.Strings;
import org.joda.time.DateTime;
Expand All @@ -23,8 +22,6 @@
import java.util.Optional;
import java.util.stream.Collectors;

import static org.avni.messaging.domain.Constants.NO_OF_DIGITS_IN_INDIAN_MOBILE_NO;

@Service
public class UserService implements NonScopeAwareService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
Expand All @@ -34,17 +31,15 @@ public class UserService implements NonScopeAwareService {
private final UserSubjectRepository userSubjectRepository;
private final IndividualRepository individualRepository;
private final SubjectTypeRepository subjectTypeRepository;
private final AccountRepository accountRepository;

@Autowired
public UserService(UserRepository userRepository, GroupRepository groupRepository, UserGroupRepository userGroupRepository, UserSubjectRepository userSubjectRepository, IndividualRepository individualRepository, SubjectTypeRepository subjectTypeRepository, AccountRepository accountRepository) {
public UserService(UserRepository userRepository, GroupRepository groupRepository, UserGroupRepository userGroupRepository, UserSubjectRepository userSubjectRepository, IndividualRepository individualRepository, SubjectTypeRepository subjectTypeRepository) {
this.userRepository = userRepository;
this.groupRepository = groupRepository;
this.userGroupRepository = userGroupRepository;
this.userSubjectRepository = userSubjectRepository;
this.individualRepository = individualRepository;
this.subjectTypeRepository = subjectTypeRepository;
this.accountRepository = accountRepository;
}

public User getCurrentUser() {
Expand Down Expand Up @@ -132,8 +127,7 @@ public Optional<User> findById(Long id) {
}

public Optional<User> findByPhoneNumber(String phoneNumber) {
phoneNumber = phoneNumber.substring(phoneNumber.length() - NO_OF_DIGITS_IN_INDIAN_MOBILE_NO);
return userRepository.findUserWithMatchingPropertyValue("phoneNumber", phoneNumber);
return userRepository.findByPhoneNumber(PhoneNumberUtil.getStandardFormatPhoneNumber(phoneNumber));
}

@Transactional
Expand Down Expand Up @@ -192,13 +186,10 @@ public void ensureSubjectForUser(User user, SubjectType subjectType) {
userSubjectRepository.save(userSubject);
}

public boolean isValidPhoneNumber(String phoneNumber) {
try {
String region = UserContextHolder.getOrganisation().getAccount().getRegion();
PhoneNumberUtil instance = PhoneNumberUtil.getInstance();
return instance.isValidNumber(instance.parse(phoneNumber, region));
} catch (NumberParseException e) {
return false;
public void setPhoneNumber(String phoneNumber, User user) {
if (!PhoneNumberUtil.isValidPhoneNumber(phoneNumber)) {
throw new ValidationException(String.format("Phone number is invalid or empty - '%s'.", phoneNumber));
}
user.setPhoneNumber(PhoneNumberUtil.getStandardFormatPhoneNumber(phoneNumber));
}
}
Loading

0 comments on commit b846c62

Please sign in to comment.