starting to setup platform subs

revamp
lza_menace 2 years ago
parent 6681315128
commit 5721eaf054

@ -20,4 +20,4 @@ shell: ## Start Quart CLI shell
QUART_APP=app.py QUART_SECRETS=config.py QUART_DEBUG=0 QUART_ENV=production .venv/bin/quart shell QUART_APP=app.py QUART_SECRETS=config.py QUART_DEBUG=0 QUART_ENV=production .venv/bin/quart shell
dev: ## Start Quart development web server dev: ## Start Quart development web server
QUART_APP=app.py QUART_SECRETS=config.py QUART_DEBUG=1 QUART_ENV=development .venv/bin/quart run QUART_APP=app.py QUART_SECRETS=config.py QUART_DEBUG=1 QUART_ENV=development QUART_RELOAD=true .venv/bin/quart run

@ -1,2 +1,6 @@
# lza-quart-app # xmrbackers
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,8 +1,16 @@
from xmrbackers.factory import create_app #!/usr/bin/env python3
from os import environ
from xmrbackers import create_app
app = create_app() app = create_app()
if __name__ == '__main__': if __name__ == '__main__':
# print('running')
# environ['QUART_SECRETS'] = 'config.py'
# environ['QUART_DEBUG'] = '1'
# environ['QUART_ENV'] = 'development'
app.run(debug=True, use_reloader=True) app.run(debug=True, use_reloader=True)

@ -3,6 +3,8 @@ DB_USER=xmrbackers
DB_NAME=xmrbackers DB_NAME=xmrbackers
DB_HOST=localhost DB_HOST=localhost
PLATFORM_WALLET=xxxxxx
XMR_WALLET_PATH=/data/xmr-wallet XMR_WALLET_PATH=/data/xmr-wallet
XMR_WALLET_PASS=xxxxxxxxxxxxxxxxxxx XMR_WALLET_PASS=xxxxxxxxxxxxxxxxxxx
XMR_WALLET_RPC_USER=xxxxxxxxxx XMR_WALLET_RPC_USER=xxxxxxxxxx

@ -1,8 +1,9 @@
from dotenv import load_dotenv
from secrets import token_urlsafe from secrets import token_urlsafe
from datetime import timedelta from datetime import timedelta
from os import getenv from os import getenv
from dotenv import load_dotenv
load_dotenv() load_dotenv()
@ -11,16 +12,21 @@ SITE_NAME = getenv('SITE_NAME', 'xmrbackers')
SECRET_KEY = getenv('SECRET_KEY') SECRET_KEY = getenv('SECRET_KEY')
STATS_TOKEN = getenv('STATS_TOKEN', token_urlsafe(8)) STATS_TOKEN = getenv('STATS_TOKEN', token_urlsafe(8))
SERVER_NAME = getenv('SERVER_NAME', 'localhost:5000') SERVER_NAME = getenv('SERVER_NAME', 'localhost:5000')
PLATFORM_WALLET = getenv('PLATFORM_WALLET')
# Constants - here for easy template references
CREATOR_SUBSCRIPTION_TERM = 60 # term of how long creator subscriptions are valid for
CREATOR_SUBSCRIPTION_GRACE = 10 # grace period after expiration of creator subscriptions until content is hidden
CREATOR_SUBSCRIPTION_DELETE = 20 # time after grace period until content is deleted
CREATOR_SUBSCRIPTION_FEE_XMR = .15 # default flat rate fee in XMR for creator subscriptions
# Crypto RPC # Crypto RPC
XMR_WALLET_PASS = getenv('XMR_WALLET_PASS') XMR_WALLET_PASS = getenv('XMR_WALLET_PASS')
XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER') XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER')
XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS') XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS')
XMR_WALLET_RPC_PORT = getenv('XMR_WALLET_RPC_PORT', 8000) XMR_WALLET_RPC_PORT = getenv('XMR_WALLET_RPC_PORT', 8000)
XMR_DAEMON_URI = getenv('XMR_DAEMON_URI')
XMR_WALLET_NETWORK = getenv('XMR_WALLET_NETWORK') XMR_WALLET_NETWORK = getenv('XMR_WALLET_NETWORK')
# Database # Database
DB_HOST = getenv('DB_HOST', 'localhost') DB_HOST = getenv('DB_HOST', 'localhost')
DB_PORT = getenv('DB_PORT', 5432) DB_PORT = getenv('DB_PORT', 5432)

@ -36,3 +36,8 @@ class ConfirmSubscription(FlaskForm):
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={"placeholder": "TX ID", "class": "form-control", "type": "text"}) 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"}) tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={"placeholder": "TX Key", "class": "form-control", "type": "text"})
wallet_address = StringField('XMR Address:', validators=[DataRequired()], render_kw={"placeholder": "XMR Address", "class": "form-control", "type": "text"}) wallet_address = StringField('XMR Address:', validators=[DataRequired()], render_kw={"placeholder": "XMR Address", "class": "form-control", "type": "text"})
class ConfirmCreatorSubscription(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"})

@ -89,10 +89,42 @@ class Profile(pw.Model):
database = db database = db
class Transaction(pw.Model):
"""
Transaction model is a simple reference to a Monero transaction so that we can track
which transactions have occurred on-chain and which subscription they correspond to.
"""
id = pw.AutoField()
tx_id = pw.CharField(unique=True)
atomic_xmr = pw.BigIntegerField()
class Meta:
database = db
class CreatorSubscription(pw.Model):
"""
CreatorSubscription model is for tracking subscriptions of creators to the platform.
The first subscription is a flat fee, the following will be based on usage/consumption
and will be re-negotiated every N days.
"""
id = pw.AutoField()
user = pw.ForeignKeyField(User)
tx = pw.ForeignKeyField(Transaction)
create_date = pw.DateTimeField(default=datetime.utcnow)
atomic_xmr = pw.BigIntegerField()
term_hours = pw.IntegerField(default=config.CREATOR_SUBSCRIPTION_TERM * 24)
grace_hours = pw.IntegerField(default=config.CREATOR_SUBSCRIPTION_GRACE * 24)
delete_hours = pw.IntegerField(default=config.CREATOR_SUBSCRIPTION_DELETE * 24)
class Meta:
database = db
class SubscriptionMeta(pw.Model): class SubscriptionMeta(pw.Model):
""" """
SubscriptionMeta model is for the Creator to define details about SubscriptionMeta model is for the Creator to define details about
their subscription plan to release for subscribers. There is no their subscription plan to release for subscribers/backers. There is no
editing in place, only creating new plans; anyone utilizing an editing in place, only creating new plans; anyone utilizing an
existing subscription (by loading it on screen) will be grandfathered in. existing subscription (by loading it on screen) will be grandfathered in.
""" """
@ -120,6 +152,7 @@ class Subscription(pw.Model):
""" """
id = pw.AutoField() id = pw.AutoField()
subscribe_date = pw.DateTimeField(default=datetime.utcnow) subscribe_date = pw.DateTimeField(default=datetime.utcnow)
tx = pw.ForeignKeyField(Transaction)
active = pw.BooleanField(default=True) active = pw.BooleanField(default=True)
creator = pw.ForeignKeyField(User) creator = pw.ForeignKeyField(User)
backer = pw.ForeignKeyField(User) backer = pw.ForeignKeyField(User)

@ -2,11 +2,10 @@ 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 xmrbackers.forms import ConfirmSubscription
from xmrbackers.models import User, Profile, Content from xmrbackers.models import User, Profile, Content
from xmrbackers.models import Subscription, SubscriptionMeta, UserRole from xmrbackers.models import Subscription, SubscriptionMeta, UserRole
from xmrbackers.helpers import check_tx_key from xmrbackers.helpers import check_tx_key
from xmrbackers import config from xmrbackers import config, forms
bp = Blueprint('creator', 'creator') bp = Blueprint('creator', 'creator')
@ -20,6 +19,26 @@ async def all():
) )
return await render_template('creator/creators.html', creators=creators) return await render_template('creator/creators.html', creators=creators)
@bp.route('/creators/join')
@login_required
async def join():
form = forms.ConfirmCreatorSubscription()
if UserRole.creator in current_user.roles:
await flash('You already are a creator!', 'warning')
if not config.PLATFORM_WALLET:
await flash('Platform operator has not setup wallet yet. Try later.', 'warning')
return render_template(
'creator/join.html',
new_fee_xmr=config.CREATOR_SUBSCRIPTION_FEE_XMR,
form=form
)
return redirect(url_for('main.index'))
@bp.route('/creator/<handle>') @bp.route('/creator/<handle>')
async def show(handle): async def show(handle):
creator = User.select().where(User.handle == handle, User.roles.contains_any(UserRole.creator)) creator = User.select().where(User.handle == handle, User.roles.contains_any(UserRole.creator))

@ -0,0 +1,30 @@
{% extends 'includes/base.html' %}
{% block content %}
<h1>Become a Creator</h1>
<p>
First time creators will pay a flat rate of {{ new_fee_xmr }} XMR. </br>
Please send fees 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>Platform Wallet: {{ config.PLATFORM_WALLET }}</p>
<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>
{% endblock %}

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html>
{% include 'includes/head.html' %}
<body>
{% include 'includes/header.html' %}
{% block content %}{% endblock %}
{% include 'includes/footer.html' %}
{% include 'includes/scripts.html' %}
</body>
</html>

@ -30,7 +30,7 @@
new Noty({ new Noty({
type: '{{ _c }}', type: '{{ _c }}',
theme: 'relax', theme: 'relax',
layout: 'topRight', layout: 'topCenter',
text: '{{ message }}', text: '{{ message }}',
timeout: 4500 timeout: 4500
}).show(); }).show();

@ -8,6 +8,12 @@
{% include 'includes/header.html' %} {% include 'includes/header.html' %}
{% if current_user.is_authenticated %}
{% if 2 not in current_user.roles %}
<a href="{{ url_for('creator.join') }}">Become a Creator</a>
{% endif %}
{% endif %}
{% if feed %} {% if feed %}
{% if feed['new_creators'] %} {% if feed['new_creators'] %}
<h2>New Creators</h2> <h2>New Creators</h2>

Loading…
Cancel
Save