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 type { Attachment as AttachedFile } from '@hcengineering/mail-common'
import attachment, { Attachment } from '@hcengineering/attachment' import attachment, { Attachment } from '@hcengineering/attachment'
import { decode64, encode64 } from '../base64' import { decode64, encode64 } from '../base64'
import { WorkspaceLoginInfo } from '@hcengineering/account-client'
jest.mock('../config') jest.mock('../config')
@ -41,7 +42,11 @@ describe('AttachmentHandler', () => {
warn: jest.fn(), warn: jest.fn(),
end: 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 = { const mockStorageAdapter = {
put: jest.fn(), put: jest.fn(),
read: jest.fn() read: jest.fn()
@ -77,7 +82,13 @@ describe('AttachmentHandler', () => {
let attachmentHandler: AttachmentHandler let attachmentHandler: AttachmentHandler
beforeEach(() => { beforeEach(() => {
attachmentHandler = new AttachmentHandler(mockCtx, mockWorkspaceId, mockStorageAdapter, mockGmail, mockClient) attachmentHandler = new AttachmentHandler(
mockCtx,
mockWorkspaceLoginInfo,
mockStorageAdapter,
mockGmail,
mockClient
)
}) })
describe('addAttachement', () => { describe('addAttachement', () => {

View File

@ -125,7 +125,14 @@ jest.mock('../config', () => ({
})) }))
jest.mock('@hcengineering/account-client', () => ({ 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', () => { describe('GmailClient', () => {

View File

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

View File

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