diff --git a/src/client/routes/feed/$channelId/index.tsx b/src/client/routes/feed/$channelId/index.tsx index 037dcab..a010486 100644 --- a/src/client/routes/feed/$channelId/index.tsx +++ b/src/client/routes/feed/$channelId/index.tsx @@ -1,9 +1,4 @@ -import { - AppRouterOutput, - defaultErrorHandler, - defaultSuccessHandler, - trpc, -} from '@/api/trpc'; +import { defaultErrorHandler, defaultSuccessHandler, trpc } from '@/api/trpc'; import { CommonHeader } from '@/components/CommonHeader'; import { CommonWrapper } from '@/components/CommonWrapper'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -19,6 +14,8 @@ import { FeedApiGuide } from '@/components/feed/FeedApiGuide'; import { FeedEventItem } from '@/components/feed/FeedEventItem'; import { FeedIntegration } from '@/components/feed/FeedIntegration'; import { DialogWrapper } from '@/components/DialogWrapper'; +import { useSocketSubscribeList } from '@/api/socketio'; +import { useMemo } from 'react'; export const Route = createFileRoute('/feed/$channelId/')({ beforeLoad: routeAuthBeforeLoad, @@ -33,7 +30,7 @@ function PageComponent() { workspaceId, channelId, }); - const { data: events } = trpc.feed.events.useQuery({ + const { data: events = [] } = trpc.feed.events.useQuery({ workspaceId, channelId, }); @@ -53,6 +50,15 @@ function PageComponent() { }); }); + const realtimeEvents = useSocketSubscribeList('onReceiveFeedEvent', { + filter: (event) => event.channelId === channelId, + }); + + const fullEvents = useMemo( + () => [...realtimeEvents, ...events], + [realtimeEvents, events] + ); + return ( } > - {events && events.length === 0 ? ( + {fullEvents && fullEvents.length === 0 ? (
) : (
- {(events ?? []).map((event) => ( + {(fullEvents ?? []).map((event) => ( ))}
diff --git a/src/server/trpc/routers/feed.ts b/src/server/trpc/routers/feed.ts index e788c1a..b2d8c12 100644 --- a/src/server/trpc/routers/feed.ts +++ b/src/server/trpc/routers/feed.ts @@ -11,6 +11,7 @@ import { OpenApiMeta } from 'trpc-openapi'; import { FeedChannelModelSchema, FeedEventModelSchema } from '../../prisma/zod'; import { prisma } from '../../model/_client'; import _ from 'lodash'; +import { subscribeEventBus } from '../../ws/shared'; export const feedIntegrationRouter = router({ github: publicProcedure @@ -32,6 +33,21 @@ export const feedIntegrationRouter = router({ const eventType = ctx.req.headers['x-github-event']; const { channelId, ...data } = input; + const workspaceId = await prisma.feedChannel + .findFirst({ + where: { + id: channelId, + }, + select: { + workspaceId: true, + }, + }) + .then((res) => res?.workspaceId); + + if (!workspaceId) { + return 'Not found'; + } + if (eventType === 'push') { const pusher = `${_.get(data, 'pusher.name')}<${_.get(data, 'pusher.email')}>`; const commits = _.map(_.get(data, 'commits') as any[], 'id').join(', '); @@ -40,7 +56,7 @@ export const feedIntegrationRouter = router({ const senderId = String(_.get(data, 'sender.id')); const senderName = String(_.get(data, 'sender.login')); const url = String(_.get(data, 'compare')); - await prisma.feedEvent.create({ + const event = await prisma.feedEvent.create({ data: { channelId: channelId, eventName: eventType, @@ -53,6 +69,7 @@ export const feedIntegrationRouter = router({ url, }, }); + subscribeEventBus.emit('onReceiveFeedEvent', workspaceId, event); return 'ok'; } else if (eventType === 'star') { @@ -61,7 +78,7 @@ export const feedIntegrationRouter = router({ const senderId = String(_.get(data, 'sender.id')); const senderName = String(_.get(data, 'sender.login')); const url = String(_.get(data, 'compare')); - await prisma.feedEvent.create({ + const event = await prisma.feedEvent.create({ data: { channelId: channelId, eventName: eventType, @@ -74,6 +91,7 @@ export const feedIntegrationRouter = router({ url, }, }); + subscribeEventBus.emit('onReceiveFeedEvent', workspaceId, event); return 'ok'; } else if (eventType === 'issues') { @@ -96,7 +114,7 @@ export const feedIntegrationRouter = router({ } if (eventContent) { - await prisma.feedEvent.create({ + const event = await prisma.feedEvent.create({ data: { channelId: channelId, eventName: eventName, @@ -109,6 +127,7 @@ export const feedIntegrationRouter = router({ url, }, }); + subscribeEventBus.emit('onReceiveFeedEvent', workspaceId, event); return 'ok'; } @@ -235,6 +254,10 @@ export const feedRouter = router({ where: { channelId: channelId, }, + take: 50, + orderBy: { + createdAt: 'desc', + }, }); return events; diff --git a/src/server/types/utils.ts b/src/server/types/utils.ts new file mode 100644 index 0000000..b8527a5 --- /dev/null +++ b/src/server/types/utils.ts @@ -0,0 +1,9 @@ +type NestedSwapDatesWithStrings = { + [k in keyof T]: T[k] extends Date | undefined + ? string + : T[k] extends object + ? NestedSwapDatesWithStrings + : T[k]; +}; + +export type Serialize = NestedSwapDatesWithStrings; diff --git a/src/server/ws/shared.ts b/src/server/ws/shared.ts index 077fec4..b60382f 100644 --- a/src/server/ws/shared.ts +++ b/src/server/ws/shared.ts @@ -1,13 +1,15 @@ -import { MonitorData } from '@prisma/client'; import { EventEmitter } from 'eventemitter-strict'; import { Socket } from 'socket.io'; import { MaybePromise, ServerStatusInfo } from '../../types'; +import { FeedEvent, MonitorData } from '@prisma/client'; +import { Serialize } from '../types/utils'; type SubscribeEventFn = (workspaceId: string, eventData: T) => void; export interface SubscribeEventMap { onServerStatusUpdate: SubscribeEventFn>; onMonitorReceiveNewData: SubscribeEventFn; + onReceiveFeedEvent: SubscribeEventFn>; } type SocketEventFn = ( @@ -29,14 +31,14 @@ export const socketEventBus = new EventEmitter(); export const subscribeEventBus = new EventEmitter(); type SubscribeInitializerFn< - T extends keyof SubscribeEventMap = keyof SubscribeEventMap + T extends keyof SubscribeEventMap = keyof SubscribeEventMap, > = ( workspaceId: string, socket: Socket ) => MaybePromise[1]> | MaybePromise; const subscribeInitializerList: [ keyof SubscribeEventMap, - SubscribeInitializerFn + SubscribeInitializerFn, ][] = []; let i = 0;