Merge pull request #1404 from omnivore-app/fix/realistic-voice-prefetch

fix/realistic voice prefetch
This commit is contained in:
Hongbo Wu 2022-11-10 18:52:30 +08:00 committed by GitHub
commit f55ebce8ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 50 additions and 41 deletions

View file

@ -40,7 +40,7 @@ export const optInFeatureResolver = authorized<
}
}
const token = signFeatureToken(optIn)
const token = signFeatureToken(optIn, claims.uid)
return {
feature: {

View file

@ -16,6 +16,7 @@ import { enqueueTextToSpeech } from '../utils/createTask'
import { htmlToSpeechFile } from '@omnivore/text-to-speech-handler'
import { UserPersonalization } from '../entity/user_personalization'
import { ArticleSavingRequestStatus } from '../elastic/types'
import { FeatureName, getFeature } from '../services/features'
const logger = buildLogger('app.dispatch')
@ -80,6 +81,11 @@ export function textToSpeechRouter() {
},
})
const feature = await getFeature(
FeatureName.UltraRealisticVoice,
userId
)
for (const utterance of speechFile.utterances) {
// enqueue a task to convert text to speech
const taskName = await enqueueTextToSpeech({
@ -91,6 +97,8 @@ export function textToSpeechRouter() {
isUltraRealisticVoice: true,
language: speechFile.language,
rate: userPersonalization?.speechRate || '1.1',
featureName: feature?.name,
grantedAt: feature?.grantedAt,
})
logger.info('Start Text to speech task', { taskName })
}

View file

@ -4,7 +4,7 @@ import * as jwt from 'jsonwebtoken'
import { env } from '../env'
import { IsNull, Not } from 'typeorm'
enum FeatureName {
export enum FeatureName {
UltraRealisticVoice = 'ultra-realistic-voice',
}
@ -56,10 +56,16 @@ const optInUltraRealisticVoice = async (uid: string): Promise<Feature> => {
})
}
export const signFeatureToken = (feature: Feature): string => {
export const signFeatureToken = (
feature: {
name?: string
grantedAt?: Date | null
},
userId: string
): string => {
return jwt.sign(
{
uid: feature.user.id,
uid: userId,
featureName: feature.name,
grantedAt: feature.grantedAt ? feature.grantedAt.getTime() / 1000 : null,
},
@ -67,3 +73,26 @@ export const signFeatureToken = (feature: Feature): string => {
{ expiresIn: '1y' }
)
}
export const isOptedIn = async (
name: FeatureName,
uid: string
): Promise<boolean> => {
const feature = await getRepository(Feature).findOneBy({
user: { id: uid },
name,
grantedAt: Not(IsNull()),
})
return !!feature
}
export const getFeature = async (
name: FeatureName,
uid: string
): Promise<Feature | null> => {
return getRepository(Feature).findOneBy({
user: { id: uid },
name,
})
}

View file

@ -1,11 +1,8 @@
import { searchPages } from '../elastic/pages'
import { Page, PageType } from '../elastic/types'
import { SortBy, SortOrder } from '../utils/search'
import { FeatureName, isOptedIn } from './features'
/*
* We should not synthesize the page when:
** 1. User has no recent listens the last 30 days
** 2. User has a recent listen but the page was saved after the listen
* We should synthesize the page when user is opted in to the feature
*/
export const shouldSynthesize = async (
userId: string,
@ -16,31 +13,5 @@ export const shouldSynthesize = async (
return false
}
if (process.env.TEXT_TO_SPEECH_BETA_TEST) {
return true
}
const [recentListenedPage, count] = (await searchPages(
{
dateFilters: [
{
field: 'listenedAt',
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
},
],
sort: {
by: SortBy.LISTENED,
order: SortOrder.DESCENDING,
},
size: 1,
},
userId
)) || [[], 0]
if (count === 0) {
return false
}
return (
!!recentListenedPage[0].listenedAt &&
page.savedAt < recentListenedPage[0].listenedAt
)
return isOptedIn(FeatureName.UltraRealisticVoice, userId)
}

View file

@ -11,6 +11,7 @@ import { google } from '@google-cloud/tasks/build/protos/protos'
import { IntegrationType } from '../entity/integration'
import { promisify } from 'util'
import * as jwt from 'jsonwebtoken'
import { signFeatureToken } from '../services/features'
import View = google.cloud.tasks.v2.Task.View
const logger = buildLogger('app.dispatch')
@ -347,6 +348,8 @@ export const enqueueTextToSpeech = async ({
isUltraRealisticVoice = false,
language,
rate,
featureName,
grantedAt,
}: {
userId: string
speechId: string
@ -360,6 +363,8 @@ export const enqueueTextToSpeech = async ({
isUltraRealisticVoice?: boolean
language?: string
rate?: string
featureName?: string
grantedAt?: Date | null
}): Promise<string> => {
const { GOOGLE_CLOUD_PROJECT } = process.env
const payload = {
@ -372,11 +377,7 @@ export const enqueueTextToSpeech = async ({
language,
rate,
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const token = await signToken({ uid: userId }, env.server.jwtSecret, {
expiresIn: '1h',
})
const token = signFeatureToken({ name: featureName, grantedAt }, userId)
const taskHandlerUrl = `${env.queue.textToSpeechTaskHandlerUrl}?token=${token}`
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {