125 lines
3.3 KiB
Python
125 lines
3.3 KiB
Python
from flask import current_app as app
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy.orm import column_property
|
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
from sqlalchemy.sql import func, select, and_
|
|
|
|
db = SQLAlchemy()
|
|
|
|
|
|
class TransferException(Exception):
|
|
pass
|
|
|
|
|
|
class NoFunds(TransferException):
|
|
pass
|
|
|
|
|
|
class Transaction(db.Model):
|
|
__tablename__ = "transactions"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
tx_hash = db.Column(db.String)
|
|
uid = db.Column(db.String(64), db.ForeignKey("users.uid"))
|
|
|
|
amount = db.Column(db.Integer)
|
|
|
|
type = db.Column(db.String(32), default="manual")
|
|
|
|
related = db.Column(db.String)
|
|
related_user = db.relationship(
|
|
"User", foreign_keys=[related], primaryjoin="Transaction.related == User.uid"
|
|
)
|
|
|
|
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
@hybrid_property
|
|
def value(self):
|
|
return self.amount
|
|
|
|
product_id = db.Column(db.Integer)
|
|
product_value = db.Column(db.Integer)
|
|
|
|
@hybrid_property
|
|
def finished(self):
|
|
return (self.type != "purchase") | (self.product_id != None)
|
|
|
|
__mapper_args__ = {"order_by": created.desc()}
|
|
|
|
def __repr__(self):
|
|
return "<Transaction {0.uid} {0.type} {0.amount} {0.created}>".format(self)
|
|
|
|
|
|
class User(db.Model):
|
|
__tablename__ = "users"
|
|
|
|
uid = db.Column(db.String(64), primary_key=True)
|
|
transactions = db.relationship("Transaction", backref="user", lazy="dynamic")
|
|
|
|
def __str__(self):
|
|
return self.uid
|
|
|
|
def __repr__(self):
|
|
return "<User {0.uid} {0.balance}>".format(self)
|
|
|
|
balance = column_property(
|
|
select([func.coalesce(func.sum(Transaction.amount), 0)])
|
|
.where(Transaction.uid == uid)
|
|
.correlate_except(Transaction)
|
|
)
|
|
|
|
purchase_count = column_property(
|
|
select([func.coalesce(func.count(Transaction.amount), 0)])
|
|
.where(and_(Transaction.uid == uid, Transaction.type == "purchase"))
|
|
.correlate_except(Transaction)
|
|
)
|
|
|
|
@hybrid_property
|
|
def purchase_amount(self):
|
|
return -sum(
|
|
(tx.amount or 0)
|
|
for tx in self.transactions.filter(Transaction.type == "purchase")
|
|
)
|
|
|
|
purchase_amount = column_property(
|
|
select([func.coalesce(-func.sum(Transaction.amount), 0)])
|
|
.where(and_(Transaction.uid == uid, Transaction.type == "purchase"))
|
|
.correlate_except(Transaction)
|
|
)
|
|
|
|
def transfer(self, target, amount):
|
|
if amount > self.amount_available:
|
|
raise NoFunds()
|
|
|
|
self.transactions.append(
|
|
Transaction(amount=-amount, type="transfer", related=target.uid)
|
|
)
|
|
target.transactions.append(
|
|
Transaction(amount=amount, type="transfer", related=self.uid)
|
|
)
|
|
|
|
@property
|
|
def debt_limit(self):
|
|
return app.config.get("DEBT_LIMIT", 5000)
|
|
|
|
@hybrid_property
|
|
def amount_available(self):
|
|
return self.balance + self.debt_limit
|
|
|
|
is_authenticated = True
|
|
is_active = True
|
|
is_anonymous = False
|
|
|
|
def get_id(self):
|
|
return self.uid
|
|
|
|
@property
|
|
def transaction_in_progress(self):
|
|
return self.transactions.filter(Transaction.finished == False).count()
|
|
|
|
@classmethod
|
|
def find(cls, uid):
|
|
return cls.query.filter(func.lower(cls.uid) == func.lower(uid)).first()
|