UBERF-10631: Fix attachments in old gmail integration (#8971)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / uitest-workspaces (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko 2025-05-16 18:42:56 +07:00 committed by GitHub
parent ede948676f
commit 845ec007bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 95 additions and 34 deletions

View File

@ -15,6 +15,7 @@ import { AttachmentHandler } from '../message/attachments'
import type { Attachment as AttachedFile } from '@hcengineering/mail-common'
import attachment, { Attachment } from '@hcengineering/attachment'
import { decode64, encode64 } from '../base64'
import { WorkspaceLoginInfo } from '@hcengineering/account-client'
jest.mock('../config')
@ -41,7 +42,11 @@ describe('AttachmentHandler', () => {
warn: jest.fn(),
end: jest.fn()
}
const mockWorkspaceId = 'test-workspace' as WorkspaceUuid
const mockWorkspaceLoginInfo: WorkspaceLoginInfo = {
endpoint: 'wss://test-endpoint.com',
workspace: 'test-workspace' as WorkspaceUuid,
token: 'test-token'
} as any
const mockStorageAdapter = {
put: jest.fn(),
read: jest.fn()
@ -77,7 +82,13 @@ describe('AttachmentHandler', () => {
let attachmentHandler: AttachmentHandler
beforeEach(() => {
attachmentHandler = new AttachmentHandler(mockCtx, mockWorkspaceId, mockStorageAdapter, mockGmail, mockClient)
attachmentHandler = new AttachmentHandler(
mockCtx,
mockWorkspaceLoginInfo,
mockStorageAdapter,
mockGmail,
mockClient
)
})
describe('addAttachement', () => {

View File

@ -125,7 +125,14 @@ jest.mock('../config', () => ({
}))
jest.mock('@hcengineering/account-client', () => ({
getClient: jest.fn()
getClient: jest.fn().mockImplementation(() => ({
getLoginInfoByToken: jest.fn().mockResolvedValue({
endpoint: 'wss://test-endpoint.com',
workspace: 'mockWorkspaceId',
token: 'test-token'
})
})),
isWorkspaceLoginInfo: jest.fn().mockImplementation(() => true)
}))
describe('GmailClient', () => {

View File

@ -19,7 +19,13 @@ import { type StorageAdapter } from '@hcengineering/server-core'
import setting from '@hcengineering/setting'
import type { Credentials, OAuth2Client } from 'google-auth-library'
import { gmail_v1, google } from 'googleapis'
import { getClient as getAccountClient, Integration } from '@hcengineering/account-client'
import {
getClient as getAccountClient,
Integration,
WorkspaceLoginInfo,
isWorkspaceLoginInfo,
AccountClient
} from '@hcengineering/account-client'
import { encode64 } from './base64'
import config from './config'
@ -94,19 +100,19 @@ export class GmailClient {
private readonly gmail: gmail_v1.Resource$Users,
private readonly user: User,
client: Client,
workspaceId: WorkspaceUuid,
accountClient: AccountClient,
wsInfo: WorkspaceLoginInfo,
storageAdapter: StorageAdapter,
private readonly workspace: WorkspaceClient,
email: string,
private socialId: SocialId
) {
this.email = email
this.integrationToken = serviceToken(workspaceId)
this.tokenStorage = new TokenStorage(this.ctx, workspaceId, this.integrationToken)
this.integrationToken = serviceToken(wsInfo.workspace)
this.tokenStorage = new TokenStorage(this.ctx, wsInfo.workspace, this.integrationToken)
this.client = new TxOperations(client, this.socialId._id)
this.account = this.user.userId
this.attachmentHandler = new AttachmentHandler(ctx, workspaceId, storageAdapter, this.gmail, this.client)
const accountClient = getAccountClient(config.AccountsURL, this.integrationToken)
this.attachmentHandler = new AttachmentHandler(ctx, wsInfo, storageAdapter, this.gmail, this.client)
const keyValueClient = getKvsClient(this.integrationToken)
this.messageManager = createMessageManager(
ctx,
@ -164,13 +170,22 @@ export class GmailClient {
}
user.socialId = socialId
const integrationToken = serviceToken(workspaceId)
const accountClient = getAccountClient(config.AccountsURL, integrationToken)
const workspaceInfo = await accountClient.getLoginInfoByToken()
if (!isWorkspaceLoginInfo(workspaceInfo)) {
ctx.error('Unable to get workspace info', { workspaceId, email })
throw new Error('Unable to get workspace info')
}
const gmailClient = new GmailClient(
ctx,
oAuth2Client,
googleClient,
user,
client,
workspaceId,
accountClient,
workspaceInfo,
storageAdapter,
workspace,
email,

View File

@ -14,18 +14,19 @@
//
import { randomUUID } from 'crypto'
import attachment, { Attachment } from '@hcengineering/attachment'
import { AttachedData, Blob, MeasureContext, Ref, TxOperations, WorkspaceUuid } from '@hcengineering/core'
import { Blob, MeasureContext, Ref, TxOperations } from '@hcengineering/core'
import { StorageAdapter } from '@hcengineering/server-core'
import { type Attachment as AttachedFile } from '@hcengineering/mail-common'
import { gmail_v1 } from 'googleapis'
import { v4 as uuid } from 'uuid'
import { WorkspaceLoginInfo } from '@hcengineering/account-client'
import { encode64 } from '../base64'
import { addFooter } from '../utils'
export class AttachmentHandler {
constructor (
private readonly ctx: MeasureContext,
private readonly workspaceId: WorkspaceUuid,
private readonly wsInfo: WorkspaceLoginInfo,
private readonly storageAdapter: StorageAdapter,
private readonly gmail: gmail_v1.Resource$Users,
private readonly client: TxOperations
@ -36,22 +37,32 @@ export class AttachmentHandler {
if (currentAttachemtns.findIndex((p) => p.name === file.name && p.lastModified === file.lastModified) !== -1) {
return
}
const id = uuid()
const data: AttachedData<Attachment> = {
name: file.name,
file: id as Ref<Blob>,
type: file.data.toString('base64') ?? 'undefined',
size: file.size ?? file.data.length,
lastModified: file.lastModified ?? Date.now()
}
await this.storageAdapter.put(this.ctx, this.workspaceId as any, id, file.data, data.type, data.size) // TODO: FIXME
const fileId = file.id ?? randomUUID()
await this.storageAdapter.put(
this.ctx,
{
uuid: this.wsInfo.workspace,
url: this.wsInfo.workspaceUrl,
dataId: this.wsInfo.workspaceDataId
},
fileId,
file.data,
file.contentType,
file.size
)
await this.client.addCollection(
attachment.class.Attachment,
message.space,
message._id,
message._class,
'attachments',
data
{
name: file.name,
file: fileId as Ref<Blob>,
type: file.data.toString('base64') ?? 'undefined',
size: file.size ?? file.data.length,
lastModified: file.lastModified ?? Date.now()
}
)
} catch (err: any) {
this.ctx.error('Add attachment error', { error: err.message })
@ -153,16 +164,33 @@ export class AttachmentHandler {
}
private async makeAttachmentPart (attachment: Attachment): Promise<string[]> {
const buffer = await this.storageAdapter.read(this.ctx, this.workspaceId as any, attachment.file) // TODO: FIXME
const data = Buffer.concat(buffer.map((b) => new Uint8Array(b))).toString('base64')
const res: string[] = []
res.push('--mail\n')
res.push(`Content-Type: ${attachment.type}\n`)
res.push('MIME-Version: 1.0\n')
res.push('Content-Transfer-Encoding: base64\n')
res.push(`Content-Disposition: attachment; filename="${attachment.name}"\n\n`)
res.push(data)
res.push('\n\n')
return res
try {
const buffer = await this.storageAdapter.read(
this.ctx,
{
uuid: this.wsInfo.workspace,
url: this.wsInfo.workspaceUrl,
dataId: this.wsInfo.workspaceDataId
},
attachment.file
)
const data = Buffer.concat(buffer.map((b) => new Uint8Array(b))).toString('base64')
const res: string[] = []
res.push('--mail\n')
res.push(`Content-Type: ${attachment.type}\n`)
res.push('MIME-Version: 1.0\n')
res.push('Content-Transfer-Encoding: base64\n')
res.push(`Content-Disposition: attachment; filename="${attachment.name}"\n\n`)
res.push(data)
res.push('\n\n')
return res
} catch (err: any) {
this.ctx.error('Failed to make attachment part', {
error: err.message,
file: attachment.file,
name: attachment.name
})
return []
}
}
}