fix(pseo): error details collapse + UNIQUE constraint on slug
1. Stop HTMX polling on job rows that have an error set, so the <details> element stays open when clicked (was being replaced every 2s by the poll cycle). 2. Migration 0030: drop redundant single-column UNIQUE on articles.slug — the real uniqueness key is (url_path, language). The slug index is kept for lookups. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
{% set pct = [((job.progress_current / job.progress_total) * 100) | int, 100] | min if job.progress_total else 0 %}
|
||||
|
||||
<tr id="job-{{ job.id }}"
|
||||
{% if job.status == 'pending' %}
|
||||
{% if job.status == 'pending' and not job.error %}
|
||||
hx-get="{{ url_for('pseo.pseo_job_status', job_id=job.id) }}"
|
||||
hx-trigger="every 2s"
|
||||
hx-target="this"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Drop UNIQUE constraint from articles.slug column.
|
||||
|
||||
The single-column UNIQUE on slug conflicts with the ON CONFLICT(url_path, language)
|
||||
upsert in pSEO generation, causing 'UNIQUE constraint failed: articles.slug' errors
|
||||
when re-running generation for the same template.
|
||||
|
||||
The slug is already unique by construction ({template_slug}-{lang}-{natural_key}),
|
||||
and the real uniqueness key is (url_path, language). The idx_articles_slug index
|
||||
is kept for fast lookups.
|
||||
"""
|
||||
|
||||
|
||||
def up(conn) -> None:
|
||||
# ── 1. Drop FTS triggers + virtual table ──────────────────────────────────
|
||||
conn.execute("DROP TRIGGER IF EXISTS articles_ai")
|
||||
conn.execute("DROP TRIGGER IF EXISTS articles_ad")
|
||||
conn.execute("DROP TRIGGER IF EXISTS articles_au")
|
||||
conn.execute("DROP TABLE IF EXISTS articles_fts")
|
||||
|
||||
# ── 2. Recreate articles without UNIQUE on slug ───────────────────────────
|
||||
conn.execute("""
|
||||
CREATE TABLE articles_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
url_path TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
meta_description TEXT,
|
||||
country TEXT,
|
||||
region TEXT,
|
||||
og_image_url TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
published_at TEXT,
|
||||
template_slug TEXT,
|
||||
language TEXT NOT NULL DEFAULT 'en',
|
||||
date_modified TEXT,
|
||||
seo_head TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT,
|
||||
group_key TEXT DEFAULT NULL,
|
||||
noindex INTEGER NOT NULL DEFAULT 0,
|
||||
article_type TEXT NOT NULL DEFAULT 'editorial',
|
||||
UNIQUE(url_path, language)
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
INSERT INTO articles_new
|
||||
(id, url_path, slug, title, meta_description, country, region,
|
||||
og_image_url, status, published_at, template_slug, language,
|
||||
date_modified, seo_head, created_at, updated_at, group_key,
|
||||
noindex, article_type)
|
||||
SELECT id, url_path, slug, title, meta_description, country, region,
|
||||
og_image_url, status, published_at, template_slug, language,
|
||||
date_modified, seo_head, created_at, updated_at, group_key,
|
||||
noindex, article_type
|
||||
FROM articles
|
||||
""")
|
||||
conn.execute("DROP TABLE articles")
|
||||
conn.execute("ALTER TABLE articles_new RENAME TO articles")
|
||||
|
||||
# ── 3. Recreate indexes ───────────────────────────────────────────────────
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_url_path ON articles(url_path)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_url_lang ON articles(url_path, language)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_slug ON articles(slug)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_status ON articles(status, published_at)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_article_type ON articles(article_type)")
|
||||
|
||||
# ── 4. Recreate FTS + triggers ────────────────────────────────────────────
|
||||
conn.execute("""
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(
|
||||
title, meta_description, country, region,
|
||||
content='articles', content_rowid='id'
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TRIGGER IF NOT EXISTS articles_ai AFTER INSERT ON articles BEGIN
|
||||
INSERT INTO articles_fts(rowid, title, meta_description, country, region)
|
||||
VALUES (new.id, new.title, new.meta_description, new.country, new.region);
|
||||
END
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TRIGGER IF NOT EXISTS articles_ad AFTER DELETE ON articles BEGIN
|
||||
INSERT INTO articles_fts(articles_fts, rowid, title, meta_description, country, region)
|
||||
VALUES ('delete', old.id, old.title, old.meta_description, old.country, old.region);
|
||||
END
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TRIGGER IF NOT EXISTS articles_au AFTER UPDATE ON articles BEGIN
|
||||
INSERT INTO articles_fts(articles_fts, rowid, title, meta_description, country, region)
|
||||
VALUES ('delete', old.id, old.title, old.meta_description, old.country, old.region);
|
||||
INSERT INTO articles_fts(rowid, title, meta_description, country, region)
|
||||
VALUES (new.id, new.title, new.meta_description, new.country, new.region);
|
||||
END
|
||||
""")
|
||||
Reference in New Issue
Block a user