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'
import axios from 'axios'
import { env } from '../env'
import { CreateTaskError } from './errors'
import { buildLogger } from './logger'
import { nanoid } from 'nanoid'
import { google } from '@google-cloud/tasks/build/protos/protos'
2022-08-05 04:33:32 +00:00
import { IntegrationType } from '../entity/integration'
2022-08-18 11:16:40 +00:00
import { promisify } from 'util'
import * as jwt from 'jsonwebtoken'
2022-02-11 17:24:33 +00:00
import View = google . cloud . tasks . v2 . Task . View
const logger = buildLogger ( 'app.dispatch' )
2022-08-18 11:16:40 +00:00
const signToken = promisify ( jwt . sign )
2022-02-11 17:24:33 +00:00
// Instantiates a client.
const client = new CloudTasksClient ( )
const createHttpTaskWithToken = async ( {
project ,
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 ,
} : {
project : string
queue? : string
location? : string
taskHandlerUrl? : string
serviceAccountEmail? : string
payload : unknown
priority ? : 'low' | 'high'
scheduleTime? : number
} ) : Promise <
[
protos . google . cloud . tasks . v2 . ITask ,
protos . google . cloud . tasks . v2 . ICreateTaskRequest | undefined ,
unknown | undefined
]
> = > {
// Construct the fully qualified queue name.
if ( priority === 'low' ) {
queue = ` ${ queue } -low `
2022-06-23 14:22:12 +00:00
// use GCF url for low priority tasks
taskHandlerUrl = env . queue . contentFetchGCFUrl
2022-02-11 17:24:33 +00:00
}
const parent = client . queuePath ( project , location , queue )
console . log ( ` Task creation options: ` , {
project ,
location ,
queue ,
taskHandlerUrl ,
serviceAccountEmail ,
payload ,
} )
// 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' ,
} ,
body ,
. . . ( serviceAccountEmail
? {
oidcToken : {
serviceAccountEmail ,
} ,
}
: null ) ,
} ,
scheduleTime : scheduleTime
? protos . google . protobuf . Timestamp . fromObject ( {
seconds : scheduleTime / 1000 ,
nanos : ( scheduleTime % 1000 ) * 1 e6 ,
} )
: null ,
}
return client . createTask ( { parent , task } )
}
export const createAppEngineTask = async ( {
project ,
queue = env . queue . name ,
location = env . queue . location ,
taskHandlerUrl = env . queue . reminderTaskHanderUrl ,
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 )
console . log ( ` App Engine task creation options: ` , {
project ,
location ,
queue ,
taskHandlerUrl ,
payload ,
} )
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 ,
}
}
console . log ( 'Sending task:' )
console . log ( task )
// Send create task request.
const request = { parent : parent , task : task }
const [ response ] = await client . createTask ( request )
const name = response . name
console . log ( ` Created task ${ name } ` )
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-02-11 17:24:33 +00:00
* @returns Name of the task created
* /
export const enqueueParseRequest = async (
url : string ,
userId : string ,
saveRequestId : string ,
priority : 'low' | 'high' = 'high'
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
url ,
userId ,
saveRequestId ,
}
// 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 ) = > {
2022-02-11 17:24:33 +00:00
console . error ( error )
logger . warning (
` 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 ''
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
priority ,
} )
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 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.reminderTaskHanderUrl ,
} )
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-08-05 04:33:32 +00:00
export const enqueueSyncWithIntegration = async (
userId : string ,
integrationType : IntegrationType
) : Promise < string > = > {
2022-08-08 15:11:35 +00:00
const { GOOGLE_CLOUD_PROJECT , PUBSUB_VERIFICATION_TOKEN } = process . env
// use pubsub data format to send the userId to the task handler
2022-08-05 04:33:32 +00:00
const payload = {
2022-08-08 15:11:35 +00:00
message : {
data : Buffer.from (
JSON . stringify ( {
userId ,
} )
) . toString ( 'base64' ) ,
publishTime : new Date ( ) . toISOString ( ) ,
} ,
2022-08-05 04:33:32 +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 ) {
return nanoid ( )
}
const createdTasks = await createHttpTaskWithToken ( {
project : GOOGLE_CLOUD_PROJECT ,
payload ,
taskHandlerUrl : ` ${
env . queue . integrationTaskHandlerUrl
2022-08-08 15:11:35 +00:00
} / $ { integrationType . toLowerCase ( ) } / sync_all ? token = $ { PUBSUB_VERIFICATION_TOKEN } ` ,
2022-08-05 04:33:32 +00:00
priority : 'low' ,
} )
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-08-12 09:53:41 +00:00
export const enqueueTextToSpeech = async (
userId : string ,
2022-08-26 08:30:09 +00:00
speechId : string ,
text : string ,
textType : 'text' | 'ssml' ,
voice : string ,
bucket : string
2022-08-12 09:53:41 +00:00
) : Promise < string > = > {
const { GOOGLE_CLOUD_PROJECT } = process . env
const payload = {
2022-08-26 08:30:09 +00:00
id : speechId ,
text ,
voice ,
bucket ,
textType ,
2022-08-12 09:53:41 +00:00
}
2022-08-18 11:16:40 +00:00
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const token = await signToken ( { uid : userId } , env . server . jwtSecret , {
expiresIn : '1h' ,
} )
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 ) = > {
logger . error ( error )
} )
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-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-02-11 17:24:33 +00:00
export default createHttpTaskWithToken