44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import type { Database } from '@pig/db';
|
|
import { Hono, type Context } from 'hono';
|
|
import { z } from 'zod';
|
|
import type { ApiEnv } from '../lib/mutation';
|
|
import { apiError } from '../lib/mutation';
|
|
import { CustomerLifecycleService } from '../services/customer-lifecycle';
|
|
|
|
const accountIdSchema = z.string().uuid();
|
|
|
|
export function growthReadAllowed(scopes: readonly string[]): boolean {
|
|
return scopes.includes('read');
|
|
}
|
|
|
|
function requireGrowthRead(context: Context<ApiEnv>) {
|
|
if (growthReadAllowed(context.get('principal').scopes)) return null;
|
|
return context.json(
|
|
apiError('insufficient_scope', "This credential lacks the 'read' scope."),
|
|
403,
|
|
);
|
|
}
|
|
|
|
export function createGrowthRoutes(db: Database): Hono<ApiEnv> {
|
|
const routes = new Hono<ApiEnv>();
|
|
const service = new CustomerLifecycleService(db);
|
|
|
|
routes.get('/api/growth', async (context) => {
|
|
const denied = requireGrowthRead(context);
|
|
return denied ?? context.json(await service.report());
|
|
});
|
|
routes.get('/api/growth/accounts/:id', async (context) => {
|
|
const denied = requireGrowthRead(context);
|
|
if (denied) return denied;
|
|
const accountId = accountIdSchema.safeParse(context.req.param('id'));
|
|
if (!accountId.success) {
|
|
return context.json(apiError('invalid_account', 'Invalid account ID.', accountId.error.issues), 400);
|
|
}
|
|
const customer = await service.account(accountId.data);
|
|
return customer
|
|
? context.json(customer)
|
|
: context.json(apiError('not_found', 'Growth account not found.'), 404);
|
|
});
|
|
return routes;
|
|
}
|