mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-28 11:46:02 +08:00
Cleans up code to better support previewing
Also fixed every template field being link wrapped
This commit is contained in:
parent
477c453876
commit
b46dd783f4
19 changed files with 1716 additions and 1568 deletions
|
@ -31,7 +31,14 @@
|
|||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"operator-linebreak": ["error", "before"],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": true }]
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"vars": "all",
|
||||
"args": "after-used",
|
||||
"ignoreRestSiblings": true,
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}]
|
||||
},
|
||||
"globals": {
|
||||
"NodeJS": true
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -71,7 +71,7 @@ typings/
|
|||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.*
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
|
3015
package-lock.json
generated
3015
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -55,7 +55,7 @@
|
|||
"hashids": "^2.2.10",
|
||||
"jsonpath": "^1.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^2.1.0",
|
||||
"knex": "^2.3.0",
|
||||
"koa": "^2.13.4",
|
||||
"koa-body": "5.0.0",
|
||||
"koa-jwt": "^4.0.3",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Render, { Variables } from '../../render'
|
||||
import { Variables, Wrap } from '../../render'
|
||||
import { EmailTemplate } from '../../render/Template'
|
||||
import { Email } from './Email'
|
||||
import EmailProvider from './EmailProvider'
|
||||
|
||||
export default class EmailChannel {
|
||||
|
@ -14,21 +13,16 @@ export default class EmailChannel {
|
|||
}
|
||||
}
|
||||
|
||||
async send(options: EmailTemplate, variables: Variables) {
|
||||
async send(template: EmailTemplate, variables: Variables) {
|
||||
if (!variables.user.email) throw new Error('Unable to send a text message to a user with no email.')
|
||||
|
||||
const message: Email = {
|
||||
const compiled = template.compile(variables)
|
||||
const email = {
|
||||
...compiled,
|
||||
to: variables.user.email,
|
||||
subject: Render(options.subject, variables),
|
||||
from: Render(options.from, variables),
|
||||
html: Render(options.html_body, variables),
|
||||
text: Render(options.text_body, variables),
|
||||
html: Wrap(compiled.html, variables), // Add link and open tracking
|
||||
}
|
||||
if (options.reply_to) message.reply_to = Render(options.reply_to, variables)
|
||||
if (options.cc) message.cc = Render(options.cc, variables)
|
||||
if (options.bcc) message.bcc = Render(options.bcc, variables)
|
||||
|
||||
await this.provider.send(message)
|
||||
await this.provider.send(email)
|
||||
}
|
||||
|
||||
async verify(): Promise<boolean> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PushTemplate } from '../../render/Template'
|
||||
import Render, { Variables } from '../../render'
|
||||
import { Variables } from '../../render'
|
||||
import { PushProvider } from './PushProvider'
|
||||
|
||||
export default class PushChannel {
|
||||
|
@ -12,22 +12,14 @@ export default class PushChannel {
|
|||
}
|
||||
}
|
||||
|
||||
async send(options: PushTemplate, variables: Variables) {
|
||||
async send(template: PushTemplate, variables: Variables) {
|
||||
|
||||
// Find tokens from active devices with push enabled
|
||||
const tokens = variables.user.pushEnabledDevices.map(device => device.token)
|
||||
|
||||
const custom = Object.keys(options.custom).reduce((body, key) => {
|
||||
body[key] = Render(options.custom[key], variables)
|
||||
return body
|
||||
}, {} as Record<string, any>)
|
||||
|
||||
const push = {
|
||||
tokens,
|
||||
topic: options.topic,
|
||||
title: Render(options.title, variables),
|
||||
body: Render(options.body, variables),
|
||||
custom,
|
||||
...template.compile(variables),
|
||||
}
|
||||
|
||||
await this.provider.send(push)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TextMessage, TextResponse } from './TextMessage'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
import TextError from './TextError'
|
||||
import { TextProvider } from './TextProvider'
|
||||
import { ProviderParams, ProviderSchema } from '../Provider'
|
||||
|
@ -8,6 +8,7 @@ import { createController } from '../ProviderService'
|
|||
interface NexmoDataParams {
|
||||
apiKey: string
|
||||
apiSecret: string
|
||||
phoneNumber: string
|
||||
}
|
||||
|
||||
interface NexmoProviderParams extends ProviderParams {
|
||||
|
@ -17,9 +18,10 @@ interface NexmoProviderParams extends ProviderParams {
|
|||
export default class NexmoTextProvider extends TextProvider {
|
||||
apiKey!: string
|
||||
apiSecret!: string
|
||||
phoneNumber!: string
|
||||
|
||||
async send(message: TextMessage): Promise<TextResponse> {
|
||||
const { from, to, text } = message
|
||||
const { to, text } = message
|
||||
const response = await fetch('https://rest.nexmo.com/sms/json', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -29,7 +31,7 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
body: JSON.stringify({
|
||||
api_key: this.apiKey,
|
||||
api_secret: this.apiSecret,
|
||||
from,
|
||||
from: this.phoneNumber,
|
||||
to,
|
||||
text,
|
||||
}),
|
||||
|
@ -55,7 +57,7 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
}
|
||||
|
||||
// https://developer.vonage.com/messaging/sms/guides/inbound-sms
|
||||
parseInbound(inbound: any): TextMessage {
|
||||
parseInbound(inbound: any): InboundTextMessage {
|
||||
return {
|
||||
to: inbound.to,
|
||||
from: inbound.msisdn,
|
||||
|
@ -66,10 +68,11 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
static controllers(): Router {
|
||||
const providerParams = ProviderSchema<NexmoProviderParams, NexmoDataParams>('nexmoTextProviderParams', {
|
||||
type: 'object',
|
||||
required: ['apiKey', 'apiSecret'],
|
||||
required: ['apiKey', 'apiSecret', 'phoneNumber'],
|
||||
properties: {
|
||||
apiKey: { type: 'string' },
|
||||
apiSecret: { type: 'string' },
|
||||
phoneNumber: { type: 'string' },
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Router from '@koa/router'
|
||||
import { ExternalProviderParams, ProviderSchema } from '../Provider'
|
||||
import { createController } from '../ProviderService'
|
||||
import { TextMessage, TextResponse } from './TextMessage'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
import { TextProvider } from './TextProvider'
|
||||
|
||||
interface PlivoDataParams {
|
||||
|
@ -24,7 +24,7 @@ export default class PlivoTextProvider extends TextProvider {
|
|||
}
|
||||
|
||||
async send(message: TextMessage): Promise<TextResponse> {
|
||||
const { from, to, text } = message
|
||||
const { to, text } = message
|
||||
const response = await fetch(`https://api.plivo.com/v1/Account/${this.authId}/Message/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -33,7 +33,7 @@ export default class PlivoTextProvider extends TextProvider {
|
|||
'User-Agent': 'parcelvoy/v1 (+https://github.com/parcelvoy/platform)',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
src: from,
|
||||
src: this.phoneNumber,
|
||||
dst: to,
|
||||
text,
|
||||
}),
|
||||
|
@ -52,7 +52,7 @@ export default class PlivoTextProvider extends TextProvider {
|
|||
}
|
||||
|
||||
// https://www.plivo.com/docs/sms/use-cases/receive-sms/node
|
||||
parseInbound(inbound: any): TextMessage {
|
||||
parseInbound(inbound: any): InboundTextMessage {
|
||||
return {
|
||||
to: inbound.To,
|
||||
from: inbound.From,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TextTemplate } from '../../render/Template'
|
||||
import Render, { Variables } from '../../render'
|
||||
import { Variables } from '../../render'
|
||||
import { TextProvider } from './TextProvider'
|
||||
import { TextMessage } from './TextMessage'
|
||||
import { InboundTextMessage } from './TextMessage'
|
||||
|
||||
export default class TextChannel {
|
||||
readonly provider: TextProvider
|
||||
|
@ -13,19 +13,18 @@ export default class TextChannel {
|
|||
}
|
||||
}
|
||||
|
||||
async send(options: TextTemplate, variables: Variables) {
|
||||
async send(template: TextTemplate, variables: Variables) {
|
||||
if (!variables.user.phone) throw new Error('Unable to send a text message to a user with no phone number.')
|
||||
|
||||
const message = {
|
||||
to: variables.user.phone,
|
||||
from: options.from,
|
||||
text: Render(options.text, variables),
|
||||
...template.compile(variables),
|
||||
}
|
||||
|
||||
await this.provider.send(message)
|
||||
}
|
||||
|
||||
parseInbound(body: any): TextMessage {
|
||||
parseInbound(body: any): InboundTextMessage {
|
||||
return this.provider.parseInbound(body)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
export interface TextMessage {
|
||||
to: string
|
||||
from: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface InboundTextMessage extends TextMessage {
|
||||
from: string
|
||||
}
|
||||
|
||||
export interface TextResponse {
|
||||
message: TextMessage
|
||||
success: boolean
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Provider from '../Provider'
|
||||
import { TextMessage, TextResponse } from './TextMessage'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
|
||||
export type TextProviderName = 'nexmo' | 'plivo' | 'twilio' | 'logger'
|
||||
|
||||
export abstract class TextProvider extends Provider {
|
||||
abstract send(message: TextMessage): Promise<TextResponse>
|
||||
abstract parseInbound(inbound: any): TextMessage
|
||||
abstract parseInbound(inbound: any): InboundTextMessage
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import Router from '@koa/router'
|
||||
import { ExternalProviderParams, ProviderSchema } from '../Provider'
|
||||
import { createController } from '../ProviderService'
|
||||
import { TextMessage, TextResponse } from './TextMessage'
|
||||
import { InboundTextMessage, TextMessage, TextResponse } from './TextMessage'
|
||||
import { TextProvider } from './TextProvider'
|
||||
|
||||
interface TwilioDataParams {
|
||||
accountSid: string
|
||||
authToken: string
|
||||
phoneNumber: string
|
||||
}
|
||||
|
||||
interface TwilioProviderParams extends ExternalProviderParams {
|
||||
|
@ -16,15 +17,16 @@ interface TwilioProviderParams extends ExternalProviderParams {
|
|||
export default class TwilioTextProvider extends TextProvider {
|
||||
accountSid!: string
|
||||
authToken!: string
|
||||
phoneNumber!: string
|
||||
|
||||
get apiKey(): string {
|
||||
return Buffer.from(`${this.accountSid}:${this.authToken}`).toString('base64')
|
||||
}
|
||||
|
||||
async send(message: TextMessage): Promise<TextResponse> {
|
||||
const { from, to, text } = message
|
||||
const { to, text } = message
|
||||
const form = new FormData()
|
||||
form.append('From', from)
|
||||
form.append('From', this.phoneNumber)
|
||||
form.append('To', to)
|
||||
form.append('Body', text)
|
||||
|
||||
|
@ -50,7 +52,7 @@ export default class TwilioTextProvider extends TextProvider {
|
|||
}
|
||||
|
||||
// https://www.twilio.com/docs/messaging/guides/webhook-request
|
||||
parseInbound(inbound: any): TextMessage {
|
||||
parseInbound(inbound: any): InboundTextMessage {
|
||||
return {
|
||||
to: inbound.To,
|
||||
from: inbound.From,
|
||||
|
@ -61,10 +63,11 @@ export default class TwilioTextProvider extends TextProvider {
|
|||
static controllers(): Router {
|
||||
const providerParams = ProviderSchema<TwilioProviderParams, TwilioDataParams>('twilioTextProviderParams', {
|
||||
type: 'object',
|
||||
required: ['accountSid', 'authToken'],
|
||||
required: ['accountSid', 'authToken', 'phoneNumber'],
|
||||
properties: {
|
||||
accountSid: { type: 'string' },
|
||||
authToken: { type: 'string' },
|
||||
phoneNumber: { type: 'string' },
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ export default class EventPostJob extends Job {
|
|||
}
|
||||
|
||||
static async handler({ project_id, event }: EventPostTrigger) {
|
||||
const user = await getUserFromClientId(project_id, event.external_id)
|
||||
const { anonymous_id, external_id } = event
|
||||
const user = await getUserFromClientId(project_id, { anonymous_id, external_id })
|
||||
if (!user) {
|
||||
logger.error({ project_id, event }, 'job:event_post:unknown-user')
|
||||
return
|
||||
|
|
|
@ -8,8 +8,8 @@ import { getUser } from '../users/UserRepository'
|
|||
import { combineURLs, decodeHashid, encodeHashid } from '../utilities'
|
||||
|
||||
export interface TrackedLinkParams {
|
||||
user: User | number
|
||||
campaign: Campaign | number
|
||||
userId: number
|
||||
campaignId: number
|
||||
}
|
||||
|
||||
interface TrackedLinkParts extends TrackedLinkParams {
|
||||
|
@ -18,10 +18,8 @@ interface TrackedLinkParts extends TrackedLinkParams {
|
|||
}
|
||||
|
||||
export const paramsToEncodedLink = (params: TrackedLinkParts): string => {
|
||||
const userId = params.user instanceof User ? params.user.id : params.user
|
||||
const campaignId = params.campaign instanceof Campaign ? params.campaign.id : params.campaign
|
||||
const hashUserId = encodeHashid(userId)
|
||||
const hashCampaignId = encodeHashid(campaignId)
|
||||
const hashUserId = encodeHashid(params.userId)
|
||||
const hashCampaignId = encodeHashid(params.campaignId)
|
||||
|
||||
const baseUrl = combineURLs([App.main.env.baseUrl, params.path])
|
||||
const url = new URL(baseUrl)
|
||||
|
@ -98,7 +96,7 @@ export const trackLinkEvent = async (parts: TrackedLinkExport, eventName: string
|
|||
const job = EventPostJob.from({
|
||||
project_id: user.project_id,
|
||||
event: {
|
||||
user_id: user.external_id,
|
||||
external_id: user.external_id,
|
||||
name: eventName,
|
||||
data: {
|
||||
campaign_id: campaign.id,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Render, { Variables } from '.'
|
||||
import { Webhook } from '../channels/webhook/Webhook'
|
||||
import { ChannelType } from '../config/channels'
|
||||
import Model, { ModelParams } from '../core/Model'
|
||||
import { templateScreenshotUrl } from './TemplateService'
|
||||
|
@ -31,9 +33,18 @@ export default class Template extends Model {
|
|||
}
|
||||
|
||||
export type TemplateParams = Omit<Template, ModelParams | 'map' | 'screenshotUrl'>
|
||||
|
||||
export type TemplateType = EmailTemplate | TextTemplate | PushTemplate | WebhookTemplate
|
||||
|
||||
export interface CompiledEmail {
|
||||
from: string
|
||||
cc?: string
|
||||
bcc?: string
|
||||
reply_to?: string
|
||||
subject: string
|
||||
text: string
|
||||
html: string
|
||||
}
|
||||
|
||||
export class EmailTemplate extends Template {
|
||||
declare type: 'email'
|
||||
from!: string
|
||||
|
@ -41,8 +52,8 @@ export class EmailTemplate extends Template {
|
|||
bcc?: string
|
||||
reply_to?: string
|
||||
subject!: string
|
||||
text_body!: string
|
||||
html_body!: string
|
||||
text!: string
|
||||
html!: string
|
||||
|
||||
parseJson(json: any) {
|
||||
super.parseJson(json)
|
||||
|
@ -51,23 +62,49 @@ export class EmailTemplate extends Template {
|
|||
this.cc = json?.data.cc
|
||||
this.bcc = json?.data.bcc
|
||||
this.reply_to = json?.data.reply_to
|
||||
this.subject = json?.data.subject
|
||||
this.text_body = json?.data.text_body
|
||||
this.html_body = json?.data.html_body
|
||||
this.subject = json?.data.subject ?? ''
|
||||
this.text = json?.data.text ?? ''
|
||||
this.html = json?.data.html ?? ''
|
||||
}
|
||||
|
||||
compile(variables: Variables): CompiledEmail {
|
||||
const email: CompiledEmail = {
|
||||
subject: Render(this.subject, variables),
|
||||
from: Render(this.from, variables),
|
||||
html: Render(this.html, variables),
|
||||
text: Render(this.text, variables),
|
||||
}
|
||||
if (this.reply_to) email.reply_to = Render(this.reply_to, variables)
|
||||
if (this.cc) email.cc = Render(this.cc, variables)
|
||||
if (this.bcc) email.bcc = Render(this.bcc, variables)
|
||||
return email
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompiledText {
|
||||
text: string
|
||||
}
|
||||
|
||||
export class TextTemplate extends Template {
|
||||
declare type: 'text'
|
||||
from!: string
|
||||
text!: string
|
||||
|
||||
parseJson(json: any) {
|
||||
super.parseJson(json)
|
||||
|
||||
this.from = json?.data.from
|
||||
this.text = json?.data.text
|
||||
}
|
||||
|
||||
compile(variables: Variables): CompiledText {
|
||||
return { text: Render(this.text, variables) }
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompiledPush {
|
||||
title: string
|
||||
topic: string
|
||||
body: string
|
||||
custom: Record<string, any>
|
||||
}
|
||||
|
||||
export class PushTemplate extends Template {
|
||||
|
@ -85,6 +122,20 @@ export class PushTemplate extends Template {
|
|||
this.body = json?.data.body
|
||||
this.custom = json?.data.custom
|
||||
}
|
||||
|
||||
compile(variables: Variables): CompiledPush {
|
||||
const custom = Object.keys(this.custom).reduce((body, key) => {
|
||||
body[key] = Render(this.custom[key], variables)
|
||||
return body
|
||||
}, {} as Record<string, any>)
|
||||
|
||||
return {
|
||||
topic: this.topic,
|
||||
title: Render(this.title, variables),
|
||||
body: Render(this.body, variables),
|
||||
custom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class WebhookTemplate extends Template {
|
||||
|
@ -92,7 +143,6 @@ export class WebhookTemplate extends Template {
|
|||
method!: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT'
|
||||
endpoint!: string
|
||||
body!: Record<string, any>
|
||||
query: Record<string, string | string[]> = {}
|
||||
headers: Record<string, string> = {}
|
||||
|
||||
parseJson(json: any) {
|
||||
|
@ -101,7 +151,27 @@ export class WebhookTemplate extends Template {
|
|||
this.method = json?.data.method
|
||||
this.endpoint = json?.data.endpoint
|
||||
this.body = json?.data.body
|
||||
this.query = json?.data.query || {}
|
||||
this.headers = json?.data.headers || {}
|
||||
}
|
||||
|
||||
compile(variables: Variables): Webhook {
|
||||
const headers = Object.keys(this.headers).reduce((headers, key) => {
|
||||
headers[key] = Render(this.headers[key], variables)
|
||||
return headers
|
||||
}, {} as Record<string, string>)
|
||||
|
||||
const body = Object.keys(this.body).reduce((body, key) => {
|
||||
body[key] = Render(this.body[key], variables)
|
||||
return body
|
||||
}, {} as Record<string, any>)
|
||||
|
||||
const endpoint = Render(this.endpoint, variables)
|
||||
const method = this.method
|
||||
return {
|
||||
endpoint,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ import { JSONSchemaType, validate } from '../core/validate'
|
|||
import { searchParamsSchema } from '../core/searchParams'
|
||||
import { extractQueryParams } from '../utilities'
|
||||
import Template, { TemplateParams } from './Template'
|
||||
import { createTemplate, getTemplate, pagedTemplates, renderTemplate, updateTemplate } from './TemplateService'
|
||||
import { createTemplate, getTemplate, pagedTemplates, updateTemplate } from './TemplateService'
|
||||
import { Variables } from '.'
|
||||
import { User } from '../users/User'
|
||||
import { UserEvent } from '../users/UserEvent'
|
||||
|
||||
const router = new Router<
|
||||
ProjectState & { template?: Template }
|
||||
|
@ -32,7 +35,7 @@ const templateParams: JSONSchemaType<TemplateParams> = {
|
|||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: ['from', 'subject', 'text_body', 'html_body'],
|
||||
required: ['from', 'subject', 'text', 'html'],
|
||||
properties: {
|
||||
from: { type: 'string' },
|
||||
cc: {
|
||||
|
@ -48,8 +51,8 @@ const templateParams: JSONSchemaType<TemplateParams> = {
|
|||
nullable: true,
|
||||
},
|
||||
subject: { type: 'string' },
|
||||
text_body: { type: 'string' },
|
||||
html_body: { type: 'string' },
|
||||
text: { type: 'string' },
|
||||
html: { type: 'string' },
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
|
@ -68,9 +71,8 @@ const templateParams: JSONSchemaType<TemplateParams> = {
|
|||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: ['from', 'text'],
|
||||
required: ['text'],
|
||||
properties: {
|
||||
from: { type: 'string' },
|
||||
text: { type: 'string' },
|
||||
},
|
||||
} as any,
|
||||
|
@ -129,9 +131,15 @@ router.patch('/:templateId', async ctx => {
|
|||
ctx.body = await updateTemplate(ctx.state.template!.id, payload)
|
||||
})
|
||||
|
||||
router.get('/:templateId/preview', async ctx => {
|
||||
router.post('/:templateId/preview', async ctx => {
|
||||
const payload = ctx.request.body as Variables
|
||||
const template = ctx.state.template!.map()
|
||||
ctx.body = renderTemplate(template)
|
||||
|
||||
ctx.body = template.compile({
|
||||
user: User.fromJson({ ...payload.user, data: payload.user }),
|
||||
event: UserEvent.fromJson(payload.event || {}),
|
||||
context: payload.context || {},
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
@ -4,6 +4,7 @@ import Template, { TemplateParams, TemplateType } from './Template'
|
|||
import nodeHtmlToImage from 'node-html-to-image'
|
||||
import App from '../app'
|
||||
import TemplateSnapshotJob from './TemplateSnapshotJob'
|
||||
import { prune } from '../utilities'
|
||||
|
||||
export const pagedTemplates = async (params: SearchParams, projectId: number) => {
|
||||
return await Template.searchParams(
|
||||
|
@ -35,7 +36,7 @@ export const createTemplate = async (projectId: number, params: TemplateParams)
|
|||
}
|
||||
|
||||
export const updateTemplate = async (templateId: number, params: TemplateParams) => {
|
||||
const template = await Template.updateAndFetch(templateId, params)
|
||||
const template = await Template.updateAndFetch(templateId, prune(params))
|
||||
|
||||
App.main.queue.enqueue(
|
||||
TemplateSnapshotJob.from({ project_id: template.project_id, template_id: template.id }),
|
||||
|
@ -46,7 +47,7 @@ export const updateTemplate = async (templateId: number, params: TemplateParams)
|
|||
|
||||
export const renderTemplate = (template: TemplateType) => {
|
||||
if (template.type === 'email') {
|
||||
return template.html_body
|
||||
return template.html
|
||||
} else if (template.type === 'text') {
|
||||
return template.text
|
||||
} else if (template.type === 'push') {
|
||||
|
|
|
@ -19,6 +19,11 @@ export interface Variables {
|
|||
event?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface TrackingParams {
|
||||
user: User
|
||||
campaign: number
|
||||
}
|
||||
|
||||
const loadHelper = (helper: Record<string, any>) => {
|
||||
const keys = Object.keys(helper)
|
||||
const values = Object.values(helper)
|
||||
|
@ -37,16 +42,25 @@ export const Compile = (template: string, context: Record<string, any> = {}) =>
|
|||
return Handlebars.compile(template)(context)
|
||||
}
|
||||
|
||||
export const Wrap = (html: string, { user, context }: Variables) => {
|
||||
const trackingParams = { userId: user.id, campaignId: context.campaign_id }
|
||||
html = clickWrapHTML(html, trackingParams)
|
||||
html = openWrapHtml(html, trackingParams)
|
||||
return html
|
||||
}
|
||||
|
||||
export default (template: string, { user, event, context }: Variables) => {
|
||||
const trackingParams = { user, campaign: context.campaign_id }
|
||||
let html = Compile(template, {
|
||||
const trackingParams = { userId: user.id, campaignId: context?.campaign_id }
|
||||
console.log('context', {
|
||||
user: user.flatten(),
|
||||
event,
|
||||
context,
|
||||
unsubscribeEmailUrl: unsubscribeEmailLink(trackingParams),
|
||||
})
|
||||
return Compile(template, {
|
||||
user: user.flatten(),
|
||||
event,
|
||||
context,
|
||||
unsubscribeEmailUrl: unsubscribeEmailLink(trackingParams),
|
||||
})
|
||||
|
||||
html = clickWrapHTML(html, trackingParams)
|
||||
html = openWrapHtml(html, trackingParams)
|
||||
return html
|
||||
}
|
||||
|
|
|
@ -13,6 +13,14 @@ export const randomInt = (min = 0, max = 100): number => {
|
|||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
export const prune = (obj: Record<string, any>): Record<string, any> => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([_, v]) => v != null && v !== '')
|
||||
.map(([k, v]) => [k, v === Object(v) ? prune(v) : v]),
|
||||
)
|
||||
}
|
||||
|
||||
export const snakeCase = (str: string): string => str.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
||||
?.map(x => x.toLowerCase())
|
||||
.join('_') ?? ''
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue