Pool admin: delete any word (tombstones + restore) + bulk import
Daily Word pool curation, full add/delete/import — no redeploys to fix tone: - Remove ANY pool word, curated or admin-added, via a word_pool_removed tombstone table. Runtime pool = (static ∪ added) − removed, so even a baked-in word can be pulled on negative feedback. Reversible: a "Removed" list with one-tap Restore lifts the tombstone. Lookup now surfaces a Remove button when in-pool, Restore when removed. - Import a vetted list (paste or .txt/.csv upload, read client-side): validates each word (alpha · 5–6 · in guess dictionary), ignores duplicates, and reports rejects with reasons. Re-adding/importing a removed word lifts its tombstone. - Word Search theme delete already existed (Edit/Remove per theme) — verified. Pool stays the clean 251/224; today's noisy LLM enrichment is discarded. Tests: +tests/test_pool_admin.py, extended test_word_pool_admin. 222 pytest + 11 vitest green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -344,6 +344,11 @@ class WordPoolBody(BaseModel):
|
||||
word: str
|
||||
|
||||
|
||||
class WordPoolImportBody(BaseModel):
|
||||
text: str = ""
|
||||
words: list[str] = []
|
||||
|
||||
|
||||
class ClientErrorBody(BaseModel):
|
||||
reason: str = ""
|
||||
path: str = ""
|
||||
@@ -1499,6 +1504,7 @@ def create_app() -> FastAPI:
|
||||
_require_admin(conn, request)
|
||||
res = games.lookup_word(word)
|
||||
res["in_pool"] = bool(res["variant"]) and res["word"] in games.answer_pool(conn, res["variant"])
|
||||
res["removed"] = bool(res["variant"]) and res["word"] in games._db_removed(conn, res["variant"])
|
||||
return res
|
||||
|
||||
@app.get("/api/admin/word/pool")
|
||||
@@ -1523,6 +1529,23 @@ def create_app() -> FastAPI:
|
||||
games.remove_pool_word(conn, word)
|
||||
return games.pool_summary(conn)
|
||||
|
||||
@app.post("/api/admin/word/pool/restore")
|
||||
def admin_word_pool_restore(body: WordPoolBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
games.restore_pool_word(conn, body.word)
|
||||
return games.pool_summary(conn)
|
||||
|
||||
@app.post("/api/admin/word/pool/import")
|
||||
def admin_word_pool_import(body: WordPoolImportBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
words = list(body.words)
|
||||
if body.text:
|
||||
words += re.findall(r"[A-Za-z]+", body.text)
|
||||
res = games.import_pool_words(conn, words)
|
||||
return {**res, "pool": games.pool_summary(conn)}
|
||||
|
||||
# --- Admin: Word Search theme authoring ---
|
||||
@app.get("/api/admin/wordsearch/themes")
|
||||
def admin_ws_themes(request: Request) -> list[dict]:
|
||||
|
||||
Reference in New Issue
Block a user