Exemplo n.º 1
0
class Cashin(BankingTransaction):
    __tablename__ = 'cashin'
    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
    }

    id = Field(Integer(), ForeignKey(BankingTransaction.id), primary_key=True)

    # TODO: Add some salt to prevent man in the middle (Extra field to send on creation and check on verification)
    transaction_id = Field(Unicode())
Exemplo n.º 2
0
class BankAccount(BankingId):
    __tablename__ = 'bank_account'
    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
    }

    id = Field(Integer(), ForeignKey(BankingId.id), primary_key=True)
    fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol'))

    iban = Field(Unicode(50), pattern=r'^[A-Z]{2}[A-Z0-9]{4,10}[0-9]{5,40}$')  # TODO: Should be unique if is_valid
    owner = Field(Unicode(100))
    bic = Field(Unicode(20), pattern=r'^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$', nullable=True)
Exemplo n.º 3
0
class BankCard(BankingId):
    __tablename__ = 'bank_card'
    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
    }

    id = Field(Integer(), ForeignKey(BankingId.id), primary_key=True)
    fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol'))

    pan = Field(Unicode(30), pattern=r'^([0-9]{4}-){3}[0-9]{4}$')  # TODO: Should be unique if is_valid
    holder = Field(Unicode(100))
    expiration = Field(Unicode(7), pattern=r'^[0-1]{1}/[0-9]{2,4}$', nullable=True, protected=True)  # mm/yy or mm/yyyy
Exemplo n.º 4
0
class Cashout(BankingTransaction):
    __tablename__ = 'cashout'
    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
    }

    id = Field(Integer(), ForeignKey(BankingTransaction.id), primary_key=True)
Exemplo n.º 5
0
class ActivationMixin:

    activated_at = Field(DateTime, nullable=True, json='activatedAt', readonly=True, protected=True)

    @hybrid_property
    def is_active(self):
        return self.activated_at is not None

    @is_active.setter
    def is_active(self, value):
        self.activated_at = datetime.now() if value else None

    @is_active.expression
    def is_active(self):
        # noinspection PyUnresolvedReferences
        return self.activated_at.isnot(None)

    @classmethod
    def filter_activated(cls, query=None):
        # noinspection PyUnresolvedReferences
        return (query or cls.query).filter(cls.is_active)

    @classmethod
    def import_value(cls, column, v):
        # noinspection PyUnresolvedReferences
        if column.key == cls.is_active.key and not isinstance(v, bool):
            return str(v).lower() == 'true'
        # noinspection PyUnresolvedReferences
        return super().import_value(column, v)
Exemplo n.º 6
0
class OrderableMixin:
    order = Field("order", Integer, default=0, nullable=False)
    __mapper_args__ = dict(order_by=order)

    @classmethod
    def apply_default_sort(cls, query=None):
        return (query or cls.query).order_by(cls.order)
Exemplo n.º 7
0
class BankingId(TimestampMixin, DeclarativeBase):
    __tablename__ = 'banking_id'

    id = Field(Integer(), primary_key=True)
    client_id = Field(Integer(), ForeignKey('client.id'))

    is_verified = Field(Boolean(), default=False)
    error = Field(Unicode(), nullable=True)

    client = relationship('Client', lazy='select', protected=True)

    type = Field(Enum('bank_account', 'bank_card', name='banking_id_type'))

    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
        'polymorphic_on': type
    }
Exemplo n.º 8
0
class Ticket(ModifiedMixin, PaginationMixin, DeclarativeBase):
    __tablename__ = 'ticket'

    id = Field(Integer(), primary_key=True)

    title = Field(Unicode())
    member_id = Field(Integer(), ForeignKey('member.id'))
    department_id = Field(Integer(), ForeignKey('ticket_department.id'))

    closed_at = Field(DateTime(), nullable=True)

    department = relationship('TicketDepartment', uselist=False)

    @hybrid_property
    def is_closed(self):
        return self.closed_at is not None

    @is_closed.setter
    def is_closed(self, value):
        self.closed_at = datetime.now() if value else None

    @is_closed.expression
    def is_closed(self):
        # noinspection PyUnresolvedReferences
        return self.closed_at.isnot(None)

    @classmethod
    def import_value(cls, column, v):
        # noinspection PyUnresolvedReferences
        if column.key == cls.is_closed.key and not isinstance(v, bool):
            return str(v).lower() == 'true'
        # noinspection PyUnresolvedReferences
        return super().import_value(column, v)

    def to_dict(self):
        result = super().to_dict()
        first_message = DBSession.query(TicketMessage) \
            .filter(TicketMessage.ticket_id == self.id) \
            .order_by(TicketMessage.created_at) \
            .first()
        result['firstMessage'] = first_message.to_dict() if (first_message is not None) else None
        return result
Exemplo n.º 9
0
class TicketMessage(ModifiedMixin, PaginationMixin, DeclarativeBase):
    __tablename__ = 'ticket_message'

    id = Field(Integer(), primary_key=True)
    ticket_id = Field(Integer(), ForeignKey(Ticket.id))
    member_id = Field(Integer(), ForeignKey('member.id'))
    text = Field(Unicode())

    is_answer = Field(Boolean(), default=False)

    _attachment = Field(TicketAttachment.as_mutable(JSON), nullable=True, protected=True)

    ticket = relationship(Ticket, lazy='select', uselist=False, protected=True)

    @property
    def attachment(self):
        return self._attachment.locate() if self._attachment else None

    @attachment.setter
    def attachment(self, value):
        if value is not None:
            self._attachment = TicketAttachment.create_from(value)
        else:
            self._attachment = None

    def to_dict(self):
        result = super().to_dict()
        result['attachment'] = self.attachment
        return result
Exemplo n.º 10
0
class BankingTransaction(ModifiedMixin, OrderingMixin, FilteringMixin, PaginationMixin, DeclarativeBase):
    __tablename__ = 'banking_transaction'

    id = Field(Integer(), primary_key=True)
    fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol'))
    member_id = Field(Integer(), ForeignKey('member.id'))  # FIXME: Change the name to `member_id`
    payment_gateway_name = Field(Unicode(30), ForeignKey('payment_gateway.name'))
    amount = Field(DECIMAL(18, 8))  # Value without commission
    commission = Field(DECIMAL(18, 8), default=Decimal(0))
    error = Field(Unicode(), nullable=True)
    reference_id = Field(Unicode(260), nullable=True)
    banking_id_id = Field(Integer(), ForeignKey('banking_id.id'), protected=True)

    payment_gateway = relationship('PaymentGateway')
    banking_id = relationship('BankingId')

    type = Field(Unicode(50))

    member = relationship('Member')

    __mapper_args__ = {
        'polymorphic_identity': __tablename__,
        'polymorphic_on': type
    }
Exemplo n.º 11
0
class ModifiedMixin(TimestampMixin):
    modified_at = Field(DateTime, nullable=True, json='modifiedAt', readonly=True)

    @property
    def last_modification_time(self):
        return self.modified_at or self.created_at

    # FIXME: rename it to before_update
    # noinspection PyUnusedLocal
    @staticmethod
    def on_update(mapper, connection, target):
        target.modified_at = datetime.now()

    @classmethod
    def __declare_last__(cls):
        event.listen(cls, 'before_update', cls.on_update)
Exemplo n.º 12
0
class SoftDeleteMixin:
    removed_at = Field(DateTime,
                       nullable=True,
                       json='removedAt',
                       readonly=True)

    def assert_is_not_deleted(self):
        if self.is_deleted:
            raise ValueError('Object is already deleted.')

    def assert_is_deleted(self):
        if not self.is_deleted:
            raise ValueError('Object is not deleted.')

    @property
    def is_deleted(self):
        return self.removed_at is not None

    def soft_delete(self, ignore_errors=False):
        if not ignore_errors:
            self.assert_is_not_deleted()
        self.removed_at = datetime.now()

    def soft_undelete(self, ignore_errors=False):
        if not ignore_errors:
            self.assert_is_deleted()
        self.removed_at = None

    @staticmethod
    def on_delete(mapper, connection, target):
        raise HttpConflict('Cannot remove this object: %s' % target)

    @classmethod
    def __declare_last__(cls):
        event.listen(cls, 'before_delete', cls.on_delete)

    @classmethod
    def filter_deleted(cls, query=None):
        # noinspection PyUnresolvedReferences
        return (query or cls.query).filter(cls.removed_at.isnot(None))

    @classmethod
    def exclude_deleted(cls, query=None):
        # noinspection PyUnresolvedReferences
        return (query or cls.query).filter(cls.removed_at.is_(None))
Exemplo n.º 13
0
class Market(OrderingMixin, FilteringMixin, DeclarativeBase):
    """
    Currency pairs are sometimes then written by concatenating the ISO currency codes (ISO 4217) of the base currency
    and the counter currency, separating them with a slash character. Often the slash character is omitted,
    alternatively the slash may be replaced by and etc. A widely traded currency pair is the relation of the euro
    against the US dollar, designated as EUR/USD. The quotation EUR/USD 1.2500 means that one euro is exchanged for
    1.2500 US dollars. Here, EUR is the base currency and USD is the quote currency(counter currency). This means that
    1 Euro can be exchangeable to 1.25 US Dollars.

    Reference: https://en.wikipedia.org/wiki/Currency_pair

    """
    __tablename__ = 'market'

    name = Field(Unicode(20),
                 pattern=r'^[A-Z0-9]{1,10}_[A-Z0-9]{1,10}$',
                 primary_key=True)  # e.g. btc_usd

    base_currency_symbol = Field(Unicode(),
                                 ForeignKey('currency.symbol'),
                                 protected=True)
    quote_currency_symbol = Field(Unicode(),
                                  ForeignKey('currency.symbol'),
                                  protected=True)

    base_currency = relationship('Currency',
                                 foreign_keys=[base_currency_symbol])
    quote_currency = relationship('Currency',
                                  foreign_keys=[quote_currency_symbol])

    buy_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100'))
    buy_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000'))

    sell_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100'))
    sell_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000'))

    taker_commission_rate = Field(Unicode(10), default='0.4')
    maker_commission_rate = Field(Unicode(10), default='0.1')

    # taker_static_commission = Field(BigInteger(), default=0)
    # taker_permille_commission = Field(Integer(), default=0)
    # taker_max_commission = Field(BigInteger(), default=0)

    # maker_static_commission = Field(BigInteger(), default=0)
    # maker_permille_commission = Field(Integer(), default=0)
    # maker_max_commission = Field(BigInteger(), default=0)

    def to_dict(self):
        result = super().to_dict()
        # TODO: Get the current user's wallet_tier_policy about this currency
        # result['tirePolicy'] = {}
        result['buyAmountMin'] = self.base_currency.normalized_to_output(
            self.buy_amount_min)
        result['buyAmountMax'] = self.base_currency.normalized_to_output(
            self.buy_amount_max)
        result['sellAmountMin'] = self.base_currency.normalized_to_output(
            self.sell_amount_min)
        result['sellAmountMax'] = self.base_currency.normalized_to_output(
            self.sell_amount_max)
        return result

    def get_last_price(self):
        try:
            return Decimal(stexchange_client.market_last(self.name))
        except StexchangeException as e:
            raise stexchange_http_exception_handler(e)

    def validate_ranges(self, type_, total_amount, price=None):

        # TODO: Review and rewrite the price threshold validator
        # threshold = Decimal(settings.trader.price_threshold_permille)
        # price_rate = Decimal(1000 * (price or self.get_last_price()) / self.get_last_price()) - Decimal(1000)

        if type_ == 'buy':
            # if price_rate > threshold:
            #     raise HttpBadRequest('Price not in valid range', 'price-not-in-range')

            if total_amount < self.buy_amount_min or \
                    (self.buy_amount_max != 0 and total_amount > self.buy_amount_max):
                raise HttpBadRequest('Amount not in range',
                                     'amount-not-in-range')

        elif type_ == 'sell':
            # if price_rate < -threshold:
            #     raise HttpBadRequest('Price not in valid range', 'price-not-in-range')

            if total_amount < self.sell_amount_min or \
                    (self.sell_amount_max != 0 and total_amount > self.sell_amount_max):
                raise HttpBadRequest('Amount not in range',
                                     'amount-not-in-range')
Exemplo n.º 14
0
class TicketDepartment(DeclarativeBase):
    __tablename__ = 'ticket_department'

    id = Field(Integer(), primary_key=True)
    title = Field(Unicode(50))
Exemplo n.º 15
0
class AutoActivationMixin(ActivationMixin):

    activated_at = Field(
        DateTime, nullable=True, json='activatedAt', readonly=True, protected=True, default=datetime.now
    )
Exemplo n.º 16
0
class TimestampMixin:
    created_at = Field(DateTime, default=datetime.now, nullable=False, json='createdAt', readonly=True)
Exemplo n.º 17
0
class PaymentGateway(DeclarativeBase):
    __tablename__ = 'payment_gateway'

    name = Field(Unicode(30), primary_key=True)
    fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol'))

    # # TODO: Will be deprecated and replaced by tiers
    cashin_min = Field(DECIMAL(18, 8), default=Decimal('0.00001000'))
    cashin_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000'))
    cashin_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000'))
    cashin_commission_rate = Field(Unicode(10), default="0.000")
    cashin_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000'))

    # # TODO: Will be deprecated and replaced by tiers
    cashout_min = Field(DECIMAL(18, 8), default=Decimal('0.00010000'))
    cashout_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000'))
    cashout_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000'))
    cashout_commission_rate = Field(Unicode(10), default="0.005")
    cashout_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000'))

    fiat = relationship('Fiat')

    def to_dict(self):
        result = super().to_dict()
        # TODO: Get the current user's fiat_tier_policy about this currency
        # result['tirePolicy'] = {}
        result['cashoutMin'] = self.fiat.normalized_to_output(self.cashout_min)
        result['cashoutMax'] = self.fiat.normalized_to_output(self.cashout_max)
        result['cashoutStaticCommission'] = self.fiat.normalized_to_output(self.cashout_static_commission)
        result['cashoutMaxCommission'] = self.fiat.normalized_to_output(self.cashout_max_commission)
        result['cashinMin'] = self.fiat.normalized_to_output(self.cashin_min)
        result['cashinMax'] = self.fiat.normalized_to_output(self.cashin_max)
        result['cashinStaticCommission'] = self.fiat.normalized_to_output(self.cashin_static_commission)
        result['cashinMaxCommission'] = self.fiat.normalized_to_output(self.cashin_max_commission)
        return result

    def calculate_cashout_commission(self, amount: Decimal) -> Decimal:
        commission = self.cashout_static_commission
        if self.cashout_commission_rate != Decimal(0):
            commission += amount * Decimal(self.cashout_commission_rate)
        return min(
            commission, self.cashout_max_commission
        ) if self.cashout_max_commission != Decimal(0) else commission

    def calculate_cashin_commission(self, amount: Decimal) -> Decimal:
        commission = self.cashin_static_commission
        if self.cashin_commission_rate != Decimal(0):
            commission += amount * Decimal(self.cashin_commission_rate)
        return min(
            commission, self.cashin_max_commission
        ) if self.cashin_max_commission != Decimal(0) else commission