mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat: refactor page-verification
This commit is contained in:
@@ -143,6 +143,18 @@ export function getPageId(documentName: string) {
|
||||
return documentName.split('.')[1];
|
||||
}
|
||||
|
||||
export function isEmptyParagraphDoc(tiptapJson: JSONContent): boolean {
|
||||
if (!tiptapJson || tiptapJson.type !== 'doc') return false;
|
||||
const content = tiptapJson.content;
|
||||
if (!Array.isArray(content) || content.length !== 1) return false;
|
||||
const child = content[0];
|
||||
if (!child || child.type !== 'paragraph') return false;
|
||||
return (
|
||||
!child.content ||
|
||||
(Array.isArray(child.content) && child.content.length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
function stripUnknownNodes(
|
||||
json: JSONContent,
|
||||
schema: Schema,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
import { isDeepStrictEqual } from 'node:util';
|
||||
import { CollabHistoryService } from '../services/collab-history.service';
|
||||
import { WatcherService } from '../../core/watcher/watcher.service';
|
||||
import { isEmptyParagraphDoc } from '../collaboration.util';
|
||||
|
||||
@Processor(QueueName.HISTORY_QUEUE)
|
||||
export class HistoryProcessor extends WorkerHost implements OnModuleDestroy {
|
||||
@@ -55,6 +56,14 @@ export class HistoryProcessor extends WorkerHost implements OnModuleDestroy {
|
||||
{ includeContent: true },
|
||||
);
|
||||
|
||||
if (!lastHistory && isEmptyParagraphDoc(page.content)) {
|
||||
this.logger.debug(
|
||||
`Skipping first history for page ${pageId}: empty content`,
|
||||
);
|
||||
await this.collabHistory.clearContributors(pageId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!lastHistory ||
|
||||
!isDeepStrictEqual(lastHistory.content, page.content)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Logger, OnModuleDestroy } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { OnWorkerEvent, Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Job } from 'bullmq';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
IPermissionGrantedNotificationJob,
|
||||
IVerificationExpiringNotificationJob,
|
||||
IVerificationExpiredNotificationJob,
|
||||
IVerificationReconcileJob,
|
||||
} from '../../integrations/queue/constants/queue.interface';
|
||||
import { CommentNotificationService } from './services/comment.notification';
|
||||
import { PageNotificationService } from './services/page.notification';
|
||||
@@ -33,6 +35,7 @@ export class NotificationProcessor
|
||||
private readonly pageNotificationService: PageNotificationService,
|
||||
private readonly verificationNotificationService: VerificationNotificationService,
|
||||
private readonly domainService: DomainService,
|
||||
private readonly moduleRef: ModuleRef,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
) {
|
||||
super();
|
||||
@@ -47,6 +50,7 @@ export class NotificationProcessor
|
||||
| IPermissionGrantedNotificationJob
|
||||
| IVerificationExpiringNotificationJob
|
||||
| IVerificationExpiredNotificationJob
|
||||
| IVerificationReconcileJob
|
||||
| IPageVerifiedNotificationJob
|
||||
| IApprovalRequestedNotificationJob
|
||||
| IApprovalRejectedNotificationJob,
|
||||
@@ -54,7 +58,12 @@ export class NotificationProcessor
|
||||
>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const workspaceId = (job.data as { workspaceId: string }).workspaceId;
|
||||
if (job.name === QueueJob.VERIFICATION_RECONCILE) {
|
||||
await this.runVerificationReconcile();
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceId = await this.resolveWorkspaceId(job);
|
||||
const appUrl = await this.getWorkspaceUrl(workspaceId);
|
||||
|
||||
switch (job.name) {
|
||||
@@ -153,6 +162,49 @@ export class NotificationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveWorkspaceId(job: Job): Promise<string> {
|
||||
if (
|
||||
job.name === QueueJob.PAGE_VERIFICATION_EXPIRING ||
|
||||
job.name === QueueJob.PAGE_VERIFICATION_EXPIRED
|
||||
) {
|
||||
const { verificationId } = job.data as { verificationId: string };
|
||||
const row = await this.db
|
||||
.selectFrom('pageVerifications')
|
||||
.select('workspaceId')
|
||||
.where('id', '=', verificationId)
|
||||
.executeTakeFirst();
|
||||
return row?.workspaceId ?? '';
|
||||
}
|
||||
return (job.data as { workspaceId: string }).workspaceId;
|
||||
}
|
||||
|
||||
private async runVerificationReconcile(): Promise<void> {
|
||||
let eeModule: { PageVerificationSchedulerService?: unknown };
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
eeModule = require('../../ee/page-verification/page-verification-scheduler.service');
|
||||
} catch {
|
||||
this.logger.debug(
|
||||
'VERIFICATION_RECONCILE fired but EE scheduler not bundled in this build',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const schedulerClass = eeModule.PageVerificationSchedulerService as
|
||||
| (new (...args: unknown[]) => { reconcile(): Promise<void> })
|
||||
| undefined;
|
||||
if (!schedulerClass) return;
|
||||
|
||||
const scheduler = this.moduleRef.get(schedulerClass, { strict: false });
|
||||
if (!scheduler) {
|
||||
this.logger.warn(
|
||||
'VERIFICATION_RECONCILE fired but scheduler service not resolvable',
|
||||
);
|
||||
return;
|
||||
}
|
||||
await scheduler.reconcile();
|
||||
}
|
||||
|
||||
private async getWorkspaceUrl(workspaceId: string): Promise<string> {
|
||||
const workspace = await this.db
|
||||
.selectFrom('workspaces')
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 {
|
||||
@@ -16,11 +17,108 @@ 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<string[]> {
|
||||
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<string[]> {
|
||||
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')
|
||||
@@ -73,19 +171,34 @@ export class NotificationService {
|
||||
}
|
||||
|
||||
async getUnreadCount(userId: string) {
|
||||
return this.notificationRepo.getUnreadCount(userId);
|
||||
const accessibleIds =
|
||||
await this.listUnreadAccessibleNotificationIds(userId);
|
||||
return accessibleIds.length;
|
||||
}
|
||||
|
||||
async markAsRead(notificationId: string, userId: string) {
|
||||
return this.notificationRepo.markAsRead(notificationId, userId);
|
||||
const accessibleIds = await this.filterAccessibleNotificationIds(
|
||||
[notificationId],
|
||||
userId,
|
||||
);
|
||||
if (accessibleIds.length === 0) return;
|
||||
return this.notificationRepo.markAsRead(accessibleIds[0], userId);
|
||||
}
|
||||
|
||||
async markMultipleAsRead(notificationIds: string[], userId: string) {
|
||||
return this.notificationRepo.markMultipleAsRead(notificationIds, userId);
|
||||
const accessibleIds = await this.filterAccessibleNotificationIds(
|
||||
notificationIds,
|
||||
userId,
|
||||
);
|
||||
if (accessibleIds.length === 0) return;
|
||||
return this.notificationRepo.markMultipleAsRead(accessibleIds, userId);
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: string) {
|
||||
return this.notificationRepo.markAllAsRead(userId);
|
||||
const accessibleIds =
|
||||
await this.listUnreadAccessibleNotificationIds(userId);
|
||||
if (accessibleIds.length === 0) return;
|
||||
return this.notificationRepo.markMultipleAsRead(accessibleIds, userId);
|
||||
}
|
||||
|
||||
async queueEmail(
|
||||
|
||||
@@ -15,34 +15,83 @@ import { VerificationExpiredEmail } from '@docmost/transactional/emails/verifica
|
||||
import { ApprovalRequestedEmail } from '@docmost/transactional/emails/approval-requested-email';
|
||||
import { ApprovalRejectedEmail } from '@docmost/transactional/emails/approval-rejected-email';
|
||||
import { getPageTitle } from '../../../common/helpers';
|
||||
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||
import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo';
|
||||
|
||||
@Injectable()
|
||||
export class VerificationNotificationService {
|
||||
constructor(
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||
private readonly pagePermissionRepo: PagePermissionRepo,
|
||||
) {}
|
||||
|
||||
private async filterAccessibleRecipients(
|
||||
userIds: string[],
|
||||
pageId: string,
|
||||
spaceId: string,
|
||||
): Promise<string[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
const inSpace = await this.spaceMemberRepo.getUserIdsWithSpaceAccess(
|
||||
userIds,
|
||||
spaceId,
|
||||
);
|
||||
if (inSpace.size === 0) return [];
|
||||
return this.pagePermissionRepo.getUserIdsWithPageAccess(pageId, [
|
||||
...inSpace,
|
||||
]);
|
||||
}
|
||||
|
||||
async processVerificationExpiring(
|
||||
data: IVerificationExpiringNotificationJob,
|
||||
appUrl: string,
|
||||
) {
|
||||
const { verifierIds, pageId, spaceId, workspaceId, expiresAt } = data;
|
||||
const verification = await this.db
|
||||
.selectFrom('pageVerifications')
|
||||
.selectAll()
|
||||
.where('id', '=', data.verificationId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!verification) return;
|
||||
if (verification.type !== 'expiring') return;
|
||||
if (!verification.expiresAt) return;
|
||||
const expiresAtMs = new Date(verification.expiresAt).getTime();
|
||||
if (expiresAtMs <= Date.now()) return;
|
||||
|
||||
const verifierRows = await this.db
|
||||
.selectFrom('pageVerifiers')
|
||||
.select('userId')
|
||||
.where('pageVerificationId', '=', verification.id)
|
||||
.execute();
|
||||
const verifierIds = verifierRows.map((r) => r.userId);
|
||||
if (verifierIds.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(pageId, spaceId, appUrl);
|
||||
const accessibleVerifierIds = await this.filterAccessibleRecipients(
|
||||
verifierIds,
|
||||
verification.pageId,
|
||||
verification.spaceId,
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(
|
||||
verification.pageId,
|
||||
verification.spaceId,
|
||||
appUrl,
|
||||
);
|
||||
if (!context) return;
|
||||
|
||||
const { pageTitle, basePageUrl } = context;
|
||||
const expiresAtIso = new Date(verification.expiresAt).toISOString();
|
||||
|
||||
for (const userId of verifierIds) {
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
const notification = await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId,
|
||||
workspaceId: verification.workspaceId,
|
||||
type: NotificationType.PAGE_VERIFICATION_EXPIRING,
|
||||
pageId,
|
||||
spaceId,
|
||||
data: { expiresAt },
|
||||
pageId: verification.pageId,
|
||||
spaceId: verification.spaceId,
|
||||
data: { expiresAt: expiresAtIso },
|
||||
});
|
||||
|
||||
const subject = `"${pageTitle}" needs to be re-verified soon`;
|
||||
@@ -54,7 +103,7 @@ export class VerificationNotificationService {
|
||||
VerificationExpiringEmail({
|
||||
pageTitle,
|
||||
pageUrl: basePageUrl,
|
||||
expiresAt: new Date(expiresAt).toLocaleDateString(),
|
||||
expiresAt: new Date(verification.expiresAt).toLocaleDateString(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -64,21 +113,44 @@ export class VerificationNotificationService {
|
||||
data: IVerificationExpiredNotificationJob,
|
||||
appUrl: string,
|
||||
) {
|
||||
const { verifierIds, pageId, spaceId, workspaceId } = data;
|
||||
const v = await this.db
|
||||
.selectFrom('pageVerifications')
|
||||
.selectAll()
|
||||
.where('id', '=', data.verificationId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!v) return;
|
||||
if (v.type !== 'expiring') return;
|
||||
if (!v.expiresAt) return;
|
||||
if (new Date(v.expiresAt).getTime() > Date.now()) return;
|
||||
|
||||
const verifierRows = await this.db
|
||||
.selectFrom('pageVerifiers')
|
||||
.select('userId')
|
||||
.where('pageVerificationId', '=', v.id)
|
||||
.execute();
|
||||
const verifierIds = verifierRows.map((r) => r.userId);
|
||||
if (verifierIds.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(pageId, spaceId, appUrl);
|
||||
const accessibleVerifierIds = await this.filterAccessibleRecipients(
|
||||
verifierIds,
|
||||
v.pageId,
|
||||
v.spaceId,
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(v.pageId, v.spaceId, appUrl);
|
||||
if (!context) return;
|
||||
|
||||
const { pageTitle, basePageUrl } = context;
|
||||
|
||||
for (const userId of verifierIds) {
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
const notification = await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId,
|
||||
workspaceId: v.workspaceId,
|
||||
type: NotificationType.PAGE_VERIFICATION_EXPIRED,
|
||||
pageId,
|
||||
spaceId,
|
||||
pageId: v.pageId,
|
||||
spaceId: v.spaceId,
|
||||
});
|
||||
|
||||
const subject = `"${pageTitle}" verification has expired`;
|
||||
@@ -99,7 +171,14 @@ export class VerificationNotificationService {
|
||||
const { verifierIds, pageId, spaceId, workspaceId, actorId } = data;
|
||||
if (verifierIds.length === 0) return;
|
||||
|
||||
for (const userId of verifierIds) {
|
||||
const accessibleVerifierIds = await this.filterAccessibleRecipients(
|
||||
verifierIds,
|
||||
pageId,
|
||||
spaceId,
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId,
|
||||
@@ -118,13 +197,20 @@ export class VerificationNotificationService {
|
||||
const { verifierIds, pageId, spaceId, workspaceId, actorId } = data;
|
||||
if (verifierIds.length === 0) return;
|
||||
|
||||
const accessibleVerifierIds = await this.filterAccessibleRecipients(
|
||||
verifierIds,
|
||||
pageId,
|
||||
spaceId,
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(pageId, spaceId, appUrl);
|
||||
if (!context) return;
|
||||
|
||||
const { pageTitle, basePageUrl } = context;
|
||||
const actorName = await this.getUserName(actorId);
|
||||
|
||||
for (const userId of verifierIds) {
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
const notification = await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId,
|
||||
@@ -156,6 +242,13 @@ export class VerificationNotificationService {
|
||||
const { pageId, spaceId, workspaceId, actorId, requestedById, comment } =
|
||||
data;
|
||||
|
||||
const recipients = await this.filterAccessibleRecipients(
|
||||
[requestedById],
|
||||
pageId,
|
||||
spaceId,
|
||||
);
|
||||
if (recipients.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(pageId, spaceId, appUrl);
|
||||
if (!context) return;
|
||||
|
||||
|
||||
@@ -452,6 +452,20 @@ export class PageService {
|
||||
.where('pageId', 'in', pageIdsToMove)
|
||||
.execute();
|
||||
|
||||
// Update page verifications
|
||||
await trx
|
||||
.updateTable('pageVerifications')
|
||||
.set({ spaceId: spaceId })
|
||||
.where('pageId', 'in', pageIdsToMove)
|
||||
.execute();
|
||||
|
||||
// Update notifications — access follows the page after a move
|
||||
await trx
|
||||
.updateTable('notifications')
|
||||
.set({ spaceId: spaceId })
|
||||
.where('pageId', 'in', pageIdsToMove)
|
||||
.execute();
|
||||
|
||||
// Update attachments
|
||||
await this.attachmentRepo.updateAttachmentsByPageId(
|
||||
{ spaceId },
|
||||
|
||||
@@ -15,9 +15,7 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
.addColumn('space_id', 'uuid', (col) =>
|
||||
col.notNull().references('spaces.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('type', 'varchar', (col) =>
|
||||
col.notNull().defaultTo('expiring'),
|
||||
)
|
||||
.addColumn('type', 'varchar', (col) => col.notNull().defaultTo('expiring'))
|
||||
.addColumn('status', 'varchar')
|
||||
.addColumn('mode', 'varchar')
|
||||
.addColumn('period_amount', 'integer')
|
||||
@@ -27,7 +25,6 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('expires_at', 'timestamptz')
|
||||
.addColumn('notified_at', 'timestamptz')
|
||||
.addColumn('requested_at', 'timestamptz')
|
||||
.addColumn('requested_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
@@ -55,17 +52,12 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('page_verification_id', 'uuid', (col) =>
|
||||
col
|
||||
.notNull()
|
||||
.references('page_verifications.id')
|
||||
.onDelete('cascade'),
|
||||
col.notNull().references('page_verifications.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('user_id', 'uuid', (col) =>
|
||||
col.notNull().references('users.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('is_primary', 'boolean', (col) =>
|
||||
col.notNull().defaultTo(false),
|
||||
)
|
||||
.addColumn('is_primary', 'boolean', (col) => col.notNull().defaultTo(false))
|
||||
.addColumn('added_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
@@ -80,12 +72,29 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_page_verifications_expires_at')
|
||||
.ifNotExists()
|
||||
.on('page_verifications')
|
||||
.column('expires_at')
|
||||
.where('expires_at', 'is not', null)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_page_verifications_workspace_id_id')
|
||||
.ifNotExists()
|
||||
.on('page_verifications')
|
||||
.columns(['workspace_id', 'id desc'])
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_page_verifications_space_id')
|
||||
.ifNotExists()
|
||||
.on('page_verifications')
|
||||
.column('space_id')
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_page_verifiers_user_id')
|
||||
.ifNotExists()
|
||||
.on('page_verifiers')
|
||||
.column('user_id')
|
||||
.execute();
|
||||
|
||||
-1
@@ -443,7 +443,6 @@ export interface PageVerifications {
|
||||
verifiedAt: Timestamp | null;
|
||||
verifiedById: string | null;
|
||||
expiresAt: Timestamp | null;
|
||||
notifiedAt: Timestamp | null;
|
||||
requestedAt: Timestamp | null;
|
||||
requestedById: string | null;
|
||||
rejectedAt: Timestamp | null;
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: e134a4ae98...5d08248017
@@ -73,6 +73,7 @@ export enum QueueJob {
|
||||
PAGE_UPDATE_DIGEST = 'page-update-digest',
|
||||
PAGE_VERIFICATION_EXPIRING = 'page-verification-expiring',
|
||||
PAGE_VERIFICATION_EXPIRED = 'page-verification-expired',
|
||||
VERIFICATION_RECONCILE = 'verification-reconcile',
|
||||
PAGE_VERIFIED_NOTIFICATION = 'page-verified-notification',
|
||||
PAGE_APPROVAL_REQUESTED_NOTIFICATION = 'page-approval-requested-notification',
|
||||
PAGE_APPROVAL_REJECTED_NOTIFICATION = 'page-approval-rejected-notification',
|
||||
|
||||
@@ -79,20 +79,14 @@ export interface IPermissionGrantedNotificationJob {
|
||||
|
||||
export interface IVerificationExpiringNotificationJob {
|
||||
verificationId: string;
|
||||
pageId: string;
|
||||
spaceId: string;
|
||||
workspaceId: string;
|
||||
verifierIds: string[];
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export interface IVerificationExpiredNotificationJob {
|
||||
verificationId: string;
|
||||
pageId: string;
|
||||
spaceId: string;
|
||||
workspaceId: string;
|
||||
verifierIds: string[];
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export interface IVerificationReconcileJob {
|
||||
// no payload
|
||||
}
|
||||
|
||||
export interface IPageVerifiedNotificationJob {
|
||||
|
||||
Reference in New Issue
Block a user