diff --git a/apps/client/src/ee/licence/components/oss-details.tsx b/apps/client/src/ee/licence/components/oss-details.tsx index 1d02daf4..23be9f1a 100644 --- a/apps/client/src/ee/licence/components/oss-details.tsx +++ b/apps/client/src/ee/licence/components/oss-details.tsx @@ -2,14 +2,15 @@ import { Group, List, Stack, Table, Text, ThemeIcon } from "@mantine/core"; import { IconCheck } from "@tabler/icons-react"; const enterpriseFeatures = [ - "SSO (SAML, OIDC, LDAP)", - "AI Integration (Search & Assistant)", - "Page-level Permissions", - "Audit Logs", - "API Keys", + "AI Integration (Chat, Search & Assistant)", "MCP Support", + "SSO (SAML, OIDC, LDAP)", "Multi-factor Authentication (2FA)", + "Page-level Permissions", + "Page verification & approval workflow", + "Audit Logs", "Enterprise Controls", + "API Keys", "Advanced Search Engine Support", "Full-text Search in Attachments (PDF, DOCX)", "Resolve Comments", @@ -68,11 +69,31 @@ export default function OssDetails() { - Get an enterprise trial key at customers.docmost.com. + Get an enterprise trial key at{" "} + + customers.docmost.com + + . - Visit docmost.com/pricing to purchase an enterprise license. + Visit{" "} + + docmost.com/pricing + {" "} + to purchase an enterprise license. + + + For inquiries, contact{" "} + sales@docmost.com diff --git a/apps/server/src/core/notification/notification.service.ts b/apps/server/src/core/notification/notification.service.ts index 2ae8e777..1f88bf59 100644 --- a/apps/server/src/core/notification/notification.service.ts +++ b/apps/server/src/core/notification/notification.service.ts @@ -8,7 +8,6 @@ import { WsGateway } from '../../ws/ws.gateway'; import { MailService } from '../../integrations/mail/mail.service'; import { NotificationTab, NotificationType, NotificationTypeToSettingKey } from './notification.constants'; import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo'; -import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; @Injectable() export class NotificationService { @@ -17,108 +16,11 @@ export class NotificationService { constructor( private readonly notificationRepo: NotificationRepo, private readonly pagePermissionRepo: PagePermissionRepo, - private readonly spaceMemberRepo: SpaceMemberRepo, private readonly wsGateway: WsGateway, private readonly mailService: MailService, @InjectKysely() private readonly db: KyselyDB, ) {} - /** - * Returns the subset of `ids` pointing to notifications the user can - * currently see. Enforces the same dual gate as `findByUserId`: - * 1. `spaceId IS NULL` or user is a current member of `spaceId`. - * 2. `pageId IS NULL` or user has page-level access to `pageId`. - * - * Returning an empty array when `ids` is empty is a shortcut callers use to - * make the mark/count paths no-ops. - */ - private async filterAccessibleNotificationIds( - ids: string[], - userId: string, - ): Promise { - if (ids.length === 0) return []; - - const rows = await this.db - .selectFrom('notifications') - .select(['id', 'pageId']) - .where('id', 'in', ids) - .where('userId', '=', userId) - .where((eb) => - eb.or([ - eb('spaceId', 'is', null), - eb( - 'spaceId', - 'in', - this.spaceMemberRepo.getUserSpaceIdsQuery(userId), - ), - ]), - ) - .execute(); - - if (rows.length === 0) return []; - - const pageIds = rows - .map((r) => r.pageId) - .filter((p): p is string => !!p); - - if (pageIds.length === 0) { - return rows.map((r) => r.id); - } - - const accessiblePageIds = - await this.pagePermissionRepo.filterAccessiblePageIds({ - pageIds, - userId, - }); - const accessibleSet = new Set(accessiblePageIds); - - return rows - .filter((r) => !r.pageId || accessibleSet.has(r.pageId)) - .map((r) => r.id); - } - - private async listUnreadAccessibleNotificationIds( - userId: string, - ): Promise { - const rows = await this.db - .selectFrom('notifications') - .select(['id', 'pageId']) - .where('userId', '=', userId) - .where('readAt', 'is', null) - .where((eb) => - eb.or([ - eb('spaceId', 'is', null), - eb( - 'spaceId', - 'in', - this.spaceMemberRepo.getUserSpaceIdsQuery(userId), - ), - ]), - ) - .execute(); - - if (rows.length === 0) return []; - - const pageIds = rows - .map((r) => r.pageId) - .filter((p): p is string => !!p); - - if (pageIds.length === 0) { - return rows.map((r) => r.id); - } - - const accessiblePageIds = - await this.pagePermissionRepo.filterAccessiblePageIds({ - pageIds, - userId, - }); - const accessibleSet = new Set(accessiblePageIds); - - return rows - .filter((r) => !r.pageId || accessibleSet.has(r.pageId)) - .map((r) => r.id); - } - async create(data: InsertableNotification) { const user = await this.db .selectFrom('users') @@ -171,34 +73,19 @@ export class NotificationService { } async getUnreadCount(userId: string) { - const accessibleIds = - await this.listUnreadAccessibleNotificationIds(userId); - return accessibleIds.length; + return this.notificationRepo.getUnreadCount(userId); } async markAsRead(notificationId: string, userId: string) { - const accessibleIds = await this.filterAccessibleNotificationIds( - [notificationId], - userId, - ); - if (accessibleIds.length === 0) return; - return this.notificationRepo.markAsRead(accessibleIds[0], userId); + return this.notificationRepo.markAsRead(notificationId, userId); } async markMultipleAsRead(notificationIds: string[], userId: string) { - const accessibleIds = await this.filterAccessibleNotificationIds( - notificationIds, - userId, - ); - if (accessibleIds.length === 0) return; - return this.notificationRepo.markMultipleAsRead(accessibleIds, userId); + return this.notificationRepo.markMultipleAsRead(notificationIds, userId); } async markAllAsRead(userId: string) { - const accessibleIds = - await this.listUnreadAccessibleNotificationIds(userId); - if (accessibleIds.length === 0) return; - return this.notificationRepo.markMultipleAsRead(accessibleIds, userId); + return this.notificationRepo.markAllAsRead(userId); } async queueEmail( diff --git a/apps/server/src/database/repos/notification/notification.repo.ts b/apps/server/src/database/repos/notification/notification.repo.ts index 2914dbfc..9abca423 100644 --- a/apps/server/src/database/repos/notification/notification.repo.ts +++ b/apps/server/src/database/repos/notification/notification.repo.ts @@ -11,7 +11,10 @@ import { ExpressionBuilder } from 'kysely'; import { DB } from '@docmost/db/types/db'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; -import { NotificationTab, NotificationType } from '../../../core/notification/notification.constants'; +import { + NotificationTab, + NotificationType, +} from '../../../core/notification/notification.constants'; @Injectable() export class NotificationRepo { @@ -43,7 +46,11 @@ export class NotificationRepo { .where((eb) => eb.or([ eb('spaceId', 'is', null), - eb('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)), + eb( + 'spaceId', + 'in', + this.spaceMemberRepo.getUserSpaceIdsQuery(userId), + ), ]), ); @@ -62,6 +69,14 @@ export class NotificationRepo { }); } + async insert(notification: InsertableNotification): Promise { + return this.db + .insertInto('notifications') + .values(notification) + .returningAll() + .executeTakeFirst(); + } + async getUnreadCount(userId: string): Promise { const result = await this.db .selectFrom('notifications') @@ -71,7 +86,11 @@ export class NotificationRepo { .where((eb) => eb.or([ eb('spaceId', 'is', null), - eb('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)), + eb( + 'spaceId', + 'in', + this.spaceMemberRepo.getUserSpaceIdsQuery(userId), + ), ]), ) .executeTakeFirst(); @@ -79,14 +98,6 @@ export class NotificationRepo { return Number(result?.count ?? 0); } - async insert(notification: InsertableNotification): Promise { - return this.db - .insertInto('notifications') - .values(notification) - .returningAll() - .executeTakeFirst(); - } - async markAsRead(notificationId: string, userId: string): Promise { await this.db .updateTable('notifications') @@ -94,12 +105,6 @@ export class NotificationRepo { .where('id', '=', notificationId) .where('userId', '=', userId) .where('readAt', 'is', null) - .where((eb) => - eb.or([ - eb('spaceId', 'is', null), - eb('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)), - ]), - ) .execute(); } @@ -116,21 +121,6 @@ export class NotificationRepo { .where('id', 'in', notificationIds) .where('userId', '=', userId) .where('readAt', 'is', null) - .where((eb) => - eb.or([ - eb('spaceId', 'is', null), - eb('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)), - ]), - ) - .execute(); - } - - async markAsEmailed(notificationId: string): Promise { - await this.db - .updateTable('notifications') - .set({ emailedAt: new Date() }) - .where('id', '=', notificationId) - .where('emailedAt', 'is', null) .execute(); } @@ -140,12 +130,15 @@ export class NotificationRepo { .set({ readAt: new Date() }) .where('userId', '=', userId) .where('readAt', 'is', null) - .where((eb) => - eb.or([ - eb('spaceId', 'is', null), - eb('spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)), - ]), - ) + .execute(); + } + + async markAsEmailed(notificationId: string): Promise { + await this.db + .updateTable('notifications') + .set({ emailedAt: new Date() }) + .where('id', '=', notificationId) + .where('emailedAt', 'is', null) .execute(); }