revamping entire backend

revamp
lza_menace 2 years ago
parent b7a5d042f7
commit 6dc1b351b8

@ -1,7 +1,7 @@
FROM ubuntu:21.10
FROM ubuntu:22.04
ENV MONERO_HASH 59e16c53b2aff8d9ab7a8ba3279ee826ac1f2480fbb98e79a149e6be23dd9086
ENV MONERO_DL_URL https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.2.0.tar.bz2
ENV MONERO_HASH 937dfcc48d91748dd2e8f58714dfc45d17a0959dff33fc7385bbe06344ff2c16
ENV MONERO_DL_URL https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.1.1.tar.bz2
ENV MONERO_DL_FILE monero.tar.bz2
ENV MONERO_SUMS_FILE sha256sums

@ -3,5 +3,6 @@ from xmrbackers.factory import create_app
app = create_app()
if __name__ == '__main__':
app.run()
app.run(debug=True, use_reloader=True)

@ -1,7 +1,7 @@
#!/bin/sh
export RPC_CREDS="${2}"
export DAEMON_ADDRESS=singapore.node.xmr.pm
export DAEMON_ADDRESS="${3}"
# Define the network we plan to operate our wallet in
if [[ "${1}" == "stagenet" ]]; then
@ -19,7 +19,7 @@ fi
if [[ ! -d /data/wallet ]]; then
monero-wallet-cli ${NETWORK} \
--generate-new-wallet /data/wallet \
--daemon-address http://${DAEMON_ADDRESS}:${PORT} \
--daemon-address ${DAEMON_ADDRESS} \
--trusted-daemon \
--use-english-language-names \
--mnemonic-language English
@ -27,7 +27,7 @@ fi
# Run RPC wallet
monero-wallet-rpc ${NETWORK} \
--daemon-address http://${DAEMON_ADDRESS}:${PORT} \
--daemon-address ${DAEMON_ADDRESS} \
--wallet-file /data/wallet \
--password "" \
--rpc-login ${RPC_CREDS} \

@ -19,8 +19,8 @@ services:
context: .
dockerfile: Dockerfile-monero
ports:
- 127.0.0.1:8000:8000
- 127.0.0.1:${XMR_WALLET_RPC_PORT:-8000}:${XMR_WALLET_RPC_PORT:-8000}
volumes:
- ./data/wallet:/data
- ${DATA_DIR:-./data/wallet}:/data
command:
bash /run_wallet.sh "${XMR_WALLET_NETWORK}" "${XMR_WALLET_RPC_USER}:${XMR_WALLET_RPC_PASS}"
bash /run_wallet.sh "${XMR_WALLET_NETWORK}" "${XMR_WALLET_RPC_USER}:${XMR_WALLET_RPC_PASS}" "${XMR_DAEMON_URI}"

@ -7,7 +7,7 @@ XMR_WALLET_PATH=/data/xmr-wallet
XMR_WALLET_PASS=xxxxxxxxxxxxxxxxxxx
XMR_WALLET_RPC_USER=xxxxxxxxxx
XMR_WALLET_RPC_PASS=xxxxxxxxxxxxxxxxxxx
XMR_WALLET_RPC_ENDPOINT=http://localhost:9090
XMR_WALLET_RPC_PORT=8000
XMR_DAEMON_URI=http://super.fast.node.xmr.pm:38089
XMR_WALLET_NETWORK=stagenet

@ -11,7 +11,7 @@ peewee
requests
SQLAlchemy
WTForms
quart
quart==0.17.0
monero
arrow
flake8

@ -0,0 +1 @@
from xmrbackers.factory import create_app

@ -16,8 +16,10 @@ SERVER_NAME = getenv('SERVER_NAME', 'localhost:5000')
XMR_WALLET_PASS = getenv('XMR_WALLET_PASS')
XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER')
XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS')
XMR_WALLET_RPC_ENDPOINT = getenv('XMR_WALLET_RPC_ENDPOINT')
XMR_WALLET_RPC_PORT = getenv('XMR_WALLET_RPC_PORT', 8000)
XMR_DAEMON_URI = getenv('XMR_DAEMON_URI')
XMR_WALLET_NETWORK = getenv('XMR_WALLET_NETWORK')
# Database
DB_HOST = getenv('DB_HOST', 'localhost')
@ -31,7 +33,7 @@ REDIS_HOST = getenv('REDIS_HOST', 'localhost')
REDIS_PORT = getenv('REDIS_PORT', 6379)
# Sessions
SESSION_LENGTH = int(getenv('SESSION_LENGTH', 30))
SESSION_LENGTH = int(getenv('SESSION_LENGTH', 300))
PERMANENT_SESSION_LIFETIME = timedelta(minutes=SESSION_LENGTH)
MAX_CONTENT_LENGTH = 50 * 1024 * 1024

@ -1,10 +1,8 @@
import quart.flask_patch
from quart import Quart
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from xmrbackers.cli import cli
from xmrbackers import config
async def _setup_db(app: Quart):
@ -20,10 +18,10 @@ def create_app():
app.config.from_envvar('QUART_SECRETS')
@app.before_serving
async def startup():
from xmrbackers.routes import meta, api, auth, creator, post
from xmrbackers.routes import api, auth, creator, post, main
from xmrbackers import filters
await _setup_db(app)
app.register_blueprint(meta.bp)
app.register_blueprint(main.bp)
app.register_blueprint(api.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(creator.bp)
@ -41,5 +39,3 @@ def create_app():
user = User.get_or_none(user_id)
return user
return app
bcrypt = Bcrypt(create_app())

@ -1,12 +1,36 @@
# import quart.flask_patch
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
from wtforms.validators import DataRequired, ValidationError
from monero.address import address
from xmrbackers import config
def is_valid_xmr_address(form, field):
try:
# Ensure the provided address is valid address/subaddress/integrated address
a = address(field.data)
# 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 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"})
class ConfirmSubscription(FlaskForm):
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={"placeholder": "TX ID", "class": "form-control", "type": "text"})

@ -1,8 +1,11 @@
import peewee as pw
import playhouse.postgres_ext as pwpg
from monero.wallet import Wallet
from monero.exceptions import WrongAddress
# from monero.exceptions import WrongAddress
from xmrbackers import config
def check_tx_key(tx_id, tx_key, wallet_address):
try:
check_data = {
@ -10,8 +13,84 @@ def check_tx_key(tx_id, tx_key, wallet_address):
'tx_key': tx_key,
'address': wallet_address
}
w = Wallet(port=8000, user=config.XMR_WALLET_RPC_USER, password=config.XMR_WALLET_RPC_PASS)
w = Wallet(port=config.XMR_WALLET_RPC_PORT, user=config.XMR_WALLET_RPC_USER, password=config.XMR_WALLET_RPC_PASS)
res = w._backend.raw_request('check_tx_key', check_data)
return res
except:
raise Exception('there was a problem i dont feel like writing good code for right now')
def make_wallet_rpc(method, data={}):
try:
w = Wallet(port=config.XMR_WALLET_RPC_PORT, user=config.XMR_WALLET_RPC_USER, password=config.XMR_WALLET_RPC_PASS)
res = w._backend.raw_request(method, data)
return res
except:
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")
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,8 +1,12 @@
from datetime import datetime
from enum import IntEnum, unique
from typing import List
from secrets import token_urlsafe
import peewee as pw
from xmrbackers import config
from xmrbackers.helpers import EnumArrayField, EnumIntField
db = pw.PostgresqlDatabase(
@ -12,16 +16,33 @@ db = pw.PostgresqlDatabase(
host=config.DB_HOST,
)
@unique
class UserRole(IntEnum):
admin = 0
backer = 1
creator = 2
@unique
class ContentType(IntEnum):
text = 0
gallery = 1
stream = 2
class User(pw.Model):
"""
User model is for pure user authentication management
and reporting.
User model is for base user management and reporting.
"""
id = pw.AutoField()
register_date = pw.DateTimeField(default=datetime.now)
last_login_date = pw.DateTimeField(default=datetime.now)
username = pw.CharField(unique=True)
password = pw.CharField()
register_date = pw.DateTimeField(default=datetime.utcnow)
last_login_date = pw.DateTimeField(default=datetime.utcnow)
handle = pw.CharField(unique=True)
wallet_address = pw.CharField(unique=True)
email = pw.CharField(unique=True, null=True)
challenge = pw.CharField(default=token_urlsafe)
roles: List[UserRole] = EnumArrayField(enum_class=UserRole, field_class=pw.IntegerField, default=[UserRole.backer])
@property
def is_authenticated(self):
@ -37,51 +58,38 @@ class User(pw.Model):
@property
def is_admin(self):
return self.admin
return UserRole.admin in self.roles
def get_id(self):
return self.id
def generate_challenge(self):
self.challenge = token_urlsafe(24)
self.save()
return self.challenge
class Meta:
database = db
class CreatorProfile(pw.Model):
class Profile(pw.Model):
"""
CreatorProfile model is for creators to provide metadata about
themselves for their fans or even just the general public.
Profile model is for users to provide metadata about
themselves; Creators for their fans or even just the general public.
Links to social media, contact info, portfolio sites, etc
should go in here.
"""
id = pw.AutoField()
user = pw.ForeignKeyField(User)
create_date = pw.DateTimeField(default=datetime.now)
wallet_address = pw.CharField(null=True)
create_date = pw.DateTimeField(default=datetime.utcnow)
website = pw.CharField(null=True)
twitter_handle = pw.CharField(null=True)
email = pw.CharField(unique=True, null=True)
bio = pw.CharField()
bio = pw.CharField(null=True)
verified = pw.CharField(default=False)
class Meta:
database = db
class BackerProfile(pw.Model):
"""
BackerProfile model is for backers to give contact info
if they wanted to retain communications in some way...ie
recurring emails and/or notifications. For now.
"""
id = pw.AutoField()
user = pw.ForeignKeyField(User, backref='backer_profile')
register_date = pw.DateTimeField(default=datetime.now)
last_login_date = pw.DateTimeField(default=datetime.now)
email = pw.CharField(unique=True, null=True)
class Meta:
database = db
class SubscriptionMeta(pw.Model):
"""
SubscriptionMeta model is for the Creator to define details about
@ -90,8 +98,8 @@ class SubscriptionMeta(pw.Model):
existing subscription (by loading it on screen) will be grandfathered in.
"""
id = pw.AutoField()
create_date = pw.DateTimeField(default=datetime.now)
creator = pw.ForeignKeyField(CreatorProfile)
user = pw.ForeignKeyField(User)
create_date = pw.DateTimeField(default=datetime.utcnow)
atomic_xmr = pw.BigIntegerField()
number_hours = pw.IntegerField()
wallet_address = pw.CharField()
@ -106,33 +114,36 @@ class SubscriptionMeta(pw.Model):
class Subscription(pw.Model):
"""
Subscription model gets created when backers can confirm payment via
the `check_tx_key` RPC method. Once a subscription is in place and is
associated with a user, that user is then elligible to view that
creator's content.
Subscription model gets created when users/backers can confirm payment via
the `check_tx_key` RPC method to the creator's wallet. Once a subscription
is in place and is associated with a user, that user is then elligible to
view that creator's content until the subscription expires (number_hours).
"""
id = pw.AutoField()
subscribe_date = pw.DateTimeField(default=datetime.now)
subscribe_date = pw.DateTimeField(default=datetime.utcnow)
active = pw.BooleanField(default=True)
creator = pw.ForeignKeyField(CreatorProfile)
backer = pw.ForeignKeyField(BackerProfile)
creator = pw.ForeignKeyField(User)
backer = pw.ForeignKeyField(User)
meta = pw.ForeignKeyField(SubscriptionMeta)
class Meta:
database = db
class TextPost(pw.Model):
class Content(pw.Model):
"""
TextPost model is the first content type available to post. Metadata
here is basic for now, let's proof out the other components first.
Content model represents any uploaded content from a creator which is only
viewable by backers with an active subscription.
"""
id = pw.AutoField()
post_date = pw.DateTimeField(default=datetime.now)
post_date = pw.DateTimeField(default=datetime.utcnow)
hidden = pw.BooleanField(default=False)
content = pw.TextField()
title = pw.CharField()
last_edit_date = pw.DateTimeField(default=datetime.now)
creator = pw.ForeignKeyField(CreatorProfile)
last_edit_date = pw.DateTimeField(default=datetime.utcnow)
creator = pw.ForeignKeyField(User)
type: List[ContentType] = EnumIntField(enum_class=ContentType, default=ContentType.text)
class Meta:
database = db

@ -2,8 +2,8 @@ from quart import Blueprint, render_template
from quart import flash, redirect, url_for
from flask_login import login_user, logout_user, current_user
from xmrbackers.factory import bcrypt
from xmrbackers.forms import UserAuth
from xmrbackers.forms import UserLogin, UserRegistration, UserChallenge
from xmrbackers.helpers import make_wallet_rpc
from xmrbackers.models import User
@ -11,60 +11,86 @@ bp = Blueprint('auth', 'auth')
@bp.route("/register", methods=["GET", "POST"])
async def register():
form = UserAuth()
form = UserRegistration()
if current_user.is_authenticated:
await flash('Already registered and authenticated.')
return redirect(url_for('meta.index'))
return redirect(url_for('main.index'))
if form.validate_on_submit():
# Check if username already exists
# Check if handle already exists
user = User.select().where(
User.username == form.username.data
User.handle == form.handle.data
).first()
if user:
await flash('This username is already registered.')
await flash('This handle is already registered.')
return redirect(url_for('auth.login'))
# Save new user
user = User(
username=form.username.data,
password=bcrypt.generate_password_hash(form.password.data).decode('utf8'),
handle=form.handle.data,
wallet_address=form.wallet_address.data,
)
user.save()
login_user(user)
return redirect(url_for('meta.index'))
return redirect(url_for('main.index'))
return await render_template("auth/register.html", form=form)
@bp.route("/login", methods=["GET", "POST"])
async def login():
form = UserAuth()
form = UserLogin()
if current_user.is_authenticated:
await flash('Already logged in.')
return redirect(url_for('meta.index'))
return redirect(url_for('main.index'))
if form.validate_on_submit():
# Check if user doesn't exist
user = User.select().where(
User.username == form.username.data
User.handle == form.handle.data
).first()
if not user:
await flash('Invalid username or password.')
await flash('That handle does not exist.')
return redirect(url_for('auth.login'))
return redirect(url_for('auth.challenge', handle=user.handle))
# Check if password is correct
password_matches = bcrypt.check_password_hash(
user.password,
form.password.data
)
if not password_matches:
await flash('Invalid username or password.')
return redirect(url_for('auth.login'))
return await render_template("auth/login.html", form=form)
login_user(user)
return redirect(url_for('meta.index'))
@bp.route("/login/challenge/<handle>", methods=["GET", "POST"])
async def challenge(handle):
form = UserChallenge()
user = User.select().where(User.handle == handle).first()
if not user:
await flash('User does not exist.')
return redirect(url_for('main.index'))
return await render_template("auth/login.html", form=form)
if current_user.is_authenticated:
await flash('Already logged in.')
return redirect(url_for('main.index'))
if form.validate_on_submit():
data = {
'data': user.challenge,
'address': user.wallet_address,
'signature': form.signature.data
}
res = make_wallet_rpc('verify', data)
print(res)
from quart import jsonify
return jsonify(res)
# # Check if user doesn't exist
# user = User.select().where(
# User.handle == form.handle.data
# ).first()
# if not user:
# await flash('That handle does not exist.')
# return redirect(url_for('auth.login'))
return redirect(url_for('main.index'))
return await render_template(
'auth/challenge.html',
user=user,
form=form
)
@bp.route("/logout")
async def logout():
@ -72,7 +98,7 @@ async def logout():
logout_user()
else:
await flash('Not authenticated!')
return redirect(url_for('meta.index'))
return redirect(url_for('main.index'))
# @auth_bp.route("/reset/<string:hash>", methods=["GET", "POST"])
# def reset(hash):

@ -3,8 +3,8 @@ from flask_login import current_user, login_required
from monero.wallet import Wallet
from xmrbackers.forms import ConfirmSubscription
from xmrbackers.models import User, CreatorProfile, BackerProfile, TextPost
from xmrbackers.models import Subscription, SubscriptionMeta
from xmrbackers.models import User, Profile, Content
from xmrbackers.models import Subscription, SubscriptionMeta, UserRole
from xmrbackers.helpers import check_tx_key
from xmrbackers import config
@ -13,22 +13,21 @@ bp = Blueprint('creator', 'creator')
@bp.route('/creators')
async def all():
creators = CreatorProfile.select().order_by(
CreatorProfile.create_date.desc()
creators = User.select().join(Profile).where(
User.roles.contains_any(UserRole.creator)
).order_by(
Profile.create_date.desc()
)
return await render_template('creator/creators.html', creators=creators)
@bp.route('/creator/<username>')
async def show(username):
user = User.select().where(User.username == username)
creator = CreatorProfile.select().where(
CreatorProfile.user == user
).first()
@bp.route('/creator/<handle>')
async def show(handle):
creator = User.select().where(User.handle == handle, User.roles.contains_any(UserRole.creator))
if creator:
posts = TextPost.select().where(
TextPost.creator == creator,
TextPost.hidden == False
).order_by(TextPost.post_date.desc())
posts = Content.select().where(
Content.creator == creator,
Content.hidden == False
).order_by(Content.post_date.desc())
return await render_template(
'creator/creator.html',
creator=creator,
@ -38,63 +37,63 @@ async def show(username):
await flash('That creator does not exist.')
return redirect(url_for('meta.index'))
@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('/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():
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))
# @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():
# 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'))
# 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'))

@ -0,0 +1,33 @@
from quart import Blueprint, render_template
from flask_login import current_user
from xmrbackers.models import User, Profile, Subscription, Content, UserRole
bp = Blueprint('main', 'main')
@bp.route('/')
async def index():
feed = None
if current_user.is_authenticated:
new_creators = User.select().where(
User.roles.contains_any(UserRole.creator)
).order_by(User.register_date.desc()).execute()
active_subscriptions = Subscription.select().where(
Subscription.active == True,
Subscription.backer == current_user
).order_by(Subscription.subscribe_date.desc()).execute()
new_posts = Content.select().where(
Content.hidden == False,
Content.creator in [c.creator for c in active_subscriptions]
).order_by(Content.post_date.desc()).execute()
feed = {
'new_creators': new_creators,
'new_posts': new_posts,
'active_subscriptions': active_subscriptions
}
return await render_template(
'index.html',
feed=feed
)

@ -1,30 +0,0 @@
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 flask_login import current_user
from xmrbackers.models import TextPost, Subscription
from xmrbackers.models import Content, Subscription
bp = Blueprint('post', 'post')
@bp.route('/post/<int:post_id>')
async def show(post_id):
post = TextPost.get_or_none(post_id)
if post:
if current_user.is_anonymous:
await flash('You must login to view this post.')
return redirect(url_for('creator.show', username=post.creator.user.username))
user_subscriptions = Subscription.select().where(
Subscription.active == True,
Subscription.backer == current_user.backer_profile.first()
)
if user_subscriptions:
return await render_template('post/show.html', post=post)
else:
await flash('Viewing posts requires a subscription.')
return redirect(url_for('creator.subscription', username=post.creator.user.username))
else:
flash('That post does not exist.')
return redirect(url_for('meta.index'))
# @bp.route('/post/<int:post_id>')
# async def show(post_id):
# post = TextPost.get_or_none(post_id)
# if post:
# if current_user.is_anonymous:
# await flash('You must login to view this post.')
# return redirect(url_for('creator.show', username=post.creator.user.username))
# user_subscriptions = Subscription.select().where(
# Subscription.active == True,
# Subscription.backer == current_user.backer_profile.first()
# )
# if user_subscriptions:
# return await render_template('post/show.html', post=post)
# else:
# await flash('Viewing posts requires a subscription.')
# return redirect(url_for('creator.subscription', username=post.creator.user.username))
# else:
# flash('That post does not exist.')
# return redirect(url_for('meta.index'))

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
{% include 'includes/head.html' %}
<body class="is-preload landing">
<div id="page-wrapper">
{% include 'includes/header.html' %}
<section id="banner">
<div class="content">
<header>
<h2>Challenge</h2>
<p>Handle: {{ user.handle }}</p>
<p>Challenge: {{ user.challenge }}</p>
<p>Wallet Address: {{ user.wallet_address }}</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="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>

@ -9,9 +9,9 @@
</nav>
<p>Authenticated: {{ current_user.is_authenticated }}</p>
{% if current_user.is_authenticated %}
<p>Username: {{ current_user.username }}</p>
<p>Username: {{ current_user.handle }}</p>
<p>Email: {{ current_user.email }}</p>
<p>Password: {{ current_user.password }}</p>
<p>Wallet Address: {{ current_user.wallet_address }}</p>
{% endif %}
</header>

@ -9,14 +9,24 @@
{% include 'includes/header.html' %}
{% if feed %}
{% for k in feed %}
<h2>{{ k }}</h2>
{% if k == 'new_creators' %}
<p><a href="{{ url_for('creator.show', username=feed[k]) }}">{{ feed[k] }}</a></p>
{% else %}
<p>{{ feed[k]}}</p>
{% endif %}
{% endfor %}
{% if feed['new_creators'] %}
<h2>New Creators</h2>
{% for c in feed['new_creators'] %}
<p><a href="{{ url_for('creator.show', handle=c.handle) }}">{{ c.handle }}</a></p>
{% endfor %}
{% endif %}
{% if feed['new_posts'] %}
<h2>New Posts</h2>
{% for p in feed['new_posts'] %}
<p>{{ p.id }}</p>
{% endfor %}
{% endif %}
{% if feed['active_subscriptions'] %}
<h2>Active Subscriptions</h2>
{% for s in feed['active_subscriptions'] %}
<p>{{ s.id }}</p>
{% endfor %}
{% endif %}
{% else %}
<section id="banner">

Loading…
Cancel
Save