"""Ads blueprint: click tracking redirect, sponsor directory. Ad creative upload lives in the admin blueprint (Phase 6). """ from flask import (Blueprint, redirect, abort, render_template, request, current_app) from flask_login import current_user from flask_babel import get_locale from app.extensions import db from app.models.ads import Ad from app.services.ads import (get_ad, record_impression, record_click, active_sponsors) from app.services.settings import get_setting ads_bp = Blueprint("ads", __name__) # ── Click tracking redirect ──────────────────────────────────────────────── @ads_bp.route("/ads//click") def click(ad_id): ad = db.session.get(Ad, ad_id) if ad is None or not ad.is_running: abort(404) try: record_click(ad_id) except Exception as e: current_app.logger.error("Click record error: %s", e) return redirect(ad.target_url) # ── Sponsor directory ────────────────────────────────────────────────────── @ads_bp.route("/sponsors") def sponsor_directory(): sponsors = active_sponsors(tier="directory") return render_template("sponsors/directory.html", sponsors=sponsors) # ── Context processor: inject ads into every template ───────────────────── def inject_ads(): """Called by the app factory context processor. Returns ad slots for use in templates. Ads suppressed for subscribers (ad_free plan limit). """ if not get_setting("ads_enabled", True): return {"ads": {}, "show_ads": False} show_ads = True if current_user.is_authenticated: plan = current_user.tier if plan and plan.limit("ad_free"): show_ads = False if not show_ads: return {"ads": {}, "show_ads": False} lang = str(get_locale()) if get_locale() else None state = request.args.get("state") or None ads = {} for slot in Ad.SLOTS: ad = get_ad(slot, lang=lang, state=state) if ad: ads[slot] = ad # record impression (fire-and-forget; errors suppressed) try: record_impression(ad.id) except Exception: pass return {"ads": ads, "show_ads": True}