From 0da0d6e80afaf583b2003179dc7b92164b7e41ef Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 24 Sep 2024 22:12:00 +0400 Subject: [PATCH] Add notifications middleware to do not send updates for all users (#6714) Signed-off-by: Kristina Fefelova --- models/notification/src/index.ts | 12 +-- plugins/notification/src/index.ts | 5 ++ server-plugins/notification/src/index.ts | 1 + server/middleware/package.json | 1 + server/middleware/src/index.ts | 1 + server/middleware/src/notifications.ts | 97 ++++++++++++++++++++++++ server/server-pipeline/src/pipeline.ts | 4 +- 7 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 server/middleware/src/notifications.ts diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 07a35e3fde..90a018a7aa 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -27,7 +27,6 @@ import { type Data, type Doc, type DocumentQuery, - type Domain, type IndexingConfiguration, type Markup, type Ref, @@ -56,6 +55,9 @@ import view, { createAction, template } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import { notificationId, + DOMAIN_USER_NOTIFY, + DOMAIN_NOTIFICATION, + DOMAIN_DOC_NOTIFY, type ActivityInboxNotification, type ActivityNotificationViewlet, type BaseNotificationType, @@ -86,16 +88,10 @@ import { type AnyComponent, type Location } from '@hcengineering/ui/src/types' import notification from './plugin' -export { notificationId } from '@hcengineering/notification' +export { notificationId, DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY } from '@hcengineering/notification' export { notificationOperation } from './migration' export { notification as default } -export const DOMAIN_NOTIFICATION = 'notification' as Domain - -export const DOMAIN_DOC_NOTIFY = 'notification-dnc' as Domain - -export const DOMAIN_USER_NOTIFY = 'notification-user' as Domain - @Model(notification.class.BrowserNotification, core.class.Doc, DOMAIN_USER_NOTIFY) export class TBrowserNotification extends TDoc implements BrowserNotification { senderId?: Ref | undefined diff --git a/plugins/notification/src/index.ts b/plugins/notification/src/index.ts index c7ff56340d..93fb7536c6 100644 --- a/plugins/notification/src/index.ts +++ b/plugins/notification/src/index.ts @@ -20,6 +20,7 @@ import { Class, Doc, DocumentQuery, + type Domain, IdMap, Markup, Mixin, @@ -42,6 +43,10 @@ import { Readable, Writable } from './types' export * from './types' +export const DOMAIN_NOTIFICATION = 'notification' as Domain +export const DOMAIN_DOC_NOTIFY = 'notification-dnc' as Domain +export const DOMAIN_USER_NOTIFY = 'notification-user' as Domain + /** * @public */ diff --git a/server-plugins/notification/src/index.ts b/server-plugins/notification/src/index.ts index c47eed36dd..36880d26ff 100644 --- a/server-plugins/notification/src/index.ts +++ b/server-plugins/notification/src/index.ts @@ -31,6 +31,7 @@ import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core' * @public */ export const serverNotificationId = 'server-notification' as Plugin +export { DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY } from '@hcengineering/notification' /** * @public diff --git a/server/middleware/package.json b/server/middleware/package.json index 5dbc9fa98d..9ef285c7b6 100644 --- a/server/middleware/package.json +++ b/server/middleware/package.json @@ -41,6 +41,7 @@ "@hcengineering/platform": "^0.6.11", "@hcengineering/server-core": "^0.6.1", "@hcengineering/server-preference": "^0.6.0", + "@hcengineering/server-notification": "^0.6.1", "@hcengineering/query": "^0.6.12", "fast-equals": "^5.0.1" } diff --git a/server/middleware/src/index.ts b/server/middleware/src/index.ts index 8c79b92a3e..512a0cd383 100644 --- a/server/middleware/src/index.ts +++ b/server/middleware/src/index.ts @@ -33,3 +33,4 @@ export * from './spacePermissions' export * from './spaceSecurity' export * from './triggers' export * from './txPush' +export * from './notifications' diff --git a/server/middleware/src/notifications.ts b/server/middleware/src/notifications.ts new file mode 100644 index 0000000000..dae7a108fc --- /dev/null +++ b/server/middleware/src/notifications.ts @@ -0,0 +1,97 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import core, { + Doc, + MeasureContext, + Tx, + TxCUD, + TxProcessor, + systemAccountEmail, + type SessionData, + TxApplyIf +} from '@hcengineering/core' +import platform, { PlatformError, Severity, Status } from '@hcengineering/platform' +import { BaseMiddleware, Middleware, TxMiddlewareResult, type PipelineContext } from '@hcengineering/server-core' +import { DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY } from '@hcengineering/server-notification' + +/** + * @public + */ +export class NotificationsMiddleware extends BaseMiddleware implements Middleware { + private readonly targetDomains = [DOMAIN_USER_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_DOC_NOTIFY] + + private constructor (context: PipelineContext, next?: Middleware) { + super(context, next) + } + + static async create ( + ctx: MeasureContext, + context: PipelineContext, + next: Middleware | undefined + ): Promise { + return new NotificationsMiddleware(context, next) + } + + isTargetDomain (tx: Tx): boolean { + if (TxProcessor.isExtendsCUD(tx._class)) { + const txCUD = tx as TxCUD + const domain = this.context.hierarchy.getDomain(txCUD.objectClass) + return this.targetDomains.includes(domain) + } + return false + } + + processTx (ctx: MeasureContext, tx: Tx): void { + let target: string[] | undefined + if (this.isTargetDomain(tx)) { + const account = ctx.contextData.account._id + if (account !== tx.modifiedBy && account !== core.account.System) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {})) + } + const modifiedByAccount = ctx.contextData.getAccount(tx.modifiedBy) + target = [ctx.contextData.userEmail, systemAccountEmail] + if (modifiedByAccount !== undefined && !target.includes(modifiedByAccount.email)) { + target.push(modifiedByAccount.email) + } + ctx.contextData.broadcast.targets['checkDomain' + account] = (tx) => { + if (this.isTargetDomain(tx)) { + return target + } + } + } + } + + tx (ctx: MeasureContext, txes: Tx[]): Promise { + for (const tx of txes) { + if (this.context.hierarchy.isDerived(tx._class, core.class.TxApplyIf)) { + for (const ttx of (tx as TxApplyIf).txes) { + this.processTx(ctx, ttx) + } + } else { + this.processTx(ctx, tx) + } + } + + return this.provideTx(ctx, txes) + } + + isAvailable (ctx: MeasureContext, doc: Doc): boolean { + const domain = this.context.hierarchy.getDomain(doc._class) + if (!this.targetDomains.includes(domain)) return true + const account = ctx.contextData.account._id + return doc.createdBy === account || account === core.account.System + } +} diff --git a/server/server-pipeline/src/pipeline.ts b/server/server-pipeline/src/pipeline.ts index 52c62fe1c5..2b060de2f4 100644 --- a/server/server-pipeline/src/pipeline.ts +++ b/server/server-pipeline/src/pipeline.ts @@ -33,7 +33,8 @@ import { SpacePermissionsMiddleware, SpaceSecurityMiddleware, TriggersMiddleware, - TxMiddleware + TxMiddleware, + NotificationsMiddleware } from '@hcengineering/middleware' import { createMongoAdapter, createMongoTxAdapter } from '@hcengineering/mongo' import { createPostgresAdapter, createPostgresTxAdapter } from '@hcengineering/postgres' @@ -121,6 +122,7 @@ export function createServerPipeline ( LookupMiddleware.create, ModifiedMiddleware.create, PrivateMiddleware.create, + NotificationsMiddleware.create, (ctx: MeasureContext, context: PipelineContext, next?: Middleware) => SpaceSecurityMiddleware.create(opt.adapterSecurity ?? false, ctx, context, next), SpacePermissionsMiddleware.create,