Merge branch 'main' of https://github.com/slopus/happy-server
This commit is contained in:
commit
ae583ffaff
6
.env.dev
6
.env.dev
@ -18,3 +18,9 @@ S3_ACCESS_KEY=minioadmin
|
||||
S3_SECRET_KEY=minioadmin
|
||||
S3_BUCKET=happy
|
||||
S3_PUBLIC_URL=http://localhost:9000/happy
|
||||
|
||||
# --- Voice Feature ---
|
||||
# 11Labs API for voice conversations (required for voice feature)
|
||||
# Secret - congiured in .env, not checked in
|
||||
|
||||
NODE_ENV=development
|
||||
|
@ -13,6 +13,7 @@ import { startSocket } from "./socket";
|
||||
import { machinesRoutes } from "./routes/machinesRoutes";
|
||||
import { devRoutes } from "./routes/devRoutes";
|
||||
import { versionRoutes } from "./routes/versionRoutes";
|
||||
import { voiceRoutes } from "./routes/voiceRoutes";
|
||||
import { enableMonitoring } from "./utils/enableMonitoring";
|
||||
import { enableErrorHandlers } from "./utils/enableErrorHandlers";
|
||||
import { enableAuthentication } from "./utils/enableAuthentication";
|
||||
@ -55,6 +56,7 @@ export async function startApi(eventRouter: EventRouter) {
|
||||
machinesRoutes(typed, eventRouter);
|
||||
devRoutes(typed);
|
||||
versionRoutes(typed);
|
||||
voiceRoutes(typed);
|
||||
|
||||
// Start HTTP
|
||||
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3005;
|
||||
|
113
sources/app/api/routes/voiceRoutes.ts
Normal file
113
sources/app/api/routes/voiceRoutes.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { z } from "zod";
|
||||
import { type Fastify } from "../types";
|
||||
import { log } from "@/utils/log";
|
||||
|
||||
export function voiceRoutes(app: Fastify) {
|
||||
app.post('/v1/voice/token', {
|
||||
preHandler: app.authenticate,
|
||||
schema: {
|
||||
body: z.object({
|
||||
agentId: z.string(),
|
||||
revenueCatPublicKey: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
allowed: z.boolean(),
|
||||
token: z.string().optional(),
|
||||
agentId: z.string().optional()
|
||||
}),
|
||||
400: z.object({
|
||||
allowed: z.boolean(),
|
||||
error: z.string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async (request, reply) => {
|
||||
const userId = request.userId; // CUID from JWT
|
||||
const { agentId, revenueCatPublicKey } = request.body;
|
||||
|
||||
log({ module: 'voice' }, `Voice token request from user ${userId}`);
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.ENV === 'dev';
|
||||
|
||||
// Production requires RevenueCat key
|
||||
if (!isDevelopment && !revenueCatPublicKey) {
|
||||
log({ module: 'voice' }, 'Production environment requires RevenueCat public key');
|
||||
return reply.code(400).send({
|
||||
allowed: false,
|
||||
error: 'RevenueCat public key required'
|
||||
});
|
||||
}
|
||||
|
||||
// Check subscription in production
|
||||
if (!isDevelopment && revenueCatPublicKey) {
|
||||
const response = await fetch(
|
||||
`https://api.revenuecat.com/v1/subscribers/${userId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${revenueCatPublicKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
log({ module: 'voice' }, `RevenueCat check failed for user ${userId}: ${response.status}`);
|
||||
return reply.send({
|
||||
allowed: false,
|
||||
agentId
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json() as any;
|
||||
const proEntitlement = data.subscriber?.entitlements?.active?.pro;
|
||||
|
||||
if (!proEntitlement) {
|
||||
log({ module: 'voice' }, `User ${userId} does not have active subscription`);
|
||||
return reply.send({
|
||||
allowed: false,
|
||||
agentId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if 11Labs API key is configured
|
||||
const elevenLabsApiKey = process.env.ELEVENLABS_API_KEY;
|
||||
if (!elevenLabsApiKey) {
|
||||
log({ module: 'voice' }, 'Missing 11Labs API key');
|
||||
return reply.code(400).send({ allowed: false, error: 'Missing 11Labs API key on the server' });
|
||||
}
|
||||
|
||||
// Get 11Labs conversation token
|
||||
const response = await fetch(
|
||||
`https://api.elevenlabs.io/v1/convai/conversation/token?agent_id=${agentId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'xi-api-key': elevenLabsApiKey,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
log({ module: 'voice' }, `Failed to get 11Labs token for user ${userId}`);
|
||||
return reply.code(400).send({
|
||||
allowed: false,
|
||||
error: `Failed to get 11Labs token for user ${userId}`
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json() as any;
|
||||
console.log(data);
|
||||
const token = data.token;
|
||||
|
||||
log({ module: 'voice' }, `Voice token issued for user ${userId}`);
|
||||
return reply.send({
|
||||
allowed: true,
|
||||
token,
|
||||
agentId
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user