Compare commits
No commits in common. 'master' and 'facelift' have entirely different histories.
@ -1,15 +0,0 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
setup:
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
|
||||
shell:
|
||||
./bin/cmd shell
|
||||
|
||||
dev:
|
||||
./bin/dev
|
||||
|
||||
prod:
|
||||
./bin/prod
|
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=1
|
||||
flask $@
|
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=1
|
||||
flask run
|
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
BASE=data/gunicorn
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=0
|
||||
export FLASK_ENV=production
|
||||
|
||||
mkdir -p $BASE
|
||||
|
||||
gunicorn \
|
||||
--bind 0.0.0.0:4000 "suchwow.app:app" \
|
||||
--daemon \
|
||||
--log-file $BASE/gunicorn.log \
|
||||
--pid $BASE/gunicorn.pid \
|
||||
--reload
|
||||
|
||||
echo "Starting gunicorn"
|
@ -1,25 +0,0 @@
|
||||
OIDC_URL=https://login.wownero.com/auth/realms/master/protocol/openid-connect
|
||||
OIDC_CLIENT_ID=suchwow-dev
|
||||
OIDC_CLIENT_SECRET=yyy-yyyyy-yyyyy-yy
|
||||
OIDC_REDIRECT_URL=http://localhost:5000/auth
|
||||
SECRET_KEY=ssssssssssss
|
||||
DATA_FOLDER=/absolute/path/to/the/place/you/store/images
|
||||
SERVER_NAME=localhost:5000
|
||||
WALLET_PATH=/absolute/path/to/the/place/you/store/wallet
|
||||
WALLET_PASS=mytopsecretpass
|
||||
WALLET_HOST=localhost
|
||||
WALLET_PORT=8888
|
||||
WALLET_PROTO=http
|
||||
WALLET_RPC_USER=suchwow
|
||||
WALLET_RPC_PASS=again
|
||||
DAEMON_URI=http://node.suchwow.xyz:34568
|
||||
PRAW_CLIENT_SECRET=xxxx
|
||||
PRAW_CLIENT_ID=xxxxx
|
||||
PRAW_USER_AGENT=xxxxx
|
||||
PRAW_USERNAME=xxxxx
|
||||
PRAW_PASSWORD=xxxx
|
||||
DISCORD_URL=https://xxxx
|
||||
MM_ICON=https://xxxx
|
||||
MM_CHANNEL=xxxx
|
||||
MM_USERNAME=xxxx
|
||||
MM_ENDPOINT=https://xxxx
|
@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=suchwow/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=0
|
||||
export FLASK_ENV=production
|
||||
|
||||
# override
|
||||
export $(cat .env)
|
||||
|
||||
if [[ ${1} == "prod" ]];
|
||||
then
|
||||
export BASE=./data/gunicorn
|
||||
mkdir -p $BASE
|
||||
pgrep -F $BASE/gunicorn.pid
|
||||
if [[ $? != 0 ]]; then
|
||||
gunicorn \
|
||||
--bind 127.0.0.1:4000 "suchwow.app:app" \
|
||||
--daemon \
|
||||
--log-file $BASE/gunicorn.log \
|
||||
--pid $BASE/gunicorn.pid \
|
||||
--reload
|
||||
sleep 2
|
||||
echo "Started gunicorn on 127.0.0.1:4000 with pid $(cat $BASE/gunicorn.pid)"
|
||||
fi
|
||||
else
|
||||
flask $@
|
||||
fi
|
@ -1,95 +0,0 @@
|
||||
from os import makedirs
|
||||
|
||||
import click
|
||||
from flask import Blueprint, url_for, current_app
|
||||
|
||||
from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban, AuditEvent
|
||||
from suchwow.utils.helpers import get_latest_tipped_posts
|
||||
from suchwow.utils.helpers import get_top_posters, get_top_posts
|
||||
from suchwow.reddit import make_post
|
||||
from suchwow import wownero
|
||||
from suchwow import config
|
||||
|
||||
bp = Blueprint('cli', 'cli', cli_group=None)
|
||||
|
||||
|
||||
@bp.cli.command("init")
|
||||
def init():
|
||||
# create subdirs
|
||||
for i in ["uploads", "db", "wallet"]:
|
||||
makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True)
|
||||
|
||||
# init db
|
||||
db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban, AuditEvent])
|
||||
|
||||
|
||||
@bp.cli.command("post_reddit")
|
||||
@click.argument('last_hours')
|
||||
def post_reddit(last_hours):
|
||||
posts = Post.select().where(
|
||||
Post.approved==True,
|
||||
Post.to_reddit==False
|
||||
).order_by(Post.timestamp.asc())
|
||||
for p in posts:
|
||||
if p.hours_elapsed() < int(last_hours):
|
||||
if not p.to_reddit:
|
||||
_p = make_post(p)
|
||||
if _p:
|
||||
p.to_reddit = True
|
||||
p.save()
|
||||
return
|
||||
|
||||
|
||||
@bp.cli.command("create_accounts")
|
||||
def create_accounts():
|
||||
wallet = wownero.Wallet()
|
||||
for post in Post.select():
|
||||
if post.account_index not in wallet.accounts():
|
||||
account = wallet.new_account()
|
||||
print(f"Created account {account}")
|
||||
|
||||
|
||||
@bp.cli.command("payout_users")
|
||||
def payout_users():
|
||||
wallet = wownero.Wallet()
|
||||
_fa = wownero.from_atomic
|
||||
_aw = wownero.as_wownero
|
||||
for post in Post.select():
|
||||
try:
|
||||
submitter = Profile.get(username=post.submitter)
|
||||
balances = wallet.balances(post.account_index)
|
||||
url = url_for('post.read', id=post.id, _external=True)
|
||||
if balances[1] > 0.05:
|
||||
print(f"Post #{post.id} has {balances[1]} funds unlocked and ready to send. Sweeping all funds to user's address ({submitter.address}).")
|
||||
sweep = wallet.sweep_all(account=post.account_index, dest_address=submitter.address)
|
||||
print(sweep)
|
||||
if "tx_hash_list" in sweep:
|
||||
amount = 0
|
||||
for amt in sweep["amount_list"]:
|
||||
amount += int(amt)
|
||||
except Exception as e:
|
||||
print(f"Failed because: {e}")
|
||||
|
||||
|
||||
@bp.cli.command("show")
|
||||
@click.argument("post_id")
|
||||
def post_id(post_id):
|
||||
p = Post.filter(id=post_id).first()
|
||||
if p:
|
||||
print(p.show())
|
||||
else:
|
||||
print("That post doesn't exist")
|
||||
|
||||
|
||||
@bp.cli.command("load_cache")
|
||||
def load_cache():
|
||||
current_app.logger.info('loading top posters into cache')
|
||||
get_top_posters()
|
||||
current_app.logger.info('done')
|
||||
current_app.logger.info('loading latest tipped into cache')
|
||||
get_latest_tipped_posts()
|
||||
current_app.logger.info('done')
|
||||
for i in [1, 3, 7, 30, 9999]:
|
||||
current_app.logger.info(f'loading top posts last {i} days into cache')
|
||||
get_top_posts(i)
|
||||
current_app.logger.info('done')
|
@ -0,0 +1,30 @@
|
||||
from os import getenv
|
||||
|
||||
|
||||
OIDC_URL = 'https://login.wownero.com/auth/realms/master/protocol/openid-connect',
|
||||
OIDC_CLIENT_ID = 'suchwowxxx',
|
||||
OIDC_CLIENT_SECRET = 'xxxxxxxxxx',
|
||||
OIDC_REDIRECT_URL = 'http://localhost:5000/auth'
|
||||
SECRET_KEY = 'yyyyyyyyyyyyy',
|
||||
SESSION_TYPE = 'filesystem'
|
||||
DATA_FOLDER = '/path/to/the/uploads'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
|
||||
WALLET_HOST = 'localhost'
|
||||
WALLET_PORT = 8888
|
||||
WALLET_PROTO = 'http'
|
||||
WALLET_USER = 'suchwow'
|
||||
WALLET_PASS = 'zzzzzzzzzzzzzzz'
|
||||
PRAW_CLIENT_SECRET = 'xxxxxxxx'
|
||||
PRAW_CLIENT_ID = 'xxxxxxxx'
|
||||
PRAW_USER_AGENT = 'suchwow-yyyy-python'
|
||||
PRAW_USERNAME = 'xxxxxxxx'
|
||||
PRAW_PASSWORD = 'xxxxxxxx'
|
||||
SERVER_NAME = 'localhost'
|
||||
DISCORD_URL = 'xxxxxxx'
|
||||
BANNED_USERS = {'username': 'reason for the ban'}
|
||||
|
||||
MM_ICON = getenv('MM_ICON', 'https://funding.wownero.com/static/wowdoge-a.jpg')
|
||||
MM_CHANNEL = getenv('MM_CHANNEL', 'suchwow')
|
||||
MM_USERNAME = getenv('MM_USERNAME', 'SuchWow!')
|
||||
MM_ENDPOINT = getenv('MM_ENDPOINT', 'ppppppppppppppppppppppppp')
|
@ -1,45 +0,0 @@
|
||||
from os import getenv
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# generated from https://login.wownero.com/developer/register
|
||||
OIDC_URL = getenv('OIDC_URL', 'https://login.wownero.com/auth/realms/master/protocol/openid-connect')
|
||||
OIDC_CLIENT_ID = getenv('OIDC_CLIENT_ID', 'suchwow-dev')
|
||||
OIDC_CLIENT_SECRET = getenv('OIDC_CLIENT_SECRET', '')
|
||||
OIDC_REDIRECT_URL = getenv('OIDC_REDIRECT_URL', 'http://localhost:5000/auth')
|
||||
|
||||
# you specify something
|
||||
SECRET_KEY = getenv('SECRET_KEY', 'yyyyyyyyyyyyy') # whatever you want it to be
|
||||
DATA_FOLDER = getenv('DATA_FOLDER', '/path/to/uploads') # some stable storage path
|
||||
SERVER_NAME = getenv('SERVER_NAME', 'localhost') # name of your DNS resolvable site (.com)
|
||||
SUPER_ADMIN = getenv('SUPER_ADMIN', 'lza_menace') # top dawg you cannot delete
|
||||
WALLET_HOST = getenv('WALLET_HOST', 'localhost') #
|
||||
WALLET_PORT = int(getenv('WALLET_PORT', 8888)) #
|
||||
WALLET_PROTO = getenv('WALLET_PROTO', 'http') #
|
||||
WALLET_RPC_USER = getenv('WALLET_RPC_USER', 'suchwow') #
|
||||
WALLET_RPC_PASS = getenv('WALLET_RPC_PASS', 'suchwow') #
|
||||
WALLET_PASS = getenv('WALLET_PASS', 'zzzzzzz') # You specify all these wallet details in .env
|
||||
|
||||
# Optional for posting to Reddit
|
||||
PRAW_CLIENT_SECRET = getenv('PRAW_CLIENT_SECRET', None)
|
||||
PRAW_CLIENT_ID = getenv('PRAW_CLIENT_ID', None)
|
||||
PRAW_USER_AGENT = getenv('PRAW_USER_AGENT', None)
|
||||
PRAW_USERNAME = getenv('PRAW_USERNAME', None)
|
||||
PRAW_PASSWORD = getenv('PRAW_PASSWORD', None)
|
||||
|
||||
# Optional for posting to Discord
|
||||
DISCORD_URL = getenv('DISCORD_URL', None)
|
||||
|
||||
# Optional for posting to Mattermost
|
||||
MM_ICON = getenv('MM_ICON', 'https://funding.wownero.com/static/wowdoge-a.jpg')
|
||||
MM_CHANNEL = getenv('MM_CHANNEL', 'suchwow')
|
||||
MM_USERNAME = getenv('MM_USERNAME', 'SuchWow!')
|
||||
MM_ENDPOINT = getenv('MM_ENDPOINT', 'ppppppppppppppppppppppppp')
|
||||
|
||||
# defaults
|
||||
SESSION_TYPE = 'filesystem'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'svg', 'mp4'}
|
||||
MAX_CONTENT_LENGTH = 32 * 1024 * 1024
|
||||
TEMPLATES_AUTO_RELOAD = getenv('TEMPLATES_AUTO_RELOAD', True)
|
@ -0,0 +1,30 @@
|
||||
from flask import render_template, Blueprint, flash
|
||||
from flask import request, redirect, url_for, session
|
||||
from suchwow.models import Post, Comment, Profile
|
||||
from suchwow.utils.decorators import login_required
|
||||
|
||||
|
||||
bp = Blueprint("comment", "comment")
|
||||
|
||||
@bp.route("/comment/create/post/<post_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def create(post_id):
|
||||
if not Post.filter(id=post_id):
|
||||
flash("WTF, that post doesn't exist. Stop it, hackerman.")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
if request.method == "POST":
|
||||
comment_text = request.form.get("comment")
|
||||
if len(comment_text) > 300:
|
||||
flash("WTF, too many characters to post, asshole.")
|
||||
return redirect(request.url)
|
||||
commenter = Profile.get(username=session["auth"]["preferred_username"])
|
||||
post = Post.get(id=post_id)
|
||||
c = Comment(
|
||||
comment=comment_text,
|
||||
commenter=commenter,
|
||||
post=post,
|
||||
)
|
||||
c.save()
|
||||
return redirect(url_for("post.read", id=post_id))
|
||||
return render_template("comment/create.html")
|
@ -1,25 +1,68 @@
|
||||
from flask import render_template, Blueprint, request
|
||||
|
||||
from suchwow.utils.helpers import get_top_posters, get_top_posts
|
||||
from datetime import datetime, timedelta
|
||||
from os import path
|
||||
from flask import render_template, Blueprint, request, session, flash
|
||||
from flask import send_from_directory, redirect, url_for, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
from suchwow import wownero
|
||||
from suchwow.models import Post
|
||||
from suchwow.utils.helpers import rw_cache
|
||||
|
||||
|
||||
bp = Blueprint("leaderboard", "leaderboard")
|
||||
|
||||
@bp.route("/leaderboards/top_posters")
|
||||
def top_posters():
|
||||
top_posters = get_top_posters()
|
||||
top_posters = {}
|
||||
posts = rw_cache('top_posters')
|
||||
if not posts:
|
||||
posts = Post.select().where(Post.approved==True)
|
||||
for post in posts:
|
||||
transfers = []
|
||||
incoming = wownero.Wallet().incoming_transfers(post.account_index)
|
||||
if "transfers" in incoming:
|
||||
for xfer in incoming["transfers"]:
|
||||
transfers.append(wownero.from_atomic(xfer["amount"]))
|
||||
total = sum(transfers)
|
||||
if post.submitter not in top_posters:
|
||||
top_posters[post.submitter] = {"amount": 0, "posts": []}
|
||||
|
||||
top_posters[post.submitter]["amount"] += float(total)
|
||||
top_posters[post.submitter]["posts"].append(post)
|
||||
rw_cache('top_posters', top_posters)
|
||||
else:
|
||||
top_posters = posts
|
||||
|
||||
return render_template("leaderboard.html", posters=top_posters)
|
||||
|
||||
@bp.route("/leaderboards/top_posts")
|
||||
def top_posts():
|
||||
top_posts = []
|
||||
days = request.args.get('days', 1)
|
||||
try:
|
||||
days = int(days)
|
||||
except:
|
||||
days = 1
|
||||
|
||||
if days not in [1, 3, 7, 30, 9999]:
|
||||
if days not in [1, 3, 7, 30]:
|
||||
days = 7
|
||||
|
||||
posts = get_top_posts(days)
|
||||
hours = 24 * days
|
||||
diff = datetime.now() - timedelta(hours=hours)
|
||||
key_name = f'top_posts_{str(hours)}'
|
||||
|
||||
posts = rw_cache(key_name)
|
||||
if not posts:
|
||||
posts = Post.select().where(
|
||||
Post.approved==True,
|
||||
Post.timestamp > diff
|
||||
).order_by(
|
||||
Post.timestamp.desc()
|
||||
)
|
||||
for post in posts:
|
||||
p = post.show()
|
||||
if isinstance(p['received_wow'], float):
|
||||
top_posts.append(p)
|
||||
|
||||
posts = rw_cache(key_name, top_posts)
|
||||
|
||||
return render_template("post/top.html", posts=posts, days=days)
|
||||
|
@ -1,50 +0,0 @@
|
||||
from math import ceil
|
||||
|
||||
from flask import Blueprint, request, render_template, flash
|
||||
|
||||
from suchwow.models import Post, Profile, Moderator
|
||||
from suchwow.utils.helpers import get_latest_tipped_posts
|
||||
|
||||
bp = Blueprint('main', 'main')
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
itp = 15
|
||||
page = request.args.get("page", 1)
|
||||
submitter = request.args.get("submitter", None)
|
||||
content = request.args.get("content", None)
|
||||
|
||||
if content == 'latest_tipped':
|
||||
posts = get_latest_tipped_posts()
|
||||
return render_template(
|
||||
"index.html",
|
||||
posts=posts[0:30],
|
||||
title="Latest Tipped Memes"
|
||||
)
|
||||
|
||||
try:
|
||||
page = int(page)
|
||||
except:
|
||||
flash("Wow, wtf hackerman. Cool it.", "is-danger")
|
||||
page = 1
|
||||
|
||||
posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc())
|
||||
if submitter:
|
||||
posts = posts.where(Post.submitter==submitter)
|
||||
|
||||
paginated_posts = posts.paginate(page, itp)
|
||||
total_pages = ceil(posts.count() / itp)
|
||||
return render_template(
|
||||
"index.html",
|
||||
posts=paginated_posts,
|
||||
page=page,
|
||||
total_pages=total_pages,
|
||||
title="Latest Memes"
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/about")
|
||||
def about():
|
||||
mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username))
|
||||
return render_template("about.html", mods=mods)
|
@ -1,114 +0,0 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
|
||||
from suchwow.models import AuditEvent, Post, Profile, Moderator, Ban, get_ban_reason
|
||||
from suchwow.utils.decorators import moderator_required
|
||||
from suchwow.utils.helpers import get_session_user, audit_event
|
||||
from suchwow import config
|
||||
|
||||
|
||||
bp = Blueprint("mod", "mod")
|
||||
|
||||
@bp.route('/mods')
|
||||
@moderator_required
|
||||
def main():
|
||||
live_posts = Post.select().where(Post.approved == True).count()
|
||||
pending_posts = Post.select().where(Post.approved == False).count()
|
||||
active_posters = Profile.select().join(Post, on=Post.submitter == Profile.username).distinct().count()
|
||||
mods = Moderator.select().count()
|
||||
return render_template(
|
||||
'mod/main.html',
|
||||
live_posts=live_posts,
|
||||
pending_posts=pending_posts,
|
||||
active_posters=active_posters,
|
||||
mods=mods
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/mods/pending')
|
||||
@moderator_required
|
||||
def pending_posts():
|
||||
posts = Post.select().where(Post.approved == False).order_by(Post.timestamp.asc())
|
||||
if not posts:
|
||||
flash('no posts pending', 'is-warning')
|
||||
return redirect(url_for('mod.main'))
|
||||
return render_template('mod/posts.html', posts=posts)
|
||||
|
||||
|
||||
@bp.route('/mods/manage', methods=['GET', 'POST'])
|
||||
@moderator_required
|
||||
def manage_mods():
|
||||
to_delete = request.args.get('delete')
|
||||
if to_delete:
|
||||
m = Moderator.select().where(Moderator.username == to_delete).first()
|
||||
if not m:
|
||||
flash('No moderator exists with that name', 'is-danger')
|
||||
elif m.username == get_session_user():
|
||||
flash('Cannot remove yourself.', 'is-danger')
|
||||
elif m.username == config.SUPER_ADMIN:
|
||||
flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger')
|
||||
else:
|
||||
m.delete_instance()
|
||||
audit_event(f'Deleted {to_delete} from mods')
|
||||
flash(f'Removed {to_delete} from mods!', 'is-success')
|
||||
return redirect(url_for('mod.manage_mods'))
|
||||
if request.method == 'POST':
|
||||
to_add = request.form.get('username', None)
|
||||
if to_add:
|
||||
u = Profile.select().where(Profile.username == to_add).first()
|
||||
if not u:
|
||||
flash('That user does not appear to exist (no profile setup yet)', 'is-danger')
|
||||
elif Moderator.select().where(Moderator.username == to_add).first():
|
||||
flash(f'{to_add} is already a mod, ya dingus.', 'is-warning')
|
||||
else:
|
||||
m = Moderator(username=to_add)
|
||||
m.save()
|
||||
audit_event(f'Added {to_add} to mods')
|
||||
flash(f'Added {to_add} to mods!', 'is-success')
|
||||
mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username))
|
||||
return render_template('mod/manage.html', mods=mods)
|
||||
|
||||
|
||||
@bp.route('/mods/bans', methods=['GET', 'POST'])
|
||||
@moderator_required
|
||||
def manage_bans():
|
||||
to_delete = request.args.get('delete')
|
||||
if to_delete:
|
||||
ban = Ban.select().join(Profile).where(Profile.username == to_delete).first()
|
||||
if not ban:
|
||||
flash('No ban exists for that user', 'is-danger')
|
||||
elif ban.user == get_session_user():
|
||||
flash('Cannot ban yourself.', 'is-danger')
|
||||
elif ban.user == config.SUPER_ADMIN:
|
||||
flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger')
|
||||
else:
|
||||
ban.delete_instance()
|
||||
audit_event(f'Removed ban on {to_delete}')
|
||||
flash(f'Unbanned {to_delete}!', 'is-success')
|
||||
return redirect(url_for('mod.manage_bans'))
|
||||
if request.method == 'POST':
|
||||
to_add = request.form.get('username', None)
|
||||
if to_add:
|
||||
u = Profile.select().where(Profile.username == to_add).first()
|
||||
if not u:
|
||||
flash('That user does not appear to exist (no profile setup yet)', 'is-danger')
|
||||
elif Ban.select().join(Profile).where(Profile.username == to_add).first():
|
||||
flash(f'{to_add} is already banned, ya dingus.', 'is-warning')
|
||||
elif to_add == config.SUPER_ADMIN:
|
||||
flash('Cannot ban the super admin you son-of-a-bitch.', 'is-danger')
|
||||
else:
|
||||
reason = request.form.get('reason')
|
||||
if not reason:
|
||||
reason = get_ban_reason()
|
||||
ban = Ban(user=u, reason=reason)
|
||||
ban.save()
|
||||
audit_event(f'Banned {to_add} ({reason})')
|
||||
flash(f'Banned {to_add}!', 'is-success')
|
||||
bans = Ban.select()
|
||||
return render_template('mod/bans.html', bans=bans)
|
||||
|
||||
|
||||
@bp.route('/mods/logs')
|
||||
@moderator_required
|
||||
def view_logs():
|
||||
events = AuditEvent.select().order_by(AuditEvent.timestamp.desc()).limit(50)
|
||||
return render_template('mod/logs.html', logs=events)
|
Before Width: | Height: | Size: 89 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 174 KiB |
@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container" style="width:40%;">
|
||||
<div class="submit">
|
||||
<h1>Leave a Comment</h1>
|
||||
<form method=post enctype=multipart/form-data class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="inlineFormInput">Text</label>
|
||||
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="inlineFormInput" placeholder="Comment text (max 300 chars)" name="comment">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-success">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,51 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container" style="text-align:center;">
|
||||
<h1 class="title">Manage Mods</h1>
|
||||
<section class="section">
|
||||
{% for ban in bans %}
|
||||
<article class="message" style="width: 30%; margin: 2em auto;">
|
||||
<div class="message-header">
|
||||
<p>{{ ban.user.username }}</p>
|
||||
<a href="?delete={{ ban.user.username }}" class="delete"></a>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{{ ban.reason }}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<div class="container" style="text-align:left; width: 30%;">
|
||||
<form method="post">
|
||||
<div class="field">
|
||||
<label class="label">Ban User</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Username" name="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Reason</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" placeholder="Reason for ban" name="reason"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">Submit</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-link is-light">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container" style="text-align:center;">
|
||||
<h1 class="title">View Logs</h1>
|
||||
<section class="section">
|
||||
{% for log in logs %}
|
||||
<article class="message" style="width: 40%; margin: 1em auto;">
|
||||
<div class="message-header">
|
||||
<p>{{ log.user.username }} - {{ log.timestamp | humanize }} </p>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{{ log.action }}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}{% endblock %}
|
@ -1,72 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container content" style="padding-top: 4em;">
|
||||
|
||||
|
||||
<nav class="level">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Live Memes</p>
|
||||
<p class="title">{{ live_posts }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Pending Memes</p>
|
||||
<p class="title">{{ pending_posts }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Active Posters</p>
|
||||
<p class="title">{{ active_posters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="heading">Mods</p>
|
||||
<p class="title">{{ mods }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tabs is-toggle is-fullwidth">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ url_for('mod.pending_posts') }}">
|
||||
<span class="icon is-small"></span>
|
||||
<span>Manage Queue</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('mod.manage_bans') }}">
|
||||
<span class="icon is-small"></span>
|
||||
<span>Manage Bans</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('mod.manage_mods') }}">
|
||||
<span class="icon is-small"></span>
|
||||
<span>Manage Mods</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('mod.view_logs') }}">
|
||||
<span class="icon is-small"></span>
|
||||
<span>View Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<figure>
|
||||
<img src="https://suchwow.xyz/uploads/pg1VwHJWeKT5dWXy-wowcomfysemifinal.gif">
|
||||
</figure>
|
||||
<div style="padding-bottom:10em;"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}{% endblock %}
|
@ -1,42 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container" style="text-align:center;">
|
||||
<h1 class="title">Manage Mods</h1>
|
||||
<section class="section">
|
||||
{% for mod in mods %}
|
||||
<div class="block">
|
||||
<span class="tag is-large">
|
||||
{{ mod.username }}
|
||||
<a href="?delete={{ mod.username }}" class="delete"></a>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<div class="container" style="text-align:left; width: 30%;">
|
||||
<form method="post">
|
||||
<div class="field">
|
||||
<label class="label">Add New Mod</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Username" name="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">Submit</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-link is-light">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}{% endblock %}
|
@ -1,61 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container" style="text-align:center;">
|
||||
|
||||
<h1 class="title">Pending Posts</h1>
|
||||
<section class="section">
|
||||
{% if posts %}
|
||||
{% for row in posts | batch(4) %}
|
||||
<div class="columns">
|
||||
{% for p in row %}
|
||||
{% set post = p.show() %}
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
<a href="{{ url_for('post.read', id=p.id) }}">
|
||||
{% if p.get_image_path().endswith('mp4') %}
|
||||
<video style="max-height: 100vh!important;" controls>
|
||||
<source src="{{ url_for('post.uploaded_file', filename=p.image_name) }}" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
{% else %}
|
||||
<img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }}" src="{{ url_for('post.uploaded_file', filename=post.thumbnail_name) }}" />
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-content">
|
||||
<p class="title is-4">
|
||||
<a href="{{ url_for('post.read', id=p.id) }}">{{ post.title }}</a>
|
||||
</p>
|
||||
<p class="subtitle is-6"><a href="/?submitter={{ post.submitter }}">{{ post.submitter }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{ post.text | truncate(60) }}
|
||||
<time datetime="2016-1-1">{{ post.timestamp.year }}-{{ post.timestamp.month }}-{{ post.timestamp.day }} {{ post.timestamp.hour }}:{{ post.timestamp.minute }} UTC</time>
|
||||
<p>({{ post.timestamp | humanize }})</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a href="{{ url_for('post.approve', id=post.id) }}" class="card-footer-item" style="color:green;"><strong>Approve</strong></a>
|
||||
<a href="{{ url_for('post.delete', id=post.id) }}" class="card-footer-item" style="color:red;"><strong>Deny</strong></a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>No posts pending!</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}{% endblock %}
|
Reference in new issue