mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-29 11:56:04 +08:00
Provider Improvements (#123)
* Fixes push notification provider Improves provider schema form building * Minor tweak
This commit is contained in:
parent
db83ff9ea6
commit
6adc68215b
16 changed files with 119 additions and 34 deletions
|
@ -38,7 +38,10 @@ export default class MailgunEmailProvider extends EmailProvider {
|
|||
type: 'object',
|
||||
required: ['api_key', 'domain'],
|
||||
properties: {
|
||||
api_key: { type: 'string' },
|
||||
api_key: {
|
||||
type: 'string',
|
||||
title: 'API Key',
|
||||
},
|
||||
domain: { type: 'string' },
|
||||
webhook_signing_key: {
|
||||
type: 'string',
|
||||
|
|
|
@ -43,8 +43,14 @@ export default class SESEmailProvider extends EmailProvider {
|
|||
type: 'object',
|
||||
required: ['accessKeyId', 'secretAccessKey'],
|
||||
properties: {
|
||||
accessKeyId: { type: 'string' },
|
||||
secretAccessKey: { type: 'string' },
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
title: 'Access Key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
title: 'Secret Access Key',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ interface APNParams {
|
|||
teamId: string
|
||||
}
|
||||
production: boolean
|
||||
bundleId: string
|
||||
}
|
||||
|
||||
interface FCMParams {
|
||||
|
@ -46,27 +47,49 @@ export default class LocalPushProvider extends PushProvider {
|
|||
type: 'object',
|
||||
nullable: true,
|
||||
required: ['production', 'token'],
|
||||
title: 'APN',
|
||||
description: 'Settings for Apple Push Notifications to send messages to iOS devices.',
|
||||
properties: {
|
||||
production: {
|
||||
type: 'boolean',
|
||||
description: 'Leave unchecked if you are wanting to send to sandbox devices only.',
|
||||
},
|
||||
token: {
|
||||
type: 'object',
|
||||
required: ['key', 'keyId', 'teamId'],
|
||||
properties: {
|
||||
key: { type: 'string' },
|
||||
keyId: { type: 'string' },
|
||||
teamId: { type: 'string' },
|
||||
key: {
|
||||
type: 'string',
|
||||
minLength: 80,
|
||||
},
|
||||
keyId: {
|
||||
type: 'string',
|
||||
title: 'Key ID',
|
||||
},
|
||||
teamId: {
|
||||
type: 'string',
|
||||
title: 'Team ID',
|
||||
},
|
||||
},
|
||||
},
|
||||
bundleId: {
|
||||
type: 'string',
|
||||
title: 'Bundle ID',
|
||||
},
|
||||
},
|
||||
},
|
||||
fcm: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
required: ['id'],
|
||||
title: 'FCM',
|
||||
description: 'Settings for Firebase Cloud Messaging to send messages to Android devices.',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Server Key',
|
||||
minLength: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -81,10 +104,11 @@ export default class LocalPushProvider extends PushProvider {
|
|||
}
|
||||
|
||||
async send(push: Push): Promise<PushResponse> {
|
||||
const { tokens, title, topic, body, custom } = push
|
||||
// TODO: Need a better way of bubbling up errors
|
||||
const { tokens, title, body, custom } = push
|
||||
const response = await this.transport.send(typeof tokens === 'string' ? [tokens] : tokens, {
|
||||
title,
|
||||
topic,
|
||||
topic: this.apn?.bundleId,
|
||||
body,
|
||||
custom,
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ export default class PushChannel {
|
|||
constructor(provider?: PushProvider) {
|
||||
if (provider) {
|
||||
this.provider = provider
|
||||
this.provider.boot?.()
|
||||
} else {
|
||||
throw new Error('A valid push notification provider must be defined!')
|
||||
}
|
||||
|
@ -17,6 +18,9 @@ export default class PushChannel {
|
|||
// Find tokens from active devices with push enabled
|
||||
const tokens = variables.user.pushEnabledDevices.map(device => device.token)
|
||||
|
||||
// If no tokens, don't send
|
||||
if (tokens?.length <= 0) return
|
||||
|
||||
const push = {
|
||||
tokens,
|
||||
...template.compile(variables),
|
||||
|
|
|
@ -30,8 +30,14 @@ export default class NexmoTextProvider extends TextProvider {
|
|||
type: 'object',
|
||||
required: ['api_key', 'api_secret', 'phone_number'],
|
||||
properties: {
|
||||
api_key: { type: 'string' },
|
||||
api_secret: { type: 'string' },
|
||||
api_key: {
|
||||
type: 'string',
|
||||
title: 'API Key',
|
||||
},
|
||||
api_secret: {
|
||||
type: 'string',
|
||||
title: 'API Secret',
|
||||
},
|
||||
phone_number: { type: 'string' },
|
||||
},
|
||||
})
|
||||
|
|
|
@ -29,7 +29,10 @@ export default class PlivoTextProvider extends TextProvider {
|
|||
type: 'object',
|
||||
required: ['auth_id', 'auth_token', 'phone_number'],
|
||||
properties: {
|
||||
auth_id: { type: 'string' },
|
||||
auth_id: {
|
||||
type: 'string',
|
||||
title: 'Auth ID',
|
||||
},
|
||||
auth_token: { type: 'string' },
|
||||
phone_number: { type: 'string' },
|
||||
},
|
||||
|
|
|
@ -37,7 +37,10 @@ export default class TwilioTextProvider extends TextProvider {
|
|||
type: 'object',
|
||||
required: ['account_sid', 'auth_token', 'phone_number'],
|
||||
properties: {
|
||||
account_sid: { type: 'string' },
|
||||
account_sid: {
|
||||
type: 'string',
|
||||
title: 'Account SID',
|
||||
},
|
||||
auth_token: { type: 'string' },
|
||||
phone_number: { type: 'string' },
|
||||
},
|
||||
|
|
|
@ -167,7 +167,7 @@ export class PushTemplate extends Template {
|
|||
this.title = json?.data.title
|
||||
this.topic = json?.data.topic
|
||||
this.body = json?.data.body
|
||||
this.custom = json?.data.custom
|
||||
this.custom = json?.data.custom ?? {}
|
||||
}
|
||||
|
||||
compile(variables: Variables): CompiledPush {
|
||||
|
|
|
@ -9,6 +9,8 @@ import { UserEvent } from '../users/UserEvent'
|
|||
import { loadTextChannel } from '../providers/text'
|
||||
import { RequestError } from '../core/errors'
|
||||
import CampaignError from '../campaigns/CampaignError'
|
||||
import { loadPushChannel } from '../providers/push'
|
||||
import { getUserFromEmail, getUserFromPhone } from '../users/UserRepository'
|
||||
|
||||
export const pagedTemplates = async (params: SearchParams, projectId: number) => {
|
||||
return await Template.searchParams(
|
||||
|
@ -65,20 +67,33 @@ export const sendProof = async (template: TemplateType, variables: Variables, re
|
|||
|
||||
const campaign = await getCampaign(template.campaign_id, template.project_id)
|
||||
if (!campaign) throw new RequestError(CampaignError.CampaignDoesNotExist)
|
||||
const event = UserEvent.fromJson(variables.event || {})
|
||||
const context = variables.context || {}
|
||||
const projectId = template.project_id
|
||||
|
||||
if (template.type === 'email') {
|
||||
const channel = await loadEmailChannel(campaign.provider_id, template.project_id)
|
||||
const channel = await loadEmailChannel(campaign.provider_id, projectId)
|
||||
await channel?.send(template, {
|
||||
user: User.fromJson({ ...variables.user, data: variables.user, email: recipient }),
|
||||
event: UserEvent.fromJson(variables.event || {}),
|
||||
context: variables.context || {},
|
||||
event,
|
||||
context,
|
||||
})
|
||||
} else if (template.type === 'text') {
|
||||
const channel = await loadTextChannel(campaign.provider_id, template.project_id)
|
||||
const channel = await loadTextChannel(campaign.provider_id, projectId)
|
||||
await channel?.send(template, {
|
||||
user: User.fromJson({ ...variables.user, data: variables.user, phone: recipient }),
|
||||
event: UserEvent.fromJson(variables.event || {}),
|
||||
context: variables.context || {},
|
||||
event,
|
||||
context,
|
||||
})
|
||||
} else if (template.type === 'push') {
|
||||
const channel = await loadPushChannel(campaign.provider_id, projectId)
|
||||
const user = await getUserFromEmail(projectId, recipient) ?? await getUserFromPhone(projectId, recipient)
|
||||
if (!user) throw new RequestError('Unable to find a user matching the criteria.')
|
||||
user.data = { ...variables.user, ...user.data }
|
||||
await channel?.send(template, {
|
||||
user,
|
||||
event,
|
||||
context,
|
||||
})
|
||||
} else {
|
||||
throw new RequestError('Sending template proofs is only supported for email and text message types as this time.')
|
||||
|
|
|
@ -255,7 +255,7 @@ export const subscriptionCreateSchema: JSONSchemaType<SubscriptionParams> = {
|
|||
},
|
||||
channel: {
|
||||
type: 'string',
|
||||
enum: ['email', 'text', 'webhook'],
|
||||
enum: ['email', 'text', 'push', 'webhook'],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
|
|
|
@ -26,7 +26,6 @@ export default function FormWrapper<T extends FieldValues>({
|
|||
})
|
||||
|
||||
const handleSubmit = form.handleSubmit(async data => {
|
||||
console.log('submitting', data)
|
||||
setIsLoading(true)
|
||||
onSubmit(data, navigate).finally(() => {
|
||||
setIsLoading(false)
|
||||
|
|
|
@ -2,4 +2,16 @@
|
|||
border: 1px solid var(--color-grey);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ui-schema-form > h5 {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.ui-schema-form > p {
|
||||
margin: 2px 0 10px;
|
||||
color: var(--color-primary-soft);
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
|
@ -5,18 +5,22 @@ import SwitchField from './SwitchField'
|
|||
|
||||
interface Schema {
|
||||
type: 'string' | 'number' | 'boolean' | 'object'
|
||||
title?: string
|
||||
description?: string
|
||||
properties?: Record<string, Schema>
|
||||
required?: string[]
|
||||
minLength?: number
|
||||
}
|
||||
|
||||
interface SchemaProps {
|
||||
title?: string
|
||||
description?: string
|
||||
parent: string
|
||||
schema: Schema
|
||||
form: any
|
||||
}
|
||||
|
||||
export default function SchemaFields({ title, parent, form, schema }: SchemaProps) {
|
||||
export default function SchemaFields({ title, description, parent, form, schema }: SchemaProps) {
|
||||
if (!schema?.properties) {
|
||||
return <></>
|
||||
}
|
||||
|
@ -25,28 +29,40 @@ export default function SchemaFields({ title, parent, form, schema }: SchemaProp
|
|||
const keys = Object.keys(schema.properties)
|
||||
return <div className="ui-schema-form">
|
||||
{title && <h5>{snakeToTitle(title)}</h5>}
|
||||
{description && <p>{description}</p> }
|
||||
<div className="ui-schema-fields">
|
||||
{keys.map(key => {
|
||||
const item = props[key]
|
||||
const required = schema.required?.includes(key)
|
||||
const title = item.title ?? snakeToTitle(key)
|
||||
if (item.type === 'string' || item.type === 'number') {
|
||||
return <TextInput.Field
|
||||
key={key}
|
||||
form={form}
|
||||
name={`${parent}.${key}`}
|
||||
label={snakeToTitle(key)}
|
||||
label={title}
|
||||
subtitle={item.description}
|
||||
required={required}
|
||||
textarea={(item.minLength ?? 0) >= 80}
|
||||
/>
|
||||
} else if (item.type === 'boolean') {
|
||||
return <SwitchField
|
||||
key={key}
|
||||
form={form}
|
||||
name={`${parent}.${key}`}
|
||||
label={snakeToTitle(key)}
|
||||
label={title}
|
||||
subtitle={item.description}
|
||||
required={required}
|
||||
/>
|
||||
} else if (item.type === 'object') {
|
||||
return SchemaFields({ title: key, form, parent: `${parent}.${key}`, schema: item })
|
||||
return <SchemaFields
|
||||
key={key}
|
||||
form={form}
|
||||
title={title}
|
||||
description={item.description}
|
||||
parent={`${parent}.${key}`}
|
||||
schema={item}
|
||||
/>
|
||||
}
|
||||
return 'no key'
|
||||
})}
|
||||
|
|
|
@ -32,7 +32,7 @@ export default function SwitchField<X extends FieldValues, P extends FieldPath<X
|
|||
id={id}
|
||||
checked={checked}
|
||||
onChange={(event) => onChange?.(event.target.checked)}
|
||||
{...form?.register(name, { disabled, required })}
|
||||
{...form?.register(name, { disabled })}
|
||||
/>
|
||||
<div className="slider round"></div>
|
||||
</div>
|
||||
|
|
|
@ -66,7 +66,7 @@ const SendProof = ({ open, onClose, onSubmit, type }: SendProofProps) => {
|
|||
open={open}
|
||||
onClose={onClose}
|
||||
title="Send Proof"
|
||||
description={`Enter the ${type === 'email' ? 'email address' : 'phone number'} of the recipient you want to receive the proof of this template.`}>
|
||||
description={`Enter the ${type === 'email' ? 'email address' : 'email or phone number'} of the recipient you want to receive the proof of this template.`}>
|
||||
<FormWrapper<TemplateProofParams>
|
||||
onSubmit={async ({ recipient }) => await onSubmit(recipient)}>
|
||||
{form => (
|
||||
|
|
|
@ -64,7 +64,6 @@ const TextForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> })
|
|||
const PushTable = ({ data }: { data: PushTemplateData }) => <InfoTable rows={{
|
||||
Title: data.title,
|
||||
Body: data.body,
|
||||
Topic: data.topic,
|
||||
}} />
|
||||
|
||||
const PushForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
|
||||
|
@ -79,11 +78,6 @@ const PushForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> })
|
|||
label="Body"
|
||||
textarea
|
||||
required />
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.topic"
|
||||
label="Topic"
|
||||
required />
|
||||
</>
|
||||
|
||||
interface TemplateDetailProps {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue