feat: add channel feed notification

This commit is contained in:
moonrailgun 2024-07-13 15:49:34 +08:00
parent 2f6e92d166
commit 67bfda30bc
8 changed files with 184 additions and 23 deletions

View File

@ -16,9 +16,19 @@ import { Input } from '@/components/ui/input';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import React from 'react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import { NotificationPicker } from '../notification/NotificationPicker';
const addFormSchema = z.object({
name: z.string(),
notificationIds: z.array(z.string()).default([]),
notifyFrequency: z.enum(['event', 'day', 'week', 'month']),
});
export type FeedChannelEditFormValues = z.infer<typeof addFormSchema>;
@ -35,6 +45,8 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
resolver: zodResolver(addFormSchema),
defaultValues: props.defaultValues ?? {
name: 'New Channel',
notificationIds: [],
notifyFrequency: 'day',
},
});
@ -66,6 +78,61 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
</FormItem>
)}
/>
<FormField
control={form.control}
name="notificationIds"
render={({ field }) => (
<FormItem>
<FormLabel>{t('Notification')}</FormLabel>
<FormControl>
<NotificationPicker
className="w-full"
{...field}
allowClear={true}
mode="multiple"
/>
</FormControl>
<FormDescription>
{t('Select Notification for send')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="notifyFrequency"
render={({ field }) => (
<FormItem>
<FormLabel>{t('Notification Frequency')}</FormLabel>
<FormControl>
<Select
defaultValue={field.value}
onValueChange={field.onChange}
>
<SelectTrigger className="w-[180px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="event">
{t('Every Event')}
</SelectItem>
<SelectItem value="day">{t('Every Day')}</SelectItem>
<SelectItem value="week">
{t('Every Week')}
</SelectItem>
<SelectItem value="month">
{t('Every Month')}
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
<CardFooter>

View File

@ -7,7 +7,7 @@ import { PlusOutlined } from '@ant-design/icons';
import { useTranslation } from '@i18next-toolkit/react';
import { useNavigate } from '@tanstack/react-router';
interface NotificationPickerProps extends SelectProps<string> {}
interface NotificationPickerProps extends SelectProps<string[]> {}
export const NotificationPicker: React.FC<NotificationPickerProps> = React.memo(
(props) => {
const { t } = useTranslation();

View File

@ -26,8 +26,8 @@ function PageComponent() {
const onSubmit = useEvent(async (values: FeedChannelEditFormValues) => {
const res = await createMutation.mutateAsync({
...values,
workspaceId,
name: values.name,
});
utils.feed.channels.refetch();

View File

@ -0,0 +1,20 @@
-- AlterTable
ALTER TABLE "FeedChannel" ADD COLUMN "notifyFrequency" TEXT NOT NULL DEFAULT 'day';
-- CreateTable
CREATE TABLE "_FeedChannelToNotification" (
"A" VARCHAR(30) NOT NULL,
"B" VARCHAR(30) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "_FeedChannelToNotification_AB_unique" ON "_FeedChannelToNotification"("A", "B");
-- CreateIndex
CREATE INDEX "_FeedChannelToNotification_B_index" ON "_FeedChannelToNotification"("B");
-- AddForeignKey
ALTER TABLE "_FeedChannelToNotification" ADD CONSTRAINT "_FeedChannelToNotification_A_fkey" FOREIGN KEY ("A") REFERENCES "FeedChannel"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FeedChannelToNotification" ADD CONSTRAINT "_FeedChannelToNotification_B_fkey" FOREIGN KEY ("B") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -281,6 +281,7 @@ model Notification {
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
monitors Monitor[]
feedChannels FeedChannel[]
@@index([workspaceId])
}
@ -449,11 +450,13 @@ model FeedChannel {
id String @id @default(cuid()) @db.VarChar(30)
workspaceId String @db.VarChar(30)
name String
notifyFrequency String @default("day")
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
events FeedEvent[]
notifications Notification[]
@@index([workspaceId])
}

View File

@ -1,11 +1,12 @@
import * as z from "zod"
import * as imports from "./schemas"
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteFeedEvent, RelatedFeedEventModelSchema } from "./index"
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteFeedEvent, RelatedFeedEventModelSchema, CompleteNotification, RelatedNotificationModelSchema } from "./index"
export const FeedChannelModelSchema = z.object({
id: z.string(),
workspaceId: z.string(),
name: z.string(),
notifyFrequency: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
})
@ -13,6 +14,7 @@ export const FeedChannelModelSchema = z.object({
export interface CompleteFeedChannel extends z.infer<typeof FeedChannelModelSchema> {
workspace: CompleteWorkspace
events: CompleteFeedEvent[]
notifications: CompleteNotification[]
}
/**
@ -23,4 +25,5 @@ export interface CompleteFeedChannel extends z.infer<typeof FeedChannelModelSche
export const RelatedFeedChannelModelSchema: z.ZodSchema<CompleteFeedChannel> = z.lazy(() => FeedChannelModelSchema.extend({
workspace: RelatedWorkspaceModelSchema,
events: RelatedFeedEventModelSchema.array(),
notifications: RelatedNotificationModelSchema.array(),
}))

View File

@ -1,6 +1,6 @@
import * as z from "zod"
import * as imports from "./schemas"
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteMonitor, RelatedMonitorModelSchema } from "./index"
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index"
// Helper schema for JSON fields
type Literal = boolean | number | string
@ -23,6 +23,7 @@ export const NotificationModelSchema = z.object({
export interface CompleteNotification extends z.infer<typeof NotificationModelSchema> {
workspace: CompleteWorkspace
monitors: CompleteMonitor[]
feedChannels: CompleteFeedChannel[]
}
/**
@ -33,4 +34,5 @@ export interface CompleteNotification extends z.infer<typeof NotificationModelSc
export const RelatedNotificationModelSchema: z.ZodSchema<CompleteNotification> = z.lazy(() => NotificationModelSchema.extend({
workspace: RelatedWorkspaceModelSchema,
monitors: RelatedMonitorModelSchema.array(),
feedChannels: RelatedFeedChannelModelSchema.array(),
}))

View File

@ -67,18 +67,39 @@ export const feedRouter = router({
channelId: z.string(),
})
)
.output(FeedChannelModelSchema.nullable())
.output(
z
.object({
notificationIds: z.array(z.string()),
})
.merge(FeedChannelModelSchema)
.nullable()
)
.query(async ({ input }) => {
const { channelId, workspaceId } = input;
const channel = prisma.feedChannel.findFirst({
const channel = await prisma.feedChannel.findFirst({
where: {
workspaceId,
id: channelId,
},
include: {
notifications: {
select: {
id: true,
},
},
},
});
return channel;
if (!channel) {
return null;
}
return {
...channel,
notificationIds: channel?.notifications.map((n) => n.id),
};
}),
updateChannelInfo: workspaceProcedure
.meta(
@ -91,28 +112,48 @@ export const feedRouter = router({
z
.object({
channelId: z.string(),
notificationIds: z.array(z.string()).default([]),
})
.merge(
FeedChannelModelSchema.pick({
name: true,
notifyFrequency: true,
})
)
)
.output(FeedChannelModelSchema.nullable())
.output(
z
.object({
notificationIds: z.array(z.string()),
})
.merge(FeedChannelModelSchema)
.nullable()
)
.mutation(async ({ input }) => {
const { channelId, workspaceId, name } = input;
const { channelId, workspaceId, name, notifyFrequency, notificationIds } =
input;
const channel = prisma.feedChannel.update({
const channel = await prisma.feedChannel.update({
where: {
workspaceId,
id: channelId,
},
data: {
name,
notifyFrequency,
notifications: {
set: notificationIds.map((id) => ({
id,
})),
},
},
});
return channel;
if (!channel) {
return null;
}
return { ...channel, notificationIds };
}),
fetchEventsByCursor: workspaceProcedure
.meta(
@ -161,20 +202,45 @@ export const feedRouter = router({
.input(
FeedChannelModelSchema.pick({
name: true,
notifyFrequency: true,
}).merge(
z.object({
notificationIds: z.array(z.string()).default([]),
})
)
.output(FeedChannelModelSchema)
)
.output(
z
.object({
notificationIds: z.array(z.string()),
})
.merge(FeedChannelModelSchema)
)
.mutation(async ({ input }) => {
const { name, workspaceId } = input;
const { name, workspaceId, notifyFrequency, notificationIds } = input;
const channel = await prisma.feedChannel.create({
data: {
workspaceId,
name,
notifyFrequency,
notifications: {
connect: notificationIds.map((id) => ({ id })),
},
},
include: {
notifications: {
select: {
id: true,
},
},
},
});
return channel;
return {
...channel,
notificationIds: channel?.notifications.map((n) => n.id),
};
}),
deleteChannel: workspaceOwnerProcedure
.meta(