Merge pull request #7 from skftn/camthegeek-pr

[Release] showing love for the wowneros
update-readme
xmrdsc 6 years ago committed by GitHub
commit 61796b2145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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']))

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

@ -0,0 +1,159 @@
import settings
import requests
from requests.auth import HTTPDigestAuth
from funding.orm.orm import User
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, account_index, label_name):
data = {
'method': 'create_address',
'params': {'account_index': account_index, 'label': 'p_%s' % label_name},
'jsonrpc': '2.0',
'id': '0'
}
try:
result = self._make_request(data)
return result['result']
except:
return
def create_account(self, pid):
data = {
'method': 'create_account',
'params': {'label': 'p_%s' % pid},
'jsonrpc': '2.0',
'id': '0'
}
try:
result = self._make_request(data)
return result['result']
except:
return
def get_accounts(self, proposal_id:int = None):
data = {
'method': 'get_accounts',
'jsonrpc': '2.0',
'id': '0'
}
try:
result = self._make_request(data)
result = result['result']
if isinstance(proposal_id, int):
account_user = [acc for acc in result.get('subaddress_accounts', []) if acc['label'] == 'p_%d' % proposal_id]
if account_user:
return account_user[0]
else:
return
return result
except Exception as ex:
return
def get_address(self, account_index: int, proposal_id: int = None):
data = {
'method': 'getaddress',
'params': {'account_index': account_index},
'jsonrpc': '2.0',
'id': '0'
}
try:
result = self._make_request(data)
addresses = result['result']['addresses']
if isinstance(proposal_id, int):
address = [addy for addy in addresses if addy['label'] == 'p_%d' % proposal_id]
if address:
return address[0]
else:
return
return addresses
except:
return
def get_transfers_in(self, proposal):
daemon = Daemon()
account = daemon.get_accounts(proposal.id)
if not account:
raise Exception('wallet error; pid not found found')
index = account['account_index']
address = daemon.get_address(index, proposal_id=proposal.id)
if not address:
print('Could not fetch transfers_in for proposal id %d' % proposal.id)
return {'sum': [], 'txs': []}
data = {
"method": "get_transfers",
"params": {"pool": True, "in": True, "account_index": index},
"jsonrpc": "2.0",
"id": "0",
}
data = self._make_request(data)
data = data['result'].get('in', [])
# filter by current proposal
txs = [tx for tx in data if tx.get('address') == address['address']]
for d in txs:
d['amount_human'] = float(d['amount'])/1e11
return {
'sum': sum([float(z['amount'])/1e11 for z in txs]),
'txs': txs
}
def get_transfers_out(self, proposal):
daemon = Daemon()
account = daemon.get_accounts(proposal.id)
if not account:
raise Exception('wallet error; pid not found found')
index = account['account_index']
address = daemon.get_address(index, proposal_id=proposal.id)
if not address:
print('Could not fetch transfers_in for proposal id %d' % proposal.id)
return {'sum': [], 'txs': []}
data = {
"method": "get_transfers",
"params": {"pool": True, "out": True, "account_index": index},
"jsonrpc": "2.0",
"id": "0",
}
data = self._make_request(data)
data = data['result'].get('out', [])
# filter by current proposal
txs = [tx for tx in data if tx.get('address') == address['address']]
for d in txs:
d['amount_human'] = float(d['amount'])/1e11
return {
'sum': sum([float(z['amount'])/1e11 for z in txs]),
'txs': txs
}
def _make_request(self, data):
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()

@ -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

@ -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

@ -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)

@ -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

@ -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

@ -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()
@ -142,28 +139,28 @@ class Proposal(base):
return
# check if we have a valid addr_donation generated. if not, make one.
if not result.addr_donation and result.status >= 2:
if not result.addr_donation and result.status == 2:
from funding.bin.daemon import Daemon
Proposal.generate_donation_addr(result)
return result
@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(proposal=self)
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)
except Exception as ex:
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(proposal=self)
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,28 +268,30 @@ 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)
if not isinstance(addr_donation, dict):
raise Exception('get_address, needs dict; %d' % cls.id)
except Exception as ex:
print('error: %s' % str(ex))
return
# 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']
if addr_donation.get('address'):
cls.addr_donation = addr_donation['address']
db_session.commit()
db_session.flush()
return addr_donation['address']
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 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 +337,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 +378,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 +403,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 +422,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 +459,4 @@ class Comment(base):
except Exception as ex:
db_session.rollback()
raise Exception(str(ex))
return comment
return comment

@ -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,7 +115,7 @@ 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:
@ -147,10 +148,14 @@ 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)
p.html = html
@ -159,13 +164,18 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
p.addr_receiving = addr_receiving
p.category = category
p.status = status
db_session.add(p)
db_session.commit()
db_session.flush()
p.addr_donation = Proposal.generate_donation_addr(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 +264,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 +287,4 @@ def logout():
@app.route('/static/<path:path>')
def static_route(path):
return send_from_directory('static', path)
return send_from_directory('static', path)

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

@ -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-top: 0px;
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;

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 111 B

After

Width:  |  Height:  |  Size: 111 B

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@ -25,6 +25,9 @@
<p>
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.
</p>
</div>
{% include 'sidebar.html' %}

@ -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>

@ -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>

@ -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 %}

@ -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 %}

@ -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");

@ -0,0 +1,70 @@
{% extends "base.html" %}
{% block content %}
{% if user %}
<div class="container">
<div class="row">
<div class="col-lg-12">
<span class="form-text text-muted" style="margin-top: -2px;">
Proposals made by '{{user.username}}'
</span>
</div>
</div>
<div class="row">
<div class="col-lg-12">
{% if user.proposals %}
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
<tbody>
<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 | 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 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>
</div>
<br>
</div>
{% else %}
No user found by that name.
{% endif %}
<!-- /.container -->
{% endblock %}

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

@ -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 = ''
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
SESSION_COOKIE_NAME = os.environ.get('WOW_SESSION_COOKIE_NAME', 'wow_id')
SESSION_PREFIX = os.environ.get('WOW_SESSION_PREFIX', 'session:')
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)
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)
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("WOW_BIND_HOST", "127.0.0.1")
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)

@ -1,52 +0,0 @@
import settings
import requests
class WowneroDaemon:
def __init__(self):
self.url = settings.RPC_LOCATION
self.headers = {"User-Agent": "Mozilla"}
def create_address(self, label_name):
data = {
'method': 'create_address',
'params': {'account_index': 0, 'label': label_name},
'jsonrpc': '2.0',
'id': '0'
}
return self._make_request(data)
def get_address(self, index):
data = {
'method': 'get_address',
'params': {'address_index': [index], 'account_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)
except:
return
def get_transfers_in(self, index):
data = {
"method":"get_transfers",
"params": {"pool": True, "in": True, "account_index": 0, "subaddr_indices": [index]},
"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
return {
'sum': sum([float(z['amount'])/1e11 for z in data]),
'txs': data
}
def _make_request(self, data):
r = requests.post(self.url, json=data, headers=self.headers)
r.raise_for_status()
return r.json()

@ -1,43 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% if user %}
<div class="container">
<div class="row">
<div class="col-lg-8">
<span class="form-text text-muted" style="margin-top: -2px;">
Details for '{{user.username}}'
</span>
</div>
</div>
<div class="row">
<div class="col-lg-8">
{% if user.proposals %}
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
<tbody>
{% for p in user.proposals %}
<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>{{ p.date_added.strftime('%Y-%m-%d') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
This user did not submit any proposals yet.
{% endif %}
</div>
{% include 'sidebar.html' %}
</div>
<br>
</div>
{% else %}
No user found by that name.
{% endif %}
<!-- /.container -->
{% endblock %}
Loading…
Cancel
Save