🚧 Add possbility to sort and filter on table headers
This commit is contained in:
+62
-7
@@ -1474,8 +1474,26 @@ def create_app(
|
||||
|
||||
@app.get("/messages", tags=["data"], dependencies=[Depends(authenticate)])
|
||||
def list_messages(
|
||||
imei: str | None = Query(None, description="Filter by IMEI (15 digits)"),
|
||||
device: str | None = Query(None, description="Filter by device name"),
|
||||
imei: list[str] | None = Query(
|
||||
None, description="Filter by IMEI (repeatable for a multi-select)"
|
||||
),
|
||||
device: list[str] | None = Query(
|
||||
None, description="Filter by device name (repeatable for a multi-select)"
|
||||
),
|
||||
imei_like: str | None = Query(
|
||||
None, description="Case-insensitive contains filter on IMEI"
|
||||
),
|
||||
device_like: str | None = Query(
|
||||
None, description="Case-insensitive contains filter on device name"
|
||||
),
|
||||
sort: str | None = Query(
|
||||
None,
|
||||
description=(
|
||||
"Sort column: id, received_at, imei, device or payload_time; "
|
||||
"prefix with - for descending (e.g. -received_at). Default: "
|
||||
"payload_time DESC when framing on it, else id DESC (newest first)."
|
||||
),
|
||||
),
|
||||
from_time: datetime | None = Query(
|
||||
None,
|
||||
alias="from",
|
||||
@@ -1532,12 +1550,23 @@ def create_app(
|
||||
# the query joins imei_map for the read-time settings merge.
|
||||
conditions: list[sql.Composable] = []
|
||||
params: list[Any] = []
|
||||
# Repeatable exact-match filters: ?imei=a&imei=b -> = ANY(...).
|
||||
# A single value keeps working (list of one).
|
||||
if imei:
|
||||
conditions.append(sql.SQL("m.imei = %s"))
|
||||
conditions.append(sql.SQL("m.imei = ANY(%s)"))
|
||||
params.append(imei)
|
||||
if device:
|
||||
conditions.append(sql.SQL("m.device = %s"))
|
||||
conditions.append(sql.SQL("m.device = ANY(%s)"))
|
||||
params.append(device)
|
||||
# Free-text contains filters for the column filter boxes. The whole
|
||||
# pattern is ONE parameter - psycopg forbids literal % inside SQL
|
||||
# strings (placeholder parsing).
|
||||
if imei_like:
|
||||
conditions.append(sql.SQL("m.imei ILIKE %s"))
|
||||
params.append(f"%{imei_like}%")
|
||||
if device_like:
|
||||
conditions.append(sql.SQL("m.device ILIKE %s"))
|
||||
params.append(f"%{device_like}%")
|
||||
if from_time is not None:
|
||||
conditions.append(sql.SQL("m.received_at >= %s"))
|
||||
params.append(_as_utc(from_time))
|
||||
@@ -1555,9 +1584,35 @@ def create_app(
|
||||
if conditions:
|
||||
where = sql.SQL(" WHERE ") + sql.SQL(" AND ").join(conditions)
|
||||
|
||||
# Order by device timestamp when framing on it (NULLS LAST matches the
|
||||
# idx_mqtt_messages_ptime index); otherwise newest-first by insert id.
|
||||
if payload_from_time is not None or payload_to_time is not None:
|
||||
# Sort: explicit ?sort= wins; default stays "payload_time DESC when
|
||||
# framing on it, else id DESC". The whitelist maps to pre-built sql
|
||||
# fragments - user input is never interpolated into the ORDER BY.
|
||||
SORTABLE = {
|
||||
"id": sql.SQL("m.id"),
|
||||
"received_at": sql.SQL("m.received_at"),
|
||||
"imei": sql.SQL("m.imei"),
|
||||
"device": sql.SQL("m.device"),
|
||||
"payload_time": sql.SQL("m.payload_time"),
|
||||
}
|
||||
if sort is not None:
|
||||
desc = sort.startswith("-")
|
||||
name = sort[1:] if desc else sort
|
||||
column = SORTABLE.get(name)
|
||||
if column is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=(
|
||||
f"unknown sort column {sort!r}; available: "
|
||||
+ ", ".join(SORTABLE)
|
||||
),
|
||||
)
|
||||
order_by = (
|
||||
sql.SQL(" ORDER BY ")
|
||||
+ column
|
||||
+ (sql.SQL(" DESC") if desc else sql.SQL(""))
|
||||
+ sql.SQL(" NULLS LAST LIMIT %s OFFSET %s")
|
||||
)
|
||||
elif payload_from_time is not None or payload_to_time is not None:
|
||||
order_by = sql.SQL(" ORDER BY m.payload_time DESC NULLS LAST LIMIT %s OFFSET %s")
|
||||
else:
|
||||
order_by = sql.SQL(" ORDER BY m.id DESC LIMIT %s OFFSET %s")
|
||||
|
||||
Reference in New Issue
Block a user