From ba76f9e5bb97e1ba648c9921b51f8fa3a6d3eddd Mon Sep 17 00:00:00 2001 From: lza_menace Date: Tue, 28 Dec 2021 01:51:47 -0800 Subject: [PATCH] redoing cli, getting basic mod queue going, setting up profile, etc --- manage.sh | 0 suchwowx/cli/__init__.py | 0 suchwowx/{ => cli}/cli.py | 0 suchwowx/cli/mod.py | 51 +++++++++++++++++++++ suchwowx/config.py | 1 + suchwowx/factory.py | 7 ++- suchwowx/helpers.py | 1 - suchwowx/models.py | 38 +++++++++++++--- suchwowx/routes/meme.py | 57 ++++++++++++++++-------- suchwowx/routes/user.py | 16 +++++++ suchwowx/templates/includes/navbar.html | 4 ++ suchwowx/templates/meme.html | 23 +++++++--- suchwowx/templates/profile.html | 36 +++++++++++++++ suchwowx/templates/review.html | 59 +++++++++++++++++++++++++ 14 files changed, 261 insertions(+), 32 deletions(-) mode change 100644 => 100755 manage.sh create mode 100644 suchwowx/cli/__init__.py rename suchwowx/{ => cli}/cli.py (100%) create mode 100644 suchwowx/cli/mod.py create mode 100644 suchwowx/routes/user.py create mode 100644 suchwowx/templates/profile.html create mode 100644 suchwowx/templates/review.html diff --git a/manage.sh b/manage.sh old mode 100644 new mode 100755 diff --git a/suchwowx/cli/__init__.py b/suchwowx/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/suchwowx/cli.py b/suchwowx/cli/cli.py similarity index 100% rename from suchwowx/cli.py rename to suchwowx/cli/cli.py diff --git a/suchwowx/cli/mod.py b/suchwowx/cli/mod.py new file mode 100644 index 0000000..4018a01 --- /dev/null +++ b/suchwowx/cli/mod.py @@ -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.') diff --git a/suchwowx/config.py b/suchwowx/config.py index 1480f16..3db5a57 100644 --- a/suchwowx/config.py +++ b/suchwowx/config.py @@ -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 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 +MOD_MODE = getenv('MOD_MODE', 1) # mod mode enabled by default (enforce queue) # Cache CACHE_HOST = getenv('CACHE_HOST', 'localhost') diff --git a/suchwowx/factory.py b/suchwowx/factory.py index fb10eac..8a38c9c 100644 --- a/suchwowx/factory.py +++ b/suchwowx/factory.py @@ -43,10 +43,13 @@ def create_app(): with app.app_context(): 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(cli.bp) app.register_blueprint(api.bp) app.register_blueprint(meme.bp) app.register_blueprint(meta.bp) + app.register_blueprint(user.bp) + app.register_blueprint(mod.bp) + app.register_blueprint(cli.bp) return app diff --git a/suchwowx/helpers.py b/suchwowx/helpers.py index 5c1c99e..41e6526 100644 --- a/suchwowx/helpers.py +++ b/suchwowx/helpers.py @@ -6,7 +6,6 @@ from suchwowx.factory import w3 def verify_signature(message, signature, public_address): msg = encode_defunct(text=message) recovered = w3.eth.account.recover_message(msg, signature=signature) - print(f'found recovered: {recovered}') if recovered.lower() == public_address.lower(): return True else: diff --git a/suchwowx/models.py b/suchwowx/models.py index 666f378..0fe5bbc 100644 --- a/suchwowx/models.py +++ b/suchwowx/models.py @@ -10,6 +10,16 @@ from suchwowx import config def rand_id(): 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): __tablename__ = 'users' @@ -20,10 +30,14 @@ class User(db.Model): public_address = db.Column(db.String(180)) nonce = db.Column(db.String(180), default=rand_id()) 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) profile_image = db.Column(db.String(300), 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 def is_authenticated(self): @@ -40,10 +54,22 @@ class User(db.Model): @property def is_admin(self): return self.admin - + + def is_moderator(self): + return len(self.moderator) > 0 + def get_id(self): 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): return rand_id() @@ -65,11 +91,11 @@ class Meme(db.Model): id = db.Column(db.String(80), default=rand_id, primary_key=True) create_date = db.Column(db.DateTime, default=datetime.utcnow()) file_name = db.Column(db.String(200), unique=True) - meta_ipfs_hash = db.Column(db.String(100), unique=True) - meme_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, nullable=True) title = db.Column(db.String(50)) - description = db.Column(db.String(400)) - creator_handle = db.Column(db.String(50)) + description = db.Column(db.String(400), nullable=True) + creator_handle = db.Column(db.String(50), nullable=True) def __repr__(self): return str(f'meme-{self.id}') diff --git a/suchwowx/routes/meme.py b/suchwowx/routes/meme.py index 2b0e833..1b498cf 100644 --- a/suchwowx/routes/meme.py +++ b/suchwowx/routes/meme.py @@ -18,7 +18,7 @@ bp = Blueprint('meme', 'meme') @bp.route('/') 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')) contract_address = w3.toChecksumAddress(config.CONTRACT_ADDRESS) contract_abi = config.CONTRACT_ABI @@ -29,6 +29,16 @@ def index(): # total_supply = contract.functions.totalSupply().call() 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']) def publish(): if not current_user.is_authenticated: @@ -60,23 +70,27 @@ def publish(): full_path = f'{config.DATA_FOLDER}/uploads/{filename}' file.save(full_path) try: - client = ipfsApi.Client('127.0.0.1', 5001) - artwork_hashes = client.add(full_path) - print(artwork_hashes) - artwork_hash = artwork_hashes[0]['Hash'] - print(artwork_hash) - print(f'[+] Uploaded artwork to IPFS: {artwork_hash}') - meta = { - 'name': title, - 'description': description, - 'image': f'ipfs://{artwork_hash}', - 'by': creator, - 'properties': { - 'creator': creator + meta_hash = None + artwork_hash = None + if current_user.verified or current_user.is_moderator(): + client = ipfsApi.Client('127.0.0.1', 5001) + artwork_hashes = client.add(full_path) + print(artwork_hashes) + artwork_hash = artwork_hashes[0]['Hash'] + print(artwork_hash) + print(f'[+] Uploaded artwork to IPFS: {artwork_hash}') + meta = { + 'name': title, + 'description': description, + 'image': f'ipfs://{artwork_hash}', + 'by': creator, + 'properties': { + 'creator': creator + } } - } - meta_hash = client.add_json(meta) - print(f'[+] Uploaded metadata to IPFS: {meta_hash}') + meta_hash = client.add_json(meta) + print(f'[+] Uploaded metadata to IPFS: {meta_hash}') + flash('Published new meme to IPFS', 'success') meme = Meme( file_name=filename, meta_ipfs_hash=meta_hash, @@ -87,7 +101,8 @@ def publish(): ) db.session.add(meme) db.session.commit() - return redirect('/') + flash('Published new meme to database', 'success') + return redirect(url_for('meme.index')) except ConnectionError: flash('[!] Unable to connect to local ipfs', 'error') except Exception as e: @@ -103,4 +118,10 @@ def meme(meme_id): meme = Meme.query.filter(Meme.id == meme_id).first() if not meme: 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) diff --git a/suchwowx/routes/user.py b/suchwowx/routes/user.py new file mode 100644 index 0000000..4f33e33 --- /dev/null +++ b/suchwowx/routes/user.py @@ -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/') +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) diff --git a/suchwowx/templates/includes/navbar.html b/suchwowx/templates/includes/navbar.html index e2e33e7..12b2146 100644 --- a/suchwowx/templates/includes/navbar.html +++ b/suchwowx/templates/includes/navbar.html @@ -8,7 +8,11 @@ {% if request.path == '/' %} About {% if current_user.is_authenticated %} + Profile Disconnect + {% if current_user.is_moderator() %} + Mod + {% endif %} New Meme {% else %} Connect diff --git a/suchwowx/templates/meme.html b/suchwowx/templates/meme.html index 939f490..3bb4415 100644 --- a/suchwowx/templates/meme.html +++ b/suchwowx/templates/meme.html @@ -1,3 +1,4 @@ + {% include 'includes/head.html' %} @@ -8,22 +9,34 @@ {% include 'includes/navbar.html' %} {% 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 %} +
{% if meme.file_name.endswith('mp4') %} {% else %} - + {% endif %}

Title: {{ meme.title }}

Description: {{ meme.description }}

-

Creator handle: {{ meme.creator_handle }}

-

Meta IPFS: {{ meme.meta_ipfs_hash }}

-

Meme IPFS: {{ meme.meme_ipfs_hash }}

+

Creator handle: {{ meme.creator_handle }}

+ {% if meme.meta_ipfs_hash %} +

Meta IPFS: {{ meme.meta_ipfs_hash }}

+ {% endif %} + {% if meme.meme_ipfs_hash %} +

Meme IPFS: {{ meme.meme_ipfs_hash }}

+ {% endif %}

Meme ID: {{ meme }}

diff --git a/suchwowx/templates/profile.html b/suchwowx/templates/profile.html new file mode 100644 index 0000000..cc49d63 --- /dev/null +++ b/suchwowx/templates/profile.html @@ -0,0 +1,36 @@ + + + {% include 'includes/head.html' %} + +
+
+ + {% include 'includes/navbar.html' %} + + {% if user %} +
+
+

From Avax Chain

+

Handle: + +

+

From Local Database

+

Register Date: {{ user.register_date }}

+

Login Date: {{ user.last_login_date }}

+

Moderator: {{ user.is_moderator() }}

+

Verified: {{ user.verified }}

+ {% if user.bio %} +

Bio: {{ user.bio }}

+ {% endif %} + {% if user.website %} +

Website: {{ user.website_url }}

+ {% endif %} +
+
+ {% endif %} + +
+
+ + + diff --git a/suchwowx/templates/review.html b/suchwowx/templates/review.html new file mode 100644 index 0000000..ac9bdca --- /dev/null +++ b/suchwowx/templates/review.html @@ -0,0 +1,59 @@ + + + {% include 'includes/head.html' %} + +
+
+ {% include 'includes/navbar.html' %} + + {% if memes %} + {% for _meme in memes | batch(4) %} +
+ + {% for meme in _meme %} +
+ +
+
+
+ +
+
+

John Smith

+

@johnsmith

+
+
+ +
+ {{ meme.description }} +
+ +
+
+
+ + {% endfor %} +
+ {% endfor %} + {% endif %} + +
+
+ {% include 'includes/footer.html' %} + +