Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-11-28 16:47:13 +06:00 committed by GitHub
parent ee57808311
commit 9b27f289cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 122 deletions

View File

@ -17,6 +17,9 @@
import activity from '@hcengineering/activity'
import chunter from '@hcengineering/chunter'
import {
DOMAIN_MODEL,
Hierarchy,
IndexKind,
type Account,
type AttachedDoc,
type Class,
@ -24,26 +27,22 @@ import {
type Data,
type Doc,
type Domain,
DOMAIN_MODEL,
Hierarchy,
IndexKind,
type Ref,
type Timestamp,
type Tx,
type TxCUD
} from '@hcengineering/core'
import { ArrOf, type Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
import { ArrOf, Index, Mixin, Model, Prop, TypeRef, TypeString, UX, type Builder } from '@hcengineering/model'
import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import {
type DocUpdates,
notificationId,
type DocUpdateTx,
type EmailNotification,
type DocUpdates,
type Notification,
type NotificationGroup,
notificationId,
type NotificationObjectPresenter,
type NotificationPreferencesGroup,
type NotificationPreview,
@ -76,29 +75,6 @@ export class TNotification extends TAttachedDoc implements Notification {
type!: Ref<NotificationType>
}
@Model(notification.class.EmailNotification, core.class.Doc, DOMAIN_NOTIFICATION)
export class TEmaiNotification extends TDoc implements EmailNotification {
@Prop(TypeString(), 'Sender' as IntlString)
sender!: string
@Prop(ArrOf(TypeString()), 'Receivers' as IntlString)
receivers!: string[]
@Prop(TypeString(), 'Subject' as IntlString)
subject!: string
@Prop(TypeString(), 'Text' as IntlString)
text!: string
@Prop(TypeString(), 'Html' as IntlString)
html?: string
@Prop(TypeString(), 'Status' as IntlString)
status!: 'new' | 'sent' | 'error'
error?: string
}
@Model(notification.class.NotificationType, core.class.Doc, DOMAIN_MODEL)
export class TNotificationType extends TDoc implements NotificationType {
generated!: boolean
@ -182,7 +158,6 @@ export class TDocUpdates extends TDoc implements DocUpdates {
export function createModel (builder: Builder): void {
builder.createModel(
TNotification,
TEmaiNotification,
TNotificationType,
TNotificationProvider,
TNotificationSetting,

View File

@ -29,10 +29,10 @@ import {
} from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Preference } from '@hcengineering/preference'
import { IntegrationType } from '@hcengineering/setting'
import { AnyComponent } from '@hcengineering/ui'
import { Writable } from './types'
import { Preference } from '@hcengineering/preference'
export * from './types'
/**
@ -45,19 +45,6 @@ export interface Notification extends AttachedDoc {
type: Ref<NotificationType>
}
/**
* @public
*/
export interface EmailNotification extends Doc {
sender: string
receivers: string[]
subject: string
text: string
html?: string
status: 'new' | 'sent' | 'error'
error?: string
}
/**
* @public
*/
@ -238,7 +225,6 @@ const notification = plugin(notificationId, {
},
class: {
Notification: '' as Ref<Class<Notification>>,
EmailNotification: '' as Ref<Class<EmailNotification>>,
NotificationType: '' as Ref<Class<NotificationType>>,
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
NotificationSetting: '' as Ref<Class<NotificationSetting>>,

View File

@ -20,6 +20,7 @@ import serverCore from '@hcengineering/server-core'
import serverToken from '@hcengineering/server-token'
import { serverFactories } from '@hcengineering/server-ws'
import { start } from '.'
import serverNotification from '@hcengineering/server-notification'
const serverPort = parseInt(process.env.SERVER_PORT ?? '3333')
@ -81,8 +82,11 @@ if (frontUrl === undefined) {
process.exit(1)
}
const sesUrl = process.env.SES_URL
setMetadata(serverCore.metadata.FrontUrl, frontUrl)
setMetadata(serverToken.metadata.Secret, serverSecret)
setMetadata(serverNotification.metadata.SesUrl, sesUrl ?? '')
// eslint-disable-next-line @typescript-eslint/no-floating-promises
console.log(

View File

@ -40,7 +40,7 @@ import notification, { NotificationType } from '@hcengineering/notification'
import { translate } from '@hcengineering/platform'
import { TriggerControl } from '@hcengineering/server-core'
import { getEmployee, getPersonAccountById } from '@hcengineering/server-notification'
import { getContent, isAllowed } from '@hcengineering/server-notification-resources'
import { getContent, isAllowed, sendEmailNotification } from '@hcengineering/server-notification-resources'
async function getOldDepartment (
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
@ -221,13 +221,13 @@ export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Pr
)
}
async function getEmailNotification (
async function sendEmailNotifications (
control: TriggerControl,
sender: PersonAccount,
doc: Request | PublicHoliday,
space: Ref<Department>,
type: Ref<NotificationType>
): Promise<Tx[]> {
): Promise<void> {
const contacts = new Set<Ref<Contact>>()
const departments = await buildHierarchy(space, control)
for (const department of departments) {
@ -257,25 +257,11 @@ async function getEmailNotification (
const senderName = senderPerson !== undefined ? formatName(senderPerson.name) : ''
const content = await getContent(doc, senderName, type, control, '')
if (content === undefined) return []
if (content === undefined) return
const res: Tx[] = []
for (const channel of channels) {
const tx = control.txFactory.createTxCreateDoc(
notification.class.EmailNotification,
notification.space.Notifications,
{
status: 'new',
sender: senderName,
receivers: [channel.value],
subject: content.subject,
text: content.text,
html: content.html
}
)
res.push(tx)
await sendEmailNotification(content.text, content.html, content.subject, channel.value)
}
return res
}
/**
@ -289,13 +275,14 @@ export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise
const request = TxProcessor.createDoc2Doc(ctx)
return await getEmailNotification(
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.CreateRequestNotification
)
return []
}
/**
@ -310,13 +297,14 @@ export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise
const request = (await control.findAll(hr.class.Request, { _id: ctx.objectId }))[0] as Request
if (request === undefined) return []
return await getEmailNotification(
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.UpdateRequestNotification
)
return []
}
/**
@ -331,13 +319,14 @@ export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise
const request = control.removedMap.get(ctx.objectId) as Request
if (request === undefined) return []
return await getEmailNotification(
await sendEmailNotifications(
control,
sender,
request,
ctx.objectSpace as Ref<Department>,
hr.ids.RemoveRequestNotification
)
return []
}
/**
@ -388,13 +377,14 @@ export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): P
if (employee === undefined) return []
const publicHoliday = TxProcessor.createDoc2Doc(ctx)
return await getEmailNotification(
await sendEmailNotifications(
control,
sender,
publicHoliday,
publicHoliday.department,
hr.ids.CreatePublicHolidayNotification
)
return []
}
/**

View File

@ -38,25 +38,24 @@ import core, {
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
generateId,
concatLink,
matchQuery
} from '@hcengineering/core'
import notification, {
ClassCollaborators,
Collaborators,
DocUpdates,
DocUpdateTx,
EmailNotification,
DocUpdates,
NotificationProvider,
NotificationType
} from '@hcengineering/notification'
import { IntlString, getResource } from '@hcengineering/platform'
import { IntlString, getMetadata, getResource } from '@hcengineering/platform'
import type { TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
HTMLPresenter,
NotificationPresenter,
TextPresenter,
getEmployee,
NotificationPresenter,
getPersonAccount,
getPersonAccountById
} from '@hcengineering/server-notification'
@ -222,15 +221,14 @@ export async function getContent (
}
}
async function createEmailNotificationTxes (
async function notifyByEmail (
control: TriggerControl,
tx: Tx,
type: Ref<NotificationType>,
doc: Doc | undefined,
senderId: Ref<PersonAccount>,
receiverId: Ref<PersonAccount>,
data: string = ''
): Promise<Tx | undefined> {
): Promise<void> {
const sender = (await control.modelDb.findAll(contact.class.PersonAccount, { _id: senderId }))[0]
const receiver = (await control.modelDb.findAll(contact.class.PersonAccount, { _id: receiverId }))[0]
@ -238,42 +236,43 @@ async function createEmailNotificationTxes (
let senderName = ''
if (sender !== undefined) {
const senderPerson = (await control.modelDb.findAll(contact.class.Person, { _id: sender.person }))[0]
const senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
senderName = senderPerson !== undefined ? formatName(senderPerson.name) : ''
}
const content = await getContent(doc, senderName, type, control, data)
if (content !== undefined) {
return await getEmailNotificationTx(tx, senderName, content.text, content.html, content.subject, receiver)
await sendEmailNotification(content.text, content.html, content.subject, receiver.email)
}
}
async function getEmailNotificationTx (
tx: Tx,
sender: string,
export async function sendEmailNotification (
text: string,
html: string,
subject: string,
receiver: PersonAccount
): Promise<TxCreateDoc<EmailNotification> | undefined> {
return {
_id: generateId(),
objectId: generateId(),
_class: core.class.TxCreateDoc,
space: core.space.DerivedTx,
objectClass: notification.class.EmailNotification,
objectSpace: notification.space.Notifications,
modifiedOn: tx.modifiedOn,
modifiedBy: tx.modifiedBy,
attributes: {
status: 'new',
sender,
receivers: [receiver.email],
subject,
text,
html
receiver: string
): Promise<void> {
try {
const sesURL = getMetadata(serverNotification.metadata.SesUrl)
if (sesURL === undefined || sesURL === '') {
console.log('Please provide email service url to enable email confirmations.')
return
}
await fetch(concatLink(sesURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text,
html,
subject,
to: [receiver]
})
})
} catch (err) {
console.log('Could not send email notification', err)
}
}
@ -600,17 +599,13 @@ async function getNotificationTxes (
const emp = await getEmployee(acc.person as Ref<Employee>, control)
if (emp?.active === true) {
for (const type of allowed.emails) {
const emailTx = await createEmailNotificationTxes(
await notifyByEmail(
control,
originTx,
type._id,
object,
originTx.modifiedBy as Ref<PersonAccount>,
target as Ref<PersonAccount>
)
if (emailTx !== undefined) {
res.push(emailTx)
}
}
}
return res

View File

@ -17,7 +17,7 @@
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
import { NotificationType, NotificationContent } from '@hcengineering/notification'
import { Plugin, Resource, plugin } from '@hcengineering/platform'
import { Metadata, Plugin, Resource, plugin } from '@hcengineering/platform'
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
/**
@ -133,6 +133,9 @@ export interface NotificationPresenter extends Class<Doc> {
* @public
*/
export default plugin(serverNotificationId, {
metadata: {
SesUrl: '' as Metadata<string>
},
mixin: {
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
TextPresenter: '' as Ref<Mixin<TextPresenter>>,

View File

@ -407,6 +407,7 @@ async function sendConfirmation (productId: string, account: Account): Promise<v
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
if (sesURL === undefined || sesURL === '') {
console.info('Please provide email service url to enable email confirmations.')
return
}
const front = getMetadata(accountPlugin.metadata.FrontURL)
if (front === undefined || front === '') {
@ -443,21 +444,19 @@ async function sendConfirmation (productId: string, account: Account): Promise<v
subject = 'Confirm your email address to sign up for ezQMS'
}
if (sesURL !== undefined) {
const to = account.email
await fetch(concatLink(sesURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text,
html,
subject,
to
})
const to = account.email
await fetch(concatLink(sesURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text,
html,
subject,
to
})
}
})
}
/**
@ -475,7 +474,16 @@ export async function signUpJoin (
console.log(`signup join:${email} ${first} ${last}`)
const invite = await getInvite(db, inviteId)
const workspace = await checkInvite(invite, email)
await createAcc(db, productId, email, password, first, last, invite?.emailMask === email)
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
await createAcc(
db,
productId,
email,
password,
first,
last,
invite?.emailMask === email || sesURL === undefined || sesURL === ''
)
await assignWorkspace(db, productId, email, workspace.name)
const token = (await login(db, productId, email, password)).token
@ -540,7 +548,8 @@ export async function createAccount (
first: string,
last: string
): Promise<LoginInfo> {
const account = await createAcc(db, productId, email, password, first, last, false)
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
const account = await createAcc(db, productId, email, password, first, last, sesURL === undefined || sesURL === '')
const result = {
endpoint: getEndpoint(),