172 lines
4.6 KiB
Python
172 lines
4.6 KiB
Python
|
|
|
|
import re
|
|
from sqlite3 import ProgrammingError
|
|
from typing import Any
|
|
import warnings
|
|
from flask import (
|
|
Flask, g, render_template,
|
|
request, send_from_directory, url_for
|
|
)
|
|
import os
|
|
import dotenv
|
|
from flask_login import LoginManager
|
|
from flask_wtf.csrf import CSRFProtect
|
|
from sqlalchemy import select
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from suou import Snowflake, ssv_list
|
|
from werkzeug.routing import BaseConverter
|
|
from sassutils.wsgi import SassMiddleware
|
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
|
from suou.configparse import ConfigOptions, ConfigValue
|
|
|
|
from freak.colors import color_themes, theme_classes
|
|
|
|
__version__ = '0.4.0-dev27'
|
|
|
|
APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
|
|
|
if not dotenv.load_dotenv():
|
|
warnings.warn('.env not loaded; application may break!', RuntimeWarning)
|
|
|
|
class AppConfig(ConfigOptions):
|
|
secret_key = ConfigValue(required=True)
|
|
database_url = ConfigValue(required=True)
|
|
app_name = ConfigValue()
|
|
domain_name = ConfigValue()
|
|
private_assets = ConfigValue(cast=ssv_list)
|
|
jquery_url = ConfigValue(default='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js')
|
|
app_is_behind_proxy = ConfigValue(cast=bool, default=False)
|
|
|
|
app_config = AppConfig()
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = app_config.secret_key
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = app_config.database_url
|
|
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
|
|
|
|
from .models import db, User, Post
|
|
|
|
# SASS
|
|
app.wsgi_app = SassMiddleware(app.wsgi_app, dict(
|
|
freak=('static/sass', 'static/css', '/static/css', True)
|
|
))
|
|
|
|
# proxy fix
|
|
if app_config.app_is_behind_proxy:
|
|
app.wsgi_app = ProxyFix(
|
|
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
|
|
)
|
|
|
|
class SlugConverter(BaseConverter):
|
|
regex = r'[a-z0-9]+(?:-[a-z0-9]+)*'
|
|
|
|
class B32lConverter(BaseConverter):
|
|
regex = r'_?[a-z2-7]+'
|
|
def to_url(self, value):
|
|
return Snowflake(value).to_b32l()
|
|
def to_python(self, value):
|
|
return Snowflake.from_b32l(value)
|
|
|
|
app.url_map.converters['slug'] = SlugConverter
|
|
app.url_map.converters['b32l'] = B32lConverter
|
|
|
|
db.init_app(app)
|
|
|
|
csrf = CSRFProtect(app)
|
|
|
|
login_manager = LoginManager(app)
|
|
login_manager.login_view = 'accounts.login'
|
|
|
|
from . import filters
|
|
|
|
|
|
PRIVATE_ASSETS = os.getenv('PRIVATE_ASSETS', '').split()
|
|
|
|
@app.context_processor
|
|
def _inject_variables():
|
|
return {
|
|
'app_name': app_config.app_name,
|
|
'app_version': __version__,
|
|
'domain_name': app_config.domain_name,
|
|
'url_for_css': (lambda name, **kwargs: url_for('static', filename=f'css/{name}.css', **kwargs)),
|
|
'private_scripts': [x for x in app_config.private_assets if x.endswith('.js')],
|
|
'private_styles': [x for x in PRIVATE_ASSETS if x.endswith('.css')],
|
|
'jquery_url': app_config.jquery_url,
|
|
'post_count': Post.count(),
|
|
'user_count': User.active_count(),
|
|
'colors': color_themes,
|
|
'theme_classes': theme_classes
|
|
}
|
|
|
|
@login_manager.user_loader
|
|
def _inject_user(userid):
|
|
try:
|
|
u = db.session.execute(select(User).where(User.id == userid)).scalar()
|
|
if u is None or u.is_disabled:
|
|
return None
|
|
return u
|
|
except SQLAlchemyError as e:
|
|
warnings.warn(f'cannot retrieve user {userid} from db (exception: {e})', RuntimeWarning)
|
|
g.no_user = True
|
|
return None
|
|
|
|
def redact_url_password(u: str | Any) -> str | Any:
|
|
if not isinstance(u, str):
|
|
return u
|
|
return re.sub(r':[^@:/ ]+@', ':***@', u)
|
|
|
|
@app.errorhandler(ProgrammingError)
|
|
def error_db(body):
|
|
g.no_user = True
|
|
warnings.warn(f'No database access! (url is {redact_url_password(app.config['SQLALCHEMY_DATABASE_URI'])!r})', RuntimeWarning)
|
|
return render_template('500.html'), 500
|
|
|
|
@app.errorhandler(400)
|
|
def error_400(body):
|
|
return render_template('400.html'), 400
|
|
|
|
@app.errorhandler(403)
|
|
def error_403(body):
|
|
return render_template('403.html'), 403
|
|
|
|
@app.errorhandler(404)
|
|
def error_404(body):
|
|
return render_template('404.html'), 404
|
|
|
|
@app.errorhandler(405)
|
|
def error_405(body):
|
|
return render_template('405.html'), 405
|
|
|
|
@app.errorhandler(451)
|
|
def error_451(body):
|
|
return render_template('451.html'), 451
|
|
|
|
@app.errorhandler(500)
|
|
def error_500(body):
|
|
g.no_user = True
|
|
return render_template('500.html'), 500
|
|
|
|
@app.route('/favicon.ico')
|
|
def favicon_ico():
|
|
return send_from_directory(APP_BASE_DIR, 'favicon.ico')
|
|
|
|
@app.route('/robots.txt')
|
|
def robots_txt():
|
|
return send_from_directory(APP_BASE_DIR, 'robots.txt')
|
|
|
|
|
|
from .website import blueprints
|
|
for bp in blueprints:
|
|
app.register_blueprint(bp)
|
|
|
|
from .ajax import bp
|
|
app.register_blueprint(bp)
|
|
|
|
from .rest import rest_bp
|
|
app.register_blueprint(rest_bp)
|
|
|
|
|
|
|
|
|