fix(whatsapp,roles): self-heal legacy whatsapp_log schema; map underscore role aliases
CT115 (Mumuni relay #747) — two live-instance defects after PR #12:
1. GET /api/whatsapp/mock-log 500'd with a valid admin token:
'no such column: whatsapp_log.message_text'. The model gained
message_text/wa_message_id/ticket_number (and dropped command) in
4afdc36 with no migration, so legacy DBs keep the (command, ...) shape.
ensure_legacy_schema (startup, app/main.py) now adds the three missing
columns idempotently and backfills legacy command bodies into
message_text before dropping the obsolete NOT NULL command column, so
both the mock-log read path and the ORM write path work on healed DBs.
New producer/consumer regression (tests/test_whatsapp_log_legacy_heal.py)
reproduces the exact OperationalError, then asserts 200 + data.
2. ROLE_ALIASES gap: underscore legacy roles (cs_rep, cs_manager,
fm_dispatcher) were not mapped, so normalize_legacy_user_roles could not
converge rows like user 18 (test@denya.com, role 'cs_rep') and the
frontend stranded them on /tickets. Added the underscore aliases; tests
assert normalize_role('cs_rep') == 'CS Rep' and a cs_rep row converges
and authenticates.
This commit is contained in:
+6
-2
@@ -12,8 +12,9 @@ uses (PRD §4, ``app/services/seed.py``, the frontend nav in base.html):
|
||||
|
||||
Legacy databases created under the pre-P0 open-registration builds can carry
|
||||
lowercase/nickname role strings (``technician``, ``cs``, ``fm``, ``ceo``,
|
||||
``admin``, ``superadmin`` …). ``ROLE_ALIASES`` maps the *unambiguous*
|
||||
nicknames onto a canonical role so startup normalization (see
|
||||
``admin``, ``superadmin`` …) and underscore variants of the space-separated
|
||||
ones (``cs_rep``, ``cs_manager``, ``fm_dispatcher``). ``ROLE_ALIASES`` maps
|
||||
the *unambiguous* nicknames onto a canonical role so startup normalization (see
|
||||
``app/services/seed.py::normalize_legacy_user_roles``) can converge the data.
|
||||
|
||||
``admin`` / ``superadmin`` are deliberately NOT aliased: they are
|
||||
@@ -52,10 +53,13 @@ ROLE_ALIASES: dict[str, str] = {
|
||||
"tech": TECHNICIAN_ROLE,
|
||||
"cs": "CS Rep",
|
||||
"cs rep": "CS Rep",
|
||||
"cs_rep": "CS Rep",
|
||||
"cs representative": "CS Rep",
|
||||
"cs manager": "CS Manager",
|
||||
"cs_manager": "CS Manager",
|
||||
"fm": "FM Dispatcher",
|
||||
"fm dispatcher": "FM Dispatcher",
|
||||
"fm_dispatcher": "FM Dispatcher",
|
||||
"ceo": "CEO",
|
||||
"director": "Director",
|
||||
}
|
||||
|
||||
+41
@@ -52,6 +52,47 @@ async def ensure_legacy_schema(conn) -> None:
|
||||
text("UPDATE tickets SET reported_at = created_at WHERE reported_at IS NULL")
|
||||
)
|
||||
logger.info("Added missing tickets.reported_at column (legacy database)")
|
||||
|
||||
# whatsapp_log predates the real Meta webhook (the model gained
|
||||
# message_text/wa_message_id/ticket_number and dropped `command` in commit
|
||||
# 4afdc36 with no migration), so legacy DBs still carry the old shape and
|
||||
# every read/write through the ORM 500s (no such column: message_text).
|
||||
result = await conn.execute(
|
||||
text("SELECT name FROM sqlite_master WHERE type='table' AND name='whatsapp_log'")
|
||||
)
|
||||
if result.scalar():
|
||||
result = await conn.execute(text("PRAGMA table_info(whatsapp_log)"))
|
||||
log_columns = {row[1] for row in result}
|
||||
if "message_text" not in log_columns:
|
||||
await conn.execute(
|
||||
text("ALTER TABLE whatsapp_log ADD COLUMN message_text TEXT NOT NULL DEFAULT ''")
|
||||
)
|
||||
logger.info("Added missing whatsapp_log.message_text column (legacy database)")
|
||||
if "wa_message_id" not in log_columns:
|
||||
await conn.execute(
|
||||
text("ALTER TABLE whatsapp_log ADD COLUMN wa_message_id VARCHAR(100)")
|
||||
)
|
||||
logger.info("Added missing whatsapp_log.wa_message_id column (legacy database)")
|
||||
if "ticket_number" not in log_columns:
|
||||
await conn.execute(
|
||||
text("ALTER TABLE whatsapp_log ADD COLUMN ticket_number VARCHAR(30)")
|
||||
)
|
||||
logger.info("Added missing whatsapp_log.ticket_number column (legacy database)")
|
||||
if "command" in log_columns:
|
||||
# Legacy rows stored the message body in `command`, which the model
|
||||
# no longer defines (NOT NULL, no default): any ORM insert omitting
|
||||
# it would violate NOT NULL. Preserve the old bodies in
|
||||
# message_text, then drop the obsolete column to match the model.
|
||||
await conn.execute(
|
||||
text(
|
||||
"UPDATE whatsapp_log SET message_text = command "
|
||||
"WHERE (message_text IS NULL OR message_text = '') "
|
||||
"AND command IS NOT NULL AND command != ''"
|
||||
)
|
||||
)
|
||||
await conn.execute(text("ALTER TABLE whatsapp_log DROP COLUMN command"))
|
||||
logger.info("Dropped obsolete whatsapp_log.command column (legacy database)")
|
||||
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"UPDATE categories SET name = 'Missing Item' "
|
||||
|
||||
Reference in New Issue
Block a user