Beispiel #1
0
class StaticPageStorage(object):
    __ENTITY_PROPERTY_DESCRIPTOR = '__descriptor'
    __ENTITY_PROPERTY_NAME = '__name'
    __ENTITY_PROPERTY_CONTENT = '__content'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'STATIC_PAGE'
        self.__reflector = Reflector()

    def save(self, entity: StaticPage) -> None:
        if not isinstance(entity, StaticPage):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_DESCRIPTOR,
            self.__class__.__ENTITY_PROPERTY_NAME,
            self.__class__.__ENTITY_PROPERTY_CONTENT,
        ))

        self.__storage.put_item(
            data[self.__class__.__ENTITY_PROPERTY_DESCRIPTOR], {
                'name': data[self.__class__.__ENTITY_PROPERTY_NAME],
                'content': data[self.__class__.__ENTITY_PROPERTY_CONTENT]
            })

    def __get_instance(self, data: dict) -> StaticPage:
        entity: StaticPage = self.__reflector.construct(
            StaticPage, {
                self.__class__.__ENTITY_PROPERTY_DESCRIPTOR: data['sk'],
                self.__class__.__ENTITY_PROPERTY_NAME: data['name'],
                self.__class__.__ENTITY_PROPERTY_CONTENT: data['content'],
            })
        return entity

    def get_by_descriptor(self, descriptor: str) -> Optional[StaticPage]:
        if not isinstance(descriptor, str):
            raise ArgumentTypeException(self.get_by_descriptor, 'descriptor',
                                        descriptor)
        elif not str(descriptor).strip():
            raise ArgumentCannotBeEmptyException(self.get_by_descriptor,
                                                 'descriptor')

        data = self.__storage.find_item(descriptor)
        return self.__get_instance(data) if data else None

    def remove(self, descriptor: str) -> None:
        if not isinstance(descriptor, str):
            raise ArgumentTypeException(self.get_by_descriptor, 'descriptor',
                                        descriptor)
        elif not str(descriptor).strip():
            raise ArgumentCannotBeEmptyException(self.get_by_descriptor,
                                                 'descriptor')

        self.__storage.delete_item(descriptor)
Beispiel #2
0
class _CreditCardsStorageDynamoDb(CreditCardsStorageInterface):
    __ENTITY_PROPERTY_TOKEN = '__token'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_BRAND = '__brand'
    __ENTITY_PROPERTY_NUMBER_HIDDEN = '__number_hidden'
    __ENTITY_PROPERTY_EXPIRES = '__expires'
    __ENTITY_PROPERTY_HOLDER_NAME = '__holder_name'
    __ENTITY_PROPERTY_IS_VERIFIED = '__is_verified'
    __ENTITY_PROPERTY_CREATED_AT = '__created_at'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_CREDIT_CARDS'
        self.__reflector = Reflector()

    def __restore(self, row: dict) -> CreditCard:
        card = self.__reflector.construct(
            CreditCard, {
                self.__class__.__ENTITY_PROPERTY_TOKEN:
                row['sk'],
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                row['customer_id'],
                self.__class__.__ENTITY_PROPERTY_BRAND:
                row['brand'],
                self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN:
                row['number_hidden'],
                self.__class__.__ENTITY_PROPERTY_EXPIRES: (datetime.date(
                    year=int('20' + row['expires'][0:2]), month=12, day=31))
                if int(row['expires'][2:4]) == 12 else
                (datetime.date(year=int('20' + row['expires'][0:2]),
                               month=int(row['expires'][2:4]) + 1,
                               day=1) - datetime.timedelta(days=1)),
                self.__class__.__ENTITY_PROPERTY_HOLDER_NAME:
                row['holder_name'],
                self.__class__.__ENTITY_PROPERTY_IS_VERIFIED:
                row['is_verified'],
                self.__class__.__ENTITY_PROPERTY_CREATED_AT:
                datetime.datetime.strptime(row['created_at'],
                                           '%Y-%m-%d %H:%M:%S')
            })

        return card

    def get_by_token(self, token: str) -> Optional[CreditCard]:
        if not isinstance(token, str):
            raise ArgumentTypeException(self.get_by_token, 'token', token)
        elif not token.strip():
            raise ArgumentCannotBeEmptyException(self.get_by_token, 'token')

        row = self.__dynamo_db.find_item(token)
        return self.__restore(row) if row else None

    def get_all_by_customer(self, customer_id: str) -> Tuple[CreditCard]:
        if not isinstance(customer_id, str):
            raise ArgumentTypeException(self.get_all_by_customer,
                                        'customer_id', customer_id)
        elif not customer_id.strip():
            raise ArgumentCannotBeEmptyException(self.get_all_by_customer,
                                                 'customer_id')

        items = self.__dynamo_db.find_by_attribute('customer_id', customer_id)
        result = [self.__restore(item) for item in items]
        return tuple(result)

    def save(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.save, 'card', card)

        data = self.__reflector.extract(card, [
            self.__class__.__ENTITY_PROPERTY_TOKEN,
            self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID,
            self.__class__.__ENTITY_PROPERTY_BRAND,
            self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN,
            self.__class__.__ENTITY_PROPERTY_EXPIRES,
            self.__class__.__ENTITY_PROPERTY_HOLDER_NAME,
            self.__class__.__ENTITY_PROPERTY_IS_VERIFIED,
            self.__class__.__ENTITY_PROPERTY_CREATED_AT,
        ])

        self.__dynamo_db.put_item(
            data[self.__class__.__ENTITY_PROPERTY_TOKEN], {
                'customer_id':
                data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID],
                'brand':
                data[self.__class__.__ENTITY_PROPERTY_BRAND],
                'number_hidden':
                data[self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN],
                'expires':
                data[self.__class__.__ENTITY_PROPERTY_EXPIRES].strftime(
                    '%y%m'),
                'holder_name':
                data[self.__class__.__ENTITY_PROPERTY_HOLDER_NAME],
                'is_verified':
                data[self.__class__.__ENTITY_PROPERTY_IS_VERIFIED],
                'created_at':
                data[self.__class__.__ENTITY_PROPERTY_CREATED_AT].strftime(
                    '%Y-%m-%d %H:%M:%S')
            })

    def remove(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.remove, 'card', card)

        if not self.__dynamo_db.find_item(card.token):
            raise ArgumentValueException('Card #{} is already Removed!'.format(
                card.token))

        self.__dynamo_db.delete_item(card.token)
Beispiel #3
0
class _CreditCardsElasticStorage(CreditCardsStorageInterface):
    __ENTITY_PROPERTY_TOKEN = '__token'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_BRAND = '__brand'
    __ENTITY_PROPERTY_NUMBER_HIDDEN = '__number_hidden'
    __ENTITY_PROPERTY_EXPIRES = '__expires'
    __ENTITY_PROPERTY_HOLDER_NAME = '__holder_name'
    __ENTITY_PROPERTY_IS_VERIFIED = '__is_verified'
    __ENTITY_PROPERTY_CREATED_AT = '__created_at'

    def __init__(self):
        """
        curl -X DELETE localhost:9200/purchase_customer_credit_cards
        curl -X PUT localhost:9200/purchase_customer_credit_cards -H "Content-Type: application/json" -d'{
            "mappings": {
                "purchase_customer_credit_cards": {
                    "properties": {
                        "token": {"type": "keyword"},
                        "customer_id": {"type": "keyword"},
                        "brand": {"type": "keyword"},
                        "number_hidden": {"type": "keyword"},
                        "expires": {"type": "keyword"}, //2005 -> 2020/05
                        "holder_name": {"type": "keyword"},
                        "is_verified": {"type": "boolean"},
                        "created_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"}
                    }
                }
            }
        }'
        curl -X DELETE localhost:9200/purchase_customer_credit_cards_customer_map
        curl -X PUT localhost:9200/purchase_customer_credit_cards_customer_map -H "Content-Type: application/json" -d'{
            "mappings": {
                "purchase_customer_credit_cards_customer_map": {
                    "properties": {
                        "tokens_json": {"type": "keyword"}
                    }
                }
            }
        }'
        """
        self.__elastic_cards = Elastic(
            settings.AWS_ELASTICSEARCH_PURCHASE_CUSTOMER_CREDIT_CARDS,
            settings.AWS_ELASTICSEARCH_PURCHASE_CUSTOMER_CREDIT_CARDS)
        self.__elastic_customer_cards_map = Elastic(
            settings.
            AWS_ELASTICSEARCH_PURCHASE_CUSTOMER_CREDIT_CARDS_CUSTOMER_MAP,
            settings.
            AWS_ELASTICSEARCH_PURCHASE_CUSTOMER_CREDIT_CARDS_CUSTOMER_MAP)
        self.__reflector = Reflector()

    def __restore(self, row: dict) -> CreditCard:
        card = self.__reflector.construct(
            CreditCard, {
                self.__class__.__ENTITY_PROPERTY_TOKEN:
                row['token'],
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                row['customer_id'],
                self.__class__.__ENTITY_PROPERTY_BRAND:
                row['brand'],
                self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN:
                row['number_hidden'],
                self.__class__.__ENTITY_PROPERTY_EXPIRES: (datetime.date(
                    year=int('20' + row['expires'][0:2]), month=12, day=31))
                if int(row['expires'][2:4]) == 12 else
                (datetime.date(year=int('20' + row['expires'][0:2]),
                               month=int(row['expires'][2:4]) + 1,
                               day=1) - datetime.timedelta(days=1)),
                self.__class__.__ENTITY_PROPERTY_HOLDER_NAME:
                row['holder_name'],
                self.__class__.__ENTITY_PROPERTY_IS_VERIFIED:
                row['is_verified'],
                self.__class__.__ENTITY_PROPERTY_CREATED_AT:
                datetime.datetime.strptime(row['created_at'],
                                           '%Y-%m-%d %H:%M:%S')
            })

        return card

    def get_by_token(self, token: str) -> Optional[CreditCard]:
        if not isinstance(token, str):
            raise ArgumentTypeException(self.get_by_token, 'token', token)
        elif not token.strip():
            raise ArgumentCannotBeEmptyException(self.get_by_token, 'token')

        row = self.__elastic_cards.get_data(token)
        return self.__restore(row) if row else None

    def get_all_by_customer(self, customer_id: str) -> Tuple[CreditCard]:
        if not isinstance(customer_id, str):
            raise ArgumentTypeException(self.get_all_by_customer,
                                        'customer_id', customer_id)
        elif not customer_id.strip():
            raise ArgumentCannotBeEmptyException(self.get_all_by_customer,
                                                 'customer_id')

        customer_cards_map = self.__elastic_customer_cards_map.get_data(
            customer_id)
        tokens = json.loads(
            customer_cards_map['tokens_json']) if customer_cards_map else []

        result = [self.get_by_token(token) for token in tokens]
        result = [card for card in result if card]
        if len(result) != len(tokens):
            raise ValueError(
                'Incorrect cards set for customer #{}: existed cards - {}, tokens in map - {}'
                .format(customer_id, len(result), len(tokens)))

        return tuple(result)

    def save(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.save, 'card', card)

        data = self.__reflector.extract(card, [
            self.__class__.__ENTITY_PROPERTY_TOKEN,
            self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID,
            self.__class__.__ENTITY_PROPERTY_BRAND,
            self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN,
            self.__class__.__ENTITY_PROPERTY_EXPIRES,
            self.__class__.__ENTITY_PROPERTY_HOLDER_NAME,
            self.__class__.__ENTITY_PROPERTY_IS_VERIFIED,
            self.__class__.__ENTITY_PROPERTY_CREATED_AT,
        ])

        token = data[self.__class__.__ENTITY_PROPERTY_TOKEN]
        customer_id = data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID]
        if self.__elastic_cards.get_data(token):
            self.__elastic_cards.update_data(
                token, {
                    'doc': {
                        'is_verified':
                        data[self.__class__.__ENTITY_PROPERTY_IS_VERIFIED]
                    }
                })
        else:
            self.__elastic_cards.create(
                token, {
                    'token':
                    data[self.__class__.__ENTITY_PROPERTY_TOKEN],
                    'customer_id':
                    data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID],
                    'brand':
                    data[self.__class__.__ENTITY_PROPERTY_BRAND],
                    'number_hidden':
                    data[self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN],
                    'expires':
                    data[self.__class__.__ENTITY_PROPERTY_EXPIRES].strftime(
                        '%y%m'),
                    'holder_name':
                    data[self.__class__.__ENTITY_PROPERTY_HOLDER_NAME],
                    'is_verified':
                    data[self.__class__.__ENTITY_PROPERTY_IS_VERIFIED],
                    'created_at':
                    data[self.__class__.__ENTITY_PROPERTY_CREATED_AT].strftime(
                        '%Y-%m-%d %H:%M:%S')
                })

            # Elastic can search by attributes only after 1 second from last update.
            # We need all data, when we are searching by customer_id,
            # so in this case we will lost fresh data, if search directly after creation of new card.
            # In this case we need to use another index and get data by elastic doc_id.
            customer_cards_map = self.__elastic_customer_cards_map.get_data(
                customer_id)
            if customer_cards_map:
                tokens = json.loads(customer_cards_map['tokens_json'])
                tokens.append(token)
                self.__elastic_customer_cards_map.update_data(
                    customer_id, {'doc': {
                        'tokens_json': json.dumps(tokens)
                    }})
            else:
                self.__elastic_customer_cards_map.create(
                    customer_id, {'tokens_json': json.dumps([token])})

    def remove(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.remove, 'card', card)

        if not self.__elastic_cards.get_data(card.token):
            raise ArgumentValueException('Card #{} is already Removed!'.format(
                card.token))

        self.__elastic_cards.delete_by_id(card.token)

        customer_cards_map = self.__elastic_customer_cards_map.get_data(
            card.customer_id)
        tokens = json.loads(customer_cards_map['tokens_json'])
        tokens = [token for token in tokens if token != card.token]
        self.__elastic_customer_cards_map.update_data(
            card.customer_id, {'doc': {
                'tokens_json': json.dumps(tokens)
            }})
Beispiel #4
0
class SubscriptionStorage:
    __ENTITY_PROPERTY_SUBSCRIPTION_ID = '__subscription_id'
    __ENTITY_PROPERTY_EMAIL = '__email'
    __ENTITY_PROPERTY_USER_ID = '__user_id'
    __ENTITY_PROPERTY_SUBSCRIBED_AT = '__subscribed_at'
    __ENTITY_PROPERTY_UNSUBSCRIBED_AT = '__unsubscribed_at'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'EMAIL_SUBSCRIPTION'
        self.__reflector = Reflector()

    def __get_instance(self, data: dict) -> Subscription:
        subscription_id = Id(data.get('sk'))
        email = Email(data.get('email'))
        user_id = Id(data.get('user_id')) if data.get('user_id') else None
        subscribed_at = datetime.strptime(data.get('subscribed_at'), '%Y-%m-%d %H:%M:%S')
        unsubscribed_at = data.get('unsubscribed_at') or None
        unsubscribed_at = datetime.strptime(unsubscribed_at, '%Y-%m-%d %H:%M:%S') if unsubscribed_at else None

        entity: Subscription = self.__reflector.construct(Subscription, {
            self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID: subscription_id,
            self.__class__.__ENTITY_PROPERTY_EMAIL: email,
            self.__class__.__ENTITY_PROPERTY_USER_ID: user_id,
            self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT: subscribed_at,
            self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT: unsubscribed_at,
        })
        return entity

    def get_by_id(self, subscription_id: Id) -> Optional[Subscription]:
        if not isinstance(subscription_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'subscription_id', subscription_id)

        data = self.__storage.find_item(subscription_id.value)
        return self.__get_instance(data) if data else None

    def get_by_email(self, email: Email) -> Optional[Subscription]:
        if not isinstance(email, Email):
            raise ArgumentTypeException(self.get_by_email, 'email', email)

        data = (self.__storage.find_by_attribute('email', email.value) or [None])[0] or None
        return self.__get_instance(data) if data else None

    def save(self, entity: Subscription) -> None:
        if not isinstance(entity, Subscription):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID,
            self.__class__.__ENTITY_PROPERTY_EMAIL,
            self.__class__.__ENTITY_PROPERTY_USER_ID,
            self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT,
            self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT,
        ))

        subscription_id: Id = data[self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID]
        email: Email = data[self.__class__.__ENTITY_PROPERTY_EMAIL]
        user_id: Optional[Id] = data[self.__class__.__ENTITY_PROPERTY_USER_ID]
        subscribed_at: datetime = data[self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT]
        unsubscribed_at: Optional[datetime] = data[self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT]

        self.__storage.put_item(subscription_id.value, {
            'email': email.value,
            'user_id': user_id.value if user_id else None,
            'subscribed_at': subscribed_at.strftime('%Y-%m-%d %H:%M:%S'),
            'unsubscribed_at': unsubscribed_at.strftime('%Y-%m-%d %H:%M:%S') if unsubscribed_at else None
        })
Beispiel #5
0
class _MessageStorageDynamoDb(MessageStorageInterface):
    __ENTITY_PROPERTY_MESSAGE_ID = '__id'
    __ENTITY_PROPERTY_CUSTOMER_EMAIL = '__customer_email'
    __ENTITY_PROPERTY_TITLE = '__title'
    __ENTITY_PROPERTY_TEXT = '__text'
    __ENTITY_PROPERTY_CREATED_AT = '__created_at'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'NOTIFICATION_SIMPLE'
        self.__reflector = Reflector()

    def save(self, entity: Message) -> None:
        if not isinstance(entity, Message):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_MESSAGE_ID,
            self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL,
            self.__class__.__ENTITY_PROPERTY_TITLE,
            self.__class__.__ENTITY_PROPERTY_TEXT,
            self.__class__.__ENTITY_PROPERTY_CREATED_AT,
        ))

        self.__storage.put_item(
            data[self.__class__.__ENTITY_PROPERTY_MESSAGE_ID], {
                'customer_email':
                data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL],
                'title':
                data[self.__class__.__ENTITY_PROPERTY_TITLE],
                'text':
                data[self.__class__.__ENTITY_PROPERTY_TEXT],
                'created_at':
                data[self.__class__.__ENTITY_PROPERTY_CREATED_AT].strftime(
                    '%Y-%m-%d %H:%M:%S')
            })

    def remove(self, message_id: str) -> None:
        if not isinstance(message_id, str):
            raise ArgumentTypeException(self.remove, 'message_id', message_id)
        elif not str(message_id).strip():
            raise ArgumentCannotBeEmptyException(self.remove, 'message_id')

        self.__storage.delete_item(message_id)

    def get_all_for_customer(self, customer_email: str) -> Tuple[Message]:
        if not isinstance(customer_email, str):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_email', customer_email)
        elif not str(customer_email).strip():
            raise ArgumentCannotBeEmptyException(self.get_all_for_customer,
                                                 'customer_email')

        rows = self.__storage.filter_by_field_value('customer_email',
                                                    customer_email)
        return tuple([self.__get_instance(row) for row in rows])

    def __get_instance(self, data: dict) -> Message:
        entity: Message = self.__reflector.construct(
            Message, {
                self.__class__.__ENTITY_PROPERTY_MESSAGE_ID:
                data.get('sk'),
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL:
                data.get('customer_email'),
                self.__class__.__ENTITY_PROPERTY_TITLE:
                data.get('title'),
                self.__class__.__ENTITY_PROPERTY_TEXT:
                data.get('text'),
                self.__class__.__ENTITY_PROPERTY_CREATED_AT:
                datetime.strptime(data.get('created_at'), '%Y-%m-%d %H:%M:%S')
            })
        return entity
Beispiel #6
0
class _CustomerTiersElasticStorage(CustomerTierStorageInterface):
    __ENTITY_PROPERTY_ID = '__id'
    __ENTITY_PROPERTY_NAME = '__name'
    __ENTITY_PROPERTY_CREDIT_BACK_PERCENT = '__credit_back_percent'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MIN = 'spent_amount_min'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MAX = 'spent_amount_max'
    __ENTITY_PROPERTY_IS_DELETED = '__is_deleted'

    def __init__(self):
        """
        curl -X DELETE localhost:9200/customer_tiers_tiers
        curl -X PUT localhost:9200/customer_tiers_tiers -H "Content-Type: application/json" -d'{
            "mappings": {
                "customer_tiers_tiers": {
                    "properties": {
                        "id": {"type": "integer"},
                        "name": {"type": "keyword"},
                        "credit_back_percent": {"type": "integer"},
                        "spent_amount_min": {"type": "integer"},
                        "spent_amount_max": {"type": "integer"},
                        "is_deleted": {"type": "boolean"}
                    }
                }
            }
        }'
        """
        self.__elastic = Elastic(
            settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_TIERS,
            settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_TIERS
        )
        self.__reflector = Reflector()

    def save(self, entity: CustomerTier) -> None:
        entity_data = self.__reflector.extract(entity, [
            self.__class__.__ENTITY_PROPERTY_ID,
            self.__class__.__ENTITY_PROPERTY_NAME,
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX,
            self.__class__.__ENTITY_PROPERTY_IS_DELETED
        ])

        document_id = entity_data[self.__class__.__ENTITY_PROPERTY_ID].value
        document_data = {
            'id': entity_data[self.__class__.__ENTITY_PROPERTY_ID].value,
            'name': entity_data[self.__class__.__ENTITY_PROPERTY_NAME].value,
            'credit_back_percent': entity_data[self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT].value,
            'spent_amount_min': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN],
            'spent_amount_max': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX],
            'is_deleted': entity_data[self.__class__.__ENTITY_PROPERTY_IS_DELETED],
        }

        if self.__elastic.get_data(document_id):
            self.__elastic.update_data(document_id, {'doc': document_data})
        else:
            self.__elastic.create(document_id, document_data)

    def __create_entity(self, row: dict) -> CustomerTier:
        entity = self.__reflector.construct(CustomerTier, {
            self.__class__.__ENTITY_PROPERTY_ID: Id(str(row['id'])),
            self.__class__.__ENTITY_PROPERTY_NAME: Name(row['name']),
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT: Percentage(int(row['credit_back_percent'])),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN: int(row['spent_amount_min']),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX: int(row['spent_amount_max']),
            self.__class__.__ENTITY_PROPERTY_IS_DELETED: row['is_deleted'],
        })
        return entity

    def get_by_id(self, tier_id: Id) -> Optional[CustomerTier]:
        if not isinstance(tier_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'tier_id', tier_id)

        row = self.__elastic.get_data(tier_id.value)
        return self.__create_entity(row) if row else None

    def get_all(self) -> Tuple[CustomerTier]:
        rows = self.__elastic.post_search({'query': {'match_all': {}}}).get('hits', {}).get('hits')
        result = [self.__create_entity(row['_source']) for row in rows]
        result = [entity for entity in result if not entity.is_deleted]
        result = tuple(result)
        return result

    def get_neutral(self) -> CustomerTier:
        for tier in self.get_all():
            if tier.is_neutral:
                return tier
        else:
            raise ApplicationLogicException('Neutral Tier does not exist!')
Beispiel #7
0
class _CustomerTiersStorageDynamoDb(CustomerTierStorageInterface):
    __ENTITY_PROPERTY_ID = '__id'
    __ENTITY_PROPERTY_NAME = '__name'
    __ENTITY_PROPERTY_CREDIT_BACK_PERCENT = '__credit_back_percent'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MIN = 'spent_amount_min'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MAX = 'spent_amount_max'
    __ENTITY_PROPERTY_IS_DELETED = '__is_deleted'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_TIERS_TIER'
        self.__reflector = Reflector()

    def save(self, entity: CustomerTier) -> None:
        entity_data = self.__reflector.extract(entity, [
            self.__class__.__ENTITY_PROPERTY_ID,
            self.__class__.__ENTITY_PROPERTY_NAME,
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX,
            self.__class__.__ENTITY_PROPERTY_IS_DELETED
        ])

        document_id = entity_data[self.__class__.__ENTITY_PROPERTY_ID].value
        document_data = {
            'name': entity_data[self.__class__.__ENTITY_PROPERTY_NAME].value,
            'credit_back_percent': entity_data[self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT].value,
            'spent_amount_min': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN],
            'spent_amount_max': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX],
            'is_deleted': entity_data[self.__class__.__ENTITY_PROPERTY_IS_DELETED],
        }

        # fix of "TypeError: Float types are not supported. Use Decimal types instead." error
        document_data = json.loads(json.dumps(document_data), parse_float=Decimal)

        self.__dynamo_db.put_item(document_id, document_data)

    def __restore(self, row: dict) -> CustomerTier:
        entity = self.__reflector.construct(CustomerTier, {
            self.__class__.__ENTITY_PROPERTY_ID: Id(str(row['sk'])),
            self.__class__.__ENTITY_PROPERTY_NAME: Name(row['name']),
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT: Percentage(int(row['credit_back_percent'])),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN: int(row['spent_amount_min']),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX: int(row['spent_amount_max']),
            self.__class__.__ENTITY_PROPERTY_IS_DELETED: row['is_deleted'],
        })

        return entity

    def get_by_id(self, tier_id: Id) -> Optional[CustomerTier]:
        if not isinstance(tier_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'tier_id', tier_id)

        row = self.__dynamo_db.find_item(tier_id.value)
        return self.__restore(row) if row else None

    def get_all(self) -> Tuple[CustomerTier]:
        rows = self.__dynamo_db.find_all()
        result = [self.__restore(row) for row in rows]
        result = [entity for entity in result if not entity.is_deleted]
        result = tuple(result)
        return result

    def get_neutral(self) -> CustomerTier:
        for tier in self.get_all():
            if tier.is_neutral:
                return tier
        else:
            raise ApplicationLogicException('Neutral Tier does not exist!')
class _ReturnRequestStorageElastic(ReturnRequestStorageInterface):
    """
        curl -X DELETE localhost:9200/purchase_return_requests
        curl -X PUT localhost:9200/purchase_return_requests -H "Content-Type: application/json" -d'{
            "mappings": {
                "purchase_return_requests": {
                    "properties": {
                        "request_number": {"type": "keyword"},
                        "customer_id": {"type": "keyword"},
                        "request_items": {
                            "properties": {
                                "order_number": {"type": "keyword"},
                                "simple_sku": {"type": "keyword"},
                                "qty": {"type": "integer"},
                                "cost": {"type": "float"},
                                "reason": {"type": "keyword"},
                                "additional_comment": {"type": "keyword"},
                                "attached_files_urls_json": {"type": "keyword"},
                                "status_history": {
                                    "properties": {
                                        "status": {"type": "keyword"},
                                        "datetime": {"type": "date", "format": "date_hour_minute_second_millis"}
                                    }
                                }
                            }
                        },
                        "delivery_method": {"type": "keyword"},
                        "refund_method": {"type": "keyword"},
                        "refund_method_extra_data_json": {"type": "keyword"}
                    }
                }
            }
        }'

        curl -X DELETE localhost:9200/purchase_return_requests_customer_map
        curl -X PUT localhost:9200/purchase_return_requests_customer_map -H "Content-Type: application/json" -d'{
            "mappings": {
                "purchase_return_requests_customer_map": {
                    "properties": {
                        "request_numbers_json": {"type": "keyword"}
                    }
                }
            }
        }'
    """

    __ENTITY_PROPERTY_REQUEST_NUMBER = '__number'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_REFUND_METHOD = '__refund_method'
    __ENTITY_PROPERTY_DELIVERY_METHOD = '__delivery_method'
    __ENTITY_PROPERTY_ITEMS = '__items'
    __ENTITY_PROPERTY_ITEMS_ORDER_NUMBER = '__order_number'
    __ENTITY_PROPERTY_ITEMS_SIMPLE_SKU = '__simple_sku'
    __ENTITY_PROPERTY_ITEMS_QTY = '__qty'
    __ENTITY_PROPERTY_ITEMS_COST = '__cost'
    __ENTITY_PROPERTY_ITEMS_REASON = '__reason'
    __ENTITY_PROPERTY_ITEMS_ATTACHED_FILES = '__attached_files'
    __ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT = '__additional_comment'
    __ENTITY_PROPERTY_ITEMS_STATUS_HISTORY = '__status_history'

    def __init__(self):
        self.__requests_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_PURCHASE_RETURN_REQUESTS,
            settings.AWS_ELASTICSEARCH_PURCHASE_RETURN_REQUESTS)
        self.__customer_requests_map_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_PURCHASE_RETURN_REQUESTS_CUSTOMER_MAP,
            settings.AWS_ELASTICSEARCH_PURCHASE_RETURN_REQUESTS_CUSTOMER_MAP)
        self.__reflector = Reflector()

    def save(self, return_request: ReturnRequest) -> None:
        if not isinstance(return_request, ReturnRequest):
            raise ArgumentTypeException(self.save, 'return_request',
                                        return_request)

        items_data = []
        for item in return_request.items:
            status_history: ReturnRequest.Item.StatusChangesHistory = self.__reflector.extract(
                item,
                (self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY,
                 ))[self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY]

            items_data.append({
                "order_number":
                item.order_number.value,
                "simple_sku":
                item.simple_sku.value,
                "qty":
                item.qty.value,
                "cost":
                item.cost.value,
                "reason":
                item.reason.descriptor,
                "additional_comment":
                item.additional_comment.value
                if item.additional_comment else None,
                "attached_files_urls_json":
                json.dumps([file.url for file in item.attached_files]),
                "status_history": [
                    {
                        'status':
                        status_change.status.value,
                        # elastic supports only 3 digits for milliseconds
                        'datetime':
                        status_change.datetime.strftime('%Y-%m-%dT%H:%M:%S.%f')
                        [:-3],
                    } for status_change in status_history.get_all()
                ]
            })

        document_id = return_request.number.value
        document_data = {
            "request_number":
            return_request.number.value,
            "customer_id":
            return_request.customer_id.value,
            "request_items":
            items_data,
            "delivery_method":
            return_request.delivery_method.descriptor,
            "refund_method":
            return_request.refund_method.descriptor,
            "refund_method_extra_data_json":
            json.dumps(return_request.refund_method.extra_data),
        }

        existed_request = self.load(return_request.number)
        if existed_request:
            # just a double check of number uniqueness
            if existed_request.customer_id != return_request.customer_id:
                raise RuntimeError(
                    'Return Request "{}" already exists and belongs to another Customer!'
                    .format(return_request.number))

            self.__requests_elastic.update_data(document_id,
                                                {'doc': document_data})
        else:
            self.__requests_elastic.create(document_id, document_data)

            # Elastic can search by attributes only after 1 second from last update.
            # We need all data, when we are searching by customer_id,
            # so in this case we will lost fresh data, if search directly after creation of a new return request.
            # In this case we need to use another index and get data by elastic doc_id.

            customer_requests_map = self.__customer_requests_map_elastic.get_data(
                return_request.customer_id.value)
            if customer_requests_map:
                request_numbers = list(
                    json.loads(
                        customer_requests_map.get('request_numbers_json',
                                                  '[]')) or [])
                request_numbers.append(return_request.number.value)
                request_numbers = list(set(request_numbers))
                self.__customer_requests_map_elastic.update_data(
                    return_request.customer_id.value, {
                        'doc': {
                            'request_numbers_json': json.dumps(request_numbers)
                        }
                    })
            else:
                self.__customer_requests_map_elastic.create(
                    return_request.customer_id.value, {
                        'request_numbers_json':
                        json.dumps([return_request.number.value])
                    })

    def load(self,
             request_number: ReturnRequest.Number) -> Optional[ReturnRequest]:
        if not isinstance(request_number, ReturnRequest.Number):
            raise ArgumentTypeException(self.load, 'request_number',
                                        request_number)

        data = self.__requests_elastic.get_data(request_number.value)
        result = self.__restore(data) if data else None
        return result

    def __restore(self, data: dict) -> ReturnRequest:
        request_items = []
        for item_data in data['request_items']:
            attached_files = json.loads(item_data['attached_files_urls_json'])
            attached_files = tuple([
                ReturnRequest.Item.AttachedFile(url) for url in attached_files
            ])

            additional_comment = ReturnRequest.Item.AdditionalComment(
                item_data['additional_comment'])

            status_history = ReturnRequest.Item.StatusChangesHistory(
                tuple([
                    self.__reflector.construct(
                        ReturnRequest.Item.StatusChangesHistory.Change,
                        {
                            '__status':
                            ReturnRequest.Item.Status(change['status']),
                            # elastic supports only 3 digits for milliseconds
                            '__datetime':
                            datetime.datetime.strptime(
                                change['datetime'] + '000',
                                '%Y-%m-%dT%H:%M:%S.%f'),
                        }) for change in item_data['status_history']
                ]))

            request_items.append(
                self.__reflector.construct(
                    ReturnRequest.Item, {
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ORDER_NUMBER:
                        OrderNumber(item_data['order_number']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_SIMPLE_SKU:
                        SimpleSku(item_data['simple_sku']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_QTY:
                        Qty(item_data['qty']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_COST:
                        Cost(item_data['cost']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_REASON:
                        ReturnRequest.Item.Reason(item_data['reason']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ATTACHED_FILES:
                        attached_files,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT:
                        additional_comment,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY:
                        status_history,
                    }))

        return_request = self.__reflector.construct(
            ReturnRequest, {
                self.__class__.__ENTITY_PROPERTY_REQUEST_NUMBER:
                ReturnRequest.Number(data['request_number']),
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                Id(data['customer_id']),
                self.__class__.__ENTITY_PROPERTY_ITEMS:
                tuple(request_items),
                self.__class__.__ENTITY_PROPERTY_DELIVERY_METHOD:
                _restore_delivery_method(data['delivery_method']),
                self.__class__.__ENTITY_PROPERTY_REFUND_METHOD:
                _restore_refund_method(
                    data['refund_method'],
                    json.loads(data['refund_method_extra_data_json'])),
            })

        return return_request

    def get_all_for_customer(self, customer_id: Id) -> Tuple[ReturnRequest]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_id', customer_id)

        data = self.__customer_requests_map_elastic.get_data(customer_id.value)
        request_numbers = json.loads((
            data.get('request_numbers_json') or '[]') if data else '[]') or []
        if not request_numbers:
            return tuple()

        rows = self.__requests_elastic.post_search({
            "query": {
                "ids": {
                    "values": request_numbers
                }
            },
            "size": 10000
        }).get('hits', {}).get('hits', []) or []

        result = [self.__restore(row['_source']) for row in rows]

        if len(result) != len(request_numbers):
            message = '{} can\'t find all Return-Requests for Customer #{}! Not existed Return-Requests in map: {}'
            raise ValueError(
                message.format(self.get_all_for_customer, customer_id.value, [
                    request_number
                    for request_number in request_numbers if request_number
                    not in [request.number.value for request in result]
                ]))

        return tuple(result)
class _ReturnRequestStorageDynamoDb(ReturnRequestStorageInterface):
    __ENTITY_PROPERTY_REQUEST_NUMBER = '__number'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_REFUND_METHOD = '__refund_method'
    __ENTITY_PROPERTY_DELIVERY_METHOD = '__delivery_method'
    __ENTITY_PROPERTY_ITEMS = '__items'
    __ENTITY_PROPERTY_ITEMS_ORDER_NUMBER = '__order_number'
    __ENTITY_PROPERTY_ITEMS_SIMPLE_SKU = '__simple_sku'
    __ENTITY_PROPERTY_ITEMS_QTY = '__qty'
    __ENTITY_PROPERTY_ITEMS_COST = '__cost'
    __ENTITY_PROPERTY_ITEMS_REASON = '__reason'
    __ENTITY_PROPERTY_ITEMS_ATTACHED_FILES = '__attached_files'
    __ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT = '__additional_comment'
    __ENTITY_PROPERTY_ITEMS_STATUS_HISTORY = '__status_history'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_RETURN_REQUESTS'

        self.__reflector = Reflector()

    def save(self, return_request: ReturnRequest) -> None:
        if not isinstance(return_request, ReturnRequest):
            raise ArgumentTypeException(self.save, 'return_request',
                                        return_request)

        items_data = []
        for item in return_request.items:
            status_history: ReturnRequest.Item.StatusChangesHistory = self.__reflector.extract(
                item,
                (self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY,
                 ))[self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY]

            items_data.append({
                'order_number':
                item.order_number.value,
                'simple_sku':
                item.simple_sku.value,
                'qty':
                item.qty.value,
                'cost':
                item.cost.value,
                'reason':
                item.reason.descriptor,
                'additional_comment':
                item.additional_comment.value
                if item.additional_comment else None,
                'attached_files_urls_json':
                json.dumps([file.url for file in item.attached_files]),
                'status_history': [{
                    'status':
                    status_change.status.value,
                    'datetime':
                    status_change.datetime.strftime('%Y-%m-%dT%H:%M:%S.%f'),
                } for status_change in status_history.get_all()]
            })

        document_id = return_request.number.value
        document_data = {
            'customer_id':
            return_request.customer_id.value,
            'request_items':
            items_data,
            'delivery_method':
            return_request.delivery_method.descriptor,
            'refund_method':
            return_request.refund_method.descriptor,
            'refund_method_extra_data_json':
            json.dumps(return_request.refund_method.extra_data),
        }

        # fix of "TypeError: Float types are not supported. Use Decimal types instead." error
        document_data = json.loads(json.dumps(document_data),
                                   parse_float=Decimal)

        self.__dynamo_db.put_item(document_id, document_data)

    def load(self,
             request_number: ReturnRequest.Number) -> Optional[ReturnRequest]:
        if not isinstance(request_number, ReturnRequest.Number):
            raise ArgumentTypeException(self.load, 'request_number',
                                        request_number)

        data = self.__dynamo_db.find_item(request_number.value)
        result = self.__restore(data) if data else None
        return result

    def __restore(self, data: dict) -> ReturnRequest:
        request_items = []
        for item_data in data['request_items']:
            attached_files = json.loads(item_data['attached_files_urls_json'])
            attached_files = tuple([
                ReturnRequest.Item.AttachedFile(url) for url in attached_files
            ])

            additional_comment = ReturnRequest.Item.AdditionalComment(
                item_data['additional_comment'])

            status_history = ReturnRequest.Item.StatusChangesHistory(
                tuple([
                    self.__reflector.construct(
                        ReturnRequest.Item.StatusChangesHistory.Change, {
                            '__status':
                            ReturnRequest.Item.Status(change['status']),
                            '__datetime':
                            datetime.datetime.strptime(change['datetime'],
                                                       '%Y-%m-%dT%H:%M:%S.%f'),
                        }) for change in item_data['status_history']
                ]))

            request_items.append(
                self.__reflector.construct(
                    ReturnRequest.Item, {
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ORDER_NUMBER:
                        OrderNumber(item_data['order_number']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_SIMPLE_SKU:
                        SimpleSku(item_data['simple_sku']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_QTY:
                        Qty(item_data['qty']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_COST:
                        Cost(item_data['cost']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_REASON:
                        ReturnRequest.Item.Reason(item_data['reason']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ATTACHED_FILES:
                        attached_files,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT:
                        additional_comment,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY:
                        status_history,
                    }))

        return_request = self.__reflector.construct(
            ReturnRequest, {
                self.__class__.__ENTITY_PROPERTY_REQUEST_NUMBER:
                ReturnRequest.Number(data['request_number']),
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                Id(data['customer_id']),
                self.__class__.__ENTITY_PROPERTY_ITEMS:
                tuple(request_items),
                self.__class__.__ENTITY_PROPERTY_DELIVERY_METHOD:
                _restore_delivery_method(data['delivery_method']),
                self.__class__.__ENTITY_PROPERTY_REFUND_METHOD:
                _restore_refund_method(
                    data['refund_method'],
                    json.loads(data['refund_method_extra_data_json'])),
            })

        return return_request

    def get_all_for_customer(self, customer_id: Id) -> Tuple[ReturnRequest]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_id', customer_id)

        items = self.__dynamo_db.find_by_attribute('customer_id',
                                                   customer_id.value)
        result = [self.__restore(item) for item in items]
        return tuple(result)