1
0
Fork 0
mirror of https://github.com/Findus23/se-simulator.git synced 2024-09-19 15:53:45 +02:00
se-simulator/server.py

281 lines
9.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2018-04-13 21:43:05 +02:00
import subprocess
2018-03-27 20:41:57 +02:00
import time
2018-04-28 18:22:11 +02:00
from io import BytesIO
from random import shuffle, randint
2018-03-30 22:42:38 +02:00
2018-03-23 21:28:37 +01:00
import sass
2018-04-28 18:22:11 +02:00
from PIL import ImageFont, Image, ImageDraw
2018-04-15 15:45:32 +02:00
from flask import render_template, send_from_directory, abort, session, jsonify, make_response, redirect, url_for, \
2018-04-28 18:22:11 +02:00
request, send_file
2018-03-25 20:35:03 +02:00
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
2018-03-27 20:41:57 +02:00
from flask_session import Session
2018-03-24 17:33:10 +01:00
from playhouse.flask_utils import PaginatedQuery, get_object_or_404
from playhouse.shortcuts import model_to_dict
2018-03-22 22:52:14 +01:00
from sassutils.wsgi import SassMiddleware
2018-03-25 11:28:37 +02:00
import config
2018-03-22 22:52:14 +01:00
import utils
from app import app
from models import *
app.jinja_env.globals.update(prettydate=utils.prettydate)
2018-04-05 21:01:06 +02:00
app.jinja_env.globals.update(is_light_color=utils.is_light_color)
2018-03-22 22:52:14 +01:00
SESSION_TYPE = config.session_type
2018-10-10 17:37:02 +02:00
if config.session_type == "redis":
SESSION_REDIS = config.redis_instance
2018-03-27 20:41:57 +02:00
SESSION_COOKIE_SECURE = config.production
SESSION_USE_SIGNER = True
SESSION_KEY_PREFIX = "StackDataSessions:"
app.config.from_object(__name__)
2018-03-25 11:28:37 +02:00
app.secret_key = config.secret_key
2018-03-27 20:41:57 +02:00
Session(app)
2018-03-25 11:28:37 +02:00
2018-03-25 20:35:03 +02:00
limiter = Limiter(
2024-05-04 20:43:19 +02:00
app=app,
2018-03-25 20:35:03 +02:00
key_func=get_remote_address,
headers_enabled=True
)
question_count = utils.load_question_count()
2018-04-13 21:43:05 +02:00
@app.context_processor
def git_hash():
2018-05-13 13:47:50 +02:00
return dict(git_hash=subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip())
2018-04-13 21:43:05 +02:00
2018-03-22 22:52:14 +01:00
2018-05-13 13:47:50 +02:00
@app.route("/")
@app.route("/s/<string:site>")
def index(site=None):
query = Question.select(Question, User, Site, Title, SQL(utils.rating_sql)).join(Site).switch(Question).join(
User).switch(
Question).join(
Title)
if site:
query = query.where(Site.url == site)
2021-09-11 18:08:13 +02:00
try:
site_element = Site.select().where(Site.url == site).get()
except DoesNotExist:
abort(404)
2022-01-19 18:40:46 +01:00
return
else:
site_element = utils.get_fallback_site()
query = query.order_by(SQL("ci_lower_bound DESC, random"))
# return jsonify(model_to_dict(query.get()))
2018-03-23 21:28:37 +01:00
paginated_query = PaginatedQuery(query, paginate_by=10, check_bounds=True)
pagearray = utils.create_pagination(paginated_query.get_page_count(), paginated_query.get_page())
return render_template(
2018-05-13 13:47:50 +02:00
"list.html",
2018-03-23 21:28:37 +01:00
pagearray=pagearray,
num_pages=paginated_query.get_page_count(),
page=paginated_query.get_page(),
questions=paginated_query.get_object_list(),
2018-03-27 20:41:57 +02:00
site=site_element,
2018-04-15 15:45:32 +02:00
voted=session["voted"] if "voted" in session and not config.make_cacheable else None,
infohidden="hide" in request.cookies
2018-03-23 21:28:37 +01:00
)
2018-03-22 22:52:14 +01:00
2018-05-13 13:47:50 +02:00
@app.route("/q/<string:slug>")
2018-03-22 22:52:14 +01:00
def question(slug):
query = Question.select(Question, Title, User, Site) \
.join(Title).switch(Question) \
.join(User).switch(Question) \
.join(Site).where(Title.slug == slug)
2018-03-24 17:33:10 +01:00
question = get_object_or_404(query)
answers = Answer.select(Answer, User, SQL(utils.rating_sql)) \
.join(User).where(Answer.question == question) \
.order_by(SQL("ci_lower_bound DESC"))
2018-03-24 17:33:10 +01:00
return render_template(
"detail.html",
2018-03-25 23:01:32 +02:00
question=question,
2018-03-27 20:41:57 +02:00
answers=answers,
2018-04-15 15:45:32 +02:00
voted=session["voted"] if "voted" in session and not config.make_cacheable else None,
infohidden="hide" in request.cookies
2018-03-27 20:41:57 +02:00
)
2018-05-13 13:47:50 +02:00
@app.route("/quiz/")
2018-03-30 22:42:38 +02:00
def hello():
return redirect(url_for("quiz", difficulty="easy"), code=302)
2018-05-13 13:47:50 +02:00
@app.route("/quiz/<string:difficulty>")
2018-03-30 22:42:38 +02:00
def quiz(difficulty):
if difficulty not in ["easy", "hard"]:
return abort(404)
2018-03-27 20:41:57 +02:00
time1 = time.time()
while True:
random = randint(0, question_count - 1)
print(random)
try:
question = Question.select(Question, Title, User, Site) \
.join(Title).switch(Question) \
.join(User).switch(Question) \
.join(Site).where((Question.upvotes - Question.downvotes >= 0) & (Question.random == random)).get()
except DoesNotExist:
continue
break
2018-03-30 22:42:38 +02:00
if difficulty == "easy":
sites = [question.site]
query = Site.select().where((Site.last_download.is_null(False)) & (Site.id != question.site.id)) \
.order_by(SQL("RAND()")).limit(3)
for site in query:
sites.append(site)
shuffle(sites)
else:
sites = None
2018-03-27 20:41:57 +02:00
time2 = time.time()
2018-05-13 13:47:50 +02:00
print("{} ms".format((time2 - time1) * 1000.0))
2018-03-27 20:41:57 +02:00
return render_template(
"quiz.html",
2018-03-28 15:01:16 +02:00
question=question,
2018-04-13 21:28:25 +02:00
stats=session["quiz"][difficulty] if "quiz" in session else {"total": 0, "correct": 0},
2018-03-30 22:42:38 +02:00
difficulty=difficulty,
2018-04-15 15:45:32 +02:00
choices=sites,
infohidden="hide" in request.cookies
2018-03-24 17:33:10 +01:00
)
2018-03-22 22:52:14 +01:00
2018-04-13 21:28:25 +02:00
@app.route("/api/quiz/<int:id>/<string:guess>/<string:difficulty>", methods=["POST"])
def quiz_api(id, guess, difficulty):
if difficulty not in ["easy", "hard"]:
return abort(404)
2018-03-28 15:01:16 +02:00
if "quiz" not in session:
2018-04-13 21:28:25 +02:00
session["quiz"] = {"easy": {"total": 0, "correct": 0}, "hard": {"total": 0, "correct": 0}}
session["quiz"][difficulty]["total"] += 1
2018-03-28 15:01:16 +02:00
query = Question.select(Site).join(Site).where(Question.id == id).get()
if guess == query.site.url:
correct = True
2018-04-13 21:28:25 +02:00
session["quiz"][difficulty]["correct"] += 1
2018-03-28 15:01:16 +02:00
else:
correct = False
return jsonify({"site": model_to_dict(query)["site"], "correct": correct})
2018-05-13 13:47:50 +02:00
@app.route("/api/sites")
def sites():
sites = Site.select().where(Site.last_download.is_null(False))
data = {}
for site in sites:
data[site.url] = (model_to_dict(site))
return jsonify(data)
2018-05-13 13:47:50 +02:00
@app.route("/image")
@app.route("/image/<int:site_id>")
2018-04-28 18:22:11 +02:00
@limiter.limit("10 per minute")
def image(site_id=None):
if site_id:
query = Site.select().where((Site.last_download.is_null(False)) & (Site.id == site_id))
site = get_object_or_404(query)
else:
class DummySite(object):
pass
site = DummySite()
2018-05-13 13:47:50 +02:00
site.foreground_color = "black"
site.background_color = "white"
2018-04-28 18:22:11 +02:00
# parameters
text = "Stack Exchange\nSimulator"
selected_font = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
font_size = 70
W, H = (600, 600)
# # get the size of the text
2018-05-13 13:47:50 +02:00
img = Image.new("RGBA", (W, H), (site.background_color if site.background_color else "white"))
2018-04-28 18:22:11 +02:00
font = ImageFont.truetype(selected_font, font_size)
draw = ImageDraw.Draw(img)
2024-05-05 01:14:00 +02:00
left, top, right, bottom = draw.multiline_textbbox((0, 0), text, font)
w, h = right - left, bottom - top
2018-04-28 18:22:11 +02:00
draw.multiline_text(((W - w) / 2, (H - h) / 2), text,
font=font, align="center",
fill=(site.foreground_color if site.foreground_color else "black"))
byte_io = BytesIO()
2018-05-13 13:47:50 +02:00
img.save(byte_io, "PNG", optimize=True)
2018-04-28 18:22:11 +02:00
byte_io.seek(0)
2018-05-13 13:47:50 +02:00
return send_file(byte_io, mimetype="image/png")
2018-03-25 23:01:32 +02:00
2018-05-13 13:47:50 +02:00
@app.route("/api/vote/<string:type>/<int:id>/<string:vote>", methods=["POST"])
2018-03-25 20:35:03 +02:00
@limiter.limit("10 per minute")
def vote(type, id, vote):
2024-05-04 20:40:09 +02:00
abort(403) # remove voting completely to not change historical data anymore
2018-03-25 11:28:37 +02:00
if "voted" not in session:
2018-03-27 20:41:57 +02:00
session["voted"] = {}
print(session["voted"])
if (type, id) in session["voted"]:
2018-03-25 11:28:37 +02:00
abort(403)
if type == "question":
if vote == "up":
query = Question.update(upvotes=Question.upvotes + 1).where(Question.id == id)
elif vote == "down":
query = Question.update(downvotes=Question.downvotes + 1).where(Question.id == id)
else:
return abort(404)
elif type == "answer":
if vote == "up":
query = Answer.update(upvotes=Answer.upvotes + 1).where(Answer.id == id)
elif vote == "down":
query = Answer.update(downvotes=Answer.downvotes + 1).where(Answer.id == id)
else:
return abort(404)
2018-03-25 11:28:37 +02:00
else:
return abort(404)
2018-03-27 20:41:57 +02:00
session["voted"][(type, id)] = vote == "up"
2018-03-25 11:28:37 +02:00
query.execute()
if type == "question":
query = Question.select(Question.upvotes, Question.downvotes).where(Question.id == id).get()
else:
query = Answer.select(Answer.upvotes, Answer.downvotes).where(Answer.id == id).get()
2018-03-25 11:28:37 +02:00
return jsonify({
"upvotes": query.upvotes,
"downvotes": query.downvotes
})
2018-03-25 20:35:03 +02:00
@app.errorhandler(429)
def ratelimit_handler(e):
return make_response(jsonify(error="ratelimit exceeded {}".format(e.description)), 429)
2018-03-25 20:35:03 +02:00
@app.errorhandler(403)
def ratelimit_handler(e):
return make_response(jsonify(error="access denied"), 403)
2018-03-25 20:35:03 +02:00
2018-05-13 13:47:50 +02:00
if __name__ == "__main__":
import logging
2018-05-13 13:47:50 +02:00
logger = logging.getLogger("peewee")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
2018-05-13 13:47:50 +02:00
@app.route("/static/js/<path:path>")
2018-03-25 11:28:37 +02:00
def send_js(path):
2018-05-13 13:47:50 +02:00
return send_from_directory("web/static/js", path)
2018-03-25 11:28:37 +02:00
2018-03-22 22:52:14 +01:00
app.debug = True
app.wsgi_app = SassMiddleware(app.wsgi_app, manifests={
2018-05-13 13:47:50 +02:00
"web": ("static/sass", "static/css", "/static/css")
2018-03-22 22:52:14 +01:00
})
app.run()
2018-03-25 11:28:37 +02:00
2018-03-23 21:28:37 +01:00
else:
2018-04-02 21:29:42 +02:00
css, sourcemap = sass.compile(
2018-05-13 13:47:50 +02:00
filename="web/static/sass/style.scss",
output_style="compressed",
source_map_filename="web/static/css/style.css.map"
2018-04-02 21:29:42 +02:00
)
2018-05-13 13:47:50 +02:00
with open("web/static/css/style.css", "w") as style_css:
2018-04-02 21:29:42 +02:00
style_css.write(css)
2018-05-13 13:47:50 +02:00
with open("web/static/css/style.css.map", "w") as style_css_map:
2018-04-02 21:29:42 +02:00
style_css_map.write(sourcemap)