From 301746d49400c5a72f961939c2aa38a6fbfffcc3 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 16 Aug 2022 22:17:30 +0800 Subject: [PATCH] Add /:id/mp3 router to redirect request to speech MP3 URL --- packages/api/src/routers/article_router.ts | 46 +++++++++++++++++-- .../svc/{textToSpeech.ts => speech.ts} | 22 ++++++--- packages/api/src/server.ts | 2 + 3 files changed, 60 insertions(+), 10 deletions(-) rename packages/api/src/routers/svc/{textToSpeech.ts => speech.ts} (69%) diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts index 5774483d6..836980cac 100644 --- a/packages/api/src/routers/article_router.ts +++ b/packages/api/src/routers/article_router.ts @@ -3,15 +3,20 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import express from 'express' -import { CreateArticleErrorCode } from './../generated/graphql' -import { isSiteBlockedForParse } from './../utils/blocked' +import { CreateArticleErrorCode } from '../generated/graphql' +import { isSiteBlockedForParse } from '../utils/blocked' import cors from 'cors' -import { buildLogger } from './../utils/logger' +import { buildLogger } from '../utils/logger' import { corsConfig } from '../utils/corsConfig' import { createPageSaveRequest } from '../services/create_page_save_request' import { initModels } from '../server' import { kx } from '../datalayer/knex_config' import { getClaimsByToken } from '../utils/auth' +import * as jwt from 'jsonwebtoken' +import { env } from '../env' +import { Claims } from '../resolvers/types' +import { getRepository } from '../entity/utils' +import { Speech } from '../entity/speech' const logger = buildLogger('app.dispatch') @@ -61,5 +66,40 @@ export function articleRouter() { articleSavingRequestId: result.id, }) }) + + router.get( + '/:id/mp3', + cors(corsConfig), + async (req, res) => { + const id = req.params.id + const token = req.cookies?.auth || req.headers?.authorization + if (!token || !jwt.verify(token, env.server.jwtSecret)) { + return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) + } + const { uid } = jwt.decode(token) as Claims + + logger.info('Get article speech in mp3 format', { + params: req.params, + labels: { + userId: uid, + source: 'GetArticleSpeechMp3', + articleId: id, + }, + }) + + const speech = await getRepository(Speech).findOneBy({ + elasticPageId: id, + user: { id: uid }, + }) + + if (!speech) { + return res.status(404).send({ errorCode: 'NOT_FOUND' }) + } + + logger.info('Found speech mp3', { audioUrl: speech.audioUrl }) + res.redirect(speech.audioUrl) + } + ) + return router } diff --git a/packages/api/src/routers/svc/textToSpeech.ts b/packages/api/src/routers/svc/speech.ts similarity index 69% rename from packages/api/src/routers/svc/textToSpeech.ts rename to packages/api/src/routers/svc/speech.ts index 56b8e2a82..958750fc9 100644 --- a/packages/api/src/routers/svc/textToSpeech.ts +++ b/packages/api/src/routers/svc/speech.ts @@ -6,19 +6,20 @@ import { User } from '../../entity/user' import { getPageById } from '../../elastic/pages' import { synthesizeTextToSpeech } from '../../utils/textToSpeech' import { Speech } from '../../entity/speech' +import { parseHTML } from 'linkedom' -export function textToSpeechServiceRouter() { +export function speechServiceRouter() { const router = express.Router() router.options('/', cors({ ...corsConfig, maxAge: 600 })) + // eslint-disable-next-line @typescript-eslint/no-misused-promises router.post('/', async (req, res) => { - const { userId, pageId, text } = req.body as { + const { userId, pageId } = req.body as { userId: string pageId: string - text: string } - if (!userId || !pageId || !text) { + if (!userId || !pageId) { return res.status(200).send('Invalid data') } @@ -35,7 +36,12 @@ export function textToSpeechServiceRouter() { return res.status(200).send('Page not found') } - const audioAndSpeechMarks = await synthesizeTextToSpeech({ + const text = parseHTML(page.content).document.textContent + if (!text) { + return res.status(200).send('Page has no text') + } + + const speech = await synthesizeTextToSpeech({ id: pageId, text, languageCode: page.language, @@ -44,11 +50,13 @@ export function textToSpeechServiceRouter() { await getRepository(Speech).save({ elasticPageId: pageId, - audioUrl: audioAndSpeechMarks.audioUrl, - speechMarks: JSON.stringify(audioAndSpeechMarks.speechMarks), + audioUrl: speech.audioUrl, + speechMarks: JSON.stringify(speech.speechMarks), user, }) res.status(200).send('OK') }) + + return router } diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 619a4fda5..1945b3e2c 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -45,6 +45,7 @@ import { uploadServiceRouter } from './routers/svc/upload' import rateLimit from 'express-rate-limit' import { webhooksServiceRouter } from './routers/svc/webhooks' import { integrationsServiceRouter } from './routers/svc/integrations' +import { speechServiceRouter } from './routers/svc/speech' const PORT = process.env.PORT || 4000 @@ -119,6 +120,7 @@ export const createApp = (): { app.use('/svc/pubsub/integrations', integrationsServiceRouter()) app.use('/svc/reminders', remindersServiceRouter()) app.use('/svc/pdf-attachments', pdfAttachmentsRouter()) + app.use('/svc/speech', speechServiceRouter()) if (env.dev.isLocal) { app.use('/local/debug', localDebugRouter())