"""Application factory.""" from flask import Flask, render_template from app.config import get_config from app.extensions import (db, migrate, login_manager, csrf, babel, limiter) def create_app(config_object=None): app = Flask(__name__) app.config.from_object(config_object or get_config()) _init_extensions(app) _init_login(app) _init_babel(app) _register_blueprints(app) _register_errorhandlers(app) _register_context(app) _register_cli(app) return app def _init_extensions(app): # Honor X-Forwarded-* from the Nginx reverse proxy (scheme, host, client IP). # Correct is_secure, secure cookies, and rate-limit keying behind the proxy. from werkzeug.middleware.proxy_fix import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) db.init_app(app) migrate.init_app(app, db) csrf.init_app(app) limiter.init_app(app) # ensure models are imported so migrations see them from app import models # noqa: F401 def _init_login(app): login_manager.init_app(app) @login_manager.user_loader def load_user(user_id): from app.models.user import User return User.query.get(int(user_id)) def _init_babel(app): from app.blueprints.i18n.routes import select_locale babel.init_app(app, locale_selector=select_locale) def _register_blueprints(app): from app.blueprints.main.routes import main_bp from app.blueprints.auth.routes import auth_bp from app.blueprints.i18n.routes import i18n_bp from app.blueprints.listings.routes import listings_bp from app.blueprints.messaging.routes import messaging_bp from app.blueprints.payments.routes import payments_bp from app.blueprints.ads.routes import ads_bp from app.blueprints.admin.routes import admin_bp app.register_blueprint(main_bp) app.register_blueprint(auth_bp) app.register_blueprint(i18n_bp) app.register_blueprint(listings_bp) app.register_blueprint(messaging_bp) app.register_blueprint(payments_bp) app.register_blueprint(ads_bp) app.register_blueprint(admin_bp) def _register_errorhandlers(app): @app.errorhandler(403) def forbidden(e): return render_template("errors/403.html"), 403 @app.errorhandler(404) def not_found(e): return render_template("errors/404.html"), 404 @app.errorhandler(500) def server_error(e): return render_template("errors/500.html"), 500 def _register_context(app): from flask_babel import get_locale from flask import request from flask_login import current_user from app.blueprints.ads.routes import inject_ads def merge_query(**overrides): merged = request.args.to_dict() merged.update({k: v for k, v in overrides.items() if v is not None}) return merged @app.context_processor def inject_globals(): unread = 0 if current_user.is_authenticated: try: from app.services.messaging import total_unread unread = total_unread(current_user) except Exception: pass ad_context = {} try: ad_context = inject_ads() except Exception: pass return { "get_locale": get_locale, "merge_query": merge_query, "SUPPORTED_LOCALES": app.config["SUPPORTED_LOCALES"], "TURNSTILE_SITE_KEY": app.config.get("TURNSTILE_SITE_KEY", ""), "unread_count": unread, **ad_context, } def _register_cli(app): @app.cli.command("expire-listings") def expire_listings_cmd(): """Sweep: flip past-due active listings to expired.""" from app.services.listings import expire_due_listings n = expire_due_listings() print(f"Expired {n} listing(s).") @app.cli.command("expire-boosts") def expire_boosts_cmd(): """Sweep: clear expired boosts, revert listing effects.""" from app.services.billing import expire_boosts n = expire_boosts() print(f"Cleared {n} expired boost(s).") @app.cli.command("expire-promoted-keywords") def expire_promoted_cmd(): """Sweep: remove expired promoted keyword rows.""" from app.services.ads import expire_promoted_keywords n = expire_promoted_keywords() print(f"Removed {n} expired promoted keyword(s).") @app.cli.command("reconcile-subscriptions") def reconcile_cmd(): """Nightly: sync local subscription status vs Stripe.""" from app.services.billing import reconcile_subscriptions checked, fixed = reconcile_subscriptions() print(f"Reconciled {checked} subscription(s), fixed {fixed}.")