Files
fivenode_go/app/api/session-export.prg
Charles KWON OhJun ed956a1504 revert: drop AP_RPUTS single-arg patch and fn_HGet renames
Both workarounds existed because Five was missing two features that
just landed upstream:

  Five 7629f95 (variadic PValue) makes FUNCTION foo(...) / PValue()
  actually return the caller's variadic args instead of the caller's
  first LOCAL slot. AP_RPUTS / AP_ECHO can go back to their `( ... )`
  signature now.

  Five f3e0ffe (file-local STATIC FUNCTION) gives each .prg its own
  namespace for `STATIC FUNCTION name`, so the seven duplicate
  `STATIC FUNCTION fn_HGet` definitions across labdb's api/*.prg
  files no longer collide. The sed-renamed unique names can revert
  to the upstream definitions.

Files

  app/bridge/bridge_request.prg     ← cp from fivenode/native/
  app/api/{device-status,record-detail,records-list,session-detail,
           session-stats,sessions-list,session-export}.prg
                                    ← cp from fivenode/labdb/api/

fivenode-upstream is now byte-identical to fivenode_go's app/ copy
of those files. No more "// fivenode_go patch" comments, no more
file-prefix renames.

Verified end-to-end against the same live postgres@16 cluster:
  /api/admin-stats.prg    -> {"active_sessions":1,"devices":2,...}
  /api/sessions-list.prg  -> 2 rows w/ full session data
  /api/admin-devices.prg  -> 2 devices w/ api_key, created_at
  /api/hello.prg          -> hello (unchanged)
  /                       -> 200 text/html (static)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 17:36:14 +09:00

93 lines
2.9 KiB
Plaintext

// api/session-export.prg — Format CSV/JSON export
//
// ctx:
// format ("csv"|"json")
// session (JSON of session row)
// rows (JSON array of records)
//
// PRG generates the body string. server.js sets Content-Type/Disposition.
FUNCTION Main()
LOCAL cFormat, hSession, aRows, hRow, hSensor, aChannels, hCh
LOCAL cBOM, cCRLF, aLines, aCells, i, hChMap
cFormat := Lower(ctx_get("format", "csv"))
hSession := hb_jsonDecode(ctx_get("session", "{}"))
aRows := hb_jsonDecode(ctx_get("rows", "[]"))
IF ! HB_ISHASH(hSession) ; hSession := { => } ; ENDIF
IF ! HB_ISARRAY(aRows) ; aRows := {} ; ENDIF
IF cFormat == "json"
AP_JSONRESPONSE({ "session" => hSession, "records" => aRows })
RETURN NIL
ENDIF
// CSV with UTF-8 BOM (Excel Korean support)
cBOM := Chr(239) + Chr(187) + Chr(191)
cCRLF := Chr(13) + Chr(10)
aLines := {}
AAdd(aLines, "# Session: " + AllTrim(fn_HGet(hSession, "session_name")))
AAdd(aLines, "# Device: " + AllTrim(fn_HGet(hSession, "device_name")) + " (" + AllTrim(fn_HGet(hSession, "device_address")) + ")")
// Header
AAdd(aLines, "row,timestamp,cmd,batt_mv,batt_pct,temp_c,raa,ch0_peak,ch0_pidx,ch1_peak,ch1_pidx,ch2_peak,ch2_pidx,ch3_peak,ch3_pidx,ch4_peak,ch4_pidx,ch5_peak,ch5_pidx")
FOR EACH hRow IN aRows
hSensor := fn_HGet(hRow, "sensor")
IF ! HB_ISHASH(hSensor) ; hSensor := { => } ; ENDIF
// Build channel map (ch → {peak, peakIdx})
hChMap := { => }
aChannels := fn_HGet(hRow, "channels")
IF HB_ISARRAY(aChannels)
FOR EACH hCh IN aChannels
IF HB_ISHASH(hCh) .AND. hb_HHasKey(hCh, "ch")
hChMap[ AllTrim(Str(hCh["ch"])) ] := hCh
ENDIF
NEXT
ENDIF
aCells := { ;
AllTrim(Str(fn_HGet(hRow, "row_index"))), ;
AllTrim(fn_HGet(hRow, "timestamp")), ;
AllTrim(fn_HGet(hRow, "command_type")), ;
numStr(fn_HGet(hSensor, "batteryMv")), ;
numStr(fn_HGet(hSensor, "batteryPct")), ;
numStr(fn_HGet(hSensor, "tempC")), ;
numStr(fn_HGet(hRow, "raa_status")) ;
}
FOR i := 0 TO 5
IF hb_HHasKey(hChMap, AllTrim(Str(i)))
AAdd(aCells, numStr(fn_HGet(hChMap[AllTrim(Str(i))], "peak")))
AAdd(aCells, numStr(fn_HGet(hChMap[AllTrim(Str(i))], "peakIdx")))
ELSE
AAdd(aCells, "")
AAdd(aCells, "")
ENDIF
NEXT
AAdd(aLines, fn_Join(aCells, ","))
NEXT
// Use AP_RPUTS to send raw CSV string
AP_RPUTS(cBOM + fn_Join(aLines, cCRLF))
RETURN NIL
STATIC FUNCTION fn_HGet(h, k)
IF HB_ISHASH(h) .AND. hb_HHasKey(h, k) ; RETURN h[k] ; ENDIF
RETURN ""
STATIC FUNCTION numStr(x)
IF x == NIL .OR. Empty(x) ; RETURN "" ; ENDIF
IF ValType(x) == "N" ; RETURN AllTrim(Str(x)) ; ENDIF
RETURN AllTrim(x)
STATIC FUNCTION fn_Join(arr, sep)
LOCAL out := "", i
FOR i := 1 TO Len(arr)
IF i > 1 ; out += sep ; ENDIF
out += arr[i]
NEXT
RETURN out