-
- {notification.actor?.name}
- {" "}
+ {notification.actor?.name ? (
+ <>
+
+ {notification.actor.name}
+ {" "}
+ >
+ ) : null}
{getNotificationMessage()}
diff --git a/apps/client/src/features/notification/types/notification.types.ts b/apps/client/src/features/notification/types/notification.types.ts
index 811805d0..aac6efb4 100644
--- a/apps/client/src/features/notification/types/notification.types.ts
+++ b/apps/client/src/features/notification/types/notification.types.ts
@@ -3,7 +3,12 @@ export type NotificationType =
| "comment.created"
| "comment.resolved"
| "page.user_mention"
- | "page.permission_granted";
+ | "page.permission_granted"
+ | "page.verification_expiring"
+ | "page.verification_expired"
+ | "page.verified"
+ | "page.approval_requested"
+ | "page.approval_rejected";
export type INotification = {
id: string;
diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx
index 2660b2ba..cd7fd918 100644
--- a/apps/client/src/features/page/components/header/page-header-menu.tsx
+++ b/apps/client/src/features/page/components/header/page-header-menu.tsx
@@ -40,6 +40,10 @@ import { PageStateSegmentedControl } from "@/features/user/components/page-state
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
import { PageShareModal } from "@/ee/page-permission";
+import {
+ PageVerificationMenuItem,
+ PageVerificationModal,
+} from "@/ee/page-verification";
interface PageHeaderMenuProps {
readOnly?: boolean;
@@ -121,6 +125,10 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
movePageModalOpened,
{ open: openMovePageModal, close: closeMoveSpaceModal },
] = useDisclosure(false);
+ const [
+ verificationOpened,
+ { open: openVerificationModal, close: closeVerificationModal },
+ ] = useDisclosure(false);
const [pageEditor] = useAtom(pageEditorAtom);
const pageUpdatedAt = useTimeAgo(page?.updatedAt);
@@ -200,6 +208,10 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
{t("Page history")}
+ {!readOnly && (
+
+ )}
+
{!readOnly && (
@@ -289,6 +301,12 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
onClose={closeMoveSpaceModal}
open={movePageModalOpened}
/>
+
+
>
);
}
diff --git a/apps/client/src/features/page/types/page.types.ts b/apps/client/src/features/page/types/page.types.ts
index 216a6105..572b076e 100644
--- a/apps/client/src/features/page/types/page.types.ts
+++ b/apps/client/src/features/page/types/page.types.ts
@@ -22,6 +22,7 @@ export interface IPage {
creator: ICreator;
lastUpdatedBy: ILastUpdatedBy;
deletedBy: IDeletedBy;
+ contributors?: IContributor[];
space: Partial
;
permissions?: {
canEdit: boolean;
@@ -29,6 +30,12 @@ export interface IPage {
};
}
+export interface IContributor {
+ id: string;
+ name: string;
+ avatarUrl: string;
+}
+
interface ICreator {
id: string;
name: string;
diff --git a/apps/client/src/features/websocket/types/types.ts b/apps/client/src/features/websocket/types/types.ts
index 1f20aca5..e925d0c1 100644
--- a/apps/client/src/features/websocket/types/types.ts
+++ b/apps/client/src/features/websocket/types/types.ts
@@ -85,6 +85,11 @@ export type RefetchRootTreeNodeEvent = {
spaceId: string;
};
+export type VerificationUpdatedEvent = {
+ operation: "verificationUpdated";
+ pageId: string;
+};
+
export type WebSocketEvent =
| InvalidateEvent
| CommentCreatedEvent
@@ -96,4 +101,5 @@ export type WebSocketEvent =
| AddTreeNodeEvent
| MoveTreeNodeEvent
| DeleteTreeNodeEvent
- | RefetchRootTreeNodeEvent;
+ | RefetchRootTreeNodeEvent
+ | VerificationUpdatedEvent;
diff --git a/apps/client/src/features/websocket/use-query-subscription.ts b/apps/client/src/features/websocket/use-query-subscription.ts
index c661f953..4bb11c5c 100644
--- a/apps/client/src/features/websocket/use-query-subscription.ts
+++ b/apps/client/src/features/websocket/use-query-subscription.ts
@@ -157,6 +157,11 @@ export const useQuerySubscription = () => {
});
break;
}
+ case "verificationUpdated":
+ queryClient.invalidateQueries({
+ queryKey: ["page-verification-info", data.pageId],
+ });
+ break;
}
});
}, [queryClient, socket]);
diff --git a/apps/client/src/pages/page/page.tsx b/apps/client/src/pages/page/page.tsx
index fc564b4b..f99b83cb 100644
--- a/apps/client/src/pages/page/page.tsx
+++ b/apps/client/src/pages/page/page.tsx
@@ -104,6 +104,8 @@ function PageContent({ pageSlug }: { pageSlug: string | undefined }) {
slugId={page.slugId}
spaceSlug={page?.space?.slug}
editable={canEdit}
+ creator={page.creator}
+ contributors={page.contributors}
/>
diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts
index fc1d2c8b..6280ee09 100644
--- a/apps/server/src/app.module.ts
+++ b/apps/server/src/app.module.ts
@@ -25,6 +25,7 @@ import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis';
import { LoggerModule } from './common/logger/logger.module';
import { ClsModule } from 'nestjs-cls';
+import { NoopAuditModule } from './integrations/audit/audit.module';
const enterpriseModules = [];
try {
@@ -47,6 +48,7 @@ try {
middleware: { mount: true },
}),
LoggerModule,
+ NoopAuditModule,
CoreModule,
DatabaseModule,
EnvironmentModule,
diff --git a/apps/server/src/common/events/audit-events.ts b/apps/server/src/common/events/audit-events.ts
index 144cb22d..955a6ba5 100644
--- a/apps/server/src/common/events/audit-events.ts
+++ b/apps/server/src/common/events/audit-events.ts
@@ -59,6 +59,14 @@ export const AuditEvent = {
PAGE_RESTRICTION_REMOVED: 'page.restriction_removed',
PAGE_PERMISSION_ADDED: 'page.permission_added',
PAGE_PERMISSION_REMOVED: 'page.permission_removed',
+ // Page verification
+ PAGE_VERIFICATION_CREATED: 'page.verification_created',
+ PAGE_VERIFICATION_UPDATED: 'page.verification_updated',
+ PAGE_VERIFICATION_REMOVED: 'page.verification_removed',
+ PAGE_VERIFIED: 'page.verified',
+ PAGE_APPROVAL_REQUESTED: 'page.approval_requested',
+ PAGE_APPROVAL_REJECTED: 'page.approval_rejected',
+ PAGE_MARKED_OBSOLETE: 'page.marked_obsolete',
// Share
SHARE_CREATED: 'share.created',
diff --git a/apps/server/src/core/core.module.ts b/apps/server/src/core/core.module.ts
index f336cf8c..81dfc138 100644
--- a/apps/server/src/core/core.module.ts
+++ b/apps/server/src/core/core.module.ts
@@ -20,10 +20,6 @@ import { AuditContextMiddleware } from '../common/middlewares/audit-context.midd
import { ShareModule } from './share/share.module';
import { NotificationModule } from './notification/notification.module';
import { WatcherModule } from './watcher/watcher.module';
-import {
- AUDIT_SERVICE,
- NoopAuditService,
-} from '../integrations/audit/audit.service';
import { ClsMiddleware } from 'nestjs-cls';
@Module({
@@ -43,13 +39,6 @@ import { ClsMiddleware } from 'nestjs-cls';
NotificationModule,
WatcherModule,
],
- providers: [
- {
- provide: AUDIT_SERVICE,
- useClass: NoopAuditService,
- },
- ],
- exports: [AUDIT_SERVICE],
})
export class CoreModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
diff --git a/apps/server/src/core/notification/notification.constants.ts b/apps/server/src/core/notification/notification.constants.ts
index 56d2ecad..935e5e0d 100644
--- a/apps/server/src/core/notification/notification.constants.ts
+++ b/apps/server/src/core/notification/notification.constants.ts
@@ -4,6 +4,11 @@ export const NotificationType = {
COMMENT_RESOLVED: 'comment.resolved',
PAGE_USER_MENTION: 'page.user_mention',
PAGE_PERMISSION_GRANTED: 'page.permission_granted',
+ PAGE_VERIFICATION_EXPIRING: 'page.verification_expiring',
+ PAGE_VERIFICATION_EXPIRED: 'page.verification_expired',
+ PAGE_VERIFIED: 'page.verified',
+ PAGE_APPROVAL_REQUESTED: 'page.approval_requested',
+ PAGE_APPROVAL_REJECTED: 'page.approval_rejected',
} as const;
export type NotificationType =
diff --git a/apps/server/src/core/notification/notification.module.ts b/apps/server/src/core/notification/notification.module.ts
index a142eaf8..0b35245f 100644
--- a/apps/server/src/core/notification/notification.module.ts
+++ b/apps/server/src/core/notification/notification.module.ts
@@ -4,6 +4,7 @@ import { NotificationController } from './notification.controller';
import { NotificationProcessor } from './notification.processor';
import { CommentNotificationService } from './services/comment.notification';
import { PageNotificationService } from './services/page.notification';
+import { VerificationNotificationService } from './services/verification.notification';
@Module({
imports: [],
@@ -13,6 +14,7 @@ import { PageNotificationService } from './services/page.notification';
NotificationProcessor,
CommentNotificationService,
PageNotificationService,
+ VerificationNotificationService,
],
exports: [NotificationService],
})
diff --git a/apps/server/src/core/notification/notification.processor.ts b/apps/server/src/core/notification/notification.processor.ts
index f7c8b577..8ca178d8 100644
--- a/apps/server/src/core/notification/notification.processor.ts
+++ b/apps/server/src/core/notification/notification.processor.ts
@@ -5,13 +5,19 @@ import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
import { QueueJob, QueueName } from '../../integrations/queue/constants';
import {
+ IApprovalRejectedNotificationJob,
+ IApprovalRequestedNotificationJob,
ICommentNotificationJob,
ICommentResolvedNotificationJob,
IPageMentionNotificationJob,
+ IPageVerifiedNotificationJob,
IPermissionGrantedNotificationJob,
+ IVerificationExpiringNotificationJob,
+ IVerificationExpiredNotificationJob,
} from '../../integrations/queue/constants/queue.interface';
import { CommentNotificationService } from './services/comment.notification';
import { PageNotificationService } from './services/page.notification';
+import { VerificationNotificationService } from './services/verification.notification';
import { DomainService } from '../../integrations/environment/domain.service';
@Processor(QueueName.NOTIFICATION_QUEUE)
@@ -24,6 +30,7 @@ export class NotificationProcessor
constructor(
private readonly commentNotificationService: CommentNotificationService,
private readonly pageNotificationService: PageNotificationService,
+ private readonly verificationNotificationService: VerificationNotificationService,
private readonly domainService: DomainService,
@InjectKysely() private readonly db: KyselyDB,
) {
@@ -35,7 +42,12 @@ export class NotificationProcessor
| ICommentNotificationJob
| ICommentResolvedNotificationJob
| IPageMentionNotificationJob
- | IPermissionGrantedNotificationJob,
+ | IPermissionGrantedNotificationJob
+ | IVerificationExpiringNotificationJob
+ | IVerificationExpiredNotificationJob
+ | IPageVerifiedNotificationJob
+ | IApprovalRequestedNotificationJob
+ | IApprovalRejectedNotificationJob,
void
>,
): Promise