mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(shared-hono): add credit client for Hono backends
Add credits.ts to @manacore/shared-hono as replacement for CreditClientService from @mana-core/nestjs-integration. Exports: getBalance, validateCredits, consumeCredits, refundCredits Calls mana-credits service via MANA_CREDITS_URL + X-Service-Key. Same API surface as the NestJS version but as pure functions instead of an @Injectable() service class. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87d7966b0f
commit
3fca0de680
3 changed files with 129 additions and 1 deletions
|
|
@ -12,7 +12,8 @@
|
|||
"./db": "./src/db.ts",
|
||||
"./health": "./src/health.ts",
|
||||
"./admin": "./src/admin.ts",
|
||||
"./error": "./src/error.ts"
|
||||
"./error": "./src/error.ts",
|
||||
"./credits": "./src/credits.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit"
|
||||
|
|
|
|||
125
packages/shared-hono/src/credits.ts
Normal file
125
packages/shared-hono/src/credits.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Credit client for Hono backends.
|
||||
*
|
||||
* Drop-in replacement for @mana-core/nestjs-integration CreditClientService.
|
||||
* Calls mana-credits service to validate/consume/refund credits.
|
||||
*/
|
||||
|
||||
export interface CreditBalance {
|
||||
balance: number;
|
||||
totalEarned: number;
|
||||
totalSpent: number;
|
||||
}
|
||||
|
||||
export interface CreditValidationResult {
|
||||
hasCredits: boolean;
|
||||
availableCredits: number;
|
||||
requiredCredits?: number;
|
||||
}
|
||||
|
||||
const CREDITS_URL = () =>
|
||||
process.env.MANA_CREDITS_URL || process.env.MANA_CORE_AUTH_URL || 'http://localhost:3061';
|
||||
const SERVICE_KEY = () => process.env.MANA_CORE_SERVICE_KEY || '';
|
||||
const APP_ID = () => process.env.APP_ID || 'unknown';
|
||||
|
||||
const DEFAULT_BALANCE: CreditBalance = { balance: 1000, totalEarned: 0, totalSpent: 0 };
|
||||
|
||||
async function callCredits<T>(path: string, options: RequestInit = {}): Promise<T | null> {
|
||||
const key = SERVICE_KEY();
|
||||
if (!key) {
|
||||
console.warn('[credits] Service key not configured');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${CREDITS_URL()}${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Service-Key': key,
|
||||
'X-App-Id': APP_ID(),
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
} catch (error) {
|
||||
console.error('[credits] Request failed:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's credit balance.
|
||||
*/
|
||||
export async function getBalance(userId: string): Promise<CreditBalance> {
|
||||
const result = await callCredits<CreditBalance>(`/api/v1/internal/credits/balance/${userId}`);
|
||||
return result || DEFAULT_BALANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user has enough credits for an operation.
|
||||
*/
|
||||
export async function validateCredits(
|
||||
userId: string,
|
||||
operation: string,
|
||||
amount: number
|
||||
): Promise<CreditValidationResult> {
|
||||
try {
|
||||
const balance = await getBalance(userId);
|
||||
return {
|
||||
hasCredits: balance.balance >= amount,
|
||||
availableCredits: balance.balance,
|
||||
requiredCredits: amount,
|
||||
};
|
||||
} catch {
|
||||
return { hasCredits: true, availableCredits: 0, requiredCredits: amount };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume credits after a successful operation.
|
||||
*/
|
||||
export async function consumeCredits(
|
||||
userId: string,
|
||||
operation: string,
|
||||
amount: number,
|
||||
description: string,
|
||||
metadata?: Record<string, unknown>,
|
||||
creditSource?: { type: 'guild'; guildId: string }
|
||||
): Promise<boolean> {
|
||||
const result = await callCredits('/api/v1/internal/credits/use', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
userId,
|
||||
amount,
|
||||
appId: APP_ID(),
|
||||
description,
|
||||
metadata: { operation, ...metadata },
|
||||
...(creditSource && { creditSource }),
|
||||
}),
|
||||
});
|
||||
return !!result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refund credits after a failed operation.
|
||||
*/
|
||||
export async function refundCredits(
|
||||
userId: string,
|
||||
amount: number,
|
||||
description: string,
|
||||
metadata?: Record<string, unknown>
|
||||
): Promise<boolean> {
|
||||
const result = await callCredits('/api/v1/internal/credits/refund', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
userId,
|
||||
amount,
|
||||
appId: APP_ID(),
|
||||
description,
|
||||
metadata,
|
||||
}),
|
||||
});
|
||||
return !!result;
|
||||
}
|
||||
|
|
@ -38,4 +38,6 @@ export type { DbOptions } from './db';
|
|||
export { healthRoute } from './health';
|
||||
export { adminRoutes } from './admin';
|
||||
export { errorHandler, notFoundHandler } from './error';
|
||||
export { getBalance, validateCredits, consumeCredits, refundCredits } from './credits';
|
||||
export type { CreditBalance, CreditValidationResult } from './credits';
|
||||
export type { CurrentUserData, AuthVariables } from './types';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue