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>
72 lines
2.3 KiB
Plaintext
72 lines
2.3 KiB
Plaintext
// api/device-status.prg — Format device status response
|
|
//
|
|
// ctx:
|
|
// row (JSON) — device row from server.js (or empty hash if not found)
|
|
// key_format_valid — "1" if api_key starts with xbk_, "0" otherwise
|
|
//
|
|
// Logic:
|
|
// - row empty + valid format → 404 DEVICE_DELETED
|
|
// - row empty + invalid format → 401 INVALID_API_KEY
|
|
// - row exists → 200 with status info (active/pending/revoked)
|
|
//
|
|
// Copyright (c) 2026 Charles KWON OhJun
|
|
|
|
FUNCTION Main()
|
|
LOCAL hRow, cStatus, hOut, lValid
|
|
|
|
hRow := hb_jsonDecode(ctx_get("row", "{}"))
|
|
lValid := ctx_get("key_format_valid", "0") == "1"
|
|
|
|
IF ! HB_ISHASH(hRow) .OR. Empty(hRow) .OR. ! hb_HHasKey(hRow, "device_id")
|
|
// No matching device
|
|
IF lValid
|
|
AP_JSONRESPONSE({ ;
|
|
"error" => { ;
|
|
"code" => "DEVICE_DELETED", ;
|
|
"message" => "This device has been removed from the server. Please re-register.", ;
|
|
"status" => 404 ;
|
|
} ;
|
|
}, 404)
|
|
ELSE
|
|
AP_JSONRESPONSE({ ;
|
|
"error" => { ;
|
|
"code" => "INVALID_API_KEY", ;
|
|
"message" => "API key is invalid or malformed.", ;
|
|
"status" => 401 ;
|
|
} ;
|
|
}, 401)
|
|
ENDIF
|
|
RETURN NIL
|
|
ENDIF
|
|
|
|
cStatus := fn_HGet(hRow, "status", "pending")
|
|
|
|
hOut := { ;
|
|
"deviceId" => fn_HGet(hRow, "device_id"), ;
|
|
"status" => cStatus, ;
|
|
"deviceName" => fn_HGet(hRow, "device_name"), ;
|
|
"appName" => fn_HGet(hRow, "app_name"), ;
|
|
"registeredAt" => fn_HGet(hRow, "created_at"), ;
|
|
"approvedAt" => fn_HGet(hRow, "approved_at"), ;
|
|
"revokedAt" => fn_HGet(hRow, "revoked_at"), ;
|
|
"lastSeenAt" => fn_HGet(hRow, "last_seen_at") ;
|
|
}
|
|
|
|
IF cStatus == "active"
|
|
hOut[ "sessionCount" ] := fn_HGet(hRow, "session_count", 0)
|
|
ELSEIF cStatus == "pending"
|
|
hOut[ "message" ] := "Awaiting administrator approval."
|
|
ELSEIF cStatus == "revoked"
|
|
hOut[ "message" ] := "Device has been revoked. Contact administrator."
|
|
ENDIF
|
|
|
|
AP_JSONRESPONSE(hOut)
|
|
RETURN NIL
|
|
|
|
FUNCTION device_status_fn_hget(h, k, xDefault)
|
|
IF xDefault == NIL ; xDefault := "" ; ENDIF
|
|
IF HB_ISHASH(h) .AND. hb_HHasKey(h, k) .AND. h[k] != NIL
|
|
RETURN h[k]
|
|
ENDIF
|
|
RETURN xDefault
|