
* #39 Add Flask-Migrate dep * #39 Make it such that flask db init can run https://github.com/miguelgrinberg/Flask-Migrate/issues/196#issuecomment-381343393 * Run flask db init, update migrations.env, commit artifacts * Set up a script such that you can `docker-compose exec files bash -c 'cd /service; FLASK_APP="files/cli:app" flask '` and have it do whatever flask thing you want * Fix circular dependency * import * is evil * Initial alembic migration, has issues with constraints and nullable columns * Bring alts table up to date with alembic autogenerate * Rerun flask db revision autogenerate * Bring award_relationships table up to date with alembic autogenerate * [#39/alembic] files/classes/__init__.py is evil but is at least explicitly evil now * #39 fix model in files/classes/badges.py * #39 fix model in files/classes/domains.py and files/classes/clients.py * #39 fix models: comment saves, comment flags * #39 fix models: comments * Few more imports * #39 columns that are not nullable should be flagged as not nullable * #39 Add missing indexes to model defs * [#39] add missing unique constraints to model defs * [#39] Temporarily undo any model changes which cause the sqlalchemy model to be out of sync with the actual dump * #39 Deforeignkeyify the correct column to make alembic happy * #39 flask db revision --autogenerate now creates an empty migration * #39 Migration format such that files are listed in creation order * #39 Better first revision * #39 Revert the model changes that were required to get to zero differences between db revision --autogenerate and the existing schema * #39 The first real migration * #39 Ensure that foreign key constraints are named in migration * #39 Alembic migrations for FK constraints, column defs * [#39] Run DB migrations before starting tests * [#39] New test to ensure migrations are up to date * [#39] More descriptive test failure message * Add -T flag to docker-compose exec * [#39] Run alembic migrations when starting the container
126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
|
|
import gevent.monkey
|
|
gevent.monkey.patch_all()
|
|
from os import environ, path
|
|
import secrets
|
|
from flask import *
|
|
from flask_caching import Cache
|
|
from flask_limiter import Limiter
|
|
from flask_compress import Compress
|
|
from flask_mail import Mail
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
|
from sqlalchemy import *
|
|
import gevent
|
|
import redis
|
|
import time
|
|
from sys import stdout, argv
|
|
import faulthandler
|
|
import json
|
|
|
|
app = Flask(__name__, template_folder='templates')
|
|
app.url_map.strict_slashes = False
|
|
app.jinja_env.cache = {}
|
|
app.jinja_env.auto_reload = True
|
|
faulthandler.enable()
|
|
|
|
|
|
app.config["SITE_NAME"]=environ.get("SITE_NAME").strip()
|
|
app.config["GUMROAD_LINK"]=environ.get("GUMROAD_LINK", "https://marsey1.gumroad.com/l/tfcvri").strip()
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
app.config['DATABASE_URL'] = environ.get("DATABASE_URL", "postgresql://postgres@localhost:5432")
|
|
app.config['SECRET_KEY'] = environ.get('MASTER_KEY')
|
|
app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
|
|
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600
|
|
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
|
|
app.config["VERSION"] = "1.0.0"
|
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
|
app.config["SESSION_COOKIE_SECURE"] = "localhost" not in environ.get("DOMAIN")
|
|
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
|
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
|
|
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
|
|
app.config["DEFAULT_THEME"] = "TheMotte"
|
|
app.config["FORCE_HTTPS"] = 1
|
|
app.config["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
|
|
app.config["HCAPTCHA_SITEKEY"] = environ.get("HCAPTCHA_SITEKEY","").strip()
|
|
app.config["HCAPTCHA_SECRET"] = environ.get("HCAPTCHA_SECRET","").strip()
|
|
app.config["SPAM_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_SIMILARITY_THRESHOLD", 0.5))
|
|
app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", 0.1))
|
|
app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", 10))
|
|
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
|
|
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 10))
|
|
app.config["CACHE_TYPE"] = "RedisCache"
|
|
app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL", "redis://localhost")
|
|
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
|
app.config['MAIL_PORT'] = 587
|
|
app.config['MAIL_USE_TLS'] = True
|
|
app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
|
|
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
|
|
app.config['DESCRIPTION'] = environ.get("DESCRIPTION", "DESCRIPTION GOES HERE").strip()
|
|
app.config['SETTINGS'] = {}
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = app.config['DATABASE_URL']
|
|
|
|
r=redis.Redis(host=environ.get("REDIS_URL", "redis://localhost"), decode_responses=True, ssl_cert_reqs=None)
|
|
|
|
def get_CF():
|
|
with app.app_context():
|
|
return request.headers.get('CF-Connecting-IP')
|
|
|
|
limiter = Limiter(
|
|
app,
|
|
key_func=get_CF,
|
|
default_limits=["3/second;30/minute;200/hour;1000/day"],
|
|
application_limits=["10/second;200/minute;5000/hour;10000/day"],
|
|
storage_uri=environ.get("REDIS_URL", "redis://localhost")
|
|
)
|
|
|
|
Base = declarative_base()
|
|
|
|
engine = create_engine(app.config['DATABASE_URL'])
|
|
|
|
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
|
|
|
|
cache = Cache(app)
|
|
Compress(app)
|
|
mail = Mail(app)
|
|
|
|
@app.before_request
|
|
def before_request():
|
|
|
|
with open('site_settings.json', 'r') as f:
|
|
app.config['SETTINGS'] = json.load(f)
|
|
|
|
if request.host != app.config["SERVER_NAME"]: return {"error":"Unauthorized host provided."}, 401
|
|
if request.headers.get("CF-Worker"): return {"error":"Cloudflare workers are not allowed to access this website."}, 401
|
|
|
|
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(503)
|
|
|
|
g.db = db_session()
|
|
|
|
ua = request.headers.get("User-Agent","").lower()
|
|
|
|
if '; wv) ' in ua: g.webview = True
|
|
else: g.webview = False
|
|
|
|
if 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua: g.inferior_browser = True
|
|
else: g.inferior_browser = False
|
|
|
|
g.timestamp = int(time.time())
|
|
|
|
|
|
@app.teardown_appcontext
|
|
def teardown_request(error):
|
|
if hasattr(g, 'db') and g.db:
|
|
g.db.close()
|
|
stdout.flush()
|
|
|
|
@app.after_request
|
|
def after_request(response):
|
|
response.headers.add("Strict-Transport-Security", "max-age=31536000")
|
|
response.headers.add("X-Frame-Options", "deny")
|
|
return response
|
|
|
|
if "load_chat" in argv:
|
|
from files.routes.chat import *
|
|
else:
|
|
from files.routes import *
|