rDrama/files/__main__.py
Snakes 1647b4cf20
Update dotenv support to make operative (#564)
b46ada9f72 added support for dotenv files to be loaded in lieu of
actual environment variables. This code has since been neglected across
infrastructure changes that removed the need for an env file, moved
its location, and changed dependency management style.

This won't be operative in production (and already isn't, since there'd
be an ImportError on 'dotenv' if so) but makes some local testing
setups more convenient.
2023-03-29 21:59:34 -05:00

219 lines
6.6 KiB
Python

'''
Main entry point for the application. Global state among other things are
stored here.
'''
import gevent.monkey
gevent.monkey.patch_all()
# ^ special case: in general imports should go
# stdlib - externals - internals, but gevent does monkey patching for stdlib
# functions so we want to monkey patch before importing other things
import faulthandler
from os import environ
from pathlib import Path
import flask
import flask_caching
import flask_compress
import flask_limiter
import flask_mail
import flask_profiler
import gevent
import redis
from sqlalchemy.engine import Engine, create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from files.helpers.config.const import Service
from files.helpers.strings import bool_from_string
# first, let's parse arguments to find out what type of instance this is...
service:Service = Service.from_argv()
# ...and then let's create our flask app...
app = flask.app.Flask(__name__, template_folder='templates')
app.url_map.strict_slashes = False
app.jinja_env.cache = {}
app.jinja_env.auto_reload = True
faulthandler.enable()
# ...then check that debug mode was not accidentally enabled...
if bool_from_string(environ.get("ENFORCE_PRODUCTION", True)) and app.debug:
raise ValueError("Debug mode is not allowed! If this is a dev environment, please set ENFORCE_PRODUCTION to false")
# ...and then attempt to load a .env file if the environment is not configured...
if environ.get("SITE_ID") is None:
from dotenv import load_dotenv
load_dotenv(dotenv_path=Path("bootstrap/site_env"))
load_dotenv(dotenv_path=Path("env"), override=True)
# ...and let's add the flask profiler if it's enabled...
if environ.get("FLASK_PROFILER_ENDPOINT"):
app.config["flask_profiler"] = {
"enabled": True,
"storage": {
"engine": "sqlalchemy",
},
"basicAuth": {
"enabled": True,
"username": environ.get("FLASK_PROFILER_USERNAME"),
"password": environ.get("FLASK_PROFILER_PASSWORD"),
},
"endpointRoot": environ.get("FLASK_PROFILER_ENDPOINT"),
}
profiler = flask_profiler.Profiler()
profiler.init_app(app)
# ...and then let's install code to unmangle jinja2 stacktraces for easy_profile...
try:
import inspect as inspectlib
import linecache
from easy_profile import EasyProfileMiddleware
from jinja2.utils import internal_code
def jinja_unmangle_stacktrace():
rewritten_frames = []
for record in inspectlib.stack():
# Skip jinja internalcode frames
if record.frame.f_code in internal_code:
continue
filename = record.frame.f_code.co_filename
lineno = record.frame.f_lineno
name = record.frame.f_code.co_name
template = record.frame.f_globals.get("__jinja_template__")
if template is not None:
lineno = template.get_corresponding_lineno(lineno)
line = linecache.getline(filename, lineno).strip()
rewritten_frames.append(f' File "{filename}", line {lineno}, {name}\n {line}\n')
return "".join(rewritten_frames)
app.wsgi_app = EasyProfileMiddleware(
app.wsgi_app,
stack_callback = jinja_unmangle_stacktrace)
except ModuleNotFoundError:
# failed to import, just keep on going
pass
# ...and let's load up app config...
from files.helpers.config.const import (DEFAULT_THEME, MAX_CONTENT_LENGTH,
PERMANENT_SESSION_LIFETIME,
SESSION_COOKIE_SAMESITE)
from files.helpers.config.environment import *
app.config.update({
"SITE_ID": SITE_ID,
"SITE_TITLE": SITE_TITLE,
"SQLALCHEMY_TRACK_MODIFICATIONS": SQLALCHEMY_TRACK_MODIFICATIONS,
"DATABASE_URL": DATABASE_URL,
"SECRET_KEY": SECRET_KEY,
"SERVER_NAME": SERVER_NAME,
"SEND_FILE_MAX_AGE_DEFAULT": 0 if app.debug else 3153600,
"SESSION_COOKIE_NAME": f'session_{SITE_ID.lower()}',
"VERSION": "1.0.0",
"MAX_CONTENT_LENGTH": MAX_CONTENT_LENGTH,
"SESSION_COOKIE_SECURE": SESSION_COOKIE_SECURE,
"SESSION_COOKIE_SAMESITE": SESSION_COOKIE_SAMESITE,
"PERMANENT_SESSION_LIFETIME": PERMANENT_SESSION_LIFETIME,
"DEFAULT_COLOR": DEFAULT_COLOR,
"DEFAULT_THEME": DEFAULT_THEME,
"FORCE_HTTPS": 1,
"UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
"HCAPTCHA_SITEKEY": HCAPTCHA_SITEKEY,
"HCAPTCHA_SECRET": HCAPTCHA_SECRET,
"SPAM_SIMILARITY_THRESHOLD": SPAM_SIMILARITY_THRESHOLD,
"SPAM_URL_SIMILARITY_THRESHOLD": SPAM_URL_SIMILARITY_THRESHOLD,
"SPAM_SIMILAR_COUNT_THRESHOLD": SPAM_SIMILAR_COUNT_THRESHOLD,
"COMMENT_SPAM_SIMILAR_THRESHOLD": COMMENT_SPAM_SIMILAR_THRESHOLD,
"COMMENT_SPAM_COUNT_THRESHOLD": COMMENT_SPAM_COUNT_THRESHOLD,
"CACHE_TYPE": "RedisCache",
"CACHE_REDIS_URL": CACHE_REDIS_URL,
"MAIL_SERVER": MAIL_SERVER,
"MAIL_PORT": MAIL_PORT,
"MAIL_USE_TLS": MAIL_USE_TLS,
"DESCRIPTION": DESCRIPTION,
"MAIL_USERNAME": MAIL_USERNAME,
"MAIL_PASSWORD": MAIL_PASSWORD,
"DESCRIPTION": DESCRIPTION,
"SETTINGS": {},
"SQLALCHEMY_DATABASE_URI": DATABASE_URL,
"MENTION_LIMIT": MENTION_LIMIT,
"MULTIMEDIA_EMBEDDING_ENABLED": MULTIMEDIA_EMBEDDING_ENABLED,
"RESULTS_PER_PAGE_COMMENTS": RESULTS_PER_PAGE_COMMENTS,
"SCORE_HIDING_TIME_HOURS": SCORE_HIDING_TIME_HOURS,
"ENABLE_SERVICES": ENABLE_SERVICES,
"RATE_LIMITER_ENABLED": RATE_LIMITER_ENABLED,
"DBG_VOLUNTEER_PERMISSIVE": DBG_VOLUNTEER_PERMISSIVE,
"VOLUNTEER_JANITOR_ENABLE": VOLUNTEER_JANITOR_ENABLE,
})
# ...and then let's load redis so that...
r = redis.Redis(
host=CACHE_REDIS_URL,
decode_responses=True,
ssl_cert_reqs=None
)
# ...we can configure our ratelimiter...
def get_remote_addr():
with app.app_context():
return request.headers.get('X-Real-IP', default='127.0.0.1')
if service.enable_services and not RATE_LIMITER_ENABLED:
print("Rate limiter disabled in debug mode!")
limiter = flask_limiter.Limiter(
key_func=get_remote_addr,
app=app,
default_limits=["3/second;30/minute;200/hour;1000/day"],
application_limits=["10/second;200/minute;5000/hour;10000/day"],
storage_uri=CACHE_REDIS_URL,
auto_check=False,
enabled=RATE_LIMITER_ENABLED,
)
# ...and then after that we can load the database.
engine: Engine = create_engine(DATABASE_URL)
db_session: scoped_session = scoped_session(sessionmaker(
bind=engine,
autoflush=False,
future=True,
))
# now that we've that, let's add the cache, compression, and mail extensions to our app...
cache = flask_caching.Cache(app)
flask_compress.Compress(app)
mail = flask_mail.Mail(app)
# ...and then import the before and after request handlers if this we will import routes.
if service.enable_services:
from files.routes.allroutes import *
# setup is done. let's conditionally import the rest of the routes.
if service == Service.THEMOTTE:
from files.routes import *
elif service == Service.CHAT:
from files.routes.chat import *