Minor refactors:

- Renames s_assemble to s_brief
-  Link s_local to s_brief
This commit is contained in:
Louis Holbrook
2021-03-04 16:47:13 +00:00
committed by Philip Wafula
parent 21c9d95c4b
commit 1e7fff0133
93 changed files with 2264 additions and 796 deletions

View File

@@ -1,14 +1,18 @@
# standard imports
import json
import logging
from typing import Tuple
# third party imports
import celery
# local imports
from cic_ussd.accounts import BalanceManager
from cic_ussd.balance import BalanceManager, compute_operational_balance
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import AccountStatus, User
from cic_ussd.operations import get_user_by_phone_number, save_to_in_memory_ussd_session_data
from cic_ussd.state_machine.state_machine import UssdStateMachine
from cic_ussd.operations import save_to_in_memory_ussd_session_data
from cic_ussd.phone_number import get_user_by_phone_number
from cic_ussd.redis import create_cached_data_key, get_cached_data
from cic_ussd.transactions import OutgoingTransactionProcessor
@@ -27,22 +31,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, User]) -> bool:
recipient = get_user_by_phone_number(phone_number=user_input)
is_not_initiator = user_input != user.phone_number
has_active_account_status = user.get_account_status() == AccountStatus.ACTIVE.name
logg.debug('This section requires implementation of checks for user roles and authorization status of an account.')
return is_not_initiator and has_active_account_status
def is_valid_token_agent(state_machine_data: Tuple[str, dict, User]) -> bool:
"""This function checks that a user exists, is not the initiator of the transaction, has an active account status
and is authorized to perform exchange transactions.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
:return: A user's validity
:rtype: bool
"""
user_input, ussd_session, user = state_machine_data
# is_token_agent = AccountRole.TOKEN_AGENT.value in user.get_user_roles()
logg.debug('This section requires implementation of user roles and authorization to facilitate exchanges.')
return is_valid_recipient(state_machine_data=state_machine_data)
return is_not_initiator and has_active_account_status and recipient is not None
def is_valid_transaction_amount(state_machine_data: Tuple[str, dict, User]) -> bool:
@@ -70,10 +59,17 @@ def has_sufficient_balance(state_machine_data: Tuple[str, dict, User]) -> bool:
"""
user_input, ussd_session, user = state_machine_data
balance_manager = BalanceManager(address=user.blockchain_address,
chain_str=UssdStateMachine.chain_str,
chain_str=Chain.spec.__str__(),
token_symbol='SRF')
balance = balance_manager.get_operational_balance()
return int(user_input) <= balance
# get cached balance
key = create_cached_data_key(
identifier=bytes.fromhex(user.blockchain_address[2:]),
salt='cic.balances_data'
)
cached_balance = get_cached_data(key=key)
operational_balance = compute_operational_balance(balances=json.loads(cached_balance))
return int(user_input) <= operational_balance
def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, User]):
@@ -88,6 +84,25 @@ def save_recipient_phone_to_session_data(state_machine_data: Tuple[str, dict, Us
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
def retrieve_recipient_metadata(state_machine_data: Tuple[str, dict, User]):
"""
:param state_machine_data:
:type state_machine_data:
:return:
:rtype:
"""
user_input, ussd_session, user = state_machine_data
recipient = get_user_by_phone_number(phone_number=user_input)
blockchain_address = recipient.blockchain_address
# retrieve and cache account's metadata
s_query_user_metadata = celery.signature(
'cic_ussd.tasks.metadata.query_user_metadata',
[blockchain_address]
)
s_query_user_metadata.apply_async(queue='cic-ussd')
def save_transaction_amount_to_session_data(state_machine_data: Tuple[str, dict, User]):
"""This function saves the phone number corresponding the intended recipients blockchain account.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
@@ -113,7 +128,8 @@ def process_transaction_request(state_machine_data: Tuple[str, dict, User]):
to_address = recipient.blockchain_address
from_address = user.blockchain_address
amount = int(ussd_session.get('session_data').get('transaction_amount'))
outgoing_tx_processor = OutgoingTransactionProcessor(chain_str=UssdStateMachine.chain_str,
chain_str = Chain.spec.__str__()
outgoing_tx_processor = OutgoingTransactionProcessor(chain_str=chain_str,
from_address=from_address,
to_address=to_address)
outgoing_tx_processor.process_outgoing_transfer_transaction(amount=amount)

View File

@@ -1,10 +1,20 @@
# standard imports
import json
import logging
from typing import Tuple
# third-party imports
import celery
from cic_types.models.person import Person, generate_metadata_pointer
from cic_types.models.person import generate_vcard_from_contact_data, manage_identity_data
# local imports
from cic_ussd.chain import Chain
from cic_ussd.db.models.user import User
from cic_ussd.error import UserMetadataNotFoundError
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.operations import save_to_in_memory_ussd_session_data
from cic_ussd.redis import get_cached_data
logg = logging.getLogger(__file__)
@@ -42,7 +52,29 @@ def update_account_status_to_active(state_machine_data: Tuple[str, dict, User]):
User.session.commit()
def save_profile_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
def process_gender_user_input(user: User, user_input: str):
"""
:param user:
:type user:
:param user_input:
:type user_input:
:return:
:rtype:
"""
if user.preferred_language == 'en':
if user_input == '1':
gender = 'Male'
else:
gender = 'Female'
else:
if user_input == '1':
gender = 'Mwanaume'
else:
gender = 'Mwanamke'
return gender
def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict, User]):
"""This function saves first name data to the ussd session in the redis cache.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
@@ -54,16 +86,17 @@ def save_profile_attribute_to_session_data(state_machine_data: Tuple[str, dict,
# define session data key from current state
key = ''
if 'first_name' in current_state:
key = 'first_name'
elif 'last_name' in current_state:
key = 'last_name'
if 'given_name' in current_state:
key = 'given_name'
elif 'family_name' in current_state:
key = 'family_name'
elif 'gender' in current_state:
key = 'gender'
user_input = process_gender_user_input(user=user, user_input=user_input)
elif 'location' in current_state:
key = 'location'
elif 'business_profile' in current_state:
key = 'business_profile'
elif 'products' in current_state:
key = 'products'
# check if there is existing session data
if ussd_session.get('session_data'):
@@ -76,14 +109,120 @@ def save_profile_attribute_to_session_data(state_machine_data: Tuple[str, dict,
save_to_in_memory_ussd_session_data(queue='cic-ussd', session_data=session_data, ussd_session=ussd_session)
def persist_profile_data(state_machine_data: Tuple[str, dict, User]):
"""This function persists elements of the user profile stored in session data
def format_user_metadata(metadata: dict, user: User):
"""
:param metadata:
:type metadata:
:param user:
:type user:
:return:
:rtype:
"""
gender = metadata.get('gender')
given_name = metadata.get('given_name')
family_name = metadata.get('family_name')
location = {
"area_name": metadata.get('location')
}
products = []
if metadata.get('products'):
products = metadata.get('products').split(',')
phone_number = user.phone_number
date_registered = int(user.created.replace().timestamp())
blockchain_address = user.blockchain_address
chain_spec = f'{Chain.spec.common_name()}:{Chain.spec.network_id()}'
identities = manage_identity_data(
blockchain_address=blockchain_address,
blockchain_type=Chain.spec.engine(),
chain_spec=chain_spec
)
return {
"date_registered": date_registered,
"gender": gender,
"identities": identities,
"location": location,
"products": products,
"vcard": generate_vcard_from_contact_data(
family_name=family_name,
given_name=given_name,
tel=phone_number
)
}
def save_complete_user_metadata(state_machine_data: Tuple[str, dict, User]):
"""This function persists elements of the user metadata stored in session data
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: tuple
"""
user_input, ussd_session, user = state_machine_data
# get session data
profile_data = ussd_session.get('session_data')
logg.debug('This section requires implementation of user metadata.')
metadata = ussd_session.get('session_data')
# format metadata appropriately
user_metadata = format_user_metadata(metadata=metadata, user=user)
blockchain_address = user.blockchain_address
s_create_user_metadata = celery.signature(
'cic_ussd.tasks.metadata.create_user_metadata',
[blockchain_address, user_metadata]
)
s_create_user_metadata.apply_async(queue='cic-ussd')
def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, User]):
user_input, ussd_session, user = state_machine_data
blockchain_address = user.blockchain_address
key = generate_metadata_pointer(
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
cic_type='cic.person'
)
user_metadata = get_cached_data(key=key)
if not user_metadata:
raise UserMetadataNotFoundError(f'Expected user metadata but found none in cache for key: {blockchain_address}')
given_name = ussd_session.get('session_data').get('given_name')
family_name = ussd_session.get('session_data').get('family_name')
gender = ussd_session.get('session_data').get('gender')
location = ussd_session.get('session_data').get('location')
products = ussd_session.get('session_data').get('products')
# validate user metadata
person = Person()
user_metadata = json.loads(user_metadata)
deserialized_person = person.deserialize(metadata=user_metadata)
# edit specific metadata attribute
if given_name:
deserialized_person.given_name = given_name
elif family_name:
deserialized_person.family_name = family_name
elif gender:
deserialized_person.gender = gender
elif location:
# get existing location metadata:
location_data = user_metadata.get('location')
location_data['area_name'] = location
deserialized_person.location = location_data
elif products:
deserialized_person.products = products
edited_metadata = deserialized_person.serialize()
s_edit_user_metadata = celery.signature(
'cic_ussd.tasks.metadata.edit_user_metadata',
[blockchain_address, edited_metadata, 'pgp']
)
s_edit_user_metadata.apply_async(queue='cic-ussd')
def get_user_metadata(state_machine_data: Tuple[str, dict, User]):
user_input, ussd_session, user = state_machine_data
blockchain_address = user.blockchain_address
s_get_user_metadata = celery.signature(
'cic_ussd.tasks.metadata.query_user_metadata',
[blockchain_address]
)
s_get_user_metadata.apply_async(queue='cic-ussd')

View File

@@ -3,55 +3,30 @@ import logging
import re
from typing import Tuple
# third-party imports
from cic_types.models.person import generate_metadata_pointer
# local imports
from cic_ussd.db.models.user import User
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.redis import get_cached_data
logg = logging.getLogger()
def has_complete_profile_data(state_machine_data: Tuple[str, dict, User]):
def has_cached_user_metadata(state_machine_data: Tuple[str, dict, User]):
"""This function checks whether the attributes of the user's metadata constituting a profile are filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
user_input, ussd_session, user = state_machine_data
logg.debug('This section requires implementation of user metadata.')
def has_empty_username_data(state_machine_data: Tuple[str, dict, User]):
"""This function checks whether the aspects of the user's name metadata is filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
user_input, ussd_session, user = state_machine_data
logg.debug('This section requires implementation of user metadata.')
def has_empty_gender_data(state_machine_data: Tuple[str, dict, User]):
"""This function checks whether the aspects of the user's gender metadata is filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
user_input, ussd_session, user = state_machine_data
logg.debug('This section requires implementation of user metadata.')
def has_empty_location_data(state_machine_data: Tuple[str, dict, User]):
"""This function checks whether the aspects of the user's location metadata is filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
user_input, ussd_session, user = state_machine_data
logg.debug('This section requires implementation of user metadata.')
def has_empty_business_profile_data(state_machine_data: Tuple[str, dict, User]):
"""This function checks whether the aspects of the user's business profile metadata is filled out.
:param state_machine_data: A tuple containing user input, a ussd session and user object.
:type state_machine_data: str
"""
user_input, ussd_session, user = state_machine_data
logg.debug('This section requires implementation of user metadata.')
# check for user metadata in cache
key = generate_metadata_pointer(
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
cic_type='cic.person'
)
user_metadata = get_cached_data(key=key)
return user_metadata is not None
def is_valid_name(state_machine_data: Tuple[str, dict, User]):
@@ -66,3 +41,18 @@ def is_valid_name(state_machine_data: Tuple[str, dict, User]):
return True
else:
return False
def is_valid_gender_selection(state_machine_data: Tuple[str, dict, User]):
"""
:param state_machine_data:
:type state_machine_data:
:return:
:rtype:
"""
user_input, ussd_session, user = state_machine_data
selection_matcher = "^[1-2]$"
if re.match(selection_matcher, user_input):
return True
else:
return False

View File

@@ -14,15 +14,11 @@ class UssdStateMachine(Machine):
menu as well as providing a means for navigating through these states based on different user inputs.
It defines different helper functions that co-ordinate with the stakeholder components of the ussd menu: i.e the
User, UssdSession, UssdMenu to facilitate user interaction with ussd menu.
:cvar chain_str: The chain name and network id.
:type chain_str: str
:cvar states: A list of pre-defined states.
:type states: list
:cvar transitions: A list of pre-defined transitions.
:type transitions: list
"""
chain_str = None
states = []
transitions = []