2022-02-11 17:24:33 +00:00
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
// Imports the Google Cloud Tasks library.
import { CloudTasksClient , protos } from '@google-cloud/tasks'
2023-03-22 07:13:48 +00:00
import { google } from '@google-cloud/tasks/build/protos/protos'
2022-02-11 17:24:33 +00:00
import axios from 'axios'
2023-03-22 07:13:48 +00:00
import { nanoid } from 'nanoid'
2023-03-22 09:05:37 +00:00
import { Recommendation } from '../elastic/types'
2023-07-07 13:15:42 +00:00
import { Subscription } from '../entity/subscription'
2022-02-11 17:24:33 +00:00
import { env } from '../env'
2023-03-22 09:05:37 +00:00
import {
ArticleSavingRequestStatus ,
CreateLabelInput ,
} from '../generated/graphql'
2023-03-22 07:13:48 +00:00
import { signFeatureToken } from '../services/features'
2023-06-28 09:00:11 +00:00
import { generateVerificationToken , OmnivoreAuthorizationHeader } from './auth'
2022-02-11 17:24:33 +00:00
import { CreateTaskError } from './errors'
2023-07-27 08:06:44 +00:00
import { logger } from './logger'
2022-02-11 17:24:33 +00:00
import View = google . cloud . tasks . v2 . Task . View
// Instantiates a client.
const client = new CloudTasksClient ( )
2023-08-10 03:55:59 +00:00
const logError = ( error : any ) : void = > {
2023-07-27 08:06:44 +00:00
if ( axios . isAxiosError ( error ) ) {
logger . error ( error . response )
} else {
logger . error ( error )
}
}
2022-02-11 17:24:33 +00:00
const createHttpTaskWithToken = async ( {
2023-06-06 03:56:31 +00:00
project = process . env . GOOGLE_CLOUD_PROJECT ,
2022-02-11 17:24:33 +00:00
queue = env . queue . name ,
location = env . queue . location ,
2022-06-23 14:22:12 +00:00
taskHandlerUrl = env . queue . contentFetchUrl ,
2022-02-11 17:24:33 +00:00
serviceAccountEmail = ` ${ process . env . GOOGLE_CLOUD_PROJECT } @appspot.gserviceaccount.com ` ,
payload ,
priority = 'high' ,
scheduleTime ,
2022-12-02 13:15:33 +00:00
requestHeaders ,
2022-02-11 17:24:33 +00:00
} : {
2023-06-06 03:56:31 +00:00
project? : string
2022-02-11 17:24:33 +00:00
queue? : string
location? : string
taskHandlerUrl? : string
serviceAccountEmail? : string
payload : unknown
priority ? : 'low' | 'high'
scheduleTime? : number
2022-12-02 13:15:33 +00:00
requestHeaders? : Record < string , string >
2022-02-11 17:24:33 +00:00
} ) : Promise <
2023-06-06 03:56:31 +00:00
| [
protos . google . cloud . tasks . v2 . ITask ,
protos . google . cloud . tasks . v2 . ICreateTaskRequest | undefined ,
unknown | undefined
]
| null
2022-02-11 17:24:33 +00:00
> = > {
2023-06-06 03:56:31 +00:00
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! project ) {
return null
}
2022-02-11 17:24:33 +00:00
// Construct the fully qualified queue name.
2023-06-12 04:25:04 +00:00
if ( priority === 'low' ) {
queue = ` ${ queue } -low `
}
2022-02-11 17:24:33 +00:00
const parent = client . queuePath ( project , location , queue )
// Convert message to buffer.
let convertedPayload : string | ArrayBuffer
try {
convertedPayload = JSON . stringify ( payload )
} catch ( error ) {
throw new CreateTaskError ( 'Invalid payload' )
}
const body = Buffer . from ( convertedPayload ) . toString ( 'base64' )
const task : protos.google.cloud.tasks.v2.ITask = {
httpRequest : {
httpMethod : 'POST' ,
url : taskHandlerUrl ,
headers : {
'Content-Type' : 'application/json' ,
2022-12-02 13:15:33 +00:00
. . . requestHeaders ,
2022-02-11 17:24:33 +00:00
} ,
body ,
. . . ( serviceAccountEmail
? {
oidcToken : {
serviceAccountEmail ,
} ,
}
: null ) ,
} ,
scheduleTime : scheduleTime
? protos . google . protobuf . Timestamp . fromObject ( {
seconds : scheduleTime / 1000 ,
nanos : ( scheduleTime % 1000 ) * 1 e6 ,
} )
: null ,
}
2023-08-10 03:55:59 +00:00
try {
return client . createTask ( { parent , task } )
} catch ( error ) {
logError ( error )
return null
}
2022-02-11 17:24:33 +00:00
}
export const createAppEngineTask = async ( {
project ,
queue = env . queue . name ,
location = env . queue . location ,
2022-12-02 10:49:13 +00:00
taskHandlerUrl = env . queue . reminderTaskHandlerUrl ,
2022-02-11 17:24:33 +00:00
payload ,
priority = 'high' ,
scheduleTime ,
} : {
project : string
queue? : string
location? : string
taskHandlerUrl? : string
payload : unknown
priority ? : 'low' | 'high'
scheduleTime? : number
} ) : Promise < string | undefined | null > = > {
// Construct the fully qualified queue name.
if ( priority === 'low' ) {
queue = ` ${ queue } -low `
}
const parent = client . queuePath ( project , location , queue )
const task : protos.google.cloud.tasks.v2.ITask = {
appEngineHttpRequest : {
httpMethod : 'POST' ,
relativeUri : taskHandlerUrl ,
} ,
}
if ( payload && task . appEngineHttpRequest ) {
// Convert message to buffer.
let convertedPayload : string | ArrayBuffer
try {
convertedPayload = JSON . stringify ( payload )
} catch ( error ) {
throw new CreateTaskError ( 'Invalid payload' )
}
task . appEngineHttpRequest . body =
Buffer . from ( convertedPayload ) . toString ( 'base64' )
}
if ( scheduleTime ) {
// The time when the task is scheduled to be attempted.
task . scheduleTime = {
seconds : scheduleTime / 1000 ,
}
}
2023-07-26 08:27:18 +00:00
logger . info ( 'Sending task:' )
logger . info ( task )
2022-02-11 17:24:33 +00:00
// Send create task request.
const request = { parent : parent , task : task }
const [ response ] = await client . createTask ( request )
const name = response . name
2023-07-26 08:27:18 +00:00
logger . info ( ` Created task ${ name } ` )
2022-02-11 17:24:33 +00:00
return name
}
export const getTask = async (
taskName : string
) : Promise < google.cloud.tasks.v2.ITask > = > {
// If we are in local environment
if ( env . dev . isLocal ) {
return { name : taskName } as protos . google . cloud . tasks . v2 . ITask
}
const request : protos.google.cloud.tasks.v2.GetTaskRequest = {
responseView : View.FULL ,
2022-03-03 03:53:28 +00:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2022-02-11 17:24:33 +00:00
toJSON ( ) : { [ p : string ] : any } {
return { }
} ,
name : taskName ,
}
const [ response ] = await client . getTask ( request )
return response
}
export const deleteTask = async (
taskName : string
) : Promise < google.protobuf.IEmpty > = > {
// If we are in local environment
if ( env . dev . isLocal ) {
return taskName
}
const request : protos.google.cloud.tasks.v2.IDeleteTaskRequest = {
name : taskName ,
}
const [ response ] = await client . deleteTask ( request )
return response
}
/ * *
* Enqueues the task for the article content parsing with Puppeteer by URL
* @param url - URL address of the article to parse
* @param userId - Id of the user authorized
* @param saveRequestId - Id of the article_saving_request table record
2022-03-22 10:08:08 +00:00
* @param priority - Priority of the task
2022-08-29 05:20:39 +00:00
* @param queue - Queue name
2022-02-11 17:24:33 +00:00
* @returns Name of the task created
* /
2023-03-22 07:13:48 +00:00
export const enqueueParseRequest = async ( {
url ,
userId ,
saveRequestId ,
priority = 'high' ,
queue = env . queue . name ,
2023-03-22 09:05:37 +00:00
state ,
2023-03-22 07:13:48 +00:00
labels ,
2023-07-11 08:15:32 +00:00
locale ,
timezone ,
2023-08-14 09:10:34 +00:00
savedAt ,
publishedAt ,
2023-03-22 07:13:48 +00:00
} : {
url : string
userId : string
saveRequestId : string
priority ? : 'low' | 'high'
queue? : string
2023-03-22 09:05:37 +00:00
state? : ArticleSavingRequestStatus
labels? : CreateLabelInput [ ]
2023-07-11 08:15:32 +00:00
locale? : string
timezone? : string
2023-08-14 09:10:34 +00:00
savedAt? : number // unix timestamp
publishedAt? : number // unix timestamp
2023-03-22 07:13:48 +00:00
} ) : Promise < string > = > {
2022-02-11 17:24:33 +00:00
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
url ,
userId ,
saveRequestId ,
2023-03-22 09:05:37 +00:00
state ,
2023-03-22 07:13:48 +00:00
labels ,
2023-07-11 08:15:32 +00:00
locale ,
timezone ,
2023-08-14 09:10:34 +00:00
savedAt ,
publishedAt ,
2022-02-11 17:24:33 +00:00
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
// Calling the handler function directly.
setTimeout ( ( ) = > {
2022-06-23 14:22:12 +00:00
axios . post ( env . queue . contentFetchUrl , payload ) . catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
logger . warning (
2022-02-11 17:24:33 +00:00
` Error occurred while requesting local puppeteer-parse function \ nPlease, ensure your function is set up properly and running using "yarn start" from the "/pkg/gcf/puppeteer-parse" folder `
)
} )
} , 0 )
return ''
}
2022-08-29 04:01:17 +00:00
// use GCF url for low priority tasks
const taskHandlerUrl =
priority === 'low'
? env . queue . contentFetchGCFUrl
: env . queue . contentFetchUrl
2022-02-11 17:24:33 +00:00
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
priority ,
2022-08-29 04:01:17 +00:00
taskHandlerUrl ,
2022-08-29 05:20:39 +00:00
queue ,
2022-02-11 17:24:33 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
2023-03-22 08:00:57 +00:00
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
export const enqueueReminder = async (
userId : string ,
scheduleTime : number
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
userId ,
scheduleTime ,
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
return nanoid ( )
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
scheduleTime ,
taskHandlerUrl : env.queue.reminderTaskHandlerUrl ,
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
export const enqueueSyncWithIntegration = async (
userId : string ,
integrationName : string
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT , PUBSUB_VERIFICATION_TOKEN } = process . env
// use pubsub data format to send the userId to the task handler
const payload = {
message : {
data : Buffer.from (
JSON . stringify ( {
userId ,
} )
) . toString ( 'base64' ) ,
publishTime : new Date ( ) . toISOString ( ) ,
} ,
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
return nanoid ( )
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
taskHandlerUrl : ` ${
env . queue . integrationTaskHandlerUrl
} / $ { integrationName . toLowerCase ( ) } / sync_all ? token = $ { PUBSUB_VERIFICATION_TOKEN } ` ,
priority : 'low' ,
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
2022-08-05 04:33:32 +00:00
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2022-08-29 03:47:42 +00:00
export const enqueueTextToSpeech = async ( {
userId ,
text ,
speechId ,
voice ,
priority ,
textType = 'ssml' ,
bucket = env . fileUpload . gcsUploadBucket ,
2022-11-11 02:14:46 +00:00
queue = 'omnivore-text-to-speech-queue' ,
2022-08-29 05:20:39 +00:00
location = env . gcp . location ,
2022-11-07 11:30:20 +00:00
isUltraRealisticVoice = false ,
2022-11-08 08:22:23 +00:00
language ,
rate ,
2022-11-10 10:48:00 +00:00
featureName ,
grantedAt ,
2022-08-29 03:47:42 +00:00
} : {
userId : string
speechId : string
text : string
voice : string
priority : 'low' | 'high'
bucket? : string
textType ? : 'text' | 'ssml'
queue? : string
location? : string
2022-11-07 11:30:20 +00:00
isUltraRealisticVoice? : boolean
2022-11-08 08:22:23 +00:00
language? : string
rate? : string
2022-11-10 10:48:00 +00:00
featureName? : string
grantedAt? : Date | null
2022-08-29 03:47:42 +00:00
} ) : Promise < string > = > {
2022-08-12 09:53:41 +00:00
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
2022-08-26 08:30:09 +00:00
id : speechId ,
text ,
voice ,
bucket ,
textType ,
2022-11-07 11:30:20 +00:00
isUltraRealisticVoice ,
2022-11-08 08:22:23 +00:00
language ,
rate ,
2022-08-12 09:53:41 +00:00
}
2022-11-10 10:48:00 +00:00
const token = signFeatureToken ( { name : featureName , grantedAt } , userId )
2022-08-18 11:16:40 +00:00
const taskHandlerUrl = ` ${ env . queue . textToSpeechTaskHandlerUrl } ?token= ${ token } `
2022-08-12 09:53:41 +00:00
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
2022-08-17 02:51:39 +00:00
// Calling the handler function directly.
setTimeout ( ( ) = > {
2022-08-18 11:16:40 +00:00
axios . post ( taskHandlerUrl , payload ) . catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
2022-08-18 11:16:40 +00:00
} )
2022-08-17 02:51:39 +00:00
} , 0 )
return ''
2022-08-12 09:53:41 +00:00
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
2022-08-18 11:16:40 +00:00
taskHandlerUrl ,
2022-08-29 03:47:42 +00:00
queue ,
location ,
priority ,
2022-08-12 09:53:41 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2022-12-02 10:49:13 +00:00
export const enqueueRecommendation = async (
userId : string ,
2022-12-02 11:10:41 +00:00
pageId : string ,
2022-12-02 13:15:33 +00:00
recommendation : Recommendation ,
2022-12-08 03:37:35 +00:00
authToken : string ,
highlightIds? : string [ ]
2022-12-02 10:49:13 +00:00
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
userId ,
2022-12-02 11:10:41 +00:00
pageId ,
2022-12-02 11:45:26 +00:00
recommendation ,
2022-12-08 03:37:35 +00:00
highlightIds ,
2022-12-02 10:49:13 +00:00
}
2022-12-02 13:15:33 +00:00
const headers = {
2023-06-28 09:00:11 +00:00
[ OmnivoreAuthorizationHeader ] : authToken ,
2022-12-02 13:15:33 +00:00
}
2022-12-02 10:49:13 +00:00
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
2022-12-02 13:15:33 +00:00
// Calling the handler function directly.
setTimeout ( ( ) = > {
axios
. post ( env . queue . recommendationTaskHandlerUrl , payload , {
headers ,
} )
. catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
2022-12-02 13:15:33 +00:00
} )
} , 0 )
return ''
2022-12-02 10:49:13 +00:00
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
taskHandlerUrl : env.queue.recommendationTaskHandlerUrl ,
2022-12-02 13:15:33 +00:00
requestHeaders : headers ,
2022-12-02 10:49:13 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2023-02-27 10:27:17 +00:00
export const enqueueImportFromIntegration = async (
2023-03-01 15:15:03 +00:00
integrationId : string ,
authToken : string
2023-02-27 10:27:17 +00:00
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
integrationId ,
}
2023-03-01 15:15:03 +00:00
const headers = {
Cookie : ` auth= ${ authToken } ` ,
}
2023-02-27 10:27:17 +00:00
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
2023-06-16 10:11:31 +00:00
// Calling the handler function directly.
setTimeout ( ( ) = > {
axios
. post ( ` ${ env . queue . integrationTaskHandlerUrl } /import ` , payload , {
headers ,
} )
. catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
2023-06-16 10:11:31 +00:00
} )
} , 0 )
2023-07-20 01:52:23 +00:00
return nanoid ( )
2023-02-27 10:27:17 +00:00
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
taskHandlerUrl : ` ${ env . queue . integrationTaskHandlerUrl } /import ` ,
priority : 'low' ,
2023-03-01 15:15:03 +00:00
requestHeaders : headers ,
2023-02-27 10:27:17 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2023-06-07 03:52:13 +00:00
export const enqueueThumbnailTask = async (
userId : string ,
2023-08-10 03:55:59 +00:00
slug : string
2023-06-07 03:52:13 +00:00
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
userId ,
slug ,
}
2023-06-07 06:24:52 +00:00
const headers = {
Cookie : ` auth= ${ generateVerificationToken ( userId ) } ` ,
2023-06-07 03:52:13 +00:00
}
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
// Calling the handler function directly.
setTimeout ( ( ) = > {
axios
. post ( env . queue . thumbnailTaskHandlerUrl , payload , {
2023-06-07 06:24:52 +00:00
headers ,
2023-06-07 03:52:13 +00:00
} )
. catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
2023-06-07 03:52:13 +00:00
} )
} , 0 )
return ''
}
const createdTasks = await createHttpTaskWithToken ( {
payload ,
taskHandlerUrl : env.queue.thumbnailTaskHandlerUrl ,
2023-06-07 06:24:52 +00:00
requestHeaders : headers ,
2023-06-12 04:25:04 +00:00
queue : 'omnivore-thumbnail-queue' ,
2023-06-07 03:52:13 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2023-07-07 13:15:42 +00:00
export const enqueueRssFeedFetch = async (
2023-07-21 07:28:20 +00:00
userId : string ,
2023-07-07 13:15:42 +00:00
rssFeedSubscription : Subscription
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
subscriptionId : rssFeedSubscription.id ,
feedUrl : rssFeedSubscription.url ,
2023-07-19 06:09:38 +00:00
lastFetchedAt : rssFeedSubscription.lastFetchedAt?.getTime ( ) || 0 , // unix timestamp in milliseconds
2023-07-07 13:15:42 +00:00
}
2023-07-17 09:27:12 +00:00
const headers = {
2023-07-21 07:28:20 +00:00
[ OmnivoreAuthorizationHeader ] : generateVerificationToken ( userId ) ,
2023-07-17 09:27:12 +00:00
}
2023-07-20 05:35:43 +00:00
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
if ( env . dev . isLocal || ! GOOGLE_CLOUD_PROJECT ) {
// Calling the handler function directly.
setTimeout ( ( ) = > {
axios
. post ( env . queue . rssFeedTaskHandlerUrl , payload , {
headers ,
} )
. catch ( ( error ) = > {
2023-07-27 08:06:44 +00:00
logError ( error )
2023-07-20 05:35:43 +00:00
} )
} , 0 )
return nanoid ( )
}
2023-07-07 13:15:42 +00:00
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
2023-07-13 02:27:15 +00:00
queue : 'omnivore-rss-queue' ,
2023-07-07 13:15:42 +00:00
payload ,
taskHandlerUrl : env.queue.rssFeedTaskHandlerUrl ,
2023-07-17 09:27:12 +00:00
requestHeaders : headers ,
2023-07-07 13:15:42 +00:00
} )
if ( ! createdTasks || ! createdTasks [ 0 ] . name ) {
logger . error ( ` Unable to get the name of the task ` , {
payload ,
createdTasks ,
} )
throw new CreateTaskError ( ` Unable to get the name of the task ` )
}
return createdTasks [ 0 ] . name
}
2022-02-11 17:24:33 +00:00
export default createHttpTaskWithToken