diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index f3a650da96..cc62d3db4d 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -13,9 +13,8 @@ // limitations under the License. // /* eslint-disable @typescript-eslint/no-unused-vars */ -import contact, { Channel, Employee, formatName, Person } from '@hcengineering/contact' +import contact, { Channel, formatName, Person, SocialIdentity } from '@hcengineering/contact' import core, { - AccountUuid, PersonId, Class, concatLink, @@ -30,7 +29,8 @@ import core, { TxCreateDoc, TxProcessor, groupByArray, - SocialIdType + SocialIdType, + Domain } from '@hcengineering/core' import gmail, { Message } from '@hcengineering/gmail' import { TriggerControl } from '@hcengineering/server-core' @@ -122,18 +122,10 @@ export async function sendEmailNotification ( try { const mailURL = getMetadata(serverNotification.metadata.MailUrl) if (mailURL === undefined || mailURL === '') { - ctx.error('sendEmailNotification: Email service URL not configured') + ctx.error('Please provide email service url to enable email notifications.') return } const mailAuth: string | undefined = getMetadata(serverNotification.metadata.MailAuthToken) - - ctx.info('sendEmailNotification: Sending email', { - receiver, - subject, - mailURL, - hasAuth: mailAuth != null - }) - const response = await fetch(concatLink(mailURL, '/send'), { method: 'post', keepalive: true, @@ -149,24 +141,10 @@ export async function sendEmailNotification ( }) }) if (!response.ok) { - ctx.error('sendEmailNotification: Failed to send email', { - receiver, - subject, - status: response.status, - statusText: response.statusText - }) - } else { - ctx.info('sendEmailNotification: Email sent successfully', { - receiver, - subject - }) + ctx.error(`Failed to send email notification: ${response.statusText}`) } } catch (err) { - ctx.error('sendEmailNotification: Exception while sending email', { - err, - receiver, - subject - }) + ctx.error('Could not send email notification', { err, receiver }) } } @@ -180,17 +158,6 @@ async function notifyByEmail ( data: InboxNotification, message: ActivityMessage ): Promise { - control.ctx.info('notifyByEmail: Preparing email notification', { - notificationId: data._id, - notificationUser: data.user, - type, - docId: doc._id, - docClass: doc._class, - email, - messageId: message._id, - senderSocialId - }) - let senderName = sender !== undefined ? formatName(sender.name, control.branding?.lastNameFirst) : '' if (senderName === '' && senderSocialId === core.account.System) { senderName = 'System' @@ -199,14 +166,6 @@ async function notifyByEmail ( if (content !== undefined) { await sendEmailNotification(control.ctx, content.text, content.html, content.subject, email) - } else { - control.ctx.warn('notifyByEmail: No content generated for email', { - notificationId: data._id, - notificationUser: data.user, - type, - docId: doc._id, - email - }) } } @@ -234,50 +193,19 @@ async function getNotificationMessages ( } async function processEmailNotifications (control: TriggerControl, notifications: InboxNotification[]): Promise { - if (notifications.length === 0) { - control.ctx.info('processEmailNotifications: No notifications to process') - return - } + if (notifications.length === 0) return const docId = notifications[0].objectId const docClass = notifications[0].objectClass - - control.ctx.info('processEmailNotifications: Starting email notification processing', { - docId, - docClass, - notificationCount: notifications.length - }) - const doc = (await control.findAll(control.ctx, docClass, { _id: docId }))[0] - if (doc === undefined) { - control.ctx.warn('processEmailNotifications: Document not found', { - docId, - docClass - }) - return - } + if (doc === undefined) return const messages = await getNotificationMessages(notifications, control) const { hierarchy } = control - control.ctx.info('processEmailNotifications: Retrieved activity messages', { - docId, - messageCount: messages.length - }) - const senders = new Map() - const skipped: Array<{ - notificationId: Ref - user: AccountUuid - reason: string - employeeId?: Ref - }> = [] - let sentCount = 0 for (const n of notifications) { const type = (n.types ?? [])[0] - if (type === undefined) { - skipped.push({ notificationId: n._id, user: n.user, reason: 'no_notification_type' }) - continue - } + if (type === undefined) continue let message: ActivityMessage | undefined if (hierarchy.isDerived(n._class, notification.class.ActivityInboxNotification)) { const activityNotification = n as ActivityInboxNotification @@ -289,28 +217,28 @@ async function processEmailNotifications (control: TriggerControl, notifications } } - if (message === undefined) { - skipped.push({ notificationId: n._id, user: n.user, reason: 'activity_message_not_found' }) - continue - } + if (message === undefined) continue const employee = await getEmployeeByAcc(control, n.user) - if (employee === undefined) { - skipped.push({ notificationId: n._id, user: n.user, reason: 'employee_not_found' }) - continue - } - const emails = await control.findAll(control.ctx, contact.class.SocialIdentity, { + if (employee === undefined) continue + const emailQuery = { attachedTo: employee._id, type: { $in: [SocialIdType.EMAIL, SocialIdType.GOOGLE] }, verifiedOn: { $gt: 0 }, isDeleted: { $ne: true } - }) + } + + let emails: SocialIdentity[] = [] + try { + // Use rawFindAll to avoid filters from permission middleware + emails = await control.lowLevel.rawFindAll('channel' as Domain, emailQuery, { limit: 10 }) + } catch (err) { + // Fallback to regular findAll if lowLevel fails + control.ctx.warn('processEmailNotifications: Raw find all failed', { employeeId: employee._id, err }) + const emailsResult = await control.findAll(control.ctx, contact.class.SocialIdentity, emailQuery) + emails = emailsResult as SocialIdentity[] + } + if (emails.length === 0) { - skipped.push({ - notificationId: n._id, - user: n.user, - reason: 'no_verified_email_found', - employeeId: employee._id - }) control.ctx.warn('processEmailNotifications: No verified email found for employee', { notificationId: n._id, user: n.user, @@ -319,15 +247,6 @@ async function processEmailNotifications (control: TriggerControl, notifications continue } - control.ctx.info('processEmailNotifications: Found verified email for user', { - notificationId: n._id, - user: n.user, - employeeId: employee._id, - email: emails[0].value, - emailType: emails[0].type, - totalEmails: emails.length - }) - const senderSocialId = message.createdBy ?? message.modifiedBy const sender = senders.get(senderSocialId) ?? (await getPerson(control, senderSocialId)) if (sender != null) { @@ -335,23 +254,10 @@ async function processEmailNotifications (control: TriggerControl, notifications } await notifyByEmail(control, type, doc, sender, senderSocialId, emails[0].value, n, message) - sentCount++ } - - control.ctx.info('processEmailNotifications: Completed email notification processing', { - docId, - totalNotifications: notifications.length, - sentCount, - skippedCount: skipped.length, - skipped - }) } async function NotificationsHandler (txes: TxCreateDoc[], control: TriggerControl): Promise { - control.ctx.info('NotificationsHandler: Processing email notifications', { - totalTxes: txes.length - }) - const availableProviders: AvailableProvidersCache = control.contextCache.get(AvailableProvidersCacheKey) ?? new Map() const all: InboxNotification[] = txes @@ -360,35 +266,18 @@ async function NotificationsHandler (txes: TxCreateDoc[], con (it) => availableProviders.get(it._id)?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined ) - const filteredOut = txes.length - all.length - if (filteredOut > 0) { - control.ctx.info('NotificationsHandler: Filtered out notifications without email provider', { - filteredOut, - totalTxes: txes.length, - remaining: all.length - }) - } - if (all.length === 0) { - control.ctx.info('NotificationsHandler: No notifications with email provider found') return [] } const notificationsByDocId = groupByArray(all, (n) => n.objectId) - control.ctx.info('NotificationsHandler: Grouped notifications by document', { - totalNotifications: all.length, - documentCount: notificationsByDocId.size - }) - await Promise.all( Array.from(notificationsByDocId.entries()).map(([docId, notifications]) => processEmailNotifications(control, notifications) ) ) - control.ctx.info('NotificationsHandler: Completed processing all email notifications') - return [] } diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index fdee28bfba..9df493219e 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -1501,10 +1501,6 @@ export async function getCollaborators ( const mixin = getClassCollaborators(control.modelDb, control.hierarchy, doc._class) if (mixin === undefined) { - ctx.info('getCollaborators: No collaborator mixin found', { - docId: doc._id, - docClass: doc._class - }) return [] } @@ -1513,27 +1509,12 @@ export async function getCollaborators ( }) if (collaborators.length > 0) { - const accountUuids = collaborators.map((p) => p.collaborator) - ctx.info('getCollaborators: Found existing collaborators', { - docId: doc._id, - docClass: doc._class, - collaboratorCount: accountUuids.length, - collaborators: accountUuids - }) - return accountUuids + return collaborators.map((p) => p.collaborator) } else { const collaborators = await getDocCollaborators(ctx, doc, mixin, control) - const accountUuids = collaborators - - ctx.info('getCollaborators: Computed collaborators from doc', { - docId: doc._id, - docClass: doc._class, - collaboratorCount: accountUuids.length, - collaborators: accountUuids - }) res.push(...getAddCollaboratTxes(tx.objectId, tx.objectClass, tx.objectSpace, control, collaborators)) - return accountUuids + return collaborators } } diff --git a/server-plugins/notification-resources/src/utils.ts b/server-plugins/notification-resources/src/utils.ts index ca5143695c..8bcd5809da 100644 --- a/server-plugins/notification-resources/src/utils.ts +++ b/server-plugins/notification-resources/src/utils.ts @@ -35,6 +35,7 @@ import core, { matchQuery, type MeasureContext, MixinUpdate, + notEmpty, PersonId, Ref, Space, @@ -180,33 +181,16 @@ export function isAllowed ( ) if (providerSettings.length > 0 && providerSettings.every((s) => !s.enabled)) { - control.ctx.info('isAllowed: Provider disabled by user settings', { - provider: provider._id, - type: type._id, - receiverIds, - providerSettings: providerSettings.map((s) => ({ createdBy: s.createdBy, enabled: s.enabled })) - }) return false } if (providerSettings.length === 0 && !provider.defaultEnabled) { - control.ctx.info('isAllowed: Provider disabled by default (no user settings)', { - provider: provider._id, - type: type._id, - receiverIds, - defaultEnabled: provider.defaultEnabled - }) return false } const providerDefaults = control.modelDb.findAllSync(notification.class.NotificationProviderDefaults, {}) if (providerDefaults.some((it) => it.provider === provider._id && it.ignoredTypes.includes(type._id))) { - control.ctx.info('isAllowed: Notification type ignored in provider defaults', { - provider: provider._id, - type: type._id, - receiverIds - }) return false } @@ -215,41 +199,16 @@ export function isAllowed ( ) if (setting !== undefined) { - control.ctx.info('isAllowed: Using specific type setting', { - provider: provider._id, - type: type._id, - receiverIds, - enabled: setting.enabled - }) return setting.enabled } if (providerDefaults.some((it) => it.provider === provider._id && it.enabledTypes.includes(type._id))) { - control.ctx.info('isAllowed: Notification type enabled in provider defaults', { - provider: provider._id, - type: type._id, - receiverIds - }) return true } - if (type === undefined) { - control.ctx.warn('isAllowed: Notification type is undefined', { - provider: provider._id, - receiverIds - }) - return false - } + if (type === undefined) return false - const result = type.defaultEnabled - control.ctx.info('isAllowed: Using type default enabled', { - provider: provider._id, - type: type._id, - receiverIds, - defaultEnabled: type.defaultEnabled, - result - }) - return result + return type.defaultEnabled } export async function isShouldNotifyTx ( @@ -267,35 +226,12 @@ export async function isShouldNotifyTx ( const result = new Map, NotificationType[]>() let providers: NotificationProvider[] = control.modelDb.findAllSync(notification.class.NotificationProvider, {}) - control.ctx.info('isShouldNotifyTx: Starting notification check', { - objectId: object._id, - objectClass: object._class, - receiverAccount: receiver.account, - receiverEmployee: receiver.employee, - receiverSocialIds: receiver.socialIds, - modifiedBy: modifiedByPersonId, - matchedTypesCount: types.length, - matchedTypes: types.map((t) => t._id), - isOwn, - isSpace - }) - if (getMetadata(serverNotification.metadata.InboxOnlyNotifications) === true) { providers = providers.filter((it) => it._id === notification.providers.InboxNotificationProvider) - control.ctx.info('isShouldNotifyTx: InboxOnlyNotifications enabled, filtering providers') } - const skippedTypes: Array<{ type: Ref, reason: string }> = [] - const allowedProviders: Array<{ provider: Ref, type: Ref }> = [] - const disallowedProviders: Array<{ - provider: Ref - type: Ref - reason: string - }> = [] - for (const type of types) { if (type.allowedForAuthor !== true && receiver.socialIds.includes(modifiedByPersonId)) { - skippedTypes.push({ type: type._id, reason: 'author_not_allowed_for_this_type' }) continue } @@ -307,10 +243,7 @@ export async function isShouldNotifyTx ( if (res instanceof Promise) { res = await res } - if (!res) { - skippedTypes.push({ type: type._id, reason: 'type_match_function_returned_false' }) - continue - } + if (!res) continue } } for (const provider of providers) { @@ -319,32 +252,10 @@ export async function isShouldNotifyTx ( if (allowed) { const cur = result.get(provider._id) ?? [] result.set(provider._id, [...cur, type]) - allowedProviders.push({ provider: provider._id, type: type._id }) - } else { - disallowedProviders.push({ provider: provider._id, type: type._id, reason: 'isAllowed_returned_false' }) } } } - if (result.size === 0) { - control.ctx.warn('isShouldNotifyTx: No notification providers allowed', { - objectId: object._id, - receiverAccount: receiver.account, - receiverEmployee: receiver.employee, - receiverSocialIds: receiver.socialIds, - skippedTypes, - disallowedProviders, - matchedTypesCount: types.length - }) - } else { - control.ctx.info('isShouldNotifyTx: Notification providers allowed', { - objectId: object._id, - receiverAccount: receiver.account, - allowedProviders, - resultSize: result.size - }) - } - return result } @@ -573,12 +484,7 @@ export async function getReceiversInfo ( accounts: AccountUuid[], control: TriggerControl ): Promise { - if (accounts.length === 0) { - ctx.info('getReceiversInfo: No accounts provided') - return [] - } - - ctx.info('getReceiversInfo: Processing accounts', { accountCount: accounts.length, accounts }) + if (accounts.length === 0) return [] const employees: Pick[] = await control.findAll( ctx, @@ -586,28 +492,10 @@ export async function getReceiversInfo ( { personUuid: { $in: accounts }, active: true }, { projection: { _id: 1, personUuid: 1, role: 1 } } ) - if (employees.length === 0) { - ctx.warn('getReceiversInfo: No active employees found', { accounts }) - return [] - } - - const foundAccountUuids = new Set(employees.map((it) => it.personUuid)) - const missingAccounts = accounts.filter((acc) => !foundAccountUuids.has(acc)) - if (missingAccounts.length > 0) { - ctx.warn('getReceiversInfo: Some accounts have no active employee', { - missingAccounts, - foundEmployees: employees.map((it) => ({ personUuid: it.personUuid, employeeId: it._id })) - }) - } + if (employees.length === 0) return [] const spaces = await getPersonSpaces(control) - if (spaces.length === 0) { - ctx.warn('getReceiversInfo: No person spaces found', { - accounts, - employeeCount: employees.length - }) - return [] - } + if (spaces.length === 0) return [] const socialIds: Pick[] = await control.findAll( ctx, @@ -620,55 +508,23 @@ export async function getReceiversInfo ( const spaceByPerson = new Map(spaces.map((it) => [it.person, it])) const socialIdsByEmployee = groupByArray(socialIds, (it) => it.attachedTo) - const result: ReceiverInfo[] = [] - const filteredOut: Array<{ account: AccountUuid, reason: string }> = [] + return accounts + .map((account) => { + const employee = employeeByAccount.get(account) + if (employee === undefined) return undefined + const space = spaceByPerson.get(employee._id) + if (space === undefined) return undefined - for (const account of accounts) { - const employee = employeeByAccount.get(account) - if (employee === undefined) { - filteredOut.push({ account, reason: 'no_active_employee' }) - continue - } - const space = spaceByPerson.get(employee._id) - if (space === undefined) { - filteredOut.push({ account, reason: 'no_person_space' }) - continue - } - - const employeeSocialIds = socialIdsByEmployee.get(employee._id)?.map((it) => it._id) ?? [] - if (employeeSocialIds.length === 0) { - ctx.warn('getReceiversInfo: Employee has no social identities', { + const info: ReceiverInfo = { + employee: employee._id, + role: employee.role, + space: space._id, account, - employeeId: employee._id, - personUuid: employee.personUuid - }) - } - - const info: ReceiverInfo = { - employee: employee._id, - role: employee.role, - space: space._id, - account, - socialIds: employeeSocialIds - } - result.push(info) - } - - if (filteredOut.length > 0) { - ctx.warn('getReceiversInfo: Some accounts were filtered out', { - filteredOut, - resultCount: result.length, - totalAccounts: accounts.length + socialIds: socialIdsByEmployee.get(employee._id)?.map((it) => it._id) ?? [] + } + return info }) - } - - ctx.info('getReceiversInfo: Completed', { - inputCount: accounts.length, - resultCount: result.length, - filteredOutCount: filteredOut.length - }) - - return result + .filter(notEmpty) } export async function getSenderInfo ( diff --git a/server-plugins/request-resources/src/index.ts b/server-plugins/request-resources/src/index.ts index 1d8ad09e60..253d5e36b5 100644 --- a/server-plugins/request-resources/src/index.ts +++ b/server-plugins/request-resources/src/index.ts @@ -153,53 +153,16 @@ async function getRequestNotificationTx ( ) const collaborators = await getCollaborators(control.ctx, request, control, tx, res) - ctx.info('getRequestNotificationTx: Collaborators retrieved', { - requestId: request._id, - docId: doc._id, - collaboratorCount: collaborators.length, - collaborators - }) - - if (collaborators.length === 0) { - ctx.warn('getRequestNotificationTx: No collaborators found, skipping notifications', { - requestId: request._id, - docId: doc._id - }) - return res - } + if (collaborators.length === 0) return res const notifyContexts = await control.findAll(control.ctx, notification.class.DocNotifyContext, { objectId: doc._id }) const receiverInfos = await getReceiversInfo(ctx, Array.from(collaborators), control) - - ctx.info('getRequestNotificationTx: Receiver info retrieved', { - requestId: request._id, - docId: doc._id, - collaboratorCount: collaborators.length, - receiverInfoCount: receiverInfos.length, - receivers: receiverInfos.map((r) => ({ - account: r.account, - employee: r.employee, - socialIdsCount: r.socialIds.length, - socialIds: r.socialIds - })) - }) - - if (receiverInfos.length === 0) { - ctx.warn('getRequestNotificationTx: No receiver info after filtering, skipping notifications', { - requestId: request._id, - docId: doc._id, - collaboratorCount: collaborators.length - }) - return res - } - const senderInfo = await getSenderInfo(ctx, tx.modifiedBy, control) const notificationControl = await getNotificationProviderControl(ctx, control) - let notificationTxCount = 0 for (const receiver of receiverInfos) { const txes = await getNotificationTxes( ctx, @@ -213,29 +176,9 @@ async function getRequestNotificationTx ( messages, notificationControl ) - notificationTxCount += txes.length res.push(...txes) - - if (txes.length === 0) { - ctx.warn('getRequestNotificationTx: No notification transactions generated for receiver', { - requestId: request._id, - docId: doc._id, - receiverAccount: receiver.account, - receiverEmployee: receiver.employee, - receiverSocialIds: receiver.socialIds - }) - } } - ctx.info('getRequestNotificationTx: Completed', { - requestId: request._id, - docId: doc._id, - collaboratorCount: collaborators.length, - receiverInfoCount: receiverInfos.length, - notificationTxCount, - totalTxCount: res.length - }) - return res }