Threadline.OperatorSurface.Controllers.ExportController (Threadline v0.5.0)

Copy Markdown View Source

HTTP-side parity controller for the operator-surface "Download CSV / JSON / NDJSON" affordances. Three actions (csv/2, json/2, ndjson/2); each dispatches through one private dispatch/3 so the format-vs-transport branching is in one place.

Per request:

  1. Parse URL params via Threadline.OperatorSurface.Exports.FilterParams.parse/1 (single source of truth shared with TimelineLive).
  2. Re-validate via Threadline.Query.validate_timeline_filters!/1 (single source of truth shared with the lib + Mix task).
  3. Pre-flight Threadline.Export.count_matching/2 with cap: 10_001 so multi-million-row tables short-circuit instead of hitting statement_timeout.
  4. Dispatch:
    • count <= 5_000: full iodata via to_csv_iodata/2 / to_json_document/2 and send_resp(200, iodata).
    • count > 5_000: send_chunked(200) then stream via stream_export_rows(filters, page_size: 1_000) |> Stream.take(10_000) |> Stream.chunk_every(500) |> Enum.reduce_while/3 calling Plug.Conn.chunk/2 per chunk; halts on client disconnect.

Response headers (set BEFORE send_chunked/2 per Plug API):

  • Content-Type: text/csv; charset=utf-8 / application/json; charset=utf-8 / application/x-ndjson; charset=utf-8
  • Content-Disposition: attachment; filename="<canonical>"; filename*=UTF-8''<canonical> (RFC 6266 §4.3 dual-emit)
  • Cache-Control: no-store (audit-data hygiene)

Filename comes from Threadline.OperatorSurface.Exports.Filename.for/2 (UTC, minute granularity, hyphen-not-colon between hours and minutes for Windows compatibility).

Summary

Functions

csv(conn, params)

json(conn, params)

ndjson(conn, params)