Drop-in copy of labdb's API surface from
fivenode/labdb/api/*.prg into app/api/. Single fnode invocation builds
the whole thing (bridge_server + bridge/ + 22 api/*.prg) into one
24 MB Go binary — no Node.js, no FFI, no Apache.
End-to-end smoke test (server.js not running, ctx empty so defaults
fall back) hitting six endpoints all return well-formed JSON via the
bridge layer + path -> Main dispatcher:
GET /api/hello.prg -> {"msg":"hello from PRG","ok":true}
GET /api/admin-stats.prg -> {"active_sessions":0,...}
GET /api/admin-me.prg -> {"ok":true,"user":{...}}
GET /api/sessions-list.prg -> {"sessions":[],"total":0}
GET /api/records-list.prg -> {"records":[],"sessionId":"",...}
POST /api/devices-register -> {"deviceId":"","status":"pending",...}
One small upstream patch was needed: seven .prg files each define
their own STATIC FUNCTION fn_HGet, but Five doesn't yet honour
file-local STATIC scoping for top-level functions — all definitions
land in the same symbol table and collide. Renamed each duplicate to
<file>_fn_hget so they peacefully coexist; the call sites still
reference fn_HGet and Five resolves them against _helpers.prg's
public version (signature-compatible). TODO: revert once Five gains
file-local STATIC FUNCTION scoping.
What's deferred to 1a.4-4: ctx data injection (so endpoints return
real labdb data), static asset embedding (labdb/public/), and a live
LABDB_DSN round-trip to confirm pgrtl in the request path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
2.9 KiB
Plaintext
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
|
|
|
|
FUNCTION session_export_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
|