diff --git a/src/main/java/biz/nynja/account/components/Validator.java b/src/main/java/biz/nynja/account/components/Validator.java index 8544c46497a82c16f5fd8650f49cf080ef4a3a56..2da72ad4f34203cac4f70fd11a34076806631933 100644 --- a/src/main/java/biz/nynja/account/components/Validator.java +++ b/src/main/java/biz/nynja/account/components/Validator.java @@ -19,12 +19,14 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import biz.nynja.account.grpc.AddAuthenticationProviderRequest; +import biz.nynja.account.grpc.AddContactInfoRequest; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import biz.nynja.account.grpc.AuthProviderDetails; import biz.nynja.account.grpc.AuthenticationType; import biz.nynja.account.grpc.CompletePendingAccountCreationRequest; +import biz.nynja.account.grpc.ContactType; import biz.nynja.account.grpc.CreatePendingAccountRequest; import biz.nynja.account.grpc.DeleteAuthenticationProviderRequest; import biz.nynja.account.grpc.ErrorResponse.Cause; @@ -236,6 +238,34 @@ public class Validator { return null; } + public Cause validateContactInfo(ContactType type, String contactInfoValue) { + if (contactInfoValue == null || contactInfoValue.trim().isEmpty()) { + return Cause.MISSING_CONTACT_INFO_IDENTIFIER; + } + switch (type) { + case MISSING_CONTACT_TYPE: + return Cause.MISSING_CONTACT_INFO_TYPE; + case PHONE_CONTACT: + // We expect to receive phone number in the following format : ":" + String[] provider = contactInfoValue.split(":"); + if (provider == null || provider.length != 2) { + return Cause.PHONE_NUMBER_INVALID; + } + if (!isPhoneNumberValid(provider[1], provider[0])) { + return Cause.PHONE_NUMBER_INVALID; + } + break; + case EMAIL_CONTACT: + if (!isEmailValid(contactInfoValue)) { + return Cause.EMAIL_INVALID; + } + break; + default: + break; + } + return null; + } + public Cause validateCreatePendingAccountRequest(CreatePendingAccountRequest request) { return validateAuthProvider(request.getAuthenticationType(), request.getAuthenticationProvider()); } @@ -310,6 +340,22 @@ public class Validator { request.getAuthenticationProvider().getAuthenticationProvider()); } + public Cause checkAddContactInfoRequest(AddContactInfoRequest request) { + if ((request.getAccountId() == null) || (request.getAccountId().isEmpty())) { + return Cause.MISSING_ACCOUNT_ID; + } + if (request.getContactInfo().getTypeValue() == 0) { + return Cause.MISSING_CONTACT_INFO_TYPE; + } + if (request.getContactInfo().getValue() == null || request.getContactInfo().getValue().isEmpty()) { + return Cause.MISSING_CONTACT_INFO_IDENTIFIER; + } + if (!isValidUuid(request.getAccountId())) { + return Cause.INVALID_ACCOUNT_ID; + } + return validateContactInfo(request.getContactInfo().getType(), request.getContactInfo().getValue()); + } + public Cause validateDeleteAuthenticationProviderRequest(DeleteAuthenticationProviderRequest request) { if (!isValidUuid(request.getProfileId())) { return Cause.INVALID_PROFILE_ID; diff --git a/src/main/java/biz/nynja/account/models/ContactInfo.java b/src/main/java/biz/nynja/account/models/ContactInfo.java index c4f13cc8009fc01130c1509c4824f08d90e0b48a..982681d9f49ff756f339203d77bd7defcd9d9d1c 100644 --- a/src/main/java/biz/nynja/account/models/ContactInfo.java +++ b/src/main/java/biz/nynja/account/models/ContactInfo.java @@ -79,7 +79,7 @@ public class ContactInfo { @Override public String toString() { - return new StringBuilder("Contact [type=").append(type).append(", value=").append(value).append(", label=") + return new StringBuilder("Contact info [type=").append(type).append(", value=").append(value).append(", label=") .append(label).append("]").toString(); } diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java index e7628f96465048734bfbf2d4f488e3e86035e403..382adca051ce6bd76ddf501efc52eccaa5da2695 100644 --- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java +++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java @@ -12,6 +12,7 @@ import biz.nynja.account.grpc.UpdateAccountRequest; import biz.nynja.account.grpc.UpdateProfileRequest; import biz.nynja.account.models.Account; import biz.nynja.account.models.AuthenticationProvider; +import biz.nynja.account.models.ContactInfo; import biz.nynja.account.models.PendingAccountByAuthenticationProvider; import biz.nynja.account.models.Profile; @@ -37,4 +38,6 @@ public interface AccountRepositoryAdditional { boolean addAuthenticationProvider(UUID profileId, AuthenticationProvider authProvider); boolean deleteAuthenticationProvider(Profile profile, AuthenticationProvider authProvider); + + boolean addContactInfo(UUID accountId, ContactInfo contactInfo); } diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java index e760dab22af7418c416af33ab5dd87c5db13bdbc..8aa3bfc41f8eaedf010ed0cd7943c7e363ff92ce 100644 --- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java +++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java @@ -3,6 +3,7 @@ */ package biz.nynja.account.repositories; +import java.time.Instant; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -15,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.cassandra.core.CassandraBatchOperations; import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.UpdateOptions; import org.springframework.data.cassandra.core.WriteResult; import org.springframework.stereotype.Service; @@ -62,19 +64,19 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio private ProfileRepository profileRepository; @Autowired - AccountByUsernameRepository accountByUsernameRepository; + private AccountByUsernameRepository accountByUsernameRepository; @Autowired - AccountByAuthenticationProviderRepository accountByAuthenticationProviderRepository; + private AccountByAuthenticationProviderRepository accountByAuthenticationProviderRepository; @Autowired - AccountByProfileIdRepository accountByProfileIdRepository; + private AccountByProfileIdRepository accountByProfileIdRepository; @Autowired - ProfileByAuthenticationProviderRepository profileByAuthenticationProviderRepository; + private ProfileByAuthenticationProviderRepository profileByAuthenticationProviderRepository; @Autowired - PendingAccountByAuthenticationProviderRepository pendingAccountByAuthenticationProviderRepository; + private PendingAccountByAuthenticationProviderRepository pendingAccountByAuthenticationProviderRepository; @Autowired private PendingAccountRepository pendingAccountRepository; @@ -94,7 +96,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio logger.debug("Existing pending account with the provided id {} was not found.", request.getAccountId()); return null; } - Long timeCreated = new Date().getTime(); + Long timeCreated = Instant.now().toEpochMilli(); Long checkMinutes = timeCreated - pendingAccount.getCreationTimestamp(); if (checkMinutes > COMPLETE_PENDING_ACCOUNT_TIMEOUT_IN_MINUTES * 60 * 1000) { logger.info("Account creation timeout expired."); @@ -171,7 +173,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio logger.debug("Existing profile with the provided id {} was not found.", request.getProfileId()); return null; } - Long timeUpdated = new Date().getTime(); + Long timeUpdated = Instant.now().toEpochMilli(); WriteResult wr = null; try { updateProfileData(batchOperations, request, existingProfile, timeUpdated); @@ -200,7 +202,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio logger.debug("Existing account with the provided id {} was not found.", request.getAccountId()); return null; } - Long timeUpdated = new Date().getTime(); + Long timeUpdated = Instant.now().toEpochMilli(); WriteResult wr = null; try { updateAccountData(batchOperations, request, existingAccount, timeUpdated); @@ -282,7 +284,7 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio Profile existingProfile) { // update authentication providers of the profile by removing the one of the deleted account (if not // already manually removed by the user) - Long timeUpdated = new Date().getTime(); + Long timeUpdated = Instant.now().toEpochMilli(); if (existingProfile.getAuthenticationProviders() != null) { Set existingAuthenticationProvidersSet = new HashSet( existingProfile.getAuthenticationProviders()); @@ -515,7 +517,36 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio } @Override - public boolean deleteAuthenticationProvider(Profile profile, AuthenticationProvider authProvider) { + public boolean addContactInfo(UUID accountId, ContactInfo contactInfo) { + Account accountToUpdate = accountRepository.findByAccountId(accountId); + if (accountToUpdate == null) { + logger.error("Existing account with the provided id {} was not found.", accountId); + return false; + } + Set contactsInfoSet = accountToUpdate.getContactsInfo(); + if (contactsInfoSet == null) { + contactsInfoSet = new HashSet(); + } + if (contactInfo != null) { + if (!contactsInfoSet.add(contactInfo)) { + logger.error("Error adding contact info to account {}. {} already exists.", accountId, + contactInfo.toString()); + return false; + } + accountToUpdate.setContactsInfo(contactsInfoSet); + } + Long timeUpdated = Instant.now().toEpochMilli(); + accountToUpdate.setLastUpdateTimestamp(timeUpdated); + WriteResult wr = cassandraTemplate.update(accountToUpdate, UpdateOptions.builder().ifExists(true).build()); + if (wr != null && wr.wasApplied()) { + return true; + } + return false; + } + + @Override + public boolean deleteAuthenticationProvider(Profile profile, + AuthenticationProvider authProvider) { BatchStatement batch = new BatchStatement(); ResultSet rs = null; diff --git a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java index fff536629c6105cacbba6477ca756355947aad76..fe0bea37ad83d8bff728a08297d483fc9249c0bd 100644 --- a/src/main/java/biz/nynja/account/services/AccountServiceImpl.java +++ b/src/main/java/biz/nynja/account/services/AccountServiceImpl.java @@ -3,6 +3,7 @@ */ package biz.nynja.account.services; +import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -23,9 +24,12 @@ import biz.nynja.account.grpc.AccountsByProfileIdRequest; import biz.nynja.account.grpc.AccountsList; import biz.nynja.account.grpc.AccountsResponse; import biz.nynja.account.grpc.AddAuthenticationProviderRequest; +import biz.nynja.account.grpc.AddContactInfoRequest; import biz.nynja.account.grpc.AuthProviderDetails; import biz.nynja.account.grpc.AuthenticationType; import biz.nynja.account.grpc.CompletePendingAccountCreationRequest; +import biz.nynja.account.grpc.ContactDetails; +import biz.nynja.account.grpc.ContactType; import biz.nynja.account.grpc.CreateAccountRequest; import biz.nynja.account.grpc.CreatePendingAccountRequest; import biz.nynja.account.grpc.CreatePendingAccountResponse; @@ -38,6 +42,7 @@ import biz.nynja.account.grpc.ErrorResponse.Cause; import biz.nynja.account.models.Account; import biz.nynja.account.models.AccountByProfileId; import biz.nynja.account.models.AuthenticationProvider; +import biz.nynja.account.models.ContactInfo; import biz.nynja.account.models.PendingAccount; import biz.nynja.account.models.PendingAccountByAuthenticationProvider; import biz.nynja.account.repositories.AccountByAuthenticationProviderRepository; @@ -247,7 +252,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas updatedPendingAccount.setAuthenticationProvider(foundExistingPendingAccount.getAuthenticationProvider()); updatedPendingAccount .setAuthenticationProviderType(foundExistingPendingAccount.getAuthenticationProviderType()); - updatedPendingAccount.setCreationTimestamp(new Date().getTime()); + updatedPendingAccount.setCreationTimestamp(Instant.now().toEpochMilli()); PendingAccount updatePendingAccount = pendingAccountRepository.save(updatedPendingAccount); CreatePendingAccountResponse response = CreatePendingAccountResponse.newBuilder() @@ -275,7 +280,7 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas pendingAccount.setAccountId(UUID.randomUUID()); pendingAccount.setProfileId(UUID.randomUUID()); - pendingAccount.setCreationTimestamp(new Date().getTime()); + pendingAccount.setCreationTimestamp(Instant.now().toEpochMilli()); PendingAccount savedPendingAccount = pendingAccountRepository.save(pendingAccount); logger.debug("Pending account \"{}\" saved into the DB", savedPendingAccount.toString()); @@ -550,6 +555,52 @@ public class AccountServiceImpl extends AccountServiceGrpc.AccountServiceImplBas return; } + @Override + public void addContactInfoToAccount(AddContactInfoRequest request, + StreamObserver responseObserver) { + logger.info("Adding contact info to account requested."); + logger.debug("Adding contact info to account requested: {}", request); + + Cause cause = validator.checkAddContactInfoRequest(request); + if (cause != null) { + responseObserver + .onNext(StatusResponse.newBuilder().setError(ErrorResponse.newBuilder().setCause(cause)).build()); + responseObserver.onCompleted(); + return; + } + + if (request.getContactInfo().getType() == ContactType.PHONE_CONTACT) { + request = normalizePhoneNumber(request); + } + + boolean result = accountRepositoryAdditional.addContactInfo(UUID.fromString(request.getAccountId()), + ContactInfo.createContactInfoFromProto(request.getContactInfo())); + if (result) { + logger.info("Contact info {}:{} was added to account {}.", request.getContactInfo().getType().name(), + request.getContactInfo().getValue(), request.getAccountId()); + responseObserver.onNext(StatusResponse.newBuilder().setStatus("SUCCESS").build()); + responseObserver.onCompleted(); + return; + } + logger.error("Contact info {}:{} was not added to account {}.", request.getContactInfo().getType().name(), + request.getContactInfo().getValue(), request.getAccountId()); + responseObserver.onNext(StatusResponse.newBuilder() + .setError(ErrorResponse.newBuilder().setCause(Cause.ERROR_ADDING_CONTACT_INFO)).build()); + responseObserver.onCompleted(); + return; + } + + private AddContactInfoRequest normalizePhoneNumber(AddContactInfoRequest request) { + // Get the normalized phone number from libphone + ContactDetails newContactDetails = ContactDetails.newBuilder().setType(request.getContactInfo().getType()) + .setValue(validator.getNormalizedPhoneNumber(request.getContactInfo().getValue())) + .setLabel(request.getContactInfo().getLabel()).build(); + AddContactInfoRequest newRequest = AddContactInfoRequest.newBuilder().setAccountId(request.getAccountId()) + .setContactInfo(newContactDetails).build(); + request = newRequest; + return request; + } + @Override public void deleteAuthenticationProviderFromProfile(DeleteAuthenticationProviderRequest request, StreamObserver responseObserver) {