CognxSafeTrack
Claude Sonnet 4.6
feat(billing): implement full wallet/ledger system with hard-stop enforcement
0fd3320 Download apps/api/src/middleware/requireCredits.ts from safetrack/edtech: direct link, hf CLI and curl.
- Browser
- Download file 1.56 kB
-
https://huggingface.co/spaces/safetrack/edtech/resolve/main/apps/api/src/middleware/requireCredits.ts
- Command line
-
hf download hf://spaces/safetrack/edtech/apps/api/src/middleware/requireCredits.ts
-
curl -L -o requireCredits.ts https://huggingface.co/spaces/safetrack/edtech/resolve/main/apps/api/src/middleware/requireCredits.ts
1.56 kB
| import { FastifyRequest, FastifyReply } from 'fastify'; | |
| import { prisma } from '../services/prisma'; | |
| import { redis } from '../lib/redis'; | |
| const CACHE_TTL = 30; | |
| function cacheKey(organizationId: string) { | |
| return `wallet:ok:${organizationId}`; | |
| } | |
| /** | |
| * Fastify preHandler — blocks with 402 if the org wallet is exhausted or hard-stopped. | |
| * Mirrors the worker's checkWalletBalance() using the same Redis cache key. | |
| * Fails open on DB/Redis error to avoid blocking legitimate requests. | |
| */ | |
| export async function requireCredits(request: FastifyRequest, reply: FastifyReply): Promise<void> { | |
| const organizationId = request.organizationId ?? (request.headers['x-organization-id'] as string); | |
| if (!organizationId) return; | |
| try { | |
| const cached = await redis.get(cacheKey(organizationId)); | |
| if (cached === 'ok') return; | |
| } catch { | |
| // Redis unavailable — proceed to DB check | |
| } | |
| try { | |
| const org = await prisma.organization.findUnique({ | |
| where: { id: organizationId }, | |
| select: { walletBalance: true, isHardStopped: true }, | |
| }); | |
| if (!org) return; // unknown org — fail open | |
| if (org.isHardStopped || org.walletBalance <= 0) { | |
| return reply.code(402).send({ | |
| error: 'wallet_exhausted', | |
| message: 'Wallet balance exhausted. Please top up to continue.', | |
| }); | |
| } | |
| redis.setex(cacheKey(organizationId), CACHE_TTL, 'ok').catch(() => {}); | |
| } catch { | |
| // DB error — fail open | |
| } | |
| } | |