mirror of
https://github.com/docmost/docmost.git
synced 2026-05-15 21:24:09 +08:00
feat(ee): page verification workflow (#2102)
* feat: page verification workflow * feat: refactor page-verification * sync * fix type * fix * fix * notification icon * use full word * accept .license file * - update templates - update migration and notification * fix copy * update audit labels * sync * add space name
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.createTable('page_verifications')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('page_id', 'uuid', (col) =>
|
||||
col.notNull().unique().references('pages.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('workspace_id', 'uuid', (col) =>
|
||||
col.notNull().references('workspaces.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('space_id', 'uuid', (col) =>
|
||||
col.notNull().references('spaces.id').onDelete('cascade'),
|
||||
)
|
||||
.addColumn('type', 'varchar', (col) => col.notNull().defaultTo('expiring'))
|
||||
.addColumn('status', 'varchar')
|
||||
.addColumn('mode', 'varchar')
|
||||
.addColumn('period_amount', 'integer')
|
||||
.addColumn('period_unit', 'varchar')
|
||||
.addColumn('verified_at', 'timestamptz')
|
||||
.addColumn('verified_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('expires_at', 'timestamptz')
|
||||
.addColumn('requested_at', 'timestamptz')
|
||||
.addColumn('requested_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('rejected_at', 'timestamptz')
|
||||
.addColumn('rejected_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('rejection_comment', 'text')
|
||||
.addColumn('data', 'jsonb')
|
||||
.addColumn('creator_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('updated_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createTable('page_verifiers')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('page_verification_id', 'uuid', (col) =>
|
||||
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('added_by_id', 'uuid', (col) =>
|
||||
col.references('users.id').onDelete('set null'),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addUniqueConstraint('page_verifiers_verification_user_unique', [
|
||||
'page_verification_id',
|
||||
'user_id',
|
||||
])
|
||||
.execute();
|
||||
|
||||
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();
|
||||
|
||||
await db.schema
|
||||
.alterTable('notifications')
|
||||
.addColumn('page_verification_id', 'uuid', (col) =>
|
||||
col.references('page_verifications.id').onDelete('cascade'),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.alterTable('notifications')
|
||||
.dropColumn('page_verification_id')
|
||||
.execute();
|
||||
await db.schema.dropTable('page_verifiers').ifExists().execute();
|
||||
await db.schema.dropTable('page_verifications').ifExists().execute();
|
||||
}
|
||||
@@ -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<Notification> {
|
||||
return this.db
|
||||
.insertInto('notifications')
|
||||
.values(notification)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async getUnreadCount(userId: string): Promise<number> {
|
||||
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<Notification> {
|
||||
return this.db
|
||||
.insertInto('notifications')
|
||||
.values(notification)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async markAsRead(notificationId: string, userId: string): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.db
|
||||
.updateTable('notifications')
|
||||
.set({ emailedAt: new Date() })
|
||||
.where('id', '=', notificationId)
|
||||
.where('emailedAt', 'is', null)
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
||||
+36
@@ -400,6 +400,7 @@ export interface Notifications {
|
||||
pageId: string | null;
|
||||
spaceId: string | null;
|
||||
commentId: string | null;
|
||||
pageVerificationId: string | null;
|
||||
data: Json | null;
|
||||
readAt: Timestamp | null;
|
||||
emailedAt: Timestamp | null;
|
||||
@@ -441,6 +442,39 @@ export interface PagePermissions {
|
||||
updatedAt: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
export interface PageVerifications {
|
||||
id: Generated<string>;
|
||||
pageId: string;
|
||||
workspaceId: string;
|
||||
spaceId: string;
|
||||
type: Generated<string>;
|
||||
status: string | null;
|
||||
mode: string | null;
|
||||
periodAmount: number | null;
|
||||
periodUnit: string | null;
|
||||
verifiedAt: Timestamp | null;
|
||||
verifiedById: string | null;
|
||||
expiresAt: Timestamp | null;
|
||||
requestedAt: Timestamp | null;
|
||||
requestedById: string | null;
|
||||
rejectedAt: Timestamp | null;
|
||||
rejectedById: string | null;
|
||||
rejectionComment: string | null;
|
||||
data: Json | null;
|
||||
creatorId: string | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
export interface PageVerifiers {
|
||||
id: Generated<string>;
|
||||
pageVerificationId: string;
|
||||
userId: string;
|
||||
isPrimary: Generated<boolean>;
|
||||
addedById: string | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
export interface Templates {
|
||||
id: Generated<string>;
|
||||
title: string | null;
|
||||
@@ -519,6 +553,8 @@ export interface DB {
|
||||
pageAccess: PageAccess;
|
||||
pagePermissions: PagePermissions;
|
||||
pageHistory: PageHistory;
|
||||
pageVerifications: PageVerifications;
|
||||
pageVerifiers: PageVerifiers;
|
||||
pages: Pages;
|
||||
shares: Shares;
|
||||
spaceMembers: SpaceMembers;
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
Notifications,
|
||||
PageAccess as _PageAccess,
|
||||
PagePermissions as _PagePermissions,
|
||||
PageVerifications as _PageVerifications,
|
||||
PageVerifiers as _PageVerifiers,
|
||||
Pages,
|
||||
Spaces,
|
||||
Users,
|
||||
@@ -182,6 +184,15 @@ export type PagePermission = Selectable<_PagePermissions>;
|
||||
export type InsertablePagePermission = Insertable<_PagePermissions>;
|
||||
export type UpdatablePagePermission = Updateable<Omit<_PagePermissions, 'id'>>;
|
||||
|
||||
// Page Verification
|
||||
export type PageVerification = Selectable<_PageVerifications>;
|
||||
export type InsertablePageVerification = Insertable<_PageVerifications>;
|
||||
export type UpdatablePageVerification = Updateable<Omit<_PageVerifications, 'id'>>;
|
||||
|
||||
// Page Verifier
|
||||
export type PageVerifier = Selectable<_PageVerifiers>;
|
||||
export type InsertablePageVerifier = Insertable<_PageVerifiers>;
|
||||
|
||||
// User Session
|
||||
export type UserSession = Selectable<UserSessions>;
|
||||
export type InsertableUserSession = Insertable<UserSessions>;
|
||||
|
||||
Reference in New Issue
Block a user