70 lines
2.4 KiB
JavaScript
70 lines
2.4 KiB
JavaScript
// GET /relay/balance — peek at the caller's credit balance + tier WITHOUT
|
|
// charging anything. Recap clients call this to populate the
|
|
// "N credits remaining · Tier: X" banner.
|
|
//
|
|
// Two auth surfaces (see identity.js):
|
|
// - cloud: X-Recap-User-Id + X-Recap-Operator-Key → user:<id> pool,
|
|
// tier is the relay's stored (operator-set) value.
|
|
// - license: X-Recap-Install-Id (+ optional Bearer license) → legacy
|
|
// license/install pool, tier from the license.
|
|
//
|
|
// Returns the standard envelope (result=null, credit_charged=0). No row
|
|
// mutation beyond keeping tier_snapshot synced on the license path.
|
|
|
|
import express from "express";
|
|
import { resolveIdentity, identityTier } from "../identity.js";
|
|
import { getOrCreateRow, applyTierPromotion } from "../credits.js";
|
|
import { envelope, errorEnvelope } from "./envelope.js";
|
|
|
|
export function balanceRouter() {
|
|
const router = express.Router();
|
|
|
|
router.get("/balance", async (req, res) => {
|
|
let identity;
|
|
try {
|
|
identity = await resolveIdentity(req);
|
|
} catch (err) {
|
|
const e = await errorEnvelope({
|
|
error: err?.message || "auth_error",
|
|
statusHint: err?.status || 401,
|
|
});
|
|
return res.status(e.statusHint || 401).json(e.body);
|
|
}
|
|
if (identity.kind === "license" && !identity.installId) {
|
|
const e = await errorEnvelope({
|
|
error: "missing X-Recap-Install-Id header",
|
|
statusHint: 400,
|
|
});
|
|
return res.status(400).json(e.body);
|
|
}
|
|
|
|
const row = await getOrCreateRow({
|
|
creditKey: identity.creditKey,
|
|
installId: identity.installId,
|
|
license: identity.license,
|
|
});
|
|
// License path: fire the Core→paid promotion bookkeeping (this is
|
|
// typically the FIRST relay call after a license activation) and keep
|
|
// tier_snapshot synced to the license. Cloud path: the tier is the
|
|
// relay's stored, operator-set value — leave it untouched.
|
|
if (identity.kind === "license") {
|
|
const tier = identity.license.tier;
|
|
const promoted = await applyTierPromotion(row, tier);
|
|
if (!promoted) row.tier_snapshot = tier;
|
|
}
|
|
|
|
const tier = identityTier(identity, row);
|
|
const body = await envelope({
|
|
result: null,
|
|
creditKey: identity.creditKey,
|
|
installId: identity.installId,
|
|
license: identity.license,
|
|
tier,
|
|
creditCharged: 0,
|
|
});
|
|
res.json(body);
|
|
});
|
|
|
|
return router;
|
|
}
|