aeonfunding update for wownero

a lot more generic; a lot more configurable; UI goodness with accounting; wallet account support for ezmode
This commit is contained in:
CamTheGeek 2018-09-05 18:27:26 -04:00
parent f473a4234e
commit 955de2544e
19 changed files with 361 additions and 173 deletions

View file

@ -2,11 +2,9 @@ from datetime import datetime
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_yoloapi import endpoint, parameter
import settings
from wowfunding.factory import app, db_session
from wowfunding.orm.orm import Proposal, User
from funding.factory import app, db_session
from funding.orm.orm import Proposal, User
@app.route('/api/1/proposals')
@endpoint.api(
@ -21,15 +19,13 @@ def api_proposals_get(status, cat, limit, offset):
except Exception as ex:
print(ex)
return 'error', 500
return [p.json for p in proposals]
@app.route('/api/1/convert/wow-usd')
@endpoint.api(
parameter('wow', type=int, location='args', required=True)
parameter('amount', type=int, location='args', required=True)
)
def api_wow_usd(wow):
from wowfunding.bin.utils import Summary, wow_to_usd
def api_coin_usd(amount):
from funding.bin.utils import Summary, coin_to_usd
prices = Summary.fetch_prices()
return jsonify(usd=wow_to_usd(wows=wow, btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd']))
return jsonify(usd=coin_to_usd(amt=amount, btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd']))

View file

@ -207,4 +207,4 @@ def such_xss(inp):
line = '>%s' % line
_lines.append(line)
return "\n".join(_lines)
return "\n".join(_lines)

View file

@ -1,10 +1,12 @@
import settings
import requests
from requests.auth import HTTPDigestAuth
class WowneroDaemon:
class Daemon:
def __init__(self):
self.url = settings.RPC_LOCATION
self.username = settings.RPC_USERNAME
self.password = settings.RPC_PASSWORD
self.headers = {"User-Agent": "Mozilla"}
def create_address(self, label_name):
@ -16,37 +18,65 @@ class WowneroDaemon:
}
return self._make_request(data)
def get_address(self, index):
def create_account(self, pid):
data = {
'method': 'get_address',
'params': {'address_index': [index], 'account_index': 0},
'method': 'create_account',
'params': {'label': '%s' % pid},
'jsonrpc': '2.0',
'id': '0'
}
return self._make_request(data)
def get_address(self, index, proposal_id):
data = {
'method': 'getaddress',
'params': {'account_index': proposal_id, 'address_index': '[0]'},
'jsonrpc': '2.0',
'id': '0'
}
try:
result = self._make_request(data)
return next(z for z in result['result']['addresses'] if z['address_index'] == index)
return result['result']
except:
return
def get_transfers_in(self, index):
def get_transfers_in(self, index, proposal_id):
data = {
"method":"get_transfers",
"params": {"pool": True, "in": True, "account_index": 0, "subaddr_indices": [index]},
"params": {"pool": True, "in": True, "account_index": proposal_id},
"jsonrpc": "2.0",
"id": "0",
}
data = self._make_request(data)
data = data['result'].get('in', [])
for d in data:
d['amount_human'] = float(d['amount'])/1e11
d['amount_human'] = float(d['amount'])/1e12
return {
'sum': sum([float(z['amount'])/1e11 for z in data]),
'sum': sum([float(z['amount'])/1e12 for z in data]),
'txs': data
}
def get_transfers_out(self, index, proposal_id):
data = {
"method":"get_transfers",
"params": {"pool": True, "out": True, "account_index": proposal_id},
"jsonrpc": "2.0",
"id": "0",
}
data = self._make_request(data)
data = data['result'].get('out', [])
for d in data:
d['amount_human'] = float(d['amount'])/1e12
return {
'sum': sum([float(z['amount'])/1e12 for z in data]),
'txs': data
}
def _make_request(self, data):
r = requests.post(self.url, json=data, headers=self.headers)
if self.username:
if self.password:
r = requests.post(self.url, auth=HTTPDigestAuth(settings.RPC_USERNAME, settings.RPC_PASSWORD), json=data, headers=self.headers)
else:
r = requests.post(self.url, json=data, headers=self.headers)
r.raise_for_status()
return r.json()
return r.json()

View file

@ -1,44 +1,39 @@
from datetime import datetime, date
import requests
from flask import g
from flask.json import JSONEncoder
import json
import settings
def json_encoder(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError ("Type %s not serializable" % type(obj))
class Summary:
@staticmethod
def fetch_prices():
if hasattr(g, 'wowfunding_prices') and g.wow_prices:
return g.wow_prices
from wowfunding.factory import cache
cache_key = 'wowfunding_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 = {
'wow-btc': price_tradeogre_wow_btc(),
'coin-btc': coin_btc_value(),
'btc-usd': price_cmc_btc_usd()
}
cache.set(cache_key, data=data, expiry=7200)
g.wow_prices = data
g.coin_prices = data
return data
@staticmethod
def fetch_stats(purge=False):
from wowfunding.factory import db_session
from wowfunding.orm.orm import Proposal, User, Comment
from wowfunding.factory import cache
cache_key = 'wowfunding_stats'
from funding.factory import db_session
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
@ -65,7 +60,6 @@ class Summary:
cache.set(cache_key, data=data, expiry=300)
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:
@ -75,8 +69,7 @@ def price_cmc_btc_usd():
except:
return
def price_tradeogre_wow_btc():
def coin_btc_value():
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
try:
r = requests.get('https://tradeogre.com/api/v1/ticker/BTC-WOW', headers=headers)
@ -85,9 +78,8 @@ def price_tradeogre_wow_btc():
except:
return
def wow_to_usd(wows: float, usd_per_btc: float, btc_per_wow: float):
def coin_to_usd(amt: float, usd_per_btc: float, btc_per_coin: float):
try:
return round(usd_per_btc / (1.0 / (wows * btc_per_wow)), 2)
return round(usd_per_btc / (1.0 / (amt * btc_per_coin)), 2)
except:
pass

View file

@ -1,34 +1,32 @@
from datetime import datetime
from flask import session, g
import settings
from wowfunding.bin.utils import Summary
from wowfunding.factory import app, db_session
from wowfunding.orm.orm import Proposal, User, Comment
from funding.bin.utils import Summary
from funding.factory import app, db_session
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(10).all()
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()
return dict(logged_in=current_user.is_authenticated,
current_user=current_user,
funding_categories=settings.FUNDING_CATEGORIES,
funding_statuses=settings.FUNDING_STATUSES,
summary_data=summary_data,
recent_comments=recent_comments)
recent_comments=recent_comments,
newest_users=newest_users)
@app.before_request
def before_request():
pass
@app.after_request
def after_request(res):
if hasattr(g, 'wowfunding_prices'):
delattr(g, 'wowfunding_prices')
if hasattr(g, 'funding_prices'):
delattr(g, 'funding_prices')
res.headers.add('Accept-Ranges', 'bytes')
if settings.DEBUG:
res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
@ -37,12 +35,10 @@ def after_request(res):
res.headers['Cache-Control'] = 'public, max-age=0'
return res
@app.teardown_appcontext
def shutdown_session(**kwargs):
db_session.remove()
@app.errorhandler(404)
def error(err):
return 'Error', 404
return 'Error', 404

View file

@ -4,7 +4,7 @@ import redis
from flask_session import RedisSessionInterface
import settings
from wowfunding.bin.utils import json_encoder
from funding.bin.utils import json_encoder
def redis_args():
@ -58,4 +58,4 @@ class WowCache:
return {}
def set(self, key: str, data: dict, expiry = 300):
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)

View file

@ -17,7 +17,7 @@ def create_app():
global cache
global bcrypt
from wowfunding.orm.connect import create_session
from funding.orm.connect import create_session
db_session = create_session()
app = Flask(__name__)
@ -39,18 +39,18 @@ def create_app():
@login_manager.user_loader
def load_user(_id):
from wowfunding.orm.orm import User
from funding.orm.orm import User
return User.query.get(int(_id))
# session init
from wowfunding.cache import JsonRedis, WowCache
from funding.cache import JsonRedis, WowCache
app.session_interface = JsonRedis(key_prefix=app.config['SESSION_PREFIX'], use_signer=False)
cache = WowCache()
# import routes
from wowfunding import routes
from wowfunding import api
from wowfunding.bin import utils_request
from funding import routes
from funding import api
from funding.bin import utils_request
app.app_context().push()
return app
return app

View file

@ -8,11 +8,11 @@ import settings
def create_session():
from wowfunding.orm.orm import base
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
return session

View file

@ -1,5 +1,4 @@
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
@ -7,7 +6,6 @@ import settings
base = declarative_base(name="Model")
class User(base):
__tablename__ = "users"
id = sa.Column('user_id', sa.Integer, primary_key=True)
@ -17,11 +15,10 @@ class User(base):
registered_on = sa.Column(sa.DateTime)
admin = sa.Column(sa.Boolean, default=False)
proposals = relationship('Proposal', back_populates="user")
comments = relationship("Comment", back_populates="user")
def __init__(self, username, password, email):
from wowfunding.factory import bcrypt
from funding.factory import bcrypt
self.username = username
self.password = bcrypt.generate_password_hash(password).decode('utf8')
self.email = email
@ -47,12 +44,12 @@ class User(base):
return self.id
def __repr__(self):
return '<User %r>' % self.username
return self.username
@classmethod
def add(cls, username, password, email):
from wowfunding.factory import db_session
from wowfunding.validation import val_username, val_email
from funding.factory import db_session
from funding.validation import val_username, val_email
try:
# validate incoming username/email
@ -134,7 +131,7 @@ class Proposal(base):
@classmethod
def find_by_id(cls, pid: int):
from wowfunding.factory import db_session
from funding.factory import db_session
q = cls.query
q = q.filter(Proposal.id == pid)
result = q.first()
@ -149,21 +146,21 @@ class Proposal(base):
@property
def funds_target_usd(self):
from wowfunding.bin.utils import Summary, wow_to_usd
from funding.bin.utils import Summary, coin_to_usd
prices = Summary.fetch_prices()
if not prices:
return
return wow_to_usd(wows=self.funds_target, btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd'])
return coin_to_usd(amt=self.funds_target, btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
@property
def comment_count(self):
from wowfunding.factory import db_session
from funding.factory import db_session
q = db_session.query(sa.func.count(Comment.id))
q = q.filter(Comment.proposal_id == self.id)
return q.scalar()
def get_comments(self):
from wowfunding.factory import db_session
from funding.factory import db_session
q = db_session.query(Comment)
q = q.filter(Comment.proposal_id == self.id)
q = q.filter(Comment.replied_to == None)
@ -184,46 +181,86 @@ class Proposal(base):
def balance(self):
"""This property retrieves the current funding status
of this proposal. It uses Redis cache to not spam the
wownerod too much. Returns a nice dictionary containing
daemon too much. Returns a nice dictionary containing
all relevant proposal funding info"""
from wowfunding.bin.utils import Summary, wow_to_usd
from wowfunding.factory import cache, db_session
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 = 'wow_balance_pid_%d' % self.id
cache_key = 'coin_balance_pid_%d' % self.id
data = cache.get(cache_key)
if not data:
from wowfunding.bin.daemon import WowneroDaemon
from funding.bin.daemon import Daemon
try:
data = WowneroDaemon().get_transfers_in(index=self.id)
data = Daemon().get_transfers_in(index=self.id, proposal_id=self.id)
if not isinstance(data, dict):
print('error; get_transfers; %d' % self.id)
print('error; get_transfers_in; %d' % self.id)
return rtn
cache.set(cache_key, data=data, expiry=300)
except:
print('error; get_transfers; %d' % self.id)
print('error; get_transfers_in; %d' % self.id)
return rtn
prices = Summary.fetch_prices()
for tx in data['txs']:
if prices:
tx['amount_usd'] = wow_to_usd(wows=tx['amount_human'], btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd'])
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['remaining'] = data['sum'] - self.funds_withdrew
data['available'] = data['sum']
else:
data['pct'] = 0.0
data['remaining'] = 0.0
data['available'] = 0.0
if data['pct'] != self.funds_progress:
self.funds_progress = data['pct']
db_session.commit()
db_session.flush()
if data['remaining']:
data['remaining_pct'] = 100 / float(data['sum'] / data['remaining'])
if data['available']:
data['remaining_pct'] = 100 / float(data['sum'] / data['available'])
else:
data['remaining_pct'] = 0.0
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(index=self.id, proposal_id=self.id)
if not isinstance(data, dict):
print('error; get_transfers_out; %d' % self.id)
return rtn
cache.set(cache_key, data=data, expiry=300)
except:
print('error; get_transfers_out; %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))
data['spent'] = data['sum']
else:
data['pct'] = 0.0
data['spent'] = 0.0
if data['spent']:
data['remaining_pct'] = 100 / float(data['sum'] / data['spent'])
else:
data['remaining_pct'] = 0.0
@ -231,13 +268,13 @@ class Proposal(base):
@staticmethod
def generate_donation_addr(cls):
from wowfunding.factory import db_session
from wowfunding.bin.daemon import WowneroDaemon
from funding.factory import db_session
from funding.bin.daemon import Daemon
if cls.addr_donation:
return cls.addr_donation
try:
addr_donation = WowneroDaemon().get_address(index=cls.id)
addr_donation = Daemon().get_address(index=cls.id, proposal_id=cls.id)
if not isinstance(addr_donation, dict):
raise Exception('get_address, needs dict; %d' % cls.id)
except Exception as ex:
@ -250,9 +287,19 @@ class Proposal(base):
db_session.flush()
return addr_donation['address']
@staticmethod
def generate_proposal_subaccount(pid):
from funding.bin.daemon import Daemon
try:
Daemon().create_account(pid)
except Exception as ex:
print('error: %s' % str(ex))
return
@classmethod
def find_by_args(cls, status: int = None, cat: str = None, limit: int = 20, offset=0):
from wowfunding.factory import db_session
from funding.factory import db_session
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:
@ -298,7 +345,7 @@ class Payout(base):
from flask.ext.login import current_user
if not current_user.admin:
raise Exception("user must be admin to add a payout")
from wowfunding.factory import db_session
from funding.factory import db_session
try:
payout = Payout(propsal_id=proposal_id, amount=amount, to_address=to_address)
@ -339,17 +386,17 @@ class Comment(base):
@property
def ago(self):
from wowfunding.bin.utils_time import TimeMagic
from funding.bin.utils_time import TimeMagic
return TimeMagic().ago(self.date_added)
@staticmethod
def find_by_id(cid: int):
from wowfunding.factory import db_session
from funding.factory import db_session
return db_session.query(Comment).filter(Comment.id == cid).first()
@staticmethod
def remove(cid: int):
from wowfunding.factory import db_session
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")
@ -364,7 +411,7 @@ class Comment(base):
@staticmethod
def lock(cid: int):
from wowfunding.factory import db_session
from funding.factory import db_session
from flask.ext.login import current_user
if not current_user.admin:
raise Exception("admin required")
@ -383,7 +430,7 @@ class Comment(base):
@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 wowfunding.factory import db_session
from funding.factory import db_session
if not message:
raise Exception("empty message")
@ -420,4 +467,4 @@ class Comment(base):
except Exception as ex:
db_session.rollback()
raise Exception(str(ex))
return comment
return comment

View file

@ -4,8 +4,8 @@ from flask.ext.login import login_user , logout_user , current_user, login_requi
from flask_yoloapi import endpoint, parameter
import settings
from wowfunding.factory import app, db_session
from wowfunding.orm.orm import Proposal, User, Comment
from funding.factory import app, db_session
from funding.orm.orm import Proposal, User, Comment
@app.route('/')
@ -61,7 +61,7 @@ def proposal_comment(pid, text, cid):
@app.route('/proposal/<int:pid>/comment/<int:cid>')
def propsal_comment_reply(cid, pid):
from wowfunding.orm.orm import Comment
from funding.orm.orm import Comment
c = Comment.find_by_id(cid)
if not c or c.replied_to:
return redirect(url_for('proposal', pid=pid))
@ -88,7 +88,7 @@ def proposal(pid):
parameter('title', type=str, required=True, location='json'),
parameter('content', type=str, required=True, location='json'),
parameter('pid', type=int, required=False, location='json'),
parameter('funds_target', type=float, required=True, location='json'),
parameter('funds_target', type=str, required=True, location='json'),
parameter('addr_receiving', type=str, required=True, location='json'),
parameter('category', type=str, required=True, location='json'),
parameter('status', type=int, required=True, location='json', default=1)
@ -99,8 +99,9 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
if current_user.is_anonymous:
return make_response(jsonify('err'), 500)
if len(title) <= 10:
if len(title) <= 8:
return make_response(jsonify('title too short'), 500)
if len(content) <= 20:
return make_response(jsonify('content too short'), 500)
@ -114,12 +115,14 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
return make_response(jsonify('no rights to change status'), 500)
try:
from wowfunding.bin.anti_xss import such_xss
from funding.bin.anti_xss import such_xss
content_escaped = such_xss(content)
html = markdown2.markdown(content_escaped, safe_mode=True)
except Exception as ex:
return make_response(jsonify('markdown error'), 500)
if pid:
p = Proposal.find_by_id(pid=pid)
if not p:
@ -146,13 +149,22 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
p.status = status
p.last_edited = datetime.now()
else:
if funds_target <= 1:
return make_response(jsonify('proposal asking less than 1 error :)'), 500)
try:
funds_target = float(funds_target)
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) != 97:
return make_response(jsonify('faulty addr_receiving address, should be of length 72'), 500)
return make_response(jsonify('Faulty address, should be of length 72'), 500)
p = Proposal(headline=title, content=content, category='misc', user=current_user)
proposalID = current_user
addr_donation = Proposal.generate_proposal_subaccount(proposalID)
p.addr_donation = addr_donation
p.html = html
p.last_edited = datetime.now()
p.funds_target = funds_target
@ -160,12 +172,14 @@ 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()
# reset cached statistics
from wowfunding.bin.utils import Summary
from funding.bin.utils import Summary
Summary.fetch_stats(purge=True)
return make_response(jsonify({'url': url_for('proposal', pid=p.id)}))
@ -254,7 +268,7 @@ def login(username, password):
if request.method == 'GET':
return make_response(render_template('login.html'))
from wowfunding.factory import bcrypt
from funding.factory import bcrypt
user = User.query.filter_by(username=username).first()
if user is None or not bcrypt.check_password_hash(user.password, password):
flash('Username or Password is invalid', 'error')
@ -277,4 +291,4 @@ def logout():
@app.route('/static/<path:path>')
def static_route(path):
return send_from_directory('static', path)
return send_from_directory('static', path)

View file

@ -7,23 +7,27 @@ body {
padding-top: 56px;
}
}
.center {
text-align: center;
margin: 0 auto 0 auto;
}
.navbar {
padding: .1rem 1rem;
}
.navbar .navbar-brand img#logo{
width:28px;
height:28px;
float:left;
width: 32px;
height: 32px;
display: inline-block;
vertical-align: middle;
}
.navbar .navbar-brand img#text{
width:235px;
height:13px;
float:left;
margin-top: 8px;
margin-left:8px;
display: inline-block;
}
.navbar-nav .nav-link{
@ -55,7 +59,7 @@ body {
margin-top: 6px;
font-weight: bold;
color: #ff0000;
font-size: 22px;
font-size: 18px;
margin-bottom: 0;
}
@ -268,7 +272,12 @@ a {
background-clip: padding-box;
border: 1px solid #008926;
}
.form-group {
margin-bottom: 1rem;
}
.form-group:last-of-type {
margin-bottom:2.5rem;
}
/*fuku chrome*/
input {
outline:none;

View file

@ -24,7 +24,7 @@
</p>
<b>Example:</b>
<pre>curl -vvX GET 'https://funding.wownero.com/api/1/convert/wow-usd?wow=1000'</pre>
<pre>curl -vvX GET 'https://funding.wownero.com/api/1/convert/wow-usd?amount=1000'</pre>
<b>Response:</b>
<pre>{"usd": 6.7}</pre>

View file

@ -14,7 +14,7 @@
padding-left:4px;
}
</style>
<div class="col-lg-12" id="xox">
<div class="col-lg-8" id="xox">
<h3>Secure login </h3>
<img src="/static/nasa.png"/>
<img src="/static/cyber.png"/>
@ -46,9 +46,10 @@
</div>
</form>
</div>
<div class="col-lg-12">
<div class="col-lg-5 center">
<a href="/register">Or register here</a>
</div>
{% include 'sidebar.html' %}
</div>
</div>
<br>

View file

@ -6,7 +6,7 @@
<th>Username</th>
<th id="date">Date</th>
{% if _proposals and _proposals[0].status >= 2 %}
<th style="text-align: right;">Funding</th>
<th style="text-align: right;">Funded</th>
{% else %}
<th></th>
{% endif %}
@ -41,4 +41,4 @@
{% endfor %}
</tbody>
</table>
{% endmacro %}
{% endmacro %}

View file

@ -11,11 +11,6 @@
<!-- Title -->
<h1 class="mt-4" style="margin-bottom: 0.1rem;">
{{ proposal.headline }}
<div id="point-wow-left">
<img src="/static/point-left.png" style="margin-left: 10px;width: 60px;">
<span style="color: #fc4dff;font-size: 16px;font-style: italic;font-weight: bold;margin-left: 6px;">wow</span>
</div>
</h1>
<p>
@ -78,8 +73,8 @@
</td>
</tr>
<tr>
<td>Progress</td>
<td><span class="badge">{{proposal.balance['pct'] |round}} %</span></td>
<td>Funded</td>
<td><span class="badge">{{proposal.balance['pct'] | round or 0}} %</span></td>
</tr>
</tbody>
</table>
@ -100,17 +95,43 @@
<hr>
</div>
<div class="col-lg-8">
{{proposal.balance['remaining'] or 0}} WOW available
{{proposal.balance['available']|round(3) or 0 }} WOW Raised
{% if (proposal.funds_target-proposal.balance['available']|float|round(3)) > 0 %}
({{ (proposal.funds_target-proposal.balance['available']|float|round(3)|int) }} WOW until goal)
{% else %}
({{ (proposal.balance['available']-proposal.funds_target|float|round(3)|int) }} WOW past goal!)
{% endif %}
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['pct']}}%;">
</div>
</div>
<hr>
</div>
<br/>
<br/>
<div class="col-lg-8">
{{proposal.spends['spent']|round(3) or 0}} WOW Paid out
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.spends['spent_remaining_pct']}}%;">
</div>
</div>
<hr>
</div>
<div class="col-lg-8">
{{(proposal.balance['available']-proposal.spends['spent']) |round(3) or 0}} WOW Available to Payout
<div class="progress">
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['remaining_pct']}}%;">
</div>
</div>
<hr>
</div>
<br/>
</div>
<div class="row" style="margin-top:16px;">
<div class="col-lg-12">
Donatation address:
Donation address:
<pre class="proposal_address">{% if proposal.addr_donation %}{{ proposal.addr_donation }}{% else %}<small>None generated yet</small>{% endif %}</pre>
</div>
</div>
@ -122,7 +143,6 @@
<div class="row">
<div class="col-lg-12">
<div class="alert alert-danger">
<img src="/static/doge_head.png" style="width: 64px;margin-right: 8px;">
This proposal is disabled.
</div>
</div>
@ -159,7 +179,7 @@
<br>
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
<span style="float:right;color:#008926;font-weight:bold;">
+ {{tx['amount_human']|round(2)}} WOW
+ {{tx['amount_human']|round(3)}} WOW
{% if 'amount_usd' in tx %}
<small style="color: black">
➞ $ {{tx['amount_usd']}}
@ -176,6 +196,37 @@
<!-- /.row -->
{% endif %}
{% if proposal.spends['txs'] %}
<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>
<div class="card-body">
<ul class="list-group">
{% for tx in proposal.spends['txs'] %}
<li class="list-group-item">
{{tx['datetime'].strftime('%Y-%m-%d %H:%M')}}
<span style="float:right"><b>Blockheight</b>: {{tx['height']}}</span>
<br>
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
<span style="float:right;color:#890000;font-weight:bold;">
- {{tx['amount_human']|round(3)}} WOW
{% if 'amount_usd' in tx %}
<small style="color: black">
➞ $ {{tx['amount_usd']}}
</small>
{% endif %}
</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<!-- /.row -->
{% endif %}
</div>
<script>
@ -242,4 +293,4 @@
</script>
<!-- /.container -->
{% endblock %}
{% endblock %}

View file

@ -71,6 +71,21 @@
</div>
</div>
<!-- Side Widget -->
<div class="card my-4">
<h5 class="card-header">Newest Users</h5>
<div class="card-body">
<ul class="b">
{% for user in newest_users %}
<li>
<a href="/user/{{ user.username }}"> {{ user.username }} </a>
</li>
</a>
{% endfor %}
</li>
</div>
</div>
<script>
var search_input = document.getElementById("search_input");

View file

@ -4,35 +4,62 @@
{% if user %}
<div class="container">
<div class="row">
<div class="col-lg-8">
<div class="col-lg-12">
<span class="form-text text-muted" style="margin-top: -2px;">
Details for '{{user.username}}'
Proposals made by '{{user.username}}'
</span>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="col-lg-12">
{% if user.proposals %}
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
<tbody>
{% for p in user.proposals %}
<th>Proposal</th>
<th>Category</th>
<th>Target Amount</th>
<th>Amount Raised</th>
<th>Date</th>
{% for p in user.proposals | sort(attribute='date_added', reverse=True) %}
<tr>
<td><b><a href="/proposal/{{ p.id }}">{{ p.headline }}</a></b></td>
<td><a href="/user/{{ p.user.username }}">{{ p.user.username }}</a></td>
<td><b><a href="/proposal/{{ p.id }}">{{ p.headline | truncate(42)}}</a></b></td>
<td><a href="/proposals?cat={{ p.category }}">{{ p.category |capitalize}}</a></td>
<td>{{p.funds_target}}</td>
<td>{{p.funds_progress | round(3, 'floor')}}%</td>
<td>{{ p.date_added.strftime('%Y-%m-%d') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
This user did not submit any proposals yet.
This user has not submitted any proposals yet.
{% endif %}
<hr>
</div>
</div>
<div class="row">
<div class="col-lg-12">
Comments made by {{user.username}}
{% if user.comments %}
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
<tbody>
<th>Comment</th>
<th>Proposal</th>
<th>Date</th>
{% for y in user.comments | sort(attribute='date_added', reverse=True) %}
<tr>
<td><b><a href="/proposal/{{y.proposal.id}}#comment-{{ y.id }}">{{ y.message | truncate(32)}}</a></b></td>
<td><a href="/proposal/{{y.proposal.id}}">#{{y.proposal.id }} {{ y.proposal.headline | truncate(32) }}</a></td>
<td>{{ y.date_added.strftime('%Y-%m-%d') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
This user has not submitted any proposals yet.
{% endif %}
</div>
{% include 'sidebar.html' %}
</div>
<br>
</div>
@ -40,4 +67,4 @@
No user found by that name.
{% endif %}
<!-- /.container -->
{% endblock %}
{% endblock %}

View file

@ -1,4 +1,4 @@
from wowfunding.factory import create_app
from funding.factory import create_app
import settings
if __name__ == '__main__':

View file

@ -3,30 +3,40 @@ import socket
import collections
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SECRET = 'changeme'
SECRET = ''
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://postgres:@localhost/ffs')
COINCODE = ''
PSQL_USER = ''
PSQL_PASS = ''
PSQL_DB = ''
SESSION_COOKIE_NAME = os.environ.get('WOW_SESSION_COOKIE_NAME', 'wow_id')
SESSION_PREFIX = os.environ.get('WOW_SESSION_PREFIX', 'session:')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
REDIS_HOST = os.environ.get('WOW_REDIS_HOST', '127.0.0.1')
REDIS_PORT = int(os.environ.get('WOW_REDIS_PORT', 6379))
REDIS_PASSWD = os.environ.get('WOW_REDIS_PASSWD', None)
SESSION_COOKIE_NAME = os.environ.get('{coincode}_SESSION_COOKIE_NAME', '{coincode}_id').format(coincode=COINCODE)
SESSION_PREFIX = os.environ.get('{coincode}_SESSION_PREFIX', 'session:').format(coincode=COINCODE)
BIND_HOST = os.environ.get("WOW_BIND_HOST", "127.0.0.1")
REDIS_HOST = os.environ.get('REDIS_HOST', '127.0.0.1')
REDIS_PORT = int(os.environ.get('REDIS_PORT', 6379))
REDIS_PASSWD = os.environ.get('REDIS_PASSWD', None)
BIND_HOST = os.environ.get("BIND_HOST", "0.0.0.0")
if not BIND_HOST:
raise Exception("WOW_BIND_HOST missing")
BIND_PORT = os.environ.get("WOW_BIND_PORT", 5004)
raise Exception("BIND_HOST missing")
BIND_PORT = os.environ.get("BIND_PORT", 5004)
if not BIND_PORT:
raise Exception("WOW_BIND_PORT missing")
raise Exception("BIND_PORT missing")
HOSTNAME = os.environ.get("WOW_HOSTNAME", socket.gethostname())
HOSTNAME = os.environ.get("{coincode}_HOSTNAME", socket.gethostname()).format(coincode=COINCODE)
RPC_LOCATION = "http://127.0.0.1:45678/json_rpc"
# If using a local RPC, no need for --rpc-login credentials unless you're binding wallet-rpc to 0.0.0.0. If you are, you're bad.
# elif, remote wallet-rpc, enable --rpc-login and enter credentials below.
RPC_HOST = '127.0.0.1'
RPC_PORT = '11182'
RPC_LOCATION = "http://{host}:{rpc_port}/json_rpc".format(host=RPC_HOST, rpc_port=RPC_PORT)
RPC_USERNAME = ""
RPC_PASSWORD = ""
FUNDING_CATEGORIES = [
'wallets',
@ -52,7 +62,7 @@ What problem(s) are you trying to solve?
#### How much?
What is the total cost in WOW? List expenses per item. Total hours of work and per hour rate. What exchange rates are you using?
What is the total cost in {coincode}? List expenses per item. Total hours of work and per hour rate. What exchange rates are you using?
#### What?
@ -69,4 +79,4 @@ What will be delivered? What goals will be reached?
#### Why you?
What skills and experience do you have?
""".strip()
""".strip().format(coincode=COINCODE)