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();
}