class AppUser(BaseModel): __tablename__ = "app_user" user_id = db.Column(UUID, db.ForeignKey("user.id"), primary_key=True) user = db.relationship("User", backref="user_apps") app_id = db.Column(UUID, db.ForeignKey("app.id"), primary_key=True) app = db.relationship("App", backref="app_users") role = db.Column(db.Enum(Role), nullable=False, server_default="admin")
class Deposit(BaseModel): account_id = db.Column(UUID, db.ForeignKey("account.id")) account = db.relationship("Account") amount = db.Column(db.Integer, default=0, nullable=False) real_amount = db.Column(db.Integer, default=0, nullable=False) stripe_charge_id = db.Column(db.Text) stripe_data = db.Column(JSONB)
class Payable(BaseModel): app_id = db.Column(UUID, db.ForeignKey("app.id")) app = db.relationship("App", back_populates="payables") display_name = db.Column(db.Text, nullable=False) display_price = db.Column(db.Integer, nullable=False) permalink = db.Column(db.Text, nullable=True)
class User(BaseModel): email = db.Column(db.Text, unique=True, nullable=False) name = db.Column(db.Text, nullable=True) account_id = db.Column(UUID, db.ForeignKey("account.id")) account = db.relationship("Account") # apps = db.relationship("AppUser", back_populates="user") apps = association_proxy("user_apps", "app", creator=lambda app: AppUser(app=app)) stripe_id = db.Column(db.Text) last_4 = db.Column(db.Text) card_brand = db.Column(db.Text) top_up = db.Column(db.Integer, default=0, nullable=True) def owns(self, app_id): return id in [app.id for app in self.apps] @staticmethod def create(email, name, commit=True): account = Account() user = User(email=email, name=name, account=account) db.session.add(account) db.session.add(user) if commit: db.session.commit() return user def as_stripe_customer(self, customer, commit=True): # takes in a stripe customer object and sets fields of user accordingly if customer is not None: self.stripe_id = customer.stripe_id if len(customer.sources.data) > 0: self.last_4 = customer.sources.data[0].last4 self.card_brand = customer.sources.data[0].brand if commit: db.session.commit() return self def deposit(self, amount): if self.stripe_id is None: return None return self.account.deposit(amount, self.stripe_id) @staticmethod def create_for_email(email, name): if email is None or name is None: return None user = User.query.filter_by(email=email).first() if user is not None: raise ApiException("User with this email already exists") return User.create(email, name, commit=False) @staticmethod def for_token(decoded_token): """ Given a decoded firebase token with fields 'email' and 'name', provides the user matching that email if it exists, or creates a new user. """ email = decoded_token["email"] name = decoded_token["name"] if "name" in decoded_token.keys( ) else None if email is None: return None, False user = User.query.filter_by(email=email).first() if user is not None: return user, False return User.create(email, name) @staticmethod def for_account(account_id): return User.query.filter_by(account_id=account_id).first()
class App(BaseModel): account_id = db.Column(UUID, db.ForeignKey("account.id")) account = db.relationship("Account") # users = db.relationship("AppUser", back_populates="app") users = association_proxy( "app_users", "user", creator=lambda user: AppUser(user=user) ) payables = db.relationship("Payable", back_populates="app") secret = db.Column(db.Text, nullable=False) name = db.Column(db.Text, nullable=False) url = db.Column(db.Text, nullable=True) description = db.Column(db.Text, nullable=True) image_url = db.Column(db.Text, nullable=True) stripe_account_id = db.Column(db.Text, nullable=True) privateFields = ["secret", "stripe_account_id", "account_id"] @staticmethod def for_account(account_id): return App.query.filter_by(account_id=account_id).first() @staticmethod def generate_secret(): return "".join( random.choice(string.ascii_letters + string.digits) for _ in range(40) ) @staticmethod def create_for_user(user, name, image_url=None, url=None, description=None): account = Account() app = App( account=account, name=name, secret=App.generate_secret(), url=url, image_url=image_url, description=description, ) user.apps.append(app) db.session.add(account) db.session.add(app) db.session.commit() return app def app_info(self): d = self.to_dict() d["balance"] = self.account.balance return d @staticmethod def basic_info(id): # returns a publicly viewable dict containing basic info about app given an id app = App.query.get(id) return { "id": app.id, "name": app.name, "url": app.url, "description": app.description, "image_url": app.image_url, }
class Payment(BaseModel): source_account_id = db.Column(UUID, db.ForeignKey("account.id")) source_account = db.relationship("Account", foreign_keys=[source_account_id]) dest_account_id = db.Column(UUID, db.ForeignKey("account.id")) dest_account = db.relationship("Account", foreign_keys=[dest_account_id]) payable_id = db.Column(UUID, db.ForeignKey("payable.id")) payable = db.relationship("Payable") content_url = db.Column(db.String) amount = db.Column(db.Integer, nullable=False, default=0) def get_user_info(self): d = BaseModel.to_dict(self) d["app"] = App.for_account(self.dest_account_id).to_dict() d["user"] = User.for_account(self.source_account_id).to_dict() return d @staticmethod def payments_to(dest_id, page_size, page=1): return ( Payment.query.filter_by(dest_account_id=dest_id) .order_by(Payment.time_created.desc()) .paginate(page, page_size, error_out=False) ) @staticmethod def payments_from(source_id, page_size, page=1): return ( Payment.query.filter_by(source_account_id=source_id) .order_by(Payment.time_created.desc()) .paginate(page, page_size, error_out=False) ) @staticmethod def payments_summary_to(dest_id): return ( # Payment.query(Payment.time_created, func.sum(Payment.amount).label('total')) Payment.query.filter_by(dest_account_id=dest_id) # .group_by(func.day(Payment.time_created)) .order_by(Payment.time_created.desc()) .all() ) @staticmethod def transfer(user, app, price, count): balance = user.account.balance leftover = 0 amount = count * price if balance is None or balance < price: raise ApiException("Not enough funds") if balance < amount: # Get amount that can fit amount = (balance // price) * price # Check if there is a matching payment in the last 15 minutes since = datetime.now() - timedelta(minutes=15) existing = ( Payment.query.filter(Payment.time_created > since) .filter_by(source_account_id=user.account.id) .filter_by(dest_account_id=app.account.id) .order_by(Payment.time_created.desc()) .first() ) if existing is None: payment = Payment( source_account=user.account, dest_account=app.account, amount=amount ) db.session.add(payment) else: existing.amount += amount existing.time_created = datetime.now() payment = existing user.account.balance -= amount if app.account.balance is None: app.account.balance = 0 app.account.balance += amount db.session.commit() leftover = (price * count) - amount return payment, leftover