mirror of
https://gitea.baerentsen.space/FrederikBaerentsen/BrickTracker.git
synced 2026-05-12 19:11:16 -05:00
192 lines
6.9 KiB
Python
192 lines
6.9 KiB
Python
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from flask import current_app, Flask, g
|
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
|
from bricktracker.configuration_list import BrickConfigurationList
|
|
from bricktracker.login import LoginManager
|
|
from bricktracker.navbar import Navbar
|
|
from bricktracker.sql import close
|
|
from bricktracker.template_filters import replace_query_filter
|
|
from bricktracker.version import __version__
|
|
from bricktracker.views.add import add_page
|
|
from bricktracker.views.admin.admin import admin_page
|
|
from bricktracker.views.admin.database import admin_database_page
|
|
from bricktracker.views.admin.image import admin_image_page
|
|
from bricktracker.views.admin.instructions import admin_instructions_page
|
|
from bricktracker.views.admin.owner import admin_owner_page
|
|
from bricktracker.views.admin.purchase_location import admin_purchase_location_page # noqa: E501
|
|
from bricktracker.views.admin.retired import admin_retired_page
|
|
from bricktracker.views.admin.set import admin_set_page
|
|
from bricktracker.views.admin.status import admin_status_page
|
|
from bricktracker.views.admin.storage import admin_storage_page
|
|
from bricktracker.views.admin.tag import admin_tag_page
|
|
from bricktracker.views.admin.theme import admin_theme_page
|
|
from bricktracker.views.data import data_page
|
|
from bricktracker.views.error import error_404
|
|
from bricktracker.views.index import index_page
|
|
from bricktracker.views.instructions import instructions_page
|
|
from bricktracker.views.login import login_page
|
|
from bricktracker.views.minifigure import minifigure_page
|
|
from bricktracker.views.part import part_page
|
|
from bricktracker.views.set import set_page
|
|
from bricktracker.views.statistics import statistics_page
|
|
from bricktracker.views.storage import storage_page
|
|
from bricktracker.views.wish import wish_page
|
|
|
|
|
|
def load_env_file() -> None:
|
|
"""Load .env file into os.environ with priority: data/.env > .env (root)
|
|
|
|
Also stores which BK_ variables were set via Docker environment (before loading .env)
|
|
so we can detect locked variables in the admin panel.
|
|
"""
|
|
import json
|
|
|
|
data_env = Path('data/.env')
|
|
root_env = Path('.env')
|
|
|
|
# Store which BK_ variables were already in environment BEFORE loading .env
|
|
# These are "locked" (set via Docker's environment: directive)
|
|
docker_env_vars = {k: v for k, v in os.environ.items() if k.startswith('BK_')}
|
|
|
|
# Store this in a way the admin panel can access it
|
|
# We'll use an environment variable to store the JSON list of locked var names
|
|
os.environ['_BK_DOCKER_ENV_VARS'] = json.dumps(list(docker_env_vars.keys()))
|
|
|
|
env_file = None
|
|
if data_env.exists():
|
|
env_file = data_env
|
|
logging.info(f"Loading environment from: {data_env}")
|
|
elif root_env.exists():
|
|
env_file = root_env
|
|
logging.info(f"Loading environment from: {root_env} (consider migrating to data/.env)")
|
|
|
|
if env_file:
|
|
# Simple .env parser (no external dependencies needed)
|
|
with open(env_file, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
# Skip comments and empty lines
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
# Parse key=value
|
|
if '=' in line:
|
|
key, value = line.split('=', 1)
|
|
key = key.strip()
|
|
value = value.strip()
|
|
# Remove quotes if present
|
|
if value.startswith('"') and value.endswith('"'):
|
|
value = value[1:-1]
|
|
elif value.startswith("'") and value.endswith("'"):
|
|
value = value[1:-1]
|
|
# Only set if not already in environment (environment variables take precedence)
|
|
if key not in os.environ:
|
|
os.environ[key] = value
|
|
|
|
|
|
def setup_app(app: Flask) -> None:
|
|
# Load .env file before configuration (if not already loaded by Docker Compose)
|
|
load_env_file()
|
|
|
|
# Load the configuration
|
|
BrickConfigurationList(app)
|
|
|
|
# Set the logging level
|
|
if app.config['DEBUG']:
|
|
logging.basicConfig(
|
|
stream=sys.stdout,
|
|
level=logging.DEBUG,
|
|
format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s', # noqa: E501
|
|
)
|
|
else:
|
|
logging.basicConfig(
|
|
stream=sys.stdout,
|
|
level=logging.INFO,
|
|
format='[%(asctime)s] %(levelname)s - %(message)s',
|
|
)
|
|
|
|
# Load the navbar
|
|
Navbar(app)
|
|
|
|
# Setup the login manager
|
|
LoginManager(app)
|
|
|
|
# Configure proxy header handling for reverse proxy deployments (nginx, Apache, etc.)
|
|
# This ensures proper client IP detection and HTTPS scheme recognition
|
|
app.wsgi_app = ProxyFix(
|
|
app.wsgi_app,
|
|
x_for=1,
|
|
x_proto=1,
|
|
x_host=1,
|
|
x_port=1,
|
|
x_prefix=1,
|
|
)
|
|
|
|
# Register errors
|
|
app.register_error_handler(404, error_404)
|
|
|
|
# Register app routes
|
|
app.register_blueprint(add_page)
|
|
app.register_blueprint(data_page)
|
|
app.register_blueprint(index_page)
|
|
app.register_blueprint(instructions_page)
|
|
app.register_blueprint(login_page)
|
|
app.register_blueprint(minifigure_page)
|
|
app.register_blueprint(part_page)
|
|
app.register_blueprint(set_page)
|
|
app.register_blueprint(statistics_page)
|
|
app.register_blueprint(storage_page)
|
|
app.register_blueprint(wish_page)
|
|
|
|
# Register admin routes
|
|
app.register_blueprint(admin_page)
|
|
app.register_blueprint(admin_database_page)
|
|
app.register_blueprint(admin_image_page)
|
|
app.register_blueprint(admin_instructions_page)
|
|
app.register_blueprint(admin_retired_page)
|
|
app.register_blueprint(admin_owner_page)
|
|
app.register_blueprint(admin_purchase_location_page)
|
|
app.register_blueprint(admin_set_page)
|
|
app.register_blueprint(admin_status_page)
|
|
app.register_blueprint(admin_storage_page)
|
|
app.register_blueprint(admin_tag_page)
|
|
app.register_blueprint(admin_theme_page)
|
|
|
|
# An helper to make global variables available to the
|
|
# request
|
|
@app.before_request
|
|
def before_request() -> None:
|
|
def request_time() -> str:
|
|
elapsed = time.time() - g.request_start_time
|
|
if elapsed < 1:
|
|
return '{elapsed:.0f}ms'.format(elapsed=elapsed*1000)
|
|
else:
|
|
return '{elapsed:.2f}s'.format(elapsed=elapsed)
|
|
|
|
# Login manager
|
|
g.login = LoginManager
|
|
|
|
# Execution time
|
|
g.request_start_time = time.time()
|
|
g.request_time = request_time
|
|
|
|
# Register the timezone
|
|
g.timezone = ZoneInfo(current_app.config['TIMEZONE'])
|
|
|
|
# Version
|
|
g.version = __version__
|
|
|
|
# Register custom Jinja2 filters
|
|
app.jinja_env.filters['replace_query'] = replace_query_filter
|
|
|
|
# Make sure all connections are closed at the end
|
|
@app.teardown_request
|
|
def teardown_request(_: BaseException | None) -> None:
|
|
close()
|