redoing cli, getting basic mod queue going, setting up profile, etc

main
lza_menace 3 years ago
parent 2a9b1995ce
commit ba76f9e5bb

@ -0,0 +1,51 @@
import click
from flask import Blueprint
from suchwowx.models import Moderator, User
from suchwowx.factory import db
bp = Blueprint('mod', 'mod')
@bp.cli.command('list')
def list():
"""
List current server moderators.
"""
for mod in Moderator.query.all():
click.echo(mod.user.handle)
@bp.cli.command('add')
@click.argument('moderator_handle')
def add(moderator_handle):
"""
Add server moderators by user handle.
"""
user = User.query.filter(User.handle == moderator_handle).first()
if user:
mod = Moderator.query.filter(Moderator.user_id == user.id).first()
if mod is None:
m = Moderator(user_id=user.id)
db.session.add(m)
db.session.commit()
click.echo(f'[+] Added moderator status to `{moderator_handle}`')
else:
click.echo('[.] That is not a valid user.')
@bp.cli.command('remove')
@click.argument('moderator_handle')
def remove(moderator_handle):
"""
Remove server moderator by user handle.
"""
user = User.query.filter(User.handle == moderator_handle).first()
if user:
mod = Moderator.query.filter(Moderator.user_id == user.id).first()
if mod:
db.session.delete(mod)
db.session.commit()
click.echo(f'[-] Removed moderator status from `{moderator_handle}`')
else:
click.echo(f'[.] That user is not a moderator.')
else:
click.echo('[.] That is not a valid user.')

@ -8,6 +8,7 @@ SECRET_KEY = getenv('SECRET_KEY', 'yyyyyyyyyyyyy') # whatever you wan
DATA_FOLDER = getenv('DATA_FOLDER', '/path/to/uploads') # some stable storage path DATA_FOLDER = getenv('DATA_FOLDER', '/path/to/uploads') # some stable storage path
SERVER_NAME = getenv('SERVER_NAME', '127.0.0.1:5000') # name of your DNS resolvable site (.com) SERVER_NAME = getenv('SERVER_NAME', '127.0.0.1:5000') # name of your DNS resolvable site (.com)
IPFS_SERVER = getenv('IPFS_SERVER', 'http://127.0.0.1:8080') # ip/endpoint of ipfs explorer IPFS_SERVER = getenv('IPFS_SERVER', 'http://127.0.0.1:8080') # ip/endpoint of ipfs explorer
MOD_MODE = getenv('MOD_MODE', 1) # mod mode enabled by default (enforce queue)
# Cache # Cache
CACHE_HOST = getenv('CACHE_HOST', 'localhost') CACHE_HOST = getenv('CACHE_HOST', 'localhost')

@ -43,10 +43,13 @@ def create_app():
with app.app_context(): with app.app_context():
from suchwowx import filters, cli from suchwowx import filters, cli
from suchwowx.routes import api, meme, meta from suchwowx.routes import api, meme, meta, user
from suchwowx.cli import mod, cli
app.register_blueprint(filters.bp) app.register_blueprint(filters.bp)
app.register_blueprint(cli.bp)
app.register_blueprint(api.bp) app.register_blueprint(api.bp)
app.register_blueprint(meme.bp) app.register_blueprint(meme.bp)
app.register_blueprint(meta.bp) app.register_blueprint(meta.bp)
app.register_blueprint(user.bp)
app.register_blueprint(mod.bp)
app.register_blueprint(cli.bp)
return app return app

@ -6,7 +6,6 @@ from suchwowx.factory import w3
def verify_signature(message, signature, public_address): def verify_signature(message, signature, public_address):
msg = encode_defunct(text=message) msg = encode_defunct(text=message)
recovered = w3.eth.account.recover_message(msg, signature=signature) recovered = w3.eth.account.recover_message(msg, signature=signature)
print(f'found recovered: {recovered}')
if recovered.lower() == public_address.lower(): if recovered.lower() == public_address.lower():
return True return True
else: else:

@ -10,6 +10,16 @@ from suchwowx import config
def rand_id(): def rand_id():
return uuid4().hex return uuid4().hex
class Moderator(db.Model):
__tablename__ = 'moderators'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
user = db.relationship('User', back_populates='moderator')
def __rep__(self):
return self.user.handle
class User(db.Model): class User(db.Model):
__tablename__ = 'users' __tablename__ = 'users'
@ -20,10 +30,14 @@ class User(db.Model):
public_address = db.Column(db.String(180)) public_address = db.Column(db.String(180))
nonce = db.Column(db.String(180), default=rand_id()) nonce = db.Column(db.String(180), default=rand_id())
nonce_date = db.Column(db.DateTime, default=datetime.utcnow()) nonce_date = db.Column(db.DateTime, default=datetime.utcnow())
handle = db.Column(db.String(40), unique=True, nullable=True) handle = db.Column(db.String(40), unique=True)
bio = db.Column(db.String(600), nullable=True) bio = db.Column(db.String(600), nullable=True)
profile_image = db.Column(db.String(300), nullable=True) profile_image = db.Column(db.String(300), nullable=True)
website_url = db.Column(db.String(120), nullable=True) website_url = db.Column(db.String(120), nullable=True)
moderator = db.relationship('Moderator', back_populates='user')
def __repr__(self):
return str(self.handle)
@property @property
def is_authenticated(self): def is_authenticated(self):
@ -41,9 +55,21 @@ class User(db.Model):
def is_admin(self): def is_admin(self):
return self.admin return self.admin
def is_moderator(self):
return len(self.moderator) > 0
def get_id(self): def get_id(self):
return self.id return self.id
def get_profile_image(self, full=True):
if self.profile_image:
if full:
return url_for('meta.uploaded_file', filename=self.profile_image)
else:
return self.profile_image
else:
return '/static/img/logo.png'
def generate_nonce(self): def generate_nonce(self):
return rand_id() return rand_id()
@ -65,11 +91,11 @@ class Meme(db.Model):
id = db.Column(db.String(80), default=rand_id, primary_key=True) id = db.Column(db.String(80), default=rand_id, primary_key=True)
create_date = db.Column(db.DateTime, default=datetime.utcnow()) create_date = db.Column(db.DateTime, default=datetime.utcnow())
file_name = db.Column(db.String(200), unique=True) file_name = db.Column(db.String(200), unique=True)
meta_ipfs_hash = db.Column(db.String(100), unique=True) meta_ipfs_hash = db.Column(db.String(100), unique=True, nullable=True)
meme_ipfs_hash = db.Column(db.String(100), unique=True) meme_ipfs_hash = db.Column(db.String(100), unique=True, nullable=True)
title = db.Column(db.String(50)) title = db.Column(db.String(50))
description = db.Column(db.String(400)) description = db.Column(db.String(400), nullable=True)
creator_handle = db.Column(db.String(50)) creator_handle = db.Column(db.String(50), nullable=True)
def __repr__(self): def __repr__(self):
return str(f'meme-{self.id}') return str(f'meme-{self.id}')

@ -18,7 +18,7 @@ bp = Blueprint('meme', 'meme')
@bp.route('/') @bp.route('/')
def index(): def index():
memes = Meme.query.filter().order_by(Meme.create_date.desc()) memes = Meme.query.filter(Meme.meta_ipfs_hash != None).order_by(Meme.create_date.desc())
w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:9650')) w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:9650'))
contract_address = w3.toChecksumAddress(config.CONTRACT_ADDRESS) contract_address = w3.toChecksumAddress(config.CONTRACT_ADDRESS)
contract_abi = config.CONTRACT_ABI contract_abi = config.CONTRACT_ABI
@ -29,6 +29,16 @@ def index():
# total_supply = contract.functions.totalSupply().call() # total_supply = contract.functions.totalSupply().call()
return render_template('index.html', memes=memes, contract=contract) return render_template('index.html', memes=memes, contract=contract)
@bp.route('/mod')
def mod():
if not current_user.is_moderator():
flash('You are not a moderator', 'warning')
return redirect(url_for('meme.index'))
memes = Meme.query.filter(
Meme.meta_ipfs_hash == None
).order_by(Meme.create_date.desc())
return render_template('review.html', memes=memes)
@bp.route('/publish', methods=['GET', 'POST']) @bp.route('/publish', methods=['GET', 'POST'])
def publish(): def publish():
if not current_user.is_authenticated: if not current_user.is_authenticated:
@ -60,6 +70,9 @@ def publish():
full_path = f'{config.DATA_FOLDER}/uploads/{filename}' full_path = f'{config.DATA_FOLDER}/uploads/{filename}'
file.save(full_path) file.save(full_path)
try: try:
meta_hash = None
artwork_hash = None
if current_user.verified or current_user.is_moderator():
client = ipfsApi.Client('127.0.0.1', 5001) client = ipfsApi.Client('127.0.0.1', 5001)
artwork_hashes = client.add(full_path) artwork_hashes = client.add(full_path)
print(artwork_hashes) print(artwork_hashes)
@ -77,6 +90,7 @@ def publish():
} }
meta_hash = client.add_json(meta) meta_hash = client.add_json(meta)
print(f'[+] Uploaded metadata to IPFS: {meta_hash}') print(f'[+] Uploaded metadata to IPFS: {meta_hash}')
flash('Published new meme to IPFS', 'success')
meme = Meme( meme = Meme(
file_name=filename, file_name=filename,
meta_ipfs_hash=meta_hash, meta_ipfs_hash=meta_hash,
@ -87,7 +101,8 @@ def publish():
) )
db.session.add(meme) db.session.add(meme)
db.session.commit() db.session.commit()
return redirect('/') flash('Published new meme to database', 'success')
return redirect(url_for('meme.index'))
except ConnectionError: except ConnectionError:
flash('[!] Unable to connect to local ipfs', 'error') flash('[!] Unable to connect to local ipfs', 'error')
except Exception as e: except Exception as e:
@ -103,4 +118,10 @@ def meme(meme_id):
meme = Meme.query.filter(Meme.id == meme_id).first() meme = Meme.query.filter(Meme.id == meme_id).first()
if not meme: if not meme:
return redirect('/') return redirect('/')
if not meme.meta_ipfs_hash and not current_user.is_authenticated:
flash('You need to be a moderator to view that meme', 'warning')
return redirect(url_for('meme.index'))
elif not meme.meta_ipfs_hash and not current_user.is_moderator():
flash('You need to be a moderator to view that meme', 'warning')
return redirect(url_for('meme.index'))
return render_template('meme.html', meme=meme) return render_template('meme.html', meme=meme)

@ -0,0 +1,16 @@
from flask import Blueprint, render_template
from flask import redirect, url_for
from flask_login import logout_user
from suchwowx.models import User
from suchwowx import config
bp = Blueprint('user', 'user')
@bp.route('/user/<handle>')
def show(handle):
user = User.query.filter(User.handle == handle).first()
if not user:
return redirect(url_for('meme.index'))
return render_template('profile.html', user=user)

@ -8,7 +8,11 @@
{% if request.path == '/' %} {% if request.path == '/' %}
<a class="button is-secondary" href="{{ url_for('meta.about') }}" up-target=".container">About</a> <a class="button is-secondary" href="{{ url_for('meta.about') }}" up-target=".container">About</a>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a class="button is-secondary" href="{{ url_for('user.show', handle=current_user.handle) }}" up-follow=".container" up-preload>Profile</a>
<a class="button is-danger" href="{{ url_for('meta.disconnect') }}">Disconnect</a> <a class="button is-danger" href="{{ url_for('meta.disconnect') }}">Disconnect</a>
{% if current_user.is_moderator() %}
<a class="button is-warning" href="{{ url_for('meme.mod') }}">Mod</a>
{% endif %}
<a class="button is-primary" href="{{ url_for('meme.publish') }}">New Meme</a> <a class="button is-primary" href="{{ url_for('meme.publish') }}">New Meme</a>
{% else %} {% else %}
<a class="button is-primary" href="#" id="metamaskConnect">Connect</a> <a class="button is-primary" href="#" id="metamaskConnect">Connect</a>

@ -1,3 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
{% include 'includes/head.html' %} {% include 'includes/head.html' %}
@ -8,22 +9,34 @@
{% include 'includes/navbar.html' %} {% include 'includes/navbar.html' %}
{% if meme %} {% if meme %}
{% if meme.meta_ipfs_hash %}
{% set meta_url = config.IPFS_SERVER + "/ipfs/" + meme.meta_ipfs_hash %}
{% set meme_url = config.IPFS_SERVER + "/ipfs/" + meme.meme_ipfs_hash %}
{% else %}
{% set meta_url = "" %}
{% set meme_url = url_for('meta.uploaded_file', filename=meme.file_name, _external=True) %}
{% endif %}
<div id="screen"> <div id="screen">
<div class="screen"> <div class="screen">
{% if meme.file_name.endswith('mp4') %} {% if meme.file_name.endswith('mp4') %}
<video style="max-height: 60vh!important;max-width:100%;" {% if not request.MOBILE %}autoplay{% else %}controls{% endif %} muted loop> <video style="max-height: 60vh!important;max-width:100%;" {% if not request.MOBILE %}autoplay{% else %}controls{% endif %} muted loop>
<source src="{{ url_for('meta.uploaded_file', filename=meme.file_name) }}" type="video/mp4"> <source src="{{ meme_url }}" type="video/mp4">
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
{% else %} {% else %}
<img src="{{ url_for('meta.uploaded_file', filename=meme.file_name) }}" id="memeImage" /> <img src="{{ meme_url }}" id="memeImage" />
{% endif %} {% endif %}
</br> </br>
<p>Title: <strong>{{ meme.title }}</strong></p> <p>Title: <strong>{{ meme.title }}</strong></p>
<p>Description: <strong>{{ meme.description }}</strong></p> <p>Description: <strong>{{ meme.description }}</strong></p>
<p>Creator handle: <a up-layer="new" up-align="center">{{ meme.creator_handle }}</a></p> <p>Creator handle: <a href="{{ url_for('user.show', handle=meme.creator_handle) }}" up-follow=".container">{{ meme.creator_handle }}</a></p>
<p>Meta IPFS: <a href="{{ config.IPFS_SERVER }}/ipfs/{{ meme.meta_ipfs_hash }}" target=_self up-preload up-follow=".container">{{ meme.meta_ipfs_hash }}</a></p> {% if meme.meta_ipfs_hash %}
<p>Meme IPFS: <a href="{{ config.IPFS_SERVER }}/ipfs/{{ meme.meme_ipfs_hash }}" target=_self up-preload up-follow=".container">{{ meme.meme_ipfs_hash }}</a></p> <p>Meta IPFS: <a href="{{ meta_url }}" target=_self up-preload up-follow=".container">{{ meme.meta_ipfs_hash }}</a></p>
{% endif %}
{% if meme.meme_ipfs_hash %}
<p>Meme IPFS: <a href="{{ meme_url }}" target=_self up-preload up-follow=".container">{{ meme.meme_ipfs_hash }}</a></p>
{% endif %}
<p>Meme ID: <code>{{ meme }}</code></p> <p>Meme ID: <code>{{ meme }}</code></p>
</div> </div>
</div> </div>

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
{% include 'includes/head.html' %}
<body>
<section class="section">
<div class="container">
{% include 'includes/navbar.html' %}
{% if user %}
<div id="screen">
<div class="screen">
<p><strong>From Avax Chain</strong></p>
<p>Handle: <input type="text" placeholder="{{ user.handle }}" value="{{ user.handle }}"></input>
<img src="{{ user.get_profile_image() }}" id="profileImage" />
</br></br>
<p><strong>From Local Database</strong></p>
<p>Register Date: <strong>{{ user.register_date }}</strong></p>
<p>Login Date: <strong>{{ user.last_login_date }}</strong></p>
<p>Moderator: <strong>{{ user.is_moderator() }}</strong></p>
<p>Verified: <strong>{{ user.verified }}</strong></p>
{% if user.bio %}
<p>Bio: {{ user.bio }}</p>
{% endif %}
{% if user.website %}
<p>Website: <a href="{{ user.website_url }}" target="_blank">{{ user.website_url }}</a></p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</section>
</body>
</html>

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
{% include 'includes/head.html' %}
<body>
<section class="section">
<div class="container">
{% include 'includes/navbar.html' %}
{% if memes %}
{% for _meme in memes | batch(4) %}
<div class="columns">
{% for meme in _meme %}
<div class="card">
<div class="card-image">
<figure class="image">
<a href="{{ url_for('meme.meme', meme_id=meme.id) }}" up-preload up-follow=".container">
{% if meme.file_name.endswith('mp4') %}
<video class="img-fluid" {% if not request.MOBILE %}autoplay{% else %}controls{% endif %} muted loop>
<source src="{{ url_for('meta.uploaded_file', filename=meme.file_name) }}" type="video/mp4">
Your browser does not support the video tag.
</video>
{% else %}
<img alt="{{ meme.title }}" src="{{ url_for('meta.uploaded_file', filename=meme.file_name) }}" width="200px" class="img-fluid" style="" />
{% endif %}
</a>
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<!-- <figure class="image is-48x48">
<img src="https://bulma.io/images/placeholders/96x96.png" alt="Placeholder image">
</figure> -->
</div>
<div class="media-content">
<p class="title is-4">John Smith</p>
<p class="subtitle is-6">@johnsmith</p>
</div>
</div>
<div class="content">
{{ meme.description }}
<br>
<time datetime="2016-1-1">{{ meme.create_date.strftime('%H:%M UTC - %d %b %Y') }}</time>
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% endif %}
</div>
</section>
{% include 'includes/footer.html' %}
</body>
</html>
Loading…
Cancel
Save