diff --git a/app/bridge_server.prg b/app/bridge_server.prg index f608b5e..95d973d 100644 --- a/app/bridge_server.prg +++ b/app/bridge_server.prg @@ -7,7 +7,19 @@ // and use AP_METHOD/AP_BODY/AP_JSONRESPONSE/ctx_get/... freely. FUNCTION Main() - LOCAL cErr := HTTP_SERVER_START( ":8090", "BRIDGEDISPATCH" ) + LOCAL cDsn := hb_GetEnv( "LABDB_DSN", "postgres://" + hb_GetEnv( "USER", "" ) + "@127.0.0.1:5432/labdb?sslmode=disable" ) + LOCAL nH, cErr + + ? "labdb: opening PG", cDsn + nH := PG_OPEN( cDsn ) + IF nH < 0 + ? "labdb: PG_OPEN failed (handlers will see empty ctx — server still starts)" + ELSE + ? "labdb: PG handle =", nH + ENDIF + LABDB_SET_PG( nH ) + + cErr := HTTP_SERVER_START( ":8090", "BRIDGEDISPATCH" ) IF cErr != NIL ? "httpserver:", cErr ENDIF @@ -26,6 +38,8 @@ FUNCTION BridgeDispatch( hReq ) "headers_out" => { => }, ; "status" => 200 ; } + FetchRouteData( hReq[ "path" ], hCtx ) + _CTX_SET_JSON( hb_jsonEncode( hCtx ) ) _OUT_CLEAR() @@ -52,6 +66,45 @@ FUNCTION BridgeDispatch( hReq ) "body" => _OUT_GET() ; } +// FetchRouteData runs the per-route SQL the matching server.js endpoint +// would have run and drops the values into ctx fields the corresponding +// PRG handler reads via ctx_get(). Routes without a SQL block here fall +// through to whatever default the handler uses (which is what 1a.4-3 +// validated). Extend by adding cases as more endpoints come online. +FUNCTION FetchRouteData( cPath, hCtx ) + LOCAL aRows + LOCAL nPG := LABDB_GET_PG() + + IF nPG < 0 + RETURN NIL + ENDIF + + DO CASE + CASE cPath == "/api/admin-stats.prg" .OR. cPath == "/api/admin-stats" + aRows := PG_QUERY( nPG, "SELECT (SELECT COUNT(*) FROM devices) AS devices, (SELECT COUNT(*) FROM sessions) AS sessions, (SELECT COUNT(*) FROM records) AS records, (SELECT COUNT(*) FROM sessions WHERE status = 'active') AS active_sessions" ) + IF aRows != NIL .AND. Len( aRows ) > 0 + hCtx[ "devices" ] := hb_NToS( aRows[ 1 ][ "devices" ] ) + hCtx[ "sessions" ] := hb_NToS( aRows[ 1 ][ "sessions" ] ) + hCtx[ "records" ] := hb_NToS( aRows[ 1 ][ "records" ] ) + hCtx[ "active_sessions" ] := hb_NToS( aRows[ 1 ][ "active_sessions" ] ) + ENDIF + + CASE cPath == "/api/sessions-list.prg" .OR. cPath == "/api/sessions-list" + aRows := PG_QUERY( nPG, "SELECT session_id, device_id, session_name, start_time, end_time, record_count, status FROM sessions ORDER BY start_time DESC LIMIT 50" ) + IF aRows != NIL + // sessions-list.prg reads ctx_get("rows", "[]") + hCtx[ "rows" ] := hb_jsonEncode( aRows ) + hCtx[ "total" ] := hb_NToS( Len( aRows ) ) + ENDIF + + CASE cPath == "/api/admin-devices.prg" .OR. cPath == "/api/admin-devices" + aRows := PG_QUERY( nPG, "SELECT device_id, device_name, api_key, is_active, created_at FROM devices ORDER BY created_at DESC" ) + IF aRows != NIL + hCtx[ "rows" ] := hb_jsonEncode( aRows ) + ENDIF + ENDCASE +RETURN NIL + // /api/admin-stats.prg -> ADMIN_STATS__MAIN // /api/hello.prg -> HELLO__MAIN // /api/hello -> HELLO__MAIN (extension optional) diff --git a/cmd/fnode/main.go b/cmd/fnode/main.go index 2ff5380..e85e50f 100644 --- a/cmd/fnode/main.go +++ b/cmd/fnode/main.go @@ -39,6 +39,7 @@ var defaultRTL = []string{ "fivenode_go/hbrtl_ext/bridge_capi", "fivenode_go/hbrtl_ext/pgrtl", "fivenode_go/hbrtl_ext/dispatch", + "fivenode_go/hbrtl_ext/labdb_state", } func main() { diff --git a/hbrtl_ext/labdb_state/state.go b/hbrtl_ext/labdb_state/state.go new file mode 100644 index 0000000..0fef909 --- /dev/null +++ b/hbrtl_ext/labdb_state/state.go @@ -0,0 +1,36 @@ +// Package labdb_state holds a process-global Postgres handle. Saves +// app/bridge_server.prg from depending on Five's STATIC / PUBLIC +// semantics for the same purpose. +// +// PRG surface +// +// LABDB_SET_PG(nH) -> NIL +// LABDB_GET_PG() -> integer (-1 until SET) +package labdb_state + +import ( + "sync/atomic" + + "five/hbrt" +) + +var pgHandle atomic.Int64 + +func init() { + pgHandle.Store(-1) + hbrt.HB_FUNC("LABDB_SET_PG", labdbSetPG) + hbrt.HB_FUNC("LABDB_GET_PG", labdbGetPG) +} + +func labdbSetPG(ctx *hbrt.HBContext) { + if ctx.PCount() < 1 || !ctx.IsNumeric(1) { + ctx.RetNil() + return + } + pgHandle.Store(int64(ctx.ParNI(1))) + ctx.RetNil() +} + +func labdbGetPG(ctx *hbrt.HBContext) { + ctx.RetNI(int(pgHandle.Load())) +}