mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
- update templates
- update migration and notification
This commit is contained in:
@@ -27,6 +27,19 @@ export class VerificationNotificationService {
|
||||
private readonly pagePermissionRepo: PagePermissionRepo,
|
||||
) {}
|
||||
|
||||
private async getAlreadyNotifiedUserIds(
|
||||
pageVerificationId: string,
|
||||
type: string,
|
||||
): Promise<Set<string>> {
|
||||
const rows = await this.db
|
||||
.selectFrom('notifications')
|
||||
.select('userId')
|
||||
.where('pageVerificationId', '=', pageVerificationId)
|
||||
.where('type', '=', type)
|
||||
.execute();
|
||||
return new Set(rows.map((r) => r.userId));
|
||||
}
|
||||
|
||||
private async filterAccessibleRecipients(
|
||||
userIds: string[],
|
||||
pageId: string,
|
||||
@@ -74,6 +87,15 @@ export class VerificationNotificationService {
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
const alreadyNotified = await this.getAlreadyNotifiedUserIds(
|
||||
verification.id,
|
||||
NotificationType.PAGE_VERIFICATION_EXPIRING,
|
||||
);
|
||||
const recipients = accessibleVerifierIds.filter(
|
||||
(id) => !alreadyNotified.has(id),
|
||||
);
|
||||
if (recipients.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(
|
||||
verification.pageId,
|
||||
verification.spaceId,
|
||||
@@ -81,16 +103,17 @@ export class VerificationNotificationService {
|
||||
);
|
||||
if (!context) return;
|
||||
|
||||
const { pageTitle, basePageUrl } = context;
|
||||
const { pageTitle, spaceName, basePageUrl } = context;
|
||||
const expiresAtIso = new Date(verification.expiresAt).toISOString();
|
||||
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
for (const userId of recipients) {
|
||||
const notification = await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId: verification.workspaceId,
|
||||
type: NotificationType.PAGE_VERIFICATION_EXPIRING,
|
||||
pageId: verification.pageId,
|
||||
spaceId: verification.spaceId,
|
||||
pageVerificationId: verification.id,
|
||||
data: { expiresAt: expiresAtIso },
|
||||
});
|
||||
|
||||
@@ -102,6 +125,7 @@ export class VerificationNotificationService {
|
||||
subject,
|
||||
VerificationExpiringEmail({
|
||||
pageTitle,
|
||||
spaceName,
|
||||
pageUrl: basePageUrl,
|
||||
expiresAt: new Date(verification.expiresAt).toLocaleDateString(),
|
||||
}),
|
||||
@@ -139,6 +163,15 @@ export class VerificationNotificationService {
|
||||
);
|
||||
if (accessibleVerifierIds.length === 0) return;
|
||||
|
||||
const alreadyNotified = await this.getAlreadyNotifiedUserIds(
|
||||
verification.id,
|
||||
NotificationType.PAGE_VERIFICATION_EXPIRED,
|
||||
);
|
||||
const recipients = accessibleVerifierIds.filter(
|
||||
(id) => !alreadyNotified.has(id),
|
||||
);
|
||||
if (recipients.length === 0) return;
|
||||
|
||||
const context = await this.getPageContext(
|
||||
verification.pageId,
|
||||
verification.spaceId,
|
||||
@@ -146,15 +179,16 @@ export class VerificationNotificationService {
|
||||
);
|
||||
if (!context) return;
|
||||
|
||||
const { pageTitle, basePageUrl } = context;
|
||||
const { pageTitle, spaceName, basePageUrl } = context;
|
||||
|
||||
for (const userId of accessibleVerifierIds) {
|
||||
for (const userId of recipients) {
|
||||
const notification = await this.notificationService.create({
|
||||
userId,
|
||||
workspaceId: verification.workspaceId,
|
||||
type: NotificationType.PAGE_VERIFICATION_EXPIRED,
|
||||
pageId: verification.pageId,
|
||||
spaceId: verification.spaceId,
|
||||
pageVerificationId: verification.id,
|
||||
});
|
||||
|
||||
const subject = `"${pageTitle}" verification has expired`;
|
||||
@@ -165,6 +199,7 @@ export class VerificationNotificationService {
|
||||
subject,
|
||||
VerificationExpiredEmail({
|
||||
pageTitle,
|
||||
spaceName,
|
||||
pageUrl: basePageUrl,
|
||||
}),
|
||||
);
|
||||
@@ -305,7 +340,7 @@ export class VerificationNotificationService {
|
||||
.executeTakeFirst(),
|
||||
this.db
|
||||
.selectFrom('spaces')
|
||||
.select(['id', 'slug'])
|
||||
.select(['id', 'slug', 'name'])
|
||||
.where('id', '=', spaceId)
|
||||
.executeTakeFirst(),
|
||||
]);
|
||||
@@ -313,6 +348,6 @@ export class VerificationNotificationService {
|
||||
if (!page || !space) return null;
|
||||
|
||||
const basePageUrl = `${appUrl}/s/${space.slug}/p/${page.slugId}`;
|
||||
return { pageTitle: getPageTitle(page.title), basePageUrl };
|
||||
return { pageTitle: getPageTitle(page.title), spaceName: space.name ?? space.slug, basePageUrl };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,20 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
.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();
|
||||
}
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: d7274d6e59...385f36bbae
@@ -1,7 +1,7 @@
|
||||
import { Section, Text, Button } from '@react-email/components';
|
||||
import { Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { button, content, paragraph } from '../css/styles';
|
||||
import { MailBody } from '../partials/partials';
|
||||
import { content, paragraph } from '../css/styles';
|
||||
import { EmailButton, MailBody } from '../partials/partials';
|
||||
|
||||
interface Props {
|
||||
actorName: string;
|
||||
@@ -30,19 +30,7 @@ export const ApprovalRejectedEmail = ({
|
||||
</Text>
|
||||
)}
|
||||
</Section>
|
||||
<Section
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingLeft: '15px',
|
||||
paddingBottom: '15px',
|
||||
}}
|
||||
>
|
||||
<Button href={pageUrl} style={button}>
|
||||
View page
|
||||
</Button>
|
||||
</Section>
|
||||
<EmailButton href={pageUrl}>View page</EmailButton>
|
||||
</MailBody>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Section, Text, Button } from '@react-email/components';
|
||||
import { Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { button, content, paragraph } from '../css/styles';
|
||||
import { MailBody } from '../partials/partials';
|
||||
import { content, paragraph } from '../css/styles';
|
||||
import { EmailButton, MailBody } from '../partials/partials';
|
||||
|
||||
interface Props {
|
||||
actorName: string;
|
||||
@@ -23,19 +23,7 @@ export const ApprovalRequestedEmail = ({
|
||||
<strong>{pageTitle}</strong> for your approval.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingLeft: '15px',
|
||||
paddingBottom: '15px',
|
||||
}}
|
||||
>
|
||||
<Button href={pageUrl} style={button}>
|
||||
Review page
|
||||
</Button>
|
||||
</Section>
|
||||
<EmailButton href={pageUrl}>Review page</EmailButton>
|
||||
</MailBody>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,36 +1,26 @@
|
||||
import { Section, Text, Button } from '@react-email/components';
|
||||
import { Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { button, content, paragraph } from '../css/styles';
|
||||
import { MailBody } from '../partials/partials';
|
||||
import { content, paragraph } from '../css/styles';
|
||||
import { EmailButton, MailBody } from '../partials/partials';
|
||||
|
||||
interface Props {
|
||||
pageTitle: string;
|
||||
spaceName: string;
|
||||
pageUrl: string;
|
||||
}
|
||||
|
||||
export const VerificationExpiredEmail = ({ pageTitle, pageUrl }: Props) => {
|
||||
export const VerificationExpiredEmail = ({ pageTitle, spaceName, pageUrl }: Props) => {
|
||||
return (
|
||||
<MailBody>
|
||||
<Section style={content}>
|
||||
<Text style={paragraph}>Hi there,</Text>
|
||||
<Text style={paragraph}>
|
||||
The verification for <strong>{pageTitle}</strong> has expired. Please
|
||||
re-verify the page to confirm it is still accurate.
|
||||
The verification for <strong>{pageTitle}</strong> in{' '}
|
||||
<strong>{spaceName}</strong> has expired. Please re-verify the page to
|
||||
confirm it is still accurate.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingLeft: '15px',
|
||||
paddingBottom: '15px',
|
||||
}}
|
||||
>
|
||||
<Button href={pageUrl} style={button}>
|
||||
Re-verify page
|
||||
</Button>
|
||||
</Section>
|
||||
<EmailButton href={pageUrl}>Re-verify page</EmailButton>
|
||||
</MailBody>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Section, Text, Button } from '@react-email/components';
|
||||
import { Section, Text } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { button, content, paragraph } from '../css/styles';
|
||||
import { MailBody } from '../partials/partials';
|
||||
import { content, paragraph } from '../css/styles';
|
||||
import { EmailButton, MailBody } from '../partials/partials';
|
||||
|
||||
interface Props {
|
||||
pageTitle: string;
|
||||
spaceName: string;
|
||||
pageUrl: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
export const VerificationExpiringEmail = ({
|
||||
pageTitle,
|
||||
spaceName,
|
||||
pageUrl,
|
||||
expiresAt,
|
||||
}: Props) => {
|
||||
@@ -19,23 +21,12 @@ export const VerificationExpiringEmail = ({
|
||||
<Section style={content}>
|
||||
<Text style={paragraph}>Hi there,</Text>
|
||||
<Text style={paragraph}>
|
||||
The page <strong>{pageTitle}</strong> needs to be re-verified. The
|
||||
verification expires on <strong>{expiresAt}</strong>.
|
||||
The page <strong>{pageTitle}</strong> in{' '}
|
||||
<strong>{spaceName}</strong> needs to be re-verified. The verification
|
||||
expires on <strong>{expiresAt}</strong>.
|
||||
</Text>
|
||||
</Section>
|
||||
<Section
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingLeft: '15px',
|
||||
paddingBottom: '15px',
|
||||
}}
|
||||
>
|
||||
<Button href={pageUrl} style={button}>
|
||||
Review page
|
||||
</Button>
|
||||
</Section>
|
||||
<EmailButton href={pageUrl}>Review page</EmailButton>
|
||||
</MailBody>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user