Admin: Daily Word pool curation (lookup + add/remove)
First games admin tool. A "Games" tab in the operator console for the Daily Word answer pool. * Lookup: is a word real (in the guess dictionary), the right length (5/6), and already in the pool — instant as you type. * Add: appends to the pool, enforcing the invariant (alpha · 5/6 letters · in the guess dict) so the daily answer is always guessable. Remove: drops admin-added words (curated static ones stay). * Additions persist in a new word_pool table (survives redeploys, unlike the baked-in JSON); the daily picker reads static pool ∪ DB additions. Guess dicts shipped with the package (goodnews/data/words-5/6.json) for server-side validation. Admin-gated endpoints + tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -340,6 +340,10 @@ class WordGuessRequest(BaseModel):
|
||||
n: int = 1 # this guess's position (1-based); the answer is revealed only at n >= max
|
||||
|
||||
|
||||
class WordPoolBody(BaseModel):
|
||||
word: str
|
||||
|
||||
|
||||
class EmailStartRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
@@ -1448,6 +1452,37 @@ def create_app() -> FastAPI:
|
||||
raise HTTPException(status_code=400, detail=res["error"])
|
||||
return res
|
||||
|
||||
# --- Admin: Daily Word pool curation ---
|
||||
@app.get("/api/admin/word/lookup")
|
||||
def admin_word_lookup(word: str, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_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"])
|
||||
return res
|
||||
|
||||
@app.get("/api/admin/word/pool")
|
||||
def admin_word_pool(request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
return games.pool_summary(conn)
|
||||
|
||||
@app.post("/api/admin/word/pool")
|
||||
def admin_word_pool_add(body: WordPoolBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
res = games.add_pool_word(conn, body.word)
|
||||
if "error" in res:
|
||||
raise HTTPException(status_code=400, detail=res["error"])
|
||||
return {**res, "pool": games.pool_summary(conn)}
|
||||
|
||||
@app.delete("/api/admin/word/pool/{word}")
|
||||
def admin_word_pool_remove(word: str, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
games.remove_pool_word(conn, word)
|
||||
return games.pool_summary(conn)
|
||||
|
||||
@app.get("/api/since", response_model=FeedResponse)
|
||||
def feed_since(ts: str = Query(...), prefs: str | None = Query(None)) -> FeedResponse:
|
||||
# A calm welcome-back cue: accepted/non-dup/visible articles discovered
|
||||
|
||||
Reference in New Issue
Block a user