From ebc944a6d394dbaf8698e6714f56d19d0797ac0a Mon Sep 17 00:00:00 2001 From: Deeman Date: Tue, 24 Feb 2026 03:04:01 +0100 Subject: [PATCH] fix(cms): open analytics DB in worker + fix per-language article upsert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs causing 0 articles after generation: 1. Worker never called open_analytics_db() — fetch_analytics always returned [] since _conn was None. Generation completed silently with 0 rows from DuckDB. 2. generate_articles upserted by url_path alone — after the URL prefix fix, all languages share the same url_path (e.g. /markets/de/berlin). The EN article was overwriting the DE article on every generation. Fixed: deduplicate on (url_path, language). 3. article_page and _filter_articles didn't filter by language — with shared url_paths a DE request could serve an EN article, and the markets hub would show duplicate entries per city. Fixed: both now filter by g.lang from the / blueprint prefix. Co-Authored-By: Claude Sonnet 4.6 --- web/src/padelnomics/content/__init__.py | 10 ++++++---- web/src/padelnomics/content/routes.py | 16 ++++++++++------ web/src/padelnomics/worker.py | 3 +++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/web/src/padelnomics/content/__init__.py b/web/src/padelnomics/content/__init__.py index f5d98cb..70395e4 100644 --- a/web/src/padelnomics/content/__init__.py +++ b/web/src/padelnomics/content/__init__.py @@ -434,9 +434,11 @@ async def generate_articles( md_dir.mkdir(parents=True, exist_ok=True) (md_dir / f"{article_slug}.md").write_text(body_md) - # Upsert article in SQLite + # Upsert article in SQLite — keyed by (url_path, language) since + # multiple languages share the same url_path existing_article = await fetch_one( - "SELECT id FROM articles WHERE url_path = ?", (url_path,), + "SELECT id FROM articles WHERE url_path = ? AND language = ?", + (url_path, lang), ) if existing_article: await execute( @@ -444,8 +446,8 @@ async def generate_articles( SET title = ?, meta_description = ?, template_slug = ?, language = ?, date_modified = ?, updated_at = ?, seo_head = ? - WHERE url_path = ?""", - (title, meta_desc, slug, lang, now_iso, now_iso, seo_head, url_path), + WHERE url_path = ? AND language = ?""", + (title, meta_desc, slug, lang, now_iso, now_iso, seo_head, url_path, lang), ) else: await execute( diff --git a/web/src/padelnomics/content/routes.py b/web/src/padelnomics/content/routes.py index fcf9203..be5cda7 100644 --- a/web/src/padelnomics/content/routes.py +++ b/web/src/padelnomics/content/routes.py @@ -7,7 +7,7 @@ from pathlib import Path from jinja2 import Environment, FileSystemLoader from markupsafe import Markup -from quart import Blueprint, abort, render_template, request +from quart import Blueprint, abort, g, render_template, request from ..core import capture_waitlist_email, csrf_protect, feature_gate, fetch_all, fetch_one from ..i18n import get_translations @@ -172,11 +172,14 @@ async def market_results(): async def _filter_articles(q: str, country: str, region: str) -> list[dict]: - """Query published articles with optional FTS + country/region filters.""" + """Query published articles for the current language.""" + lang = g.get("lang", "en") if q: # FTS query wheres = ["articles_fts MATCH ?"] params: list = [q] + wheres.append("a.language = ?") + params.append(lang) if country: wheres.append("a.country = ?") params.append(country) @@ -194,8 +197,8 @@ async def _filter_articles(q: str, country: str, region: str) -> list[dict]: tuple(params), ) else: - wheres = ["status = 'published'", "published_at <= datetime('now')"] - params = [] + wheres = ["status = 'published'", "published_at <= datetime('now')", "language = ?"] + params = [lang] if country: wheres.append("country = ?") params.append(country) @@ -218,12 +221,13 @@ async def _filter_articles(q: str, country: str, region: str) -> list[dict]: async def article_page(url_path: str): """Serve a published article by its url_path.""" clean_path = "/" + url_path.strip("/") + lang = g.get("lang", "en") article = await fetch_one( """SELECT * FROM articles - WHERE url_path = ? AND status = 'published' + WHERE url_path = ? AND language = ? AND status = 'published' AND published_at <= datetime('now')""", - (clean_path,), + (clean_path, lang), ) if not article: abort(404) diff --git a/web/src/padelnomics/worker.py b/web/src/padelnomics/worker.py index c0d7843..0681b25 100644 --- a/web/src/padelnomics/worker.py +++ b/web/src/padelnomics/worker.py @@ -756,6 +756,9 @@ async def run_worker(poll_interval: float = 1.0) -> None: """Main worker loop.""" print("[WORKER] Starting...") await init_db() + from .analytics import open_analytics_db + open_analytics_db() + print("[WORKER] Analytics DB opened.") while True: try: