mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-14 03:10:27 +00:00
Mailbox fixes (#8406)
Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
parent
b104585c92
commit
a226f9aa0e
@ -12,8 +12,8 @@
|
|||||||
"webpack": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development webpack --stats-error-details --progress -w",
|
"webpack": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development webpack --stats-error-details --progress -w",
|
||||||
"devp": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=production CLIENT_TYPE=dev webpack --progress -w",
|
"devp": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=production CLIENT_TYPE=dev webpack --progress -w",
|
||||||
"dev": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development webpack --progress -w",
|
"dev": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development webpack --progress -w",
|
||||||
"start": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=production electron .",
|
"start": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=production electron --no-sandbox .",
|
||||||
"start-dev": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development electron .",
|
"start-dev": "cross-env MODEL_VERSION=$(node ../common/scripts/show_version.js) VERSION=$(node ../common/scripts/show_tag.js) NODE_ENV=development electron --no-sandbox .",
|
||||||
"format": "format",
|
"format": "format",
|
||||||
"bump": "bump-package-version"
|
"bump": "bump-package-version"
|
||||||
},
|
},
|
||||||
|
@ -89,6 +89,7 @@ import '@hcengineering/lead-assets'
|
|||||||
import '@hcengineering/login-assets'
|
import '@hcengineering/login-assets'
|
||||||
import '@hcengineering/love-assets'
|
import '@hcengineering/love-assets'
|
||||||
import '@hcengineering/notification-assets'
|
import '@hcengineering/notification-assets'
|
||||||
|
import '@hcengineering/my-space-assets'
|
||||||
import '@hcengineering/preference-assets'
|
import '@hcengineering/preference-assets'
|
||||||
import '@hcengineering/print-assets'
|
import '@hcengineering/print-assets'
|
||||||
import '@hcengineering/process-assets'
|
import '@hcengineering/process-assets'
|
||||||
@ -361,6 +362,7 @@ export async function configurePlatform (): Promise<void> {
|
|||||||
addLocation(mySpaceId, () => import(/* webpackChunkName: "card" */ '@hcengineering/my-space-resources'))
|
addLocation(mySpaceId, () => import(/* webpackChunkName: "card" */ '@hcengineering/my-space-resources'))
|
||||||
addLocation(chatId, () => import(/* webpackChunkName: "chat" */ '@hcengineering/chat-resources'))
|
addLocation(chatId, () => import(/* webpackChunkName: "chat" */ '@hcengineering/chat-resources'))
|
||||||
addLocation(inboxId, () => import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources'))
|
addLocation(inboxId, () => import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources'))
|
||||||
|
addLocation(mailId, () => import(/* webpackChunkName: "card" */ '@hcengineering/mail-resources'))
|
||||||
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
|
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
|
||||||
|
|
||||||
setMetadata(client.metadata.FilterModel, 'ui')
|
setMetadata(client.metadata.FilterModel, 'ui')
|
||||||
|
@ -124,6 +124,7 @@ import '@hcengineering/view-assets'
|
|||||||
import '@hcengineering/workbench-assets'
|
import '@hcengineering/workbench-assets'
|
||||||
import '@hcengineering/chat-assets'
|
import '@hcengineering/chat-assets'
|
||||||
import '@hcengineering/inbox-assets'
|
import '@hcengineering/inbox-assets'
|
||||||
|
import '@hcengineering/mail-assets'
|
||||||
import '@hcengineering/github-assets'
|
import '@hcengineering/github-assets'
|
||||||
|
|
||||||
import { coreId } from '@hcengineering/core'
|
import { coreId } from '@hcengineering/core'
|
||||||
|
@ -93,7 +93,7 @@ export interface AccountClient {
|
|||||||
findPersonBySocialId: (socialId: PersonId, requireAccount?: boolean) => Promise<PersonUuid | undefined>
|
findPersonBySocialId: (socialId: PersonId, requireAccount?: boolean) => Promise<PersonUuid | undefined>
|
||||||
findSocialIdBySocialKey: (socialKey: string) => Promise<PersonId | undefined>
|
findSocialIdBySocialKey: (socialKey: string) => Promise<PersonId | undefined>
|
||||||
getMailboxOptions: () => Promise<MailboxOptions>
|
getMailboxOptions: () => Promise<MailboxOptions>
|
||||||
createMailbox: (name: string, domain: string) => Promise<void>
|
createMailbox: (name: string, domain: string) => Promise<{ mailbox: string, socialId: PersonId }>
|
||||||
getMailboxes: () => Promise<MailboxInfo[]>
|
getMailboxes: () => Promise<MailboxInfo[]>
|
||||||
deleteMailbox: (mailbox: string) => Promise<void>
|
deleteMailbox: (mailbox: string) => Promise<void>
|
||||||
|
|
||||||
@ -653,13 +653,13 @@ class AccountClientImpl implements AccountClient {
|
|||||||
return await this.rpc(request)
|
return await this.rpc(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMailbox (name: string, domain: string): Promise<void> {
|
async createMailbox (name: string, domain: string): Promise<{ mailbox: string, socialId: PersonId }> {
|
||||||
const request = {
|
const request = {
|
||||||
method: 'createMailbox' as const,
|
method: 'createMailbox' as const,
|
||||||
params: { name, domain }
|
params: { name, domain }
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.rpc(request)
|
return await this.rpc(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMailboxes (): Promise<MailboxInfo[]> {
|
async getMailboxes (): Promise<MailboxInfo[]> {
|
||||||
|
@ -14,12 +14,14 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { MailboxOptions } from '@hcengineering/account-client'
|
import { MailboxOptions } from '@hcengineering/account-client'
|
||||||
import presentation from '@hcengineering/presentation'
|
import presentation, { getClient } from '@hcengineering/presentation'
|
||||||
import { Dropdown, ListItem, Modal, ModernEditbox, Spinner, themeStore } from '@hcengineering/ui'
|
import { Dropdown, ListItem, Modal, ModernEditbox, Spinner, themeStore } from '@hcengineering/ui'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { getAccountClient } from '../utils'
|
import { getAccountClient } from '../utils'
|
||||||
import { IntlString, translateCB } from '@hcengineering/platform'
|
import { IntlString, translateCB } from '@hcengineering/platform'
|
||||||
|
import contact, { getCurrentEmployee, SocialIdentity } from '@hcengineering/contact'
|
||||||
|
import { buildSocialIdString, Ref, SocialIdType } from '@hcengineering/core'
|
||||||
|
|
||||||
export let mailboxOptions: MailboxOptions
|
export let mailboxOptions: MailboxOptions
|
||||||
|
|
||||||
@ -39,10 +41,42 @@
|
|||||||
return n.length >= mailboxOptions.minNameLength && n.length <= mailboxOptions.maxNameLength
|
return n.length >= mailboxOptions.minNameLength && n.length <= mailboxOptions.maxNameLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createMailbox (): Promise<void> {
|
||||||
|
const { mailbox, socialId } = await getAccountClient().createMailbox(name, (domain ?? domains[0])._id)
|
||||||
|
console.log('Mailbox created', mailbox, socialId)
|
||||||
|
const currentUser = getCurrentEmployee()
|
||||||
|
const client = getClient()
|
||||||
|
await client.addCollection(
|
||||||
|
contact.class.SocialIdentity,
|
||||||
|
contact.space.Contacts,
|
||||||
|
currentUser,
|
||||||
|
contact.class.Person,
|
||||||
|
'socialIds',
|
||||||
|
{
|
||||||
|
key: buildSocialIdString({ type: SocialIdType.EMAIL, value: mailbox }),
|
||||||
|
type: SocialIdType.EMAIL,
|
||||||
|
value: mailbox,
|
||||||
|
verifiedOn: Date.now()
|
||||||
|
},
|
||||||
|
socialId as any as Ref<SocialIdentity>
|
||||||
|
)
|
||||||
|
await client.addCollection(
|
||||||
|
contact.class.Channel,
|
||||||
|
contact.space.Contacts,
|
||||||
|
currentUser,
|
||||||
|
contact.class.Person,
|
||||||
|
'channels',
|
||||||
|
{
|
||||||
|
provider: contact.channelProvider.Email,
|
||||||
|
value: mailbox
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function save (): Promise<void> {
|
async function save (): Promise<void> {
|
||||||
loading = true
|
loading = true
|
||||||
try {
|
try {
|
||||||
await getAccountClient().createMailbox(name, (domain ?? domains[0])._id)
|
await createMailbox()
|
||||||
loading = false
|
loading = false
|
||||||
dispatch('close', true)
|
dispatch('close', true)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -25,29 +25,19 @@
|
|||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
import { MailboxInfo } from '@hcengineering/account-client'
|
import { MailboxInfo } from '@hcengineering/account-client'
|
||||||
import { MessageBox } from '@hcengineering/presentation'
|
import { getClient, MessageBox } from '@hcengineering/presentation'
|
||||||
import { getAccountClient } from '../utils'
|
import { getAccountClient } from '../utils'
|
||||||
|
import contact, { getCurrentEmployee } from '@hcengineering/contact'
|
||||||
|
import { buildSocialIdString, SocialIdType } from '@hcengineering/core'
|
||||||
|
|
||||||
export let mailbox: MailboxInfo
|
export let mailbox: MailboxInfo
|
||||||
export let mailboxIdx: number
|
export let mailboxIdx: number
|
||||||
|
export let loadingRequested: () => void
|
||||||
export let reloadRequested: () => void
|
export let reloadRequested: () => void
|
||||||
|
|
||||||
let opened = false
|
let opened = false
|
||||||
|
|
||||||
function getMenuItems (mailbox: MailboxInfo): (DropdownIntlItem & { action: () => void })[] {
|
function deleteMailboxAction (): void {
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'delete',
|
|
||||||
icon: IconDelete,
|
|
||||||
label: setting.string.DeleteMailbox,
|
|
||||||
action: () => {
|
|
||||||
deleteMailbox(mailbox)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteMailbox (mailbox: MailboxInfo): void {
|
|
||||||
showPopup(
|
showPopup(
|
||||||
MessageBox,
|
MessageBox,
|
||||||
{
|
{
|
||||||
@ -56,24 +46,71 @@
|
|||||||
dangerous: true,
|
dangerous: true,
|
||||||
okLabel: setting.string.Delete,
|
okLabel: setting.string.Delete,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
getAccountClient()
|
loadingRequested()
|
||||||
.deleteMailbox(mailbox.mailbox)
|
try {
|
||||||
.then(() => {
|
await deleteMailbox()
|
||||||
reloadRequested()
|
} catch (err) {
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
console.error('Failed to delete mailbox', err)
|
console.error('Failed to delete mailbox', err)
|
||||||
})
|
}
|
||||||
|
reloadRequested()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openMailboxMenu = (mailbox: MailboxInfo, ev: MouseEvent): void => {
|
async function deleteMailbox (): Promise<void> {
|
||||||
|
await getAccountClient().deleteMailbox(mailbox.mailbox)
|
||||||
|
const client = getClient()
|
||||||
|
const currentUser = getCurrentEmployee()
|
||||||
|
const socialIds = await client.findAll(contact.class.SocialIdentity, {
|
||||||
|
attachedTo: currentUser,
|
||||||
|
type: SocialIdType.EMAIL,
|
||||||
|
value: mailbox.mailbox
|
||||||
|
})
|
||||||
|
for (const socialId of socialIds) {
|
||||||
|
const value = `${socialId.value}#${socialId._id}`
|
||||||
|
await client.updateCollection(
|
||||||
|
socialId._class,
|
||||||
|
socialId.space,
|
||||||
|
socialId._id,
|
||||||
|
socialId.attachedTo,
|
||||||
|
socialId.attachedToClass,
|
||||||
|
socialId.collection,
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
key: buildSocialIdString({ type: SocialIdType.EMAIL, value })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const channels = await client.findAll(contact.class.Channel, {
|
||||||
|
attachedTo: currentUser,
|
||||||
|
provider: contact.channelProvider.Email,
|
||||||
|
value: mailbox.mailbox
|
||||||
|
})
|
||||||
|
for (const channel of channels) {
|
||||||
|
await client.removeCollection(
|
||||||
|
channel._class,
|
||||||
|
channel.space,
|
||||||
|
channel._id,
|
||||||
|
channel.attachedTo,
|
||||||
|
channel.attachedToClass,
|
||||||
|
channel.collection
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openMailboxMenu = (ev: MouseEvent): void => {
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
opened = true
|
opened = true
|
||||||
const items = getMenuItems(mailbox)
|
const items: (DropdownIntlItem & { action: () => void })[] = [
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
icon: IconDelete,
|
||||||
|
label: setting.string.DeleteMailbox,
|
||||||
|
action: deleteMailboxAction
|
||||||
|
}
|
||||||
|
]
|
||||||
showPopup(ModernPopup, { items }, eventToHTMLElement(ev), (result) => {
|
showPopup(ModernPopup, { items }, eventToHTMLElement(ev), (result) => {
|
||||||
items.find((it) => it.id === result)?.action()
|
items.find((it) => it.id === result)?.action()
|
||||||
opened = false
|
opened = false
|
||||||
@ -94,9 +131,7 @@
|
|||||||
pressed={opened}
|
pressed={opened}
|
||||||
inheritColor
|
inheritColor
|
||||||
hasMenu
|
hasMenu
|
||||||
on:click={(ev) => {
|
on:click={openMailboxMenu}
|
||||||
openMailboxMenu(mailbox, ev)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,7 +119,14 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<Scroller>
|
<Scroller>
|
||||||
{#each mailboxes as mailbox, i}
|
{#each mailboxes as mailbox, i}
|
||||||
<MailboxItem {mailbox} mailboxIdx={i} reloadRequested={loadMailboxes} />
|
<MailboxItem
|
||||||
|
{mailbox}
|
||||||
|
mailboxIdx={i}
|
||||||
|
reloadRequested={loadMailboxes}
|
||||||
|
loadingRequested={() => {
|
||||||
|
boxesLoading = true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</Scroller>
|
</Scroller>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -2032,7 +2032,7 @@ async function createMailbox (
|
|||||||
name: string
|
name: string
|
||||||
domain: string
|
domain: string
|
||||||
}
|
}
|
||||||
): Promise<void> {
|
): Promise<{ mailbox: string, socialId: PersonId }> {
|
||||||
const { account } = decodeTokenVerbose(ctx, token)
|
const { account } = decodeTokenVerbose(ctx, token)
|
||||||
const { name, domain } = params
|
const { name, domain } = params
|
||||||
const normalizedName = cleanEmail(name)
|
const normalizedName = cleanEmail(name)
|
||||||
@ -2060,7 +2060,14 @@ async function createMailbox (
|
|||||||
|
|
||||||
await db.mailbox.insertOne({ accountUuid: account, mailbox })
|
await db.mailbox.insertOne({ accountUuid: account, mailbox })
|
||||||
await db.mailboxSecret.insertOne({ mailbox, secret: generatePassword() })
|
await db.mailboxSecret.insertOne({ mailbox, secret: generatePassword() })
|
||||||
await db.socialId.insertOne({ personUuid: account, type: SocialIdType.EMAIL, value: mailbox })
|
const socialId: PersonId = await db.socialId.insertOne({
|
||||||
|
personUuid: account,
|
||||||
|
type: SocialIdType.EMAIL,
|
||||||
|
value: mailbox,
|
||||||
|
verifiedOn: Date.now()
|
||||||
|
})
|
||||||
|
ctx.info('Mailbox created', { mailbox, account, socialId })
|
||||||
|
return { mailbox, socialId }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMailboxes (
|
async function getMailboxes (
|
||||||
@ -2081,15 +2088,33 @@ async function deleteMailbox (
|
|||||||
params: { mailbox: string }
|
params: { mailbox: string }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { account } = decodeTokenVerbose(ctx, token)
|
const { account } = decodeTokenVerbose(ctx, token)
|
||||||
const { mailbox } = params
|
const mailbox = cleanEmail(params.mailbox)
|
||||||
|
|
||||||
|
if (!isEmail(mailbox)) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.MailboxError, { reason: 'invalid-name' }))
|
||||||
|
}
|
||||||
|
|
||||||
const mb = await db.mailbox.findOne({ mailbox, accountUuid: account })
|
const mb = await db.mailbox.findOne({ mailbox, accountUuid: account })
|
||||||
if (mb == null) {
|
if (mb == null) {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.MailboxError, { reason: 'mailbox-not-found' }))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.MailboxError, { reason: 'mailbox-not-found' }))
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.mailbox.deleteMany({ mailbox })
|
|
||||||
await db.mailboxSecret.deleteMany({ mailbox })
|
await db.mailboxSecret.deleteMany({ mailbox })
|
||||||
|
await db.mailbox.deleteMany({ mailbox })
|
||||||
|
await releaseSocialId(db, account, SocialIdType.EMAIL, mailbox)
|
||||||
|
ctx.info('Mailbox deleted', { mailbox, account })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function releaseSocialId (
|
||||||
|
db: AccountDB,
|
||||||
|
personUuid: PersonUuid,
|
||||||
|
type: SocialIdType,
|
||||||
|
value: string
|
||||||
|
): Promise<void> {
|
||||||
|
const socialIds = await db.socialId.find({ personUuid, type, value })
|
||||||
|
for (const socialId of socialIds) {
|
||||||
|
await db.socialId.updateOne({ _id: socialId._id }, { value: `${socialId.value}#${socialId._id}` })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountMethods =
|
export type AccountMethods =
|
||||||
|
@ -22,6 +22,7 @@ interface Config {
|
|||||||
accountsUrl: string
|
accountsUrl: string
|
||||||
workspaceUrl: string
|
workspaceUrl: string
|
||||||
ignoredAddresses: string[]
|
ignoredAddresses: string[]
|
||||||
|
hookToken?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
@ -34,7 +35,8 @@ const config: Config = {
|
|||||||
}
|
}
|
||||||
throw Error('WORKSPACE_URL env var is not set')
|
throw Error('WORKSPACE_URL env var is not set')
|
||||||
})(),
|
})(),
|
||||||
ignoredAddresses: process.env.IGNORED_ADDRESSES?.split(',') ?? []
|
ignoredAddresses: process.env.IGNORED_ADDRESSES?.split(',') ?? [],
|
||||||
|
hookToken: process.env.HOOK_TOKEN
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
@ -36,6 +36,13 @@ interface MtaMessage {
|
|||||||
|
|
||||||
export async function handleMtaHook (req: Request, res: Response): Promise<void> {
|
export async function handleMtaHook (req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
if (config.hookToken !== undefined) {
|
||||||
|
const token = req.headers['x-hook-token']
|
||||||
|
if (token !== config.hookToken) {
|
||||||
|
throw new Error('Invalid hook token')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mta: MtaMessage = req.body
|
const mta: MtaMessage = req.body
|
||||||
|
|
||||||
const from = { address: mta.envelope.from.address, name: '' }
|
const from = { address: mta.envelope.from.address, name: '' }
|
||||||
@ -119,5 +126,31 @@ async function getContent (mta: MtaMessage): Promise<string> {
|
|||||||
function extractContactName (fromHeader: string): string {
|
function extractContactName (fromHeader: string): string {
|
||||||
// Match name part that appears before an email in angle brackets
|
// Match name part that appears before an email in angle brackets
|
||||||
const nameMatch = fromHeader.match(/^\s*"?([^"<]+?)"?\s*<.+?>/)
|
const nameMatch = fromHeader.match(/^\s*"?([^"<]+?)"?\s*<.+?>/)
|
||||||
return nameMatch?.[1].trim() ?? ''
|
const name = nameMatch?.[1].trim() ?? ''
|
||||||
|
if (name.length > 0) {
|
||||||
|
return decodeMimeWord(name)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeMimeWord (text: string): string {
|
||||||
|
return text.replace(/=\?([^?]+)\?([BQ])\?([^?]+)\?=/gi, (match, charset, encoding, content) => {
|
||||||
|
try {
|
||||||
|
if (encoding.toUpperCase() === 'B') {
|
||||||
|
// Base64 encoding
|
||||||
|
const buffer = Buffer.from(content, 'base64')
|
||||||
|
return buffer.toString(charset as BufferEncoding)
|
||||||
|
} else if (encoding.toUpperCase() === 'Q') {
|
||||||
|
// Quoted-printable encoding
|
||||||
|
const decoded = content
|
||||||
|
.replace(/_/g, ' ')
|
||||||
|
.replace(/=([0-9A-F]{2})/gi, (_: any, hex: string) => String.fromCharCode(parseInt(hex, 16)))
|
||||||
|
return Buffer.from(decoded).toString(charset as BufferEncoding)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to decode encoded word:', match, error)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ async function main (): Promise<void> {
|
|||||||
|
|
||||||
const server = app.listen(config.port, () => {
|
const server = app.listen(config.port, () => {
|
||||||
console.log(`server started on port ${config.port}`)
|
console.log(`server started on port ${config.port}`)
|
||||||
console.log({ ...config, secret: '(stripped)' })
|
console.log({ ...config, secret: '(stripped)', hookToken: '(stripped)' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const shutdown = (): void => {
|
const shutdown = (): void => {
|
||||||
|
@ -29,7 +29,7 @@ import chunter from '@hcengineering/chunter'
|
|||||||
import contact, { PersonSpace } from '@hcengineering/contact'
|
import contact, { PersonSpace } from '@hcengineering/contact'
|
||||||
import mail from '@hcengineering/mail'
|
import mail from '@hcengineering/mail'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import { ensureGlobalPerson } from './person'
|
import { ensureGlobalPerson, ensureLocalPerson } from './person'
|
||||||
|
|
||||||
function generateToken (): string {
|
function generateToken (): string {
|
||||||
return encode(
|
return encode(
|
||||||
@ -61,6 +61,21 @@ export async function createMessages (
|
|||||||
console.error(`[${mailId}] Unable to create message without a proper FROM`)
|
console.error(`[${mailId}] Unable to create message without a proper FROM`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await ensureLocalPerson(
|
||||||
|
client,
|
||||||
|
mailId,
|
||||||
|
fromPerson.uuid,
|
||||||
|
fromPerson.socialId,
|
||||||
|
from.address,
|
||||||
|
fromPerson.firstName,
|
||||||
|
fromPerson.lastName
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[${mailId}] Failed to ensure local FROM person`, err)
|
||||||
|
console.error(`[${mailId}] Unable to create message without a proper FROM`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const toPersons: { address: string, uuid: PersonUuid, socialId: PersonId }[] = []
|
const toPersons: { address: string, uuid: PersonUuid, socialId: PersonId }[] = []
|
||||||
for (const to of tos) {
|
for (const to of tos) {
|
||||||
@ -68,6 +83,20 @@ export async function createMessages (
|
|||||||
if (toPerson === undefined) {
|
if (toPerson === undefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await ensureLocalPerson(
|
||||||
|
client,
|
||||||
|
mailId,
|
||||||
|
toPerson.uuid,
|
||||||
|
toPerson.socialId,
|
||||||
|
to.address,
|
||||||
|
toPerson.firstName,
|
||||||
|
toPerson.lastName
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[${mailId}] Failed to ensure local TO person, skip`, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
toPersons.push({ address: to.address, ...toPerson })
|
toPersons.push({ address: to.address, ...toPerson })
|
||||||
}
|
}
|
||||||
if (toPersons.length === 0) {
|
if (toPersons.length === 0) {
|
||||||
|
@ -12,26 +12,34 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { buildSocialIdString, generateId, PersonId, PersonUuid, SocialIdType, TxOperations } from '@hcengineering/core'
|
import {
|
||||||
import contact, { AvatarType, combineName } from '@hcengineering/contact'
|
buildSocialIdString,
|
||||||
|
generateId,
|
||||||
|
PersonId,
|
||||||
|
PersonUuid,
|
||||||
|
Ref,
|
||||||
|
SocialIdType,
|
||||||
|
TxOperations
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import contact, { AvatarType, combineName, SocialIdentity } from '@hcengineering/contact'
|
||||||
import { AccountClient } from '@hcengineering/account-client'
|
import { AccountClient } from '@hcengineering/account-client'
|
||||||
|
|
||||||
export async function ensureGlobalPerson (
|
export async function ensureGlobalPerson (
|
||||||
client: AccountClient,
|
client: AccountClient,
|
||||||
mailId: string,
|
mailId: string,
|
||||||
contact: { address: string, name: string }
|
contact: { address: string, name: string }
|
||||||
): Promise<{ socialId: PersonId, uuid: PersonUuid } | undefined> {
|
): Promise<{ socialId: PersonId, uuid: PersonUuid, firstName: string, lastName: string } | undefined> {
|
||||||
|
const [firstName, lastName] = contact.name.split(' ')
|
||||||
const socialKey = buildSocialIdString({ type: SocialIdType.EMAIL, value: contact.address })
|
const socialKey = buildSocialIdString({ type: SocialIdType.EMAIL, value: contact.address })
|
||||||
const socialId = await client.findSocialIdBySocialKey(socialKey)
|
const socialId = await client.findSocialIdBySocialKey(socialKey)
|
||||||
const uuid = await client.findPersonBySocialKey(socialKey)
|
const uuid = await client.findPersonBySocialKey(socialKey)
|
||||||
if (socialId !== undefined && uuid !== undefined) {
|
if (socialId !== undefined && uuid !== undefined) {
|
||||||
console.log(`[${mailId}] Found global person for ${contact.address}: ${uuid}`)
|
return { socialId, uuid, firstName, lastName }
|
||||||
return { socialId, uuid }
|
|
||||||
}
|
}
|
||||||
const [firstName, lastName] = contact.name.split(' ')
|
|
||||||
try {
|
try {
|
||||||
const globalPerson = await client.ensurePerson(SocialIdType.EMAIL, contact.address, firstName, lastName)
|
const globalPerson = await client.ensurePerson(SocialIdType.EMAIL, contact.address, firstName, lastName)
|
||||||
return globalPerson
|
console.log(`[${mailId}] Created global person for ${contact.address}: ${globalPerson.uuid}`)
|
||||||
|
return { ...globalPerson, firstName, lastName }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[${mailId}] Failed to create global person for ${contact.address}`, err)
|
console.error(`[${mailId}] Failed to create global person for ${contact.address}`, err)
|
||||||
}
|
}
|
||||||
@ -40,21 +48,14 @@ export async function ensureGlobalPerson (
|
|||||||
|
|
||||||
export async function ensureLocalPerson (
|
export async function ensureLocalPerson (
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
|
mailId: string,
|
||||||
personUuid: PersonUuid,
|
personUuid: PersonUuid,
|
||||||
socialId: PersonId,
|
personId: PersonId,
|
||||||
email: string,
|
email: string,
|
||||||
firstName: string,
|
firstName: string,
|
||||||
lastName: string
|
lastName: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let person = await client.findOne(
|
let person = await client.findOne(contact.class.Person, { personUuid })
|
||||||
contact.class.Person,
|
|
||||||
{
|
|
||||||
personUuid
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projection: { name: 1 }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (person === undefined) {
|
if (person === undefined) {
|
||||||
const newPersonId = await client.createDoc(
|
const newPersonId = await client.createDoc(
|
||||||
contact.class.Person,
|
contact.class.Person,
|
||||||
@ -69,7 +70,16 @@ export async function ensureLocalPerson (
|
|||||||
person = await client.findOne(contact.class.Person, { _id: newPersonId })
|
person = await client.findOne(contact.class.Person, { _id: newPersonId })
|
||||||
if (person === undefined) {
|
if (person === undefined) {
|
||||||
throw new Error(`Failed to create local person for ${personUuid}`)
|
throw new Error(`Failed to create local person for ${personUuid}`)
|
||||||
|
} else {
|
||||||
|
console.log(`[${mailId}] Created local person for ${personUuid}: ${person._id}`)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
const socialId = await client.findOne(contact.class.SocialIdentity, {
|
||||||
|
attachedTo: person._id,
|
||||||
|
type: SocialIdType.EMAIL,
|
||||||
|
value: email
|
||||||
|
})
|
||||||
|
if (socialId === undefined) {
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
contact.class.SocialIdentity,
|
contact.class.SocialIdentity,
|
||||||
contact.space.Contacts,
|
contact.space.Contacts,
|
||||||
@ -77,12 +87,13 @@ export async function ensureLocalPerson (
|
|||||||
contact.class.Person,
|
contact.class.Person,
|
||||||
'socialIds',
|
'socialIds',
|
||||||
{
|
{
|
||||||
key: socialId,
|
key: buildSocialIdString({ type: SocialIdType.EMAIL, value: email }),
|
||||||
type: SocialIdType.EMAIL,
|
type: SocialIdType.EMAIL,
|
||||||
value: email
|
value: email
|
||||||
},
|
},
|
||||||
generateId()
|
personId as any as Ref<SocialIdentity>
|
||||||
)
|
)
|
||||||
|
console.log(`[${mailId}] Created local socialId for ${personUuid}: ${email}`)
|
||||||
}
|
}
|
||||||
const channel = await client.findOne(contact.class.Channel, {
|
const channel = await client.findOne(contact.class.Channel, {
|
||||||
attachedTo: person._id,
|
attachedTo: person._id,
|
||||||
@ -103,5 +114,6 @@ export async function ensureLocalPerson (
|
|||||||
},
|
},
|
||||||
generateId()
|
generateId()
|
||||||
)
|
)
|
||||||
|
console.log(`[${mailId}] Created channel for ${personUuid}: ${email}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user