mirror of
https://github.com/jwinterm/tubbymemes.git
synced 2026-10-17 05:21:28 +02:00
redoing cli, getting basic mod queue going, setting up profile, etc
This commit is contained in:
@@ -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
|
||||
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')
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+32
-6
@@ -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}')
|
||||
|
||||
+39
-18
@@ -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)
|
||||
|
||||
@@ -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 == '/' %}
|
||||
<a class="button is-secondary" href="{{ url_for('meta.about') }}" up-target=".container">About</a>
|
||||
{% 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>
|
||||
{% 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>
|
||||
{% else %}
|
||||
<a class="button is-primary" href="#" id="metamaskConnect">Connect</a>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
{% 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 %}
|
||||
|
||||
<div id="screen">
|
||||
<div class="screen">
|
||||
{% 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>
|
||||
<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.
|
||||
</video>
|
||||
{% else %}
|
||||
<img src="{{ url_for('meta.uploaded_file', filename=meme.file_name) }}" id="memeImage" />
|
||||
<img src="{{ meme_url }}" id="memeImage" />
|
||||
{% endif %}
|
||||
</br>
|
||||
<p>Title: <strong>{{ meme.title }}</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>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>
|
||||
<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>Creator handle: <a href="{{ url_for('user.show', handle=meme.creator_handle) }}" up-follow=".container">{{ meme.creator_handle }}</a></p>
|
||||
{% if meme.meta_ipfs_hash %}
|
||||
<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>
|
||||
</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>
|
||||
Reference in New Issue
Block a user