CognxSafeTrack commited on
Commit
9dfe8d2
·
1 Parent(s): 9cc0a90

fix(audit): add /v1/ai/transcribe endpoint + fix cross-package storage require in worker

Browse files
apps/api/src/routes/ai.ts CHANGED
@@ -75,4 +75,30 @@ export async function aiRoutes(fastify: FastifyInstance) {
75
 
76
  return { success: true, url: downloadUrl };
77
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
 
75
 
76
  return { success: true, url: downloadUrl };
77
  });
78
+
79
+ // 5. Transcribe audio (called by Railway worker after downloading media from Meta)
80
+ // Accepts: { audioBase64: string, filename: string }
81
+ // Returns: { text: string }
82
+ fastify.post('/transcribe', async (request, reply) => {
83
+ const bodySchema = z.object({
84
+ audioBase64: z.string(),
85
+ filename: z.string().default('message.ogg')
86
+ });
87
+
88
+ const { audioBase64, filename } = bodySchema.parse(request.body);
89
+
90
+ console.log(`[AI] Transcribing audio: ${filename}`);
91
+
92
+ const buffer = Buffer.from(audioBase64, 'base64');
93
+
94
+ try {
95
+ const text = await aiService.transcribeAudio(buffer, filename);
96
+ return { success: true, text };
97
+ } catch (err: any) {
98
+ if (err?.name === 'QuotaExceededError') {
99
+ return reply.code(429).send({ error: 'quota_exceeded', retryAfterMs: err.retryAfterMs });
100
+ }
101
+ throw err;
102
+ }
103
+ });
104
  }
apps/whatsapp-worker/src/index.ts CHANGED
@@ -109,14 +109,16 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
109
  try {
110
  const { buffer } = await downloadMedia(mediaId, accessToken);
111
 
112
- // Upload audio to R2 storage for archiving
113
- try {
114
- const { uploadToR2 } = require('../../api/src/services/storage');
115
- const ext = mimeType.includes('mp4') ? 'mp4' : 'ogg';
116
- await uploadToR2(buffer, `audio/${Date.now()}-${phone}.${ext}`, mimeType);
117
- } catch {
118
- console.warn('[WORKER] R2 upload failed — continuing without storage.');
119
- }
 
 
120
 
121
  // Transcribe with Whisper via API
122
  const API_URL = process.env.API_URL || 'http://localhost:3001';
 
109
  try {
110
  const { buffer } = await downloadMedia(mediaId, accessToken);
111
 
112
+ // Best-effort: store audio on R2 via the API (non-blocking, silent if fails)
113
+ // No direct require — that path doesn't exist in the worker Docker image
114
+ const API_URL_STORE = process.env.API_URL || 'http://localhost:3001';
115
+ const apiKeyStore = process.env.ADMIN_API_KEY || '';
116
+ fetch(`${API_URL_STORE}/v1/ai/store-audio`, {
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKeyStore}` },
119
+ body: JSON.stringify({ audioBase64: buffer.toString('base64'), mimeType, phone })
120
+ }).catch(() => { /* silent — audio archiving is optional */ });
121
+
122
 
123
  // Transcribe with Whisper via API
124
  const API_URL = process.env.API_URL || 'http://localhost:3001';