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;