from flask import Flask, session from dotenv import load_dotenv from .models import Avatars, Users, PostHistory, Posts from .auth import digest from .routes.users import is_logged_in, get_active_user from .routes.threads import get_post_url from .constants import ( PermissionLevel, permission_level_string, InfoboxKind, InfoboxIcons, InfoboxHTMLClass, REACTION_EMOJI, ) from .lib.babycode import babycode_to_html, EMOJI, BABYCODE_VERSION from datetime import datetime import os import time import secrets import tomllib def create_default_avatar(): if Avatars.count() == 0: print("Creating default avatar reference") Avatars.create({ "file_path": "/static/avatars/default.webp", "uploaded_at": int(time.time()) }) def create_admin(): username = "admin" if Users.count({"username": username}) == 0: print("!!!!!Creating admin account!!!!!") password_length = 16 password = secrets.token_urlsafe(password_length) hashed = digest(password) Users.create({ "username": username, "password_hash": hashed, "permission": PermissionLevel.ADMIN.value, }) print(f"!!!!!Administrator account created, use '{username}' as the login and '{password}' as the password. This will only be shown once!!!!!") def create_deleted_user(): username = "DeletedUser" if Users.count({"username": username}) == 0: print("Creating DeletedUser") Users.create({ "username": username, "password_hash": "", "permission": PermissionLevel.SYSTEM.value, }) def reparse_posts(): from .db import db post_histories = PostHistory.findall([ ('markup_language', '=', 'babycode'), ('format_version', 'IS NOT', BABYCODE_VERSION) ]) if len(post_histories) == 0: return print('Re-parsing babycode, this may take a while...') with db.transaction(): for ph in post_histories: ph.update({ 'content': babycode_to_html(ph['original_markup']), 'format_version': BABYCODE_VERSION, }) print('Re-parsing done.') def create_app(): app = Flask(__name__) app.config.from_file('../config/pyrom_config.toml', load=tomllib.load, text=False) if os.getenv("PYROM_PROD") is None: app.static_folder = os.path.join(os.path.dirname(__file__), "../data/static") app.debug = True app.config["DB_PATH"] = "data/db/db.dev.sqlite" load_dotenv() else: app.config["DB_PATH"] = "data/db/db.prod.sqlite" app.config["SECRET_KEY"] = os.getenv("FLASK_SECRET_KEY") app.config['AVATAR_UPLOAD_PATH'] = 'data/static/avatars/' app.config['MAX_CONTENT_LENGTH'] = 1000 * 1000 os.makedirs(os.path.dirname(app.config["DB_PATH"]), exist_ok = True) with app.app_context(): from .schema import create as create_tables from .migrations import run_migrations create_tables() run_migrations() create_default_avatar() create_admin() create_deleted_user() reparse_posts() from app.routes.app import bp as app_bp from app.routes.topics import bp as topics_bp from app.routes.threads import bp as threads_bp from app.routes.users import bp as users_bp from app.routes.mod import bp as mod_bp from app.routes.api import bp as api_bp from app.routes.posts import bp as posts_bp app.register_blueprint(app_bp) app.register_blueprint(topics_bp) app.register_blueprint(threads_bp) app.register_blueprint(users_bp) app.register_blueprint(mod_bp) app.register_blueprint(api_bp) app.register_blueprint(posts_bp) app.config['SESSION_COOKIE_SECURE'] = True @app.before_request def make_session_permanent(): session.permanent = True commit = "" with open('.git/refs/heads/main') as f: commit = f.read().strip() @app.context_processor def inject_constants(): return { "InfoboxIcons": InfoboxIcons, "InfoboxHTMLClass": InfoboxHTMLClass, "InfoboxKind": InfoboxKind, "PermissionLevel": PermissionLevel, "__commit": commit, "__emoji": EMOJI, "REACTION_EMOJI": REACTION_EMOJI, } @app.context_processor def inject_auth(): return {"is_logged_in": is_logged_in, "get_active_user": get_active_user, "active_user": get_active_user()} @app.context_processor def inject_funcs(): return { 'get_post_url': get_post_url, } @app.template_filter("ts_datetime") def ts_datetime(ts, format): return datetime.utcfromtimestamp(ts or int(time.time())).strftime(format) @app.template_filter("pluralize") def pluralize(subject, num=1, singular = "", plural = "s"): if int(num) == 1: return subject + singular return subject + plural @app.template_filter("permission_string") def permission_string(term): return permission_level_string(term) @app.template_filter('babycode') def babycode_filter(markup): return babycode_to_html(markup) @app.template_filter('extract_h2') def extract_h2(content): import re pattern = r']*>(.*?)<\/h2>' matches = re.findall(pattern, content, re.IGNORECASE | re.DOTALL) return [ {'id': id_.strip(), 'text': text.strip()} for id_, text in matches ] # this only happens at build time but # build time is when updates are done anyway # sooo... /shrug @app.template_filter('cachebust') def cachebust(subject): return f"{subject}?v={str(int(time.time()))}" return app