Compare commits
No commits in common. 'revamp' and 'main' have entirely different histories.
@ -1,6 +1,2 @@
|
|||||||
# xmrbackers
|
# lza-quart-app
|
||||||
|
Template project for Quart (Python/Flask) applications.
|
||||||
That's the name for now. The intention is to provide a secure, although centralized (for now) web service for hosting private content and allowing subscribers to view with subscriptions paid in Monero (XMR).
|
|
||||||
|
|
||||||
It's intended to provide any sort of digital content experience - text, images, videos, streams, and have all the robust crypto mechanics for authenticating and validating transactions.
|
|
||||||
|
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
from xmrbackers.factory import create_app
|
||||||
|
|
||||||
from os import environ
|
|
||||||
|
|
||||||
from xmrbackers import create_app
|
|
||||||
|
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# print('running')
|
app.run()
|
||||||
# environ['QUART_SECRETS'] = 'config.py'
|
|
||||||
# environ['QUART_DEBUG'] = '1'
|
|
||||||
# environ['QUART_ENV'] = 'development'
|
|
||||||
app.run(debug=True, use_reloader=True)
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
# Platform Sub
|
|
||||||
|
|
||||||
### Good one
|
|
||||||
tx_id: c50b4c2c2ca26691ac6205c67d72052a882aa0d71d71b18bdfe0dd887d8b75aa
|
|
||||||
tx_key: aaf3d3b81e57b2c874f0428a50428cf38ba5369c2ad06df7d4d7e7e7bb43e308
|
|
||||||
|
|
||||||
### Bad one
|
|
||||||
tx_id: 7fd5111343a0cc9c44705a03cddb4115027a2671855806f0a0dffddc7cc889ab
|
|
||||||
tx_key: c7f1c8266a6109ea547c7c5d04d226f68faa5f5a4cf1eb7ad6077cdb2bbb830e
|
|
||||||
|
|
||||||
|
|
||||||
# Content Sub
|
|
||||||
|
|
||||||
tx_id: 1127feb964a2f35ea819454fc8dd17f23f374f829364704a7a51cbec676e09a5
|
|
||||||
tx_key: 12ca27135e6dfc1bb5f2b6dc6334a84e3e9c09003db4204061a553a49452f505
|
|
||||||
address: 75gXGUPpyqb1Hhxv6wSZ9218x1QBjBNo3dvYX6FnVMRPCWXoAX52DZnCkAe9LwjRjaGB1A6CGUr5c5hUTqdqywxN9jsutNP
|
|
@ -1 +0,0 @@
|
|||||||
from xmrbackers.factory import create_app
|
|
@ -1,46 +1,14 @@
|
|||||||
|
# import quart.flask_patch
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, FloatField
|
from wtforms import StringField
|
||||||
from wtforms.validators import DataRequired, ValidationError
|
from wtforms.validators import DataRequired
|
||||||
from monero.address import address
|
|
||||||
|
|
||||||
from xmrbackers import config
|
|
||||||
|
|
||||||
|
class UserAuth(FlaskForm):
|
||||||
|
username = StringField('Username:', validators=[DataRequired()], render_kw={"placeholder": "Username", "class": "form-control", "type": "text"})
|
||||||
|
password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"})
|
||||||
|
|
||||||
def is_valid_xmr_address(form, field):
|
class ConfirmSubscription(FlaskForm):
|
||||||
try:
|
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={"placeholder": "TX ID", "class": "form-control", "type": "text"})
|
||||||
# Ensure the provided address is valid address/subaddress/integrated address
|
tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={"placeholder": "TX Key", "class": "form-control", "type": "text"})
|
||||||
a = address(field.data)
|
wallet_address = StringField('XMR Address:', validators=[DataRequired()], render_kw={"placeholder": "XMR Address", "class": "form-control", "type": "text"})
|
||||||
# Ensure the provided address matches the network that the application's wallet is using
|
|
||||||
if not config.XMR_WALLET_NETWORK.startswith(a.net):
|
|
||||||
raise ValidationError('Provided Monero address does not match the configured network. Application: {}. Provided: {}'.format(
|
|
||||||
config.XMR_WALLET_NETWORK.replace('net', ''), a.net
|
|
||||||
))
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('Invalid Monero address provided')
|
|
||||||
|
|
||||||
|
|
||||||
class UserRegistration(FlaskForm):
|
|
||||||
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'})
|
|
||||||
wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
|
||||||
|
|
||||||
class UserLogin(FlaskForm):
|
|
||||||
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
|
||||||
|
|
||||||
class UserChallenge(FlaskForm):
|
|
||||||
signature = StringField('Signature:', validators=[DataRequired()], render_kw={'placeholder': 'signed data', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmPlatformSubscription(FlaskForm):
|
|
||||||
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'form-control', 'type': 'text'})
|
|
||||||
tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmCreatorSubscription(ConfirmPlatformSubscription):
|
|
||||||
wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
|
||||||
|
|
||||||
class CreateSubscription(FlaskForm):
|
|
||||||
price_xmr = FloatField('Price (XMR):', validators=[DataRequired()], render_kw={'placeholder': '.5', 'class': 'form-control', 'type': 'text'})
|
|
||||||
number_days = FloatField('Length (Days)', validators=[DataRequired()], render_kw={'placeholder': '30', 'class': 'form-control', 'type': 'text'})
|
|
||||||
|
@ -1,90 +1,17 @@
|
|||||||
import peewee as pw
|
|
||||||
import playhouse.postgres_ext as pwpg
|
|
||||||
from monero.wallet import Wallet
|
from monero.wallet import Wallet
|
||||||
|
from monero.exceptions import WrongAddress
|
||||||
|
|
||||||
from xmrbackers import config
|
from xmrbackers import config
|
||||||
|
|
||||||
|
def check_tx_key(tx_id, tx_key, wallet_address):
|
||||||
def make_wallet_rpc(method, data={}):
|
|
||||||
try:
|
try:
|
||||||
w = Wallet(
|
check_data = {
|
||||||
port=config.XMR_WALLET_RPC_PORT,
|
'txid': tx_id,
|
||||||
user=config.XMR_WALLET_RPC_USER,
|
'tx_key': tx_key,
|
||||||
password=config.XMR_WALLET_RPC_PASS,
|
'address': wallet_address
|
||||||
timeout=3
|
}
|
||||||
)
|
w = Wallet(port=8000, user=config.XMR_WALLET_RPC_USER, password=config.XMR_WALLET_RPC_PASS)
|
||||||
res = w._backend.raw_request(method, data)
|
res = w._backend.raw_request('check_tx_key', check_data)
|
||||||
return res
|
return res
|
||||||
except Exception as e:
|
except:
|
||||||
raise e
|
raise Exception('there was a problem i dont feel like writing good code for right now')
|
||||||
|
|
||||||
|
|
||||||
class EnumArrayField(pwpg.ArrayField):
|
|
||||||
def __init__(self, enum_class, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage:
|
|
||||||
from enum import IntEnum, unique
|
|
||||||
|
|
||||||
@unique
|
|
||||||
class UserRoles(IntEnum):
|
|
||||||
admin = 0
|
|
||||||
moderator = 1
|
|
||||||
guest = 2
|
|
||||||
|
|
||||||
[...]
|
|
||||||
roles = EnumArrayField(enum_class=UserRoles, field_class=IntegerField, default=[UserRoles.guest])
|
|
||||||
|
|
||||||
# Fetch results with `admin` OR `moderator` role set:
|
|
||||||
Model.select().where(
|
|
||||||
Model.roles.contains_any(UserRoles.admin, UserRoles.moderator)).get()
|
|
||||||
"""
|
|
||||||
super(EnumArrayField, self).__init__(*args, **kwargs)
|
|
||||||
self.enum_class = enum_class
|
|
||||||
|
|
||||||
def db_value(self, value):
|
|
||||||
"""python -> database"""
|
|
||||||
if not isinstance(value, (tuple, list)):
|
|
||||||
raise TypeError("Wrong type, must be a list of enums")
|
|
||||||
# if isinstance(value, tuple):
|
|
||||||
# value = value[0]
|
|
||||||
|
|
||||||
data = []
|
|
||||||
for enum in value:
|
|
||||||
if not isinstance(enum, self.enum_class):
|
|
||||||
raise TypeError("Wrong type, must be a list of enums")
|
|
||||||
data.append(enum.value)
|
|
||||||
return super().adapt(data)
|
|
||||||
|
|
||||||
def python_value(self, value):
|
|
||||||
"""database -> python"""
|
|
||||||
data = []
|
|
||||||
for val in value:
|
|
||||||
data.append(self.enum_class(val))
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class EnumIntField(pw.IntegerField):
|
|
||||||
def __init__(self, enum_class, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage:
|
|
||||||
from enum import IntEnum, unique
|
|
||||||
|
|
||||||
@unique
|
|
||||||
class UserStatus(IntEnum):
|
|
||||||
disabled = 0
|
|
||||||
enable = 1
|
|
||||||
banned = 2
|
|
||||||
|
|
||||||
[...]
|
|
||||||
status = EnumIntField(enum_class=UserStatus, default=UserStatus.active)
|
|
||||||
[...]
|
|
||||||
Model.select().where(Model.status != UserStatus.banned)
|
|
||||||
"""
|
|
||||||
super(pw.IntegerField, self).__init__(*args, **kwargs)
|
|
||||||
self.enum_class = enum_class
|
|
||||||
|
|
||||||
def db_value(self, value):
|
|
||||||
return value.value
|
|
||||||
|
|
||||||
def python_value(self, value):
|
|
||||||
return self.enum_class(value)
|
|
||||||
|
@ -1,230 +1,100 @@
|
|||||||
from quart import Blueprint, render_template, flash, redirect, url_for, request
|
from quart import Blueprint, render_template, flash, redirect, url_for
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from monero.wallet import Wallet
|
from monero.wallet import Wallet
|
||||||
from monero.address import address
|
|
||||||
from monero.numbers import to_atomic, from_atomic
|
|
||||||
from monero.backends.jsonrpc.exceptions import RPCError
|
|
||||||
|
|
||||||
from xmrbackers.models import *
|
from xmrbackers.forms import ConfirmSubscription
|
||||||
from xmrbackers.helpers import make_wallet_rpc
|
from xmrbackers.models import User, CreatorProfile, BackerProfile, TextPost
|
||||||
from xmrbackers import config, forms
|
from xmrbackers.models import Subscription, SubscriptionMeta
|
||||||
|
from xmrbackers.helpers import check_tx_key
|
||||||
|
from xmrbackers import config
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('creator', 'creator')
|
bp = Blueprint('creator', 'creator')
|
||||||
|
|
||||||
@bp.route('/creators')
|
@bp.route('/creators')
|
||||||
async def all():
|
async def all():
|
||||||
creators = User.select().join(Profile).where(
|
creators = CreatorProfile.select().order_by(
|
||||||
User.roles.contains_any(UserRole.creator)
|
CreatorProfile.create_date.desc()
|
||||||
).order_by(
|
|
||||||
Profile.create_date.desc()
|
|
||||||
)
|
)
|
||||||
return await render_template('creator/creators.html', creators=creators)
|
return await render_template('creator/creators.html', creators=creators)
|
||||||
|
|
||||||
@bp.route('/creators/join', methods=['GET', 'POST'])
|
@bp.route('/creator/<username>')
|
||||||
@login_required
|
async def show(username):
|
||||||
async def join():
|
user = User.select().where(User.username == username)
|
||||||
form = forms.ConfirmPlatformSubscription()
|
creator = CreatorProfile.select().where(
|
||||||
valid_address = False
|
CreatorProfile.user == user
|
||||||
|
|
||||||
try:
|
|
||||||
address(config.PLATFORM_WALLET)
|
|
||||||
valid_address = True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not config.PLATFORM_WALLET or valid_address is False:
|
|
||||||
await flash('Platform operator has not setup wallet yet. Try later.', 'warning')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
if UserRole.creator in current_user.roles:
|
|
||||||
await flash('You already are a creator!', 'warning')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
'txid': form.tx_id.data,
|
|
||||||
'tx_key': form.tx_key.data,
|
|
||||||
'address': config.PLATFORM_WALLET
|
|
||||||
}
|
|
||||||
res = make_wallet_rpc('check_tx_key', data)
|
|
||||||
existing_tx = Transaction.select().where(
|
|
||||||
Transaction.tx_id == form.tx_id.data.lower()
|
|
||||||
).first()
|
|
||||||
existing_sub = CreatorSubscription.select().where(
|
|
||||||
CreatorSubscription.tx == existing_tx
|
|
||||||
).first()
|
|
||||||
for i in ['confirmations', 'in_pool', 'received']:
|
|
||||||
assert i in res
|
|
||||||
if res['in_pool']:
|
|
||||||
await flash('That transaction is still in the mempool. You need to wait a few more minutes for it to clear. Try again in a bit.', 'warning')
|
|
||||||
elif res['received'] < to_atomic(config.CREATOR_SUBSCRIPTION_FEE_XMR):
|
|
||||||
await flash(f'Not enought XMR sent. {from_atomic(res["received"])} XMR sent, expected {config.CREATOR_SUBSCRIPTION_FEE_XMR} XMR.', 'error')
|
|
||||||
elif existing_tx:
|
|
||||||
if existing_sub:
|
|
||||||
await flash('This transaction was already used for another subscription.', 'warning')
|
|
||||||
else:
|
|
||||||
await flash('Adding creator subscription.', 'success')
|
|
||||||
c = CreatorSubscription(
|
|
||||||
user=current_user,
|
|
||||||
tx=existing_tx,
|
|
||||||
atomic_xmr=res['received']
|
|
||||||
)
|
|
||||||
c.save()
|
|
||||||
current_user.roles.append(UserRole.creator)
|
|
||||||
current_user.save()
|
|
||||||
elif res['received'] >= to_atomic(config.CREATOR_SUBSCRIPTION_FEE_XMR):
|
|
||||||
await flash('Success! Welcome to the creator club!', 'success')
|
|
||||||
t = Transaction(
|
|
||||||
tx_id=form.tx_id.data.lower(),
|
|
||||||
atomic_xmr=res['received'],
|
|
||||||
to_address=config.PLATFORM_WALLET
|
|
||||||
)
|
|
||||||
t.save()
|
|
||||||
c = CreatorSubscription(
|
|
||||||
user=current_user,
|
|
||||||
tx=t,
|
|
||||||
atomic_xmr=res['received']
|
|
||||||
)
|
|
||||||
c.save()
|
|
||||||
current_user.roles.append(UserRole.creator)
|
|
||||||
current_user.save()
|
|
||||||
return redirect(url_for('creator.show', handle=current_user.handle))
|
|
||||||
else:
|
|
||||||
await flash('Something went wrong. No idea what, though. Check with admin.', 'error')
|
|
||||||
except Exception as e:
|
|
||||||
await flash(f'seems bad: {e}', 'error')
|
|
||||||
|
|
||||||
return await render_template(
|
|
||||||
'creator/join.html',
|
|
||||||
form=form
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/creator/<handle>', methods=['GET', 'POST'])
|
|
||||||
async def show(handle):
|
|
||||||
form = forms.ConfirmCreatorSubscription()
|
|
||||||
creator = User.select().join(Profile, pw.JOIN.LEFT_OUTER).where(
|
|
||||||
User.handle == handle,
|
|
||||||
User.roles.contains_any(UserRole.creator)
|
|
||||||
).first()
|
).first()
|
||||||
|
if creator:
|
||||||
if not creator:
|
posts = TextPost.select().where(
|
||||||
await flash('That creator does not exist.', 'warning')
|
TextPost.creator == creator,
|
||||||
return redirect(url_for('main.index'))
|
TextPost.hidden == False
|
||||||
|
).order_by(TextPost.post_date.desc())
|
||||||
creator.derive_subscription_fees()
|
return await render_template(
|
||||||
|
'creator/creator.html',
|
||||||
posts = Post.select().where(
|
creator=creator,
|
||||||
Post.creator == creator,
|
posts=posts
|
||||||
Post.hidden == False
|
)
|
||||||
).order_by(Post.post_date.desc())
|
else:
|
||||||
subscriptions = SubscriptionMeta.select().where(
|
await flash('That creator does not exist.')
|
||||||
SubscriptionMeta.user == creator
|
return redirect(url_for('meta.index'))
|
||||||
).order_by(SubscriptionMeta.create_date.desc())
|
|
||||||
|
@bp.route('/creator/<username>/subscription')
|
||||||
|
async def subscription(username):
|
||||||
|
user = User.select().where(User.username == username)
|
||||||
|
creator = CreatorProfile.select().where(
|
||||||
|
CreatorProfile.user == user
|
||||||
|
)
|
||||||
|
if creator:
|
||||||
|
subscription_meta = SubscriptionMeta.select().where(
|
||||||
|
SubscriptionMeta.creator == creator
|
||||||
|
).order_by(SubscriptionMeta.create_date.desc()).first()
|
||||||
|
form = ConfirmSubscription()
|
||||||
|
return await render_template(
|
||||||
|
'creator/subscription.html',
|
||||||
|
subscription_meta=subscription_meta,
|
||||||
|
form=form
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await flash('That creator does not exist.')
|
||||||
|
return redirect(url_for('meta.index'))
|
||||||
|
|
||||||
|
@bp.route('/subscription/<int:subscription_id>/confirm', methods=['POST'])
|
||||||
|
async def confirm_subscription(subscription_id):
|
||||||
|
# do checks here for SubscriptionMeta assumption
|
||||||
|
sm = SubscriptionMeta.get_or_none(subscription_id)
|
||||||
|
form = ConfirmSubscription()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
data = {
|
w = Wallet(
|
||||||
|
port=8000,
|
||||||
|
user=config.XMR_WALLET_RPC_USER,
|
||||||
|
password=config.XMR_WALLET_RPC_PASS
|
||||||
|
)
|
||||||
|
check_data = {
|
||||||
'txid': form.tx_id.data,
|
'txid': form.tx_id.data,
|
||||||
'tx_key': form.tx_key.data,
|
'tx_key': form.tx_key.data,
|
||||||
'address': form.wallet_address.data
|
'address': form.wallet_address.data
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
res = make_wallet_rpc('check_tx_key', data)
|
res = w._backend.raw_request('check_tx_key', check_data)
|
||||||
subs = subscriptions.where(
|
except:
|
||||||
SubscriptionMeta.wallet_address == data['address']
|
await flash(f'Invalid transaction! No subscription for you!')
|
||||||
|
return redirect(url_for('creator.show', username=sm.creator.user.username))
|
||||||
|
|
||||||
|
if res['received'] >= sm.atomic_xmr:
|
||||||
|
backer_profile = BackerProfile.select().where(
|
||||||
|
BackerProfile.user == current_user
|
||||||
|
).first()
|
||||||
|
s = Subscription(
|
||||||
|
creator=sm.creator.id,
|
||||||
|
backer=backer_profile.id,
|
||||||
|
meta=sm.id,
|
||||||
)
|
)
|
||||||
for sub in subs:
|
s.save()
|
||||||
if res['in_pool'] is False and res['received'] >= sub.atomic_xmr:
|
await flash(f'Found valid transaction! You are now subscribed to {sm.creator.user.username}!')
|
||||||
await flash('Found a valid subscription.', 'success')
|
return redirect(url_for('creator.show', username=sm.creator.user.username))
|
||||||
print(request)
|
else:
|
||||||
return redirect(url_for('creator.show', handle=handle))
|
await flash('Not enough XMR sent! No subscription for you!')
|
||||||
except RPCError:
|
return redirect(url_for('creator.show', username=sm.creator.user.username))
|
||||||
await flash('Failed to check TX Key. Problem with provided info.', 'error')
|
else:
|
||||||
|
await flash('Unable to accept form POST.')
|
||||||
return await render_template(
|
return redirect(url_for('meta.index'))
|
||||||
'creator/show.html',
|
|
||||||
creator=creator,
|
|
||||||
subscription=subscriptions.first(),
|
|
||||||
posts=posts,
|
|
||||||
form=form
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/creator/subscriptions/manage', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
async def manage_subscriptions():
|
|
||||||
form = forms.CreateSubscription()
|
|
||||||
if UserRole.creator not in current_user.roles:
|
|
||||||
await flash('You are not a creator!', 'warning')
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
platform_subs = CreatorSubscription.select().where(
|
|
||||||
CreatorSubscription.user == current_user
|
|
||||||
).order_by(CreatorSubscription.create_date.desc())
|
|
||||||
content_subs = SubscriptionMeta.select().where(
|
|
||||||
SubscriptionMeta.user == current_user
|
|
||||||
).order_by(SubscriptionMeta.create_date.desc())
|
|
||||||
subscribers = Subscription.select().where(
|
|
||||||
Subscription.meta.in_(content_subs)
|
|
||||||
).order_by(Subscription.subscribe_date.desc())
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
s = SubscriptionMeta(
|
|
||||||
user=current_user,
|
|
||||||
atomic_xmr=to_atomic(form.price_xmr.data),
|
|
||||||
number_hours=form.number_days.data * 24.0,
|
|
||||||
wallet_address=current_user.wallet_address
|
|
||||||
)
|
|
||||||
s.save()
|
|
||||||
await flash('posting form data', 'success')
|
|
||||||
|
|
||||||
return await render_template(
|
|
||||||
'creator/manage_subscriptions.html',
|
|
||||||
platform_subs=platform_subs,
|
|
||||||
content_subs=content_subs,
|
|
||||||
subscribers=subscribers,
|
|
||||||
form=form
|
|
||||||
)
|
|
||||||
|
|
||||||
# @bp.route('/subscription/<int:subscription_id>/confirm', methods=['POST'])
|
|
||||||
# async def confirm_subscription(subscription_id):
|
|
||||||
# # do checks here for SubscriptionMeta assumption
|
|
||||||
# sm = SubscriptionMeta.get_or_none(subscription_id)
|
|
||||||
# form = ConfirmPlatformSubscription()
|
|
||||||
# if form.validate_on_submit():
|
|
||||||
# w = Wallet(
|
|
||||||
# port=8000,
|
|
||||||
# user=config.XMR_WALLET_RPC_USER,
|
|
||||||
# password=config.XMR_WALLET_RPC_PASS
|
|
||||||
# )
|
|
||||||
# check_data = {
|
|
||||||
# 'txid': form.tx_id.data,
|
|
||||||
# 'tx_key': form.tx_key.data,
|
|
||||||
# 'address': form.wallet_address.data
|
|
||||||
# }
|
|
||||||
# try:
|
|
||||||
# res = w._backend.raw_request('check_tx_key', check_data)
|
|
||||||
# except:
|
|
||||||
# await flash(f'Invalid transaction! No subscription for you!')
|
|
||||||
# return redirect(url_for('creator.show', username=sm.creator.user.username))
|
|
||||||
|
|
||||||
# if res['received'] >= sm.atomic_xmr:
|
|
||||||
# backer_profile = BackerProfile.select().where(
|
|
||||||
# BackerProfile.user == current_user
|
|
||||||
# ).first()
|
|
||||||
# s = Subscription(
|
|
||||||
# creator=sm.creator.id,
|
|
||||||
# backer=backer_profile.id,
|
|
||||||
# meta=sm.id,
|
|
||||||
# )
|
|
||||||
# s.save()
|
|
||||||
# await flash(f'Found valid transaction! You are now subscribed to {sm.creator.user.username}!')
|
|
||||||
# return redirect(url_for('creator.show', username=sm.creator.user.username))
|
|
||||||
# else:
|
|
||||||
# await flash('Not enough XMR sent! No subscription for you!')
|
|
||||||
# return redirect(url_for('creator.show', username=sm.creator.user.username))
|
|
||||||
# else:
|
|
||||||
# await flash('Unable to accept form POST.')
|
|
||||||
# return redirect(url_for('meta.index'))
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
from quart import Blueprint, render_template
|
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
from xmrbackers.models import *
|
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('main', 'main')
|
|
||||||
|
|
||||||
@bp.route('/')
|
|
||||||
async def index():
|
|
||||||
feed = dict()
|
|
||||||
new_creators = User.select().where(
|
|
||||||
User.roles.contains_any(UserRole.creator)
|
|
||||||
).order_by(User.register_date.desc()).execute()
|
|
||||||
feed['new_creators'] = new_creators
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
active_subscriptions = Subscription.select().where(
|
|
||||||
Subscription.is_active == True,
|
|
||||||
Subscription.backer == current_user
|
|
||||||
).order_by(Subscription.subscribe_date.desc()).execute()
|
|
||||||
feed['active_subscriptions'] = active_subscriptions
|
|
||||||
new_posts = Post.select().where(
|
|
||||||
Post.hidden == False,
|
|
||||||
Post.creator in [c.creator for c in active_subscriptions]
|
|
||||||
).order_by(Post.post_date.desc()).execute()
|
|
||||||
feed['new_posts'] = new_posts
|
|
||||||
return await render_template(
|
|
||||||
'index.html',
|
|
||||||
feed=feed
|
|
||||||
)
|
|
@ -0,0 +1,30 @@
|
|||||||
|
from quart import Blueprint, render_template
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from xmrbackers.models import CreatorProfile, Subscription, TextPost
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint('meta', 'meta')
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
async def index():
|
||||||
|
feed = None
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
backer = current_user.backer_profile.first()
|
||||||
|
new_creators = CreatorProfile.select().where(
|
||||||
|
CreatorProfile.verified == True
|
||||||
|
).order_by(CreatorProfile.create_date.desc()).execute()
|
||||||
|
active_subscriptions = Subscription.select().where(
|
||||||
|
Subscription.active == True,
|
||||||
|
Subscription.backer == backer
|
||||||
|
).order_by(Subscription.subscribe_date.desc()).execute()
|
||||||
|
new_posts = TextPost.select().where(
|
||||||
|
TextPost.hidden == False,
|
||||||
|
TextPost.creator in [c.creator for c in active_subscriptions]
|
||||||
|
).order_by(TextPost.post_date.desc()).execute()
|
||||||
|
feed = {
|
||||||
|
'new_creators': [i.user.username for i in new_creators],
|
||||||
|
'new_posts': [i.title for i in new_posts],
|
||||||
|
'active_subscriptions': [i.id for i in active_subscriptions]
|
||||||
|
}
|
||||||
|
return await render_template('index.html', feed=feed)
|
@ -1,27 +1,27 @@
|
|||||||
from quart import Blueprint, render_template, flash, redirect, url_for
|
from quart import Blueprint, render_template, flash, redirect, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from xmrbackers.models import Content, Subscription
|
from xmrbackers.models import TextPost, Subscription
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('post', 'post')
|
bp = Blueprint('post', 'post')
|
||||||
|
|
||||||
# @bp.route('/post/<int:post_id>')
|
@bp.route('/post/<int:post_id>')
|
||||||
# async def show(post_id):
|
async def show(post_id):
|
||||||
# post = TextPost.get_or_none(post_id)
|
post = TextPost.get_or_none(post_id)
|
||||||
# if post:
|
if post:
|
||||||
# if current_user.is_anonymous:
|
if current_user.is_anonymous:
|
||||||
# await flash('You must login to view this post.')
|
await flash('You must login to view this post.')
|
||||||
# return redirect(url_for('creator.show', username=post.creator.user.username))
|
return redirect(url_for('creator.show', username=post.creator.user.username))
|
||||||
# user_subscriptions = Subscription.select().where(
|
user_subscriptions = Subscription.select().where(
|
||||||
# Subscription.active == True,
|
Subscription.active == True,
|
||||||
# Subscription.backer == current_user.backer_profile.first()
|
Subscription.backer == current_user.backer_profile.first()
|
||||||
# )
|
)
|
||||||
# if user_subscriptions:
|
if user_subscriptions:
|
||||||
# return await render_template('post/show.html', post=post)
|
return await render_template('post/show.html', post=post)
|
||||||
# else:
|
else:
|
||||||
# await flash('Viewing posts requires a subscription.')
|
await flash('Viewing posts requires a subscription.')
|
||||||
# return redirect(url_for('creator.subscription', username=post.creator.user.username))
|
return redirect(url_for('creator.subscription', username=post.creator.user.username))
|
||||||
# else:
|
else:
|
||||||
# flash('That post does not exist.')
|
flash('That post does not exist.')
|
||||||
# return redirect(url_for('meta.index'))
|
return redirect(url_for('meta.index'))
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'includes/base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h2>Challenge</h2>
|
|
||||||
<p>Handle: {{ user.handle }}</p>
|
|
||||||
<p>Challenge: {{ user.challenge }}</p>
|
|
||||||
<p>Wallet Address: {{ user.wallet_address }}</p>
|
|
||||||
{% include 'includes/form.html' %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,8 +1,46 @@
|
|||||||
{% extends 'includes/base.html' %}
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
{% block content %}
|
{% include 'includes/head.html' %}
|
||||||
|
|
||||||
<h2>Login</h2>
|
<body class="is-preload landing">
|
||||||
{% include 'includes/form.html' %}
|
<div id="page-wrapper">
|
||||||
|
|
||||||
{% endblock %}
|
{% include 'includes/header.html' %}
|
||||||
|
|
||||||
|
<section id="banner">
|
||||||
|
<div class="content">
|
||||||
|
<header>
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form method="POST" action="{{ url_for('auth.login') }}">
|
||||||
|
|
||||||
|
{% for f in form %}
|
||||||
|
{% if f.name == 'csrf_token' %}
|
||||||
|
{{ f }}
|
||||||
|
{% else %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ f.label }}
|
||||||
|
{{ f }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<ul>
|
||||||
|
{% for field, errors in form.errors.items() %}
|
||||||
|
<li>{{ form[field].label }}: {{ ', '.join(errors) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<input type="submit" value="Login" class="btn btn-link btn-outline btn-xl">
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
<span class="image"><img src="/static/images/monero-logo.png" width=150px></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% include 'includes/footer.html' %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'includes/scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
@ -1,8 +1,46 @@
|
|||||||
{% extends 'includes/base.html' %}
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
{% block content %}
|
{% include 'includes/head.html' %}
|
||||||
|
|
||||||
<h2>Register</h2>
|
<body class="is-preload landing">
|
||||||
{% include 'includes/form.html' %}
|
<div id="page-wrapper">
|
||||||
|
|
||||||
{% endblock %}
|
{% include 'includes/header.html' %}
|
||||||
|
|
||||||
|
<section id="banner">
|
||||||
|
<div class="content">
|
||||||
|
<header>
|
||||||
|
<h2>Register</h2>
|
||||||
|
<form method="POST" action="{{ url_for('auth.register') }}">
|
||||||
|
|
||||||
|
{% for f in form %}
|
||||||
|
{% if f.name == 'csrf_token' %}
|
||||||
|
{{ f }}
|
||||||
|
{% else %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ f.label }}
|
||||||
|
{{ f }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<ul>
|
||||||
|
{% for field, errors in form.errors.items() %}
|
||||||
|
<li>{{ form[field].label }}: {{ ', '.join(errors) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<input type="submit" value="Register" class="btn btn-link btn-outline btn-xl">
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
<span class="image"><img src="/static/images/monero-logo.png" width=150px></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% include 'includes/footer.html' %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'includes/scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
{% include 'includes/head.html' %}
|
||||||
|
|
||||||
|
<body class="is-preload landing">
|
||||||
|
<div id="page-wrapper">
|
||||||
|
|
||||||
|
{% include 'includes/header.html' %}
|
||||||
|
|
||||||
|
{% if creator %}
|
||||||
|
<h1>{{ creator.user.username }}</h1>
|
||||||
|
<p>Bio: {{ creator.bio }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if posts %}
|
||||||
|
<h1>Posts</h1>
|
||||||
|
{% for post in posts %}
|
||||||
|
<p><a href="{{ url_for('post.show', post_id=post.id) }}">{{ post.id }} - {{ post.title }} - {{ post.post_date | humanize }}</a></p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'includes/footer.html' %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'includes/scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,17 +0,0 @@
|
|||||||
{% extends 'includes/base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Become a Creator</h1>
|
|
||||||
<p>
|
|
||||||
Creator subscriptions have a flat rate of {{ config.CREATOR_SUBSCRIPTION_FEE_XMR }} XMR with a modifier for how much content is currently stored. <br>
|
|
||||||
Subscriptions are a {{ config.CREATOR_SUBSCRIPTION_TERM }} day term. <br>
|
|
||||||
Please send XMR to the platform wallet below and provide your <code>tx_hash</code> and <code>tx_key</code> in the form below to initiate your platform subscription.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Subscription Price: {{ config.CREATOR_SUBSCRIPTION_FEE_XMR }} XMR <br>
|
|
||||||
Platform Wallet: {{ config.PLATFORM_WALLET }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% include 'includes/form.html' %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,49 +0,0 @@
|
|||||||
{% extends 'includes/base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h2>Platform Subscriptions</h2>
|
|
||||||
<ul>
|
|
||||||
{% for s in platform_subs %}
|
|
||||||
<li>
|
|
||||||
Platform Subscription ID: {{ s.id }} <br>
|
|
||||||
Created: {{ s.create_date }} <br>
|
|
||||||
TX ID: {{ s.tx.tx_id }} <br>
|
|
||||||
Paid: {{ s.tx.atomic_xmr | from_atomic }} XMR <br>
|
|
||||||
Term Length: {{ s.term_hours }} hours ({{ s.hours_until_content_hidden }} hours) <br>
|
|
||||||
Hide Content: {{ s.hours_until_content_hidden }} hours <br>
|
|
||||||
Archive Content: {{ s.hours_until_content_archived }} hours <br>
|
|
||||||
Active Subscription: <strong>{{ s.is_active }}</strong> <br>
|
|
||||||
<br>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Content Subscriptions</h2>
|
|
||||||
{% if not content_subs %}
|
|
||||||
<p>No subscriptions to your content yet.</p>
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for s in content_subs %}
|
|
||||||
<li>
|
|
||||||
Content Subscription ID: {{ s.id }} <br>
|
|
||||||
Created: {{ s.create_date }} <br>
|
|
||||||
Price: {{ s.atomic_xmr | from_atomic }} XMR <br>
|
|
||||||
Term Length: {{ s.number_hours }} hours <br>
|
|
||||||
Active Subscriptions: {{ s.get_active_subscriptions().count() }} <br>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
{% include 'includes/form.html' %}
|
|
||||||
|
|
||||||
<h2>Subscribers</h2>
|
|
||||||
{% if not subscribers %}
|
|
||||||
<p>No subscribers yet.</p>
|
|
||||||
{% else %}
|
|
||||||
{% for subscriber in subscribers %}
|
|
||||||
{{ subscriber }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,38 +0,0 @@
|
|||||||
{% extends 'includes/base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h1>{{ creator.handle }}</h1>
|
|
||||||
{% if creator.bio %}<p>Bio: {{ creator.bio }}</p>{% endif %}
|
|
||||||
|
|
||||||
<h2>Subscribe</h2>
|
|
||||||
{% if subscription %}
|
|
||||||
<p>
|
|
||||||
Send <code>{{ subscription.atomic_xmr | from_atomic | round(2) }}</code> XMR to
|
|
||||||
<code>{{ subscription.wallet_address }}</code>
|
|
||||||
and provide your TX ID and TX KEY to subscribe to this user
|
|
||||||
and view their content for the next {{ subscription.number_hours / 24 }} days
|
|
||||||
({{ subscription.number_hours }} hours).
|
|
||||||
</p>
|
|
||||||
{% else %}
|
|
||||||
<p>This creator has not setup a subscription plan yet.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
|
||||||
{% if not current_user.is_subscribed(subscription) %}
|
|
||||||
{% include 'includes/form.html' %}
|
|
||||||
{% else %}
|
|
||||||
subscribed
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<p><a href="{{ url_for('auth.login') }}">Login to subscribe.</a></p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h2>Content</h2>
|
|
||||||
{% if not posts %}<p>This creator has not posted any content yet.</p>{% endif %}
|
|
||||||
{% for post in posts %}
|
|
||||||
<p><a href="{{ url_for('post.show', post_id=post.id) }}">{{ post.id }} - {{ post.title }} - {{ post.post_date | humanize }}</a></p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
{% include 'includes/head.html' %}
|
|
||||||
<body style="background-color: black; color: white;">
|
|
||||||
{% include 'includes/header.html' %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
{% include 'includes/debug.html' %}
|
|
||||||
{% include 'includes/scripts.html' %}
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
a, a:visited {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||||||
{% if config.DEBUG and current_user.is_authenticated %}
|
|
||||||
<hr>
|
|
||||||
<h2>Debug</h2>
|
|
||||||
<p>
|
|
||||||
Username: {{ current_user.handle }} <br>
|
|
||||||
{% if current_user.email %}Email: {{ current_user.email }} <br>{% endif %}
|
|
||||||
Wallet Address: {{ current_user.wallet_address }} <br>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
@ -1,18 +0,0 @@
|
|||||||
<form method="POST" action="">
|
|
||||||
{% for f in form %}
|
|
||||||
{% if f.name == 'csrf_token' %}
|
|
||||||
{{ f }}
|
|
||||||
{% else %}
|
|
||||||
<div class="form-group">
|
|
||||||
{{ f.label }}
|
|
||||||
{{ f }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<ul>
|
|
||||||
{% for field, errors in form.errors.items() %}
|
|
||||||
<li>{{ form[field].label }}: {{ ', '.join(errors) }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<input type="submit" value="Confirm" class="btn btn-link btn-outline btn-xl">
|
|
||||||
</form>
|
|
@ -1,35 +1,40 @@
|
|||||||
{% extends 'includes/base.html' %}
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
{% block content %}
|
|
||||||
|
{% include 'includes/head.html' %}
|
||||||
{% if current_user.is_authenticated %}
|
|
||||||
{% if 2 not in current_user.roles %}
|
<body class="is-preload landing">
|
||||||
<a href="{{ url_for('creator.join') }}">Become a Creator</a>
|
<div id="page-wrapper">
|
||||||
{% else %}
|
|
||||||
<a href="{{ url_for('creator.manage_subscriptions') }}">Manage Subscriptions</a>
|
{% include 'includes/header.html' %}
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% if feed %}
|
||||||
|
{% for k in feed %}
|
||||||
{% if feed %}
|
<h2>{{ k }}</h2>
|
||||||
<h1>Feed</h1>
|
{% if k == 'new_creators' %}
|
||||||
{% if feed['new_creators'] %}
|
<p><a href="{{ url_for('creator.show', username=feed[k]) }}">{{ feed[k] }}</a></p>
|
||||||
<h2>New Creators</h2>
|
{% else %}
|
||||||
{% for c in feed['new_creators'] %}
|
<p>{{ feed[k]}}</p>
|
||||||
<p><a href="{{ url_for('creator.show', handle=c.handle) }}">{{ c.handle }}</a></p>
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% else %}
|
||||||
{% if feed['new_posts'] %}
|
|
||||||
<h2>New Posts</h2>
|
<section id="banner">
|
||||||
{% for p in feed['new_posts'] %}
|
<div class="content">
|
||||||
<p>{{ p.id }}</p>
|
<header>
|
||||||
{% endfor %}
|
<p>This is a simple prototype and is under heavy development.</p>
|
||||||
{% endif %}
|
</header>
|
||||||
{% if feed['active_subscriptions'] %}
|
<span class="image"><img src="/static/images/monero-logo.png" width=150px></span>
|
||||||
<h2>Active Subscriptions</h2>
|
</div>
|
||||||
{% for s in feed['active_subscriptions'] %}
|
</section>
|
||||||
<p>{{ s.id }}</p>
|
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% include 'includes/footer.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
</div>
|
||||||
|
|
||||||
|
{% include 'includes/scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
Loading…
Reference in New Issue