- update templates

- update migration and notification
This commit is contained in:
Philipinho
2026-04-13 19:35:36 +01:00
parent b6350f3a2f
commit 8235980eeb
8 changed files with 80 additions and 76 deletions
@@ -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
View File
@@ -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,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>
);
};