- Switch to integrated addresses (address+payment_id) -> `make_integrated_address @ RPC)

- Changed homegrown caching solution to use flask extension `flask_cache` instead
- Remove detection of out-going (sent) funds, needs to be manually registered in table "payouts" now
- Updated Flask version
remotes/1691605234125273493/master
dsc 4 years ago committed by dsc
parent c0b972edde
commit 401c26e85b

@ -5,7 +5,7 @@ from flask_yoloapi import endpoint, parameter
import settings
from funding.bin.utils import get_ip
from funding.bin.qr import QrCodeGenerator
from funding.factory import app, db_session
from funding.factory import app, db
from funding.orm.orm import Proposal, User

@ -34,8 +34,8 @@ class QrCodeGenerator:
:param color_to: gradient to color
:return:
"""
if len(address) != settings.COIN_ADDRESS_LENGTH:
raise Exception('faulty address length')
if len(address) not in settings.COIN_ADDRESS_LENGTH:
raise Exception(f'faulty address length, should be: {" or ".join(map(str, settings.COIN_ADDRESS_LENGTH))}')
if not dest:
dest = os.path.join(self.base, '%s.png' % address)

@ -8,6 +8,7 @@ from flask import g, request
from flask.json import JSONEncoder
import settings
from funding.factory import cache
def json_encoder(obj):
@ -18,62 +19,50 @@ def json_encoder(obj):
class Summary:
@staticmethod
@cache.cached(timeout=300, key_prefix="fetch_prices")
def fetch_prices():
if hasattr(g, 'funding_prices') and g.coin_prices:
return g.coin_prices
from funding.factory import cache
cache_key = 'funding_prices'
data = cache.get(cache_key)
if data:
return data
data = {
return {
'coin-btc': coin_btc_value(),
'btc-usd': price_cmc_btc_usd()
}
cache.set(cache_key, data=data, expiry=1200)
g.coin_prices = data
return data
@staticmethod
def fetch_stats(purge=False):
from funding.factory import db_session
@cache.cached(timeout=300, key_prefix="funding_stats")
def fetch_stats():
from funding.factory import db
from funding.orm.orm import Proposal, User, Comment
from funding.factory import cache
cache_key = 'funding_stats'
data = cache.get(cache_key)
if data and not purge:
return data
data = {}
categories = settings.FUNDING_CATEGORIES
statuses = settings.FUNDING_STATUSES.keys()
for cat in categories:
q = db_session.query(Proposal)
q = db.session.query(Proposal)
q = q.filter(Proposal.category == cat)
res = q.count()
data.setdefault('cats', {})
data['cats'][cat] = res
for status in statuses:
q = db_session.query(Proposal)
q = db.session.query(Proposal)
q = q.filter(Proposal.status == status)
res = q.count()
data.setdefault('statuses', {})
data['statuses'][status] = res
data.setdefault('users', {})
data['users']['count'] = db_session.query(User.id).count()
cache.set(cache_key, data=data, expiry=300)
data['users']['count'] = db.session.query(User.id).count()
return data
def price_cmc_btc_usd():
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
try:
print('request coinmarketcap')
r = requests.get('https://api.coinmarketcap.com/v2/ticker/1/?convert=USD', headers=headers)
r = requests.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd', headers=headers)
r.raise_for_status()
return r.json().get('data', {}).get('quotes', {}).get('USD', {}).get('price')
data = r.json()
btc = next(c for c in data if c['symbol'] == 'btc')
return btc['current_price']
except:
return
@ -81,7 +70,6 @@ def price_cmc_btc_usd():
def coin_btc_value():
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
try:
print('request TO')
r = requests.get('https://tradeogre.com/api/v1/ticker/BTC-WOW', headers=headers)
r.raise_for_status()
return float(r.json().get('high'))

@ -2,16 +2,16 @@ from datetime import datetime
from flask import session, g, request
import settings
from funding.bin.utils import Summary
from funding.factory import app, db_session
from funding.factory import app, db
from funding.orm.orm import Proposal, User, Comment
@app.context_processor
def templating():
from flask.ext.login import current_user
recent_comments = db_session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(8).all()
from flask_login import current_user
recent_comments = db.session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(8).all()
summary_data = Summary.fetch_stats()
newest_users = db_session.query(User).filter(User.admin == False).order_by(User.registered_on.desc()).limit(5).all()
newest_users = db.session.query(User).filter(User.admin == False).order_by(User.registered_on.desc()).limit(5).all()
return dict(logged_in=current_user.is_authenticated,
current_user=current_user,
funding_categories=settings.FUNDING_CATEGORIES,
@ -28,8 +28,6 @@ def before_request():
@app.after_request
def after_request(res):
if hasattr(g, 'funding_prices'):
delattr(g, 'funding_prices')
res.headers.add('Accept-Ranges', 'bytes')
if request.full_path.startswith('/api/'):
@ -45,7 +43,7 @@ def after_request(res):
@app.teardown_appcontext
def shutdown_session(**kwargs):
db_session.remove()
db.session.remove()
@app.errorhandler(404)

@ -45,17 +45,3 @@ class JsonRedis(RedisSessionInterface):
redis=redis.Redis(**redis_args()),
key_prefix=key_prefix,
use_signer=use_signer)
class WowCache:
def __init__(self):
self._cache = redis.StrictRedis(**redis_args())
def get(self, key):
try:
return json.loads(self._cache.get(key))
except:
return {}
def set(self, key: str, data: dict, expiry=300):
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)

@ -1,40 +1,79 @@
# -*- coding: utf-8 -*-
import settings
from werkzeug.contrib.fixers import ProxyFix
from flask import Flask
from flask_caching import Cache
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy
app = None
sentry = None
cache = None
db_session = None
db = None
bcrypt = None
def _setup_cache(app: Flask):
global cache
cache_config = {
"CACHE_TYPE": "redis",
"CACHE_DEFAULT_TIMEOUT": 60,
"CACHE_KEY_PREFIX": "wow_cache_"
}
app.config.from_mapping(cache_config)
cache = Cache(app)
def _setup_session(app: Flask):
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_COOKIE_NAME'] = 'bar'
Session(app) # defaults to timedelta(days=31)
def _setup_db(app: Flask):
global db
DB_URL = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format(
user=settings.PSQL_USER,
pw=settings.PSQL_PASS,
url=settings.PSQL_HOST,
db=settings.PSQL_DB)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
import funding.orm
with app.app_context():
db.create_all()
db.session.commit()
def create_app():
global app
global db_session
global sentry
global db
global cache
global bcrypt
from funding.orm.connect import create_session
db_session = create_session()
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
app = Flask(import_name=__name__,
static_folder='static',
template_folder='templates')
app.config.from_object(settings)
app.config['PERMANENT_SESSION_LIFETIME'] = 2678400
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 30
app.secret_key = settings.SECRET
_setup_cache(app)
_setup_session(app)
_setup_db(app)
# flask-login
from flask.ext.login import LoginManager
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
from flask.ext.bcrypt import Bcrypt
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
@login_manager.user_loader
@ -42,15 +81,10 @@ def create_app():
from funding.orm.orm import User
return User.query.get(int(_id))
# session init
from funding.cache import JsonRedis, WowCache
app.session_interface = JsonRedis(key_prefix=app.config['SESSION_PREFIX'], use_signer=False)
cache = WowCache()
# import routes
from funding import routes
from funding import api
from funding.bin import utils_request
app.app_context().push()
return app
return app

@ -1,18 +0,0 @@
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
import settings
def create_session():
from funding.orm.orm import base
engine = sa.create_engine(settings.SQLALCHEMY_DATABASE_URI, echo=False, encoding="latin")
session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
base.query = session.query_property()
base.metadata.create_all(bind=engine)
return session

@ -1,19 +1,29 @@
from datetime import datetime
import string
import random
import requests
from sqlalchemy.orm import relationship, backref
import sqlalchemy as sa
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import Float
from sqlalchemy_json import MutableJson
import settings
from funding.factory import db
base = declarative_base(name="Model")
class User(base):
class User(db.Model):
__tablename__ = "users"
id = sa.Column('user_id', sa.Integer, primary_key=True)
username = sa.Column(sa.String(20), unique=True, index=True)
password = sa.Column(sa.String(60))
email = sa.Column(sa.String(50), unique=True, index=True)
registered_on = sa.Column(sa.DateTime)
admin = sa.Column(sa.Boolean, default=False)
id = db.Column('user_id', db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, index=True)
password = db.Column(db.String(60))
email = db.Column(db.String(50), unique=True, index=True)
registered_on = db.Column(db.DateTime)
admin = db.Column(db.Boolean, default=False)
proposals = relationship('Proposal', back_populates="user")
comments = relationship("Comment", back_populates="user")
@ -48,7 +58,7 @@ class User(base):
@classmethod
def add(cls, username, password, email):
from funding.factory import db_session
from funding.factory import db
from funding.validation import val_username, val_email
try:
@ -57,37 +67,39 @@ class User(base):
val_email(email)
user = User(username, password, email)
db_session.add(user)
db_session.commit()
db_session.flush()
db.session.add(user)
db.session.commit()
db.session.flush()
return user
except Exception as ex:
db_session.rollback()
db.session.rollback()
raise
class Proposal(base):
class Proposal(db.Model):
__tablename__ = "proposals"
id = sa.Column(sa.Integer, primary_key=True)
headline = sa.Column(sa.VARCHAR, nullable=False)
content = sa.Column(sa.VARCHAR, nullable=False)
category = sa.Column(sa.VARCHAR, nullable=False)
date_added = sa.Column(sa.TIMESTAMP, default=datetime.now)
html = sa.Column(sa.VARCHAR)
last_edited = sa.Column(sa.TIMESTAMP)
id = db.Column(db.Integer, primary_key=True)
archived = db.Column(db.Boolean, default=False)
headline = db.Column(db.VARCHAR, nullable=False)
content = db.Column(db.VARCHAR, nullable=False)
category = db.Column(db.VARCHAR, nullable=False)
date_added = db.Column(db.TIMESTAMP, default=datetime.now)
html = db.Column(db.VARCHAR)
last_edited = db.Column(db.TIMESTAMP)
# the FFS target
funds_target = sa.Column(sa.Float, nullable=False)
funds_target = db.Column(db.Float, nullable=False)
# the FFS progress (cached)
funds_progress = sa.Column(sa.Float, nullable=False, default=0)
funds_progress = db.Column(db.Float, nullable=False, default=0)
# the FFS withdrawal amount (paid to the author)
funds_withdrew = sa.Column(sa.Float, nullable=False, default=0)
funds_withdrew = db.Column(db.Float, nullable=False, default=0)
# the FFS receiving and withdrawal addresses
addr_donation = sa.Column(sa.VARCHAR)
addr_receiving = sa.Column(sa.VARCHAR)
addr_donation = db.Column(db.VARCHAR)
addr_receiving = db.Column(db.VARCHAR)
payment_id = db.Column(db.VARCHAR)
# proposal status:
# 0: disabled
@ -95,9 +107,9 @@ class Proposal(base):
# 2: funding required
# 3: wip
# 4: completed
status = sa.Column(sa.INTEGER, default=1)
status = db.Column(db.INTEGER, default=1)
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'))
user = relationship("User", back_populates="proposals")
payouts = relationship("Payout", back_populates="proposal")
@ -131,17 +143,12 @@ class Proposal(base):
@classmethod
def find_by_id(cls, pid: int):
from funding.factory import db_session
from funding.factory import db
q = cls.query
q = q.filter(Proposal.id == pid)
result = q.first()
if not result:
return
# check if we have a valid addr_donation generated. if not, make one.
if not result.addr_donation and result.status == 2:
from funding.bin.daemon import Daemon
Proposal.generate_donation_addr(result)
return result
@property
@ -154,21 +161,21 @@ class Proposal(base):
@property
def comment_count(self):
from funding.factory import db_session
q = db_session.query(sa.func.count(Comment.id))
from funding.factory import db
q = db.session.query(db.func.count(Comment.id))
q = q.filter(Comment.proposal_id == self.id)
return q.scalar()
def get_comments(self):
from funding.factory import db_session
q = db_session.query(Comment)
from funding.factory import db
q = db.session.query(Comment)
q = q.filter(Comment.proposal_id == self.id)
q = q.filter(Comment.replied_to == None)
q = q.filter(Comment.replied_to.is_(None))
q = q.order_by(Comment.date_added.desc())
comments = q.all()
for c in comments:
q = db_session.query(Comment)
q = db.session.query(Comment)
q = q.filter(Comment.proposal_id == self.id)
q = q.filter(Comment.replied_to == c.id)
_c = q.all()
@ -177,6 +184,12 @@ class Proposal(base):
setattr(self, '_comments', comments)
return self
@property
def spends(self):
amount = sum([p.amount for p in self.payouts])
pct = amount / 100 * self.balance['sum']
return {"amount": amount, "pct": pct}
@property
def balance(self):
"""This property retrieves the current funding status
@ -184,28 +197,49 @@ class Proposal(base):
daemon too much. Returns a nice dictionary containing
all relevant proposal funding info"""
from funding.bin.utils import Summary, coin_to_usd
from funding.factory import cache, db_session
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
from funding.factory import cache, db
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0, 'available': 0}
cache_key = 'coin_balance_pid_%d' % self.id
data = cache.get(cache_key)
if not data:
from funding.bin.daemon import Daemon
try:
data = Daemon().get_transfers_in(proposal=self)
if not isinstance(data, dict):
print('error; get_transfers_in; %d' % self.id)
return rtn
cache.set(cache_key, data=data, expiry=60)
except Exception as ex:
print('error; get_transfers_in; %d' % self.id)
return rtn
if self.archived:
return rtn
try:
r = requests.get(f'http://{settings.RPC_HOST}:{settings.RPC_PORT}/json_rpc', json={
"jsonrpc": "2.0",
"id": "0",
"method": "get_payments",
"params": {
"payment_id": self.payment_id
}
})
r.raise_for_status()
blob = r.json()
assert 'result' in blob
assert 'payments' in blob['result']
assert isinstance(blob['result']['payments'], list)
except Exception as ex:
return rtn
txs = blob['result']['payments']
for tx in txs:
tx['amount_human'] = float(tx['amount'])/1e11
tx['txid'] = tx['tx_hash']
tx['type'] = 'in'
data = {
'sum': sum([float(z['amount']) / 1e11 for z in txs]),
'txs': txs
}
if not isinstance(data, dict):
print('error; get_transfers_in; %d' % self.id)
return rtn
prices = Summary.fetch_prices()
for tx in data['txs']:
if prices:
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
if data.get('sum', 0.0):
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
@ -216,8 +250,8 @@ class Proposal(base):
if data['pct'] != self.funds_progress:
self.funds_progress = data['pct']
db_session.commit()
db_session.flush()
db.session.commit()
db.session.flush()
if data['available']:
data['remaining_pct'] = 100 / float(data['sum'] / data['available'])
@ -226,74 +260,9 @@ class Proposal(base):
return data
@property
def spends(self):
from funding.bin.utils import Summary, coin_to_usd
from funding.factory import cache, db_session
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
cache_key = 'coin_spends_pid_%d' % self.id
data = cache.get(cache_key)
if not data:
from funding.bin.daemon import Daemon
try:
data = Daemon().get_transfers_out(proposal=self)
if not isinstance(data, dict):
print('error; get_transfers_out; %d' % self.id)
return rtn
cache.set(cache_key, data=data, expiry=60)
except:
print('error; get_transfers_out; %d' % self.id)
return rtn
data['remaining_pct'] = 0.0
prices = Summary.fetch_prices()
for tx in data['txs']:
if prices:
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
if data.get('sum', 0.0):
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
data['spent'] = data['sum']
else:
data['pct'] = 0.0
data['spent'] = 0.0
cache_key_in = 'coin_balance_pid_%d' % self.id
data_in = cache.get(cache_key_in)
if data_in and data['spent']:
data['remaining_pct'] = 100 / float(data_in['sum'] / data['spent'])
return data
@staticmethod
def generate_donation_addr(cls):
from funding.factory import db_session
from funding.bin.daemon import Daemon
if cls.addr_donation:
return cls.addr_donation
# check if the current user has an account in the wallet
account = Daemon().get_accounts(cls.id)
if not account:
account = Daemon().create_account(cls.id)
index = account['account_index']
address = account.get('address') or account.get('base_address')
if not address:
raise Exception('Cannot generate account/address for pid %d' % cls.id)
# assign donation address, commit to db
cls.addr_donation = address
db_session.commit()
db_session.flush()
return address
@classmethod
def find_by_args(cls, status: int = None, cat: str = None, limit: int = 20, offset=0):
from funding.factory import db_session
from funding.factory import db
if isinstance(status, int) and status not in settings.FUNDING_STATUSES.keys():
raise NotImplementedError('invalid status')
if isinstance(cat, str) and cat not in settings.FUNDING_CATEGORIES:
@ -313,71 +282,82 @@ class Proposal(base):
@classmethod
def search(cls, key: str):
key_ilike = '%' + key.replace('%', '') + '%'
key_ilike = f"%{key.replace('%', '')}%"
q = Proposal.query
q = q.filter(sa.or_(
q = q.filter(db.or_(
Proposal.headline.ilike(key_ilike),
Proposal.content.ilike(key_ilike)))
return q.all()
class Payout(base):
class Payout(db.Model):
__tablename__ = "payouts"
id = sa.Column(sa.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
proposal_id = sa.Column(sa.Integer, sa.ForeignKey('proposals.id'))
proposal_id = db.Column(db.Integer, db.ForeignKey('proposals.id'))
proposal = relationship("Proposal", back_populates="payouts")
amount = sa.Column(sa.Integer, nullable=False)
to_address = sa.Column(sa.VARCHAR, nullable=False)
amount = db.Column(db.Integer, nullable=False)
to_address = db.Column(db.VARCHAR, nullable=False)
date_sent = db.Column(db.TIMESTAMP, default=datetime.now)
ix_proposal_id = sa.Index("ix_proposal_id", proposal_id)
ix_proposal_id = db.Index("ix_proposal_id", proposal_id)
@classmethod
def add(cls, proposal_id, amount, to_address):
# @TODO: validate that we can make this payout; check previous payouts
from flask.ext.login import current_user
from flask_login import current_user
if not current_user.admin:
raise Exception("user must be admin to add a payout")
from funding.factory import db_session
from funding.factory import db
try:
payout = Payout(propsal_id=proposal_id, amount=amount, to_address=to_address)
db_session.add(payout)
db_session.commit()
db_session.flush()
db.session.add(payout)
db.session.commit()
db.session.flush()
return payout
except Exception as ex:
db_session.rollback()
db.session.rollback()
raise
@staticmethod
def get_payouts(proposal_id):
from funding.factory import db_session
return db_session.query(Payout).filter(Payout.proposal_id == proposal_id).all()
from funding.factory import db
return db.session.query(Payout).filter(Payout.proposal_id == proposal_id).all()
@property
def as_tx(self):
return {
"block_height": "-",
"type": "out",
"amount_human": self.amount,
"amount": self.amount
}
class Comment(base):
class Comment(db.Model):
__tablename__ = "comments"
id = sa.Column(sa.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
proposal_id = sa.Column(sa.Integer, sa.ForeignKey('proposals.id'))
proposal_id = db.Column(db.Integer, db.ForeignKey('proposals.id'))
proposal = relationship("Proposal", back_populates="comments")
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), nullable=False)
user = relationship("User", back_populates="comments")
date_added = sa.Column(sa.TIMESTAMP, default=datetime.now)
date_added = db.Column(db.TIMESTAMP, default=datetime.now)
message = sa.Column(sa.VARCHAR, nullable=False)
replied_to = sa.Column(sa.ForeignKey("comments.id"))
message = db.Column(db.VARCHAR, nullable=False)
replied_to = db.Column(db.ForeignKey("comments.id"))
locked = sa.Column(sa.Boolean, default=False)
locked = db.Column(db.Boolean, default=False)
automated = sa.Column(sa.Boolean, default=False)
automated = db.Column(db.Boolean, default=False)
ix_comment_replied_to = sa.Index("ix_comment_replied_to", replied_to)
ix_comment_proposal_id = sa.Index("ix_comment_proposal_id", proposal_id)
ix_comment_replied_to = db.Index("ix_comment_replied_to", replied_to)
ix_comment_proposal_id = db.Index("ix_comment_proposal_id", proposal_id)
@property
def message_html(self):
@ -390,28 +370,30 @@ class Comment(base):
@staticmethod
def find_by_id(cid: int):
from funding.factory import db_session
return db_session.query(Comment).filter(Comment.id == cid).first()
from funding.factory import db
return db.session.query(Comment).filter(Comment.id == cid).first()
@staticmethod
def remove(cid: int):
from funding.factory import db_session
from flask.ext.login import current_user
if current_user.id != user_id and not current_user.admin:
raise Exception("no rights to remove this comment")
from funding.factory import db
from flask_login import current_user
comment = Comment.get(cid=cid)
if current_user.id != comment.user_id and not current_user.admin:
raise Exception("no rights to remove this comment")
try:
comment.delete()
db_session.commit()
db_session.flush()
db.session.commit()
db.session.flush()
except:
db_session.rollback()
db.session.rollback()
raise
@staticmethod
def lock(cid: int):
from funding.factory import db_session
from flask.ext.login import current_user
from funding.factory import db
from flask_login import current_user
if not current_user.admin:
raise Exception("admin required")
comment = Comment.find_by_id(cid=cid)
@ -419,17 +401,17 @@ class Comment(base):
raise Exception("comment by that id not found")
comment.locked = True
try:
db_session.commit()
db_session.flush()
db.session.commit()
db.session.flush()
return comment
except:
db_session.rollback()
db.session.rollback()
raise
@classmethod
def add_comment(cls, pid: int, user_id: int, message: str, cid: int = None, message_id: int = None, automated=False):
from flask.ext.login import current_user
from funding.factory import db_session
from flask_login import current_user
from funding.factory import db
if not message:
raise Exception("empty message")
@ -448,7 +430,7 @@ class Comment(base):
comment.replied_to = parent.id
else:
try:
user = db_session.query(User).filter(User.id == user_id).first()
user = db.session.query(User).filter(User.id == user_id).first()
if not user:
raise Exception("no user by that id")
comment = next(c for c in user.comments if c.id == message_id)
@ -460,10 +442,10 @@ class Comment(base):
raise Exception("unknown error")
try:
comment.message = message
db_session.add(comment)
db_session.commit()
db_session.flush()
db.session.add(comment)
db.session.commit()
db.session.flush()
except Exception as ex:
db_session.rollback()
db.session.rollback()
raise Exception(str(ex))
return comment
return comment

@ -1,12 +1,14 @@
from datetime import datetime
import requests
from flask import request, redirect, Response, abort, render_template, url_for, flash, make_response, send_from_directory, jsonify
from flask.ext.login import login_user , logout_user , current_user, login_required, current_user
from flask_login import login_user , logout_user , current_user, login_required, current_user
from dateutil.parser import parse as dateutil_parse
from flask_yoloapi import endpoint, parameter
import settings
from funding.factory import app, db_session
from funding.bin.utils import Summary
from funding.factory import app, db, cache
from funding.orm.orm import Proposal, User, Comment
@ -27,7 +29,7 @@ def api():
@app.route('/proposal/add/disclaimer')
def proposal_add_disclaimer():
return make_response(render_template(('proposal/disclaimer.html')))
return make_response(render_template('proposal/disclaimer.html'))
@app.route('/proposal/add')
@ -79,9 +81,9 @@ def propsal_comment_reply(cid, pid):
@app.route('/proposal/<int:pid>')
def proposal(pid):
p = Proposal.find_by_id(pid=pid)
p.get_comments()
if not p:
return make_response(redirect(url_for('proposals')))
p.get_comments()
return make_response(render_template(('proposal/proposal.html'), proposal=p))
@ -155,9 +157,9 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
except Exception as ex:
return make_response(jsonify('letters detected'),500)
if funds_target < 1:
return make_response(jsonify('Proposal asking less than 1 error :)'), 500)
if len(addr_receiving) != settings.COIN_ADDRESS_LENGTH:
return make_response(jsonify('Faulty address, should be of length 72'), 500)
return make_response(jsonify('Proposal asking less than 1 error :)'), 500)
if len(addr_receiving) not in settings.COIN_ADDRESS_LENGTH:
return make_response(jsonify(f'Faulty address, should be of length: {" or ".join(map(str, settings.COIN_ADDRESS_LENGTH))}'), 500)
p = Proposal(headline=title, content=content, category='misc', user=current_user)
p.html = html
@ -167,18 +169,32 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
p.category = category
p.status = status
db_session.add(p)
db_session.commit()
db_session.flush()
# generate integrated address
try:
r = requests.get(f'http://{settings.RPC_HOST}:{settings.RPC_PORT}/json_rpc', json={
"jsonrpc": "2.0",
"id": "0",
"method": "make_integrated_address"
})
r.raise_for_status()
blob = r.json()
assert 'result' in blob
assert 'integrated_address' in blob['result']
assert 'payment_id' in blob['result']
except Exception as ex:
raise
p.addr_donation = Proposal.generate_donation_addr(p)
p.addr_donation = blob['result']['integrated_address']
p.payment_id = blob['result']['payment_id']
db_session.commit()
db_session.flush()
db.session.add(p)
# reset cached statistics
from funding.bin.utils import Summary
Summary.fetch_stats(purge=True)
db.session.commit()
db.session.flush()
# reset cached stuffz
cache.delete('funding_stats')
return make_response(jsonify({'url': url_for('proposal', pid=p.id)}))
@ -189,7 +205,7 @@ def proposal_edit(pid):
if not p:
return make_response(redirect(url_for('proposals')))
return make_response(render_template(('proposal/edit.html'), proposal=p))
return make_response(render_template('proposal/edit.html', proposal=p))
@app.route('/search')
@ -205,7 +221,7 @@ def search(key=None):
@app.route('/user/<path:name>')
def user(name):
q = db_session.query(User)
q = db.session.query(User)
q = q.filter(User.username == name)
user = q.first()
return render_template('user.html', user=user)
@ -241,7 +257,9 @@ def proposals(status, page, cat):
@app.route('/donate')
def donate():
from funding.bin.daemon import Daemon
from funding.factory import cache, db_session
from funding.factory import cache, db
return "devfund page currently not working :D"
data_default = {'sum': 0, 'txs': []}
cache_key = 'devfund_txs_in'
@ -280,6 +298,7 @@ def register():
try:
user = User.add(username, password, email)
flash('Successfully registered. No confirmation email required. You can login!')
cache.delete('funding_stats') # reset cached stuffz
return redirect(url_for('login'))
except Exception as ex:
flash('Could not register user. Probably a duplicate username or email that already exists.', 'error')
@ -318,4 +337,4 @@ def logout():
@app.route('/static/<path:path>')
def static_route(path):
return send_from_directory('static', path)
return send_from_directory('static', path)

@ -1,5 +1,5 @@
from flask.ext.login import login_required
from wowfunding.factory import app, db_session
from flask_login import login_required
from funding.factory import app, db
@app.route('/admin/index')

@ -650,10 +650,6 @@ ul.b {
color: #999;
}
.tx_item .height {
float:right
}
.tx_item .height b {
font-size:14px;
}

@ -26,7 +26,7 @@
When you encounter problems; please visit #wownero on chat.freenode.org
</p>
<p>
AEON (<a target="_blank" href="https://github.com/camthegeek">camthegeek</a>) has contributed commits to <a href="http://github.com/skftn/wownero-wfs">upstream</a>; WOW is grateful.
AEON (<a target="_blank" href="https://github.com/camthegeek">camthegeek</a>) has contributed commits <a href="http://github.com/skftn/wownero-wfs">upstream</a>; WOW is grateful.
</p>
</div>

@ -64,7 +64,7 @@
<!-- Footer -->
<footer class="bg-dark footer">
<div class="container">
<p class="m-0 text-center text-white">WOW 2018</p>
<p class="m-0 text-center text-white">WOW 2018-2020</p>
</div>
</footer>

@ -27,7 +27,7 @@
</div>
<hr>
<div class="row">
<div class="col-lg-3">
<div class="col-lg-4">
<form class="form-horizontal" action="" method=post>
<div class="form-group">
<label class="sr-only" for="inlineFormInput">Name</label>
@ -46,10 +46,13 @@
<div class="form-group">
<button type="submit" class="btn btn-primary btn-sm">Login</button>
</div>
<div class="form-group">
<small>Forgot your password? Out of luck! Create a new account lol</small>
</div>
</form>
</div>
<div class="col-lg-5">
<a href="/register">Or register here</a>
<a href="/register">Register here</a>
</div>
</div>
</div>

@ -1,20 +1,28 @@
{% macro tx_item(tx) %}
<li class="list-group-item tx_item">
<span class="datetime">
{{tx['datetime'].strftime('%Y-%m-%d %H:%M')}}
</span>
<span class="height">
<b>Blockheight</b>:
{% if tx['type'] == 'pool' %}
soon^tm
{% else %}
{{tx['height']}}
{% endif %}
{% if tx['type'] == 'pool' %}
soon^tm
{% elif tx['type'] == 'out' %}
<small>hidden</small>
{% else %}
{{tx['block_height']}}
{% endif %}
</span>
<br>
{% if tx['type'] in ['in', 'pool'] %}
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
{% else %}
{% set lulz = [
'vodka', 'hookers', 'booze', 'strippers', 'new lambo',
'new ferrari', 'new villa', 'new vacation home', 'new tesla',
'new watch', 'new home cinema set', 'throphy wife', 'drugs']
%}
<a style="font-size:11px;" href="#">Sent to author. Enjoy the {{ lulz|random }}!</a>
{% endif %}
<span class="amount{% if tx['type'] in ['pool', 'in'] %} in{% endif %}">
{% if tx['type'] in ['in', 'pool'] %}
+

@ -97,11 +97,13 @@
{{proposal.balance['available']|round(3) or 0 }} WOW Raised
{% set remaining = proposal.funds_target - proposal.balance['available']|float|round(3) %}
<small>
{% if remaining > 0 %}
({{ (proposal.funds_target - proposal.balance['available']|float|round(3)|int) }} WOW until goal)
{% elif remaining < 0 %}
({{ (proposal.balance['available']-proposal.funds_target|float|round(3)|int) }} WOW past goal!)
{% endif %}
</small>
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['pct']}}%;">
@ -113,15 +115,16 @@
<br/>
<div class="col-lg-8">
{{proposal.spends['spent']|round(3) or 0}} WOW Paid out
{{ proposal.spends['amount'] }} WOW Paid out <small>({{ proposal.spends['pct']|round(1) }}%)</small>
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.spends['remaining_pct']}}%;">
<!-- @todo: spends remaining pct -->
<div class="progress-bar progress-warning progress-bar" style="width: {{ proposal.spends['pct'] }}%;">
</div>
</div>
<hr>
</div>
<div class="col-lg-8">
{{(proposal.balance['available']-proposal.spends['spent']) |round(3) or 0}} WOW Available to Payout
{{ (proposal.balance['available']-proposal.spends['amount']) |round(3) or 0}} WOW Available for payout :-)
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['remaining_pct']}}%;">
</div>
@ -185,15 +188,15 @@
<!-- /.row -->
{% endif %}
{% if proposal.spends['txs'] %}
{% if proposal.payouts %}
<div class="row">
<div class="col-md-12">
<div class="card my-6" id="incoming_txs">
<h5 class="card-header">Outgoing transactions <small>({{proposal.spends['txs']|length}})</small></h5>
<h5 class="card-header">Outgoing transactions</h5>
<div class="card-body">
<ul class="list-group">
{% for tx in proposal.spends['txs'] %}
{{ tx_item(tx) }}
{% for payout in proposal.payouts %}
{{ tx_item(payout.as_tx) }}
{% endfor %}
</ul>
</div>

@ -1,5 +1,5 @@
sqlalchemy==1.3.4
flask==0.12.3
flask
flask-yoloapi==0.1.5
flask_session
flask-login
@ -12,3 +12,6 @@ requests
pyqrcode
pypng
pillow-simd
Flask-Caching
flask-sqlalchemy
sqlalchemy_json

@ -7,11 +7,12 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SECRET = ''
DEBUG = True
COIN_ADDRESS_LENGTH = 97
COIN_ADDRESS_LENGTH = [97, 108]
COINCODE = ''
PSQL_USER = ''
PSQL_PASS = ''
PSQL_HOST = "127.0.0.1:5432"
PSQL_DB = ''
PSQL_USER = 'postgres'
PSQL_PASS = ''
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)

Loading…
Cancel
Save