feat: add feed page
This commit is contained in:
parent
f459c6beea
commit
96a5a33ad6
@ -1167,6 +1167,45 @@ export class FeedService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.channelId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedChannelInfo(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/info']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/info']['get']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/workspace/{workspaceId}/feed/{channelId}/info',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
channelId: data.channelId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.channelId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedUpdateChannelInfo(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/update']['post']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/update']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/workspace/{workspaceId}/feed/{channelId}/update',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
channelId: data.channelId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
* @param data.workspaceId
|
* @param data.workspaceId
|
||||||
@ -1185,4 +1224,60 @@ export class FeedService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedCreateChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/createChannel']['post']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/createChannel']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/workspace/{workspaceId}/feed/createChannel',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.channelId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedDeleteChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/workspace/{workspaceId}/feed/{channelId}',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
channelId: data.channelId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.channelId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedSendEvent(data: $OpenApiTs['/feed/{channelId}/send']['post']['req']): CancelablePromise<$OpenApiTs['/feed/{channelId}/send']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/feed/{channelId}/send',
|
||||||
|
path: {
|
||||||
|
channelId: data.channelId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1397,6 +1397,49 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace/{workspaceId}/feed/{channelId}/info': {
|
||||||
|
get: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace/{workspaceId}/feed/{channelId}/update': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
requestBody: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'/workspace/{workspaceId}/feed/{channelId}/events': {
|
'/workspace/{workspaceId}/feed/{channelId}/events': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
@ -1423,4 +1466,80 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace/{workspaceId}/feed/createChannel': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace/{workspaceId}/feed/{channelId}': {
|
||||||
|
delete: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/feed/{channelId}/send': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
requestBody: {
|
||||||
|
eventName: string;
|
||||||
|
eventContent: string;
|
||||||
|
tags: Array<(string)>;
|
||||||
|
source: string;
|
||||||
|
senderId?: string | null;
|
||||||
|
senderName?: string | null;
|
||||||
|
important: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
channelId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
eventName: string;
|
||||||
|
eventContent: string;
|
||||||
|
tags: Array<(string)>;
|
||||||
|
source: string;
|
||||||
|
senderId?: string | null;
|
||||||
|
senderName?: string | null;
|
||||||
|
important: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
@ -4,6 +4,7 @@ import { Button } from './ui/button';
|
|||||||
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { ScrollBar } from './ui/scroll-area';
|
||||||
|
|
||||||
export const CodeBlock: React.FC<{
|
export const CodeBlock: React.FC<{
|
||||||
code: string;
|
code: string;
|
||||||
@ -20,7 +21,7 @@ export const CodeBlock: React.FC<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative overflow-auto">
|
<div className="group relative w-full overflow-auto">
|
||||||
<pre className="rounded-sm border border-zinc-800 bg-zinc-900 p-3 pr-12 text-sm">
|
<pre className="rounded-sm border border-zinc-800 bg-zinc-900 p-3 pr-12 text-sm">
|
||||||
<code>{props.code}</code>
|
<code>{props.code}</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
36
src/client/components/feed/FeedApiGuide.tsx
Normal file
36
src/client/components/feed/FeedApiGuide.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import React from 'react';
|
||||||
|
import { CodeBlock } from '../CodeBlock';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
|
export const FeedApiGuide: React.FC<{ channelId: string }> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const code = `fetch('${window.location.origin}/open/feed/${props.channelId}/send', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
eventName: 'test name',
|
||||||
|
eventContent: 'test content',
|
||||||
|
tags: ['test'],
|
||||||
|
source: 'custom',
|
||||||
|
important: false,
|
||||||
|
})
|
||||||
|
})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full overflow-hidden">
|
||||||
|
<CardHeader>
|
||||||
|
<div>{t('You can send any message into this channel with:')}</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex w-full overflow-hidden">
|
||||||
|
<CodeBlock code={code} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
FeedApiGuide.displayName = 'FeedApiGuide';
|
81
src/client/components/feed/FeedChannelEditForm.tsx
Normal file
81
src/client/components/feed/FeedChannelEditForm.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||||
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const addFormSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FeedChannelEditFormValues = z.infer<typeof addFormSchema>;
|
||||||
|
|
||||||
|
interface FeedChannelEditFormProps {
|
||||||
|
defaultValues?: FeedChannelEditFormValues;
|
||||||
|
onSubmit: (values: FeedChannelEditFormValues) => Promise<void>;
|
||||||
|
}
|
||||||
|
export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
||||||
|
React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const form = useForm<FeedChannelEditFormValues>({
|
||||||
|
resolver: zodResolver(addFormSchema),
|
||||||
|
defaultValues: props.defaultValues ?? {
|
||||||
|
name: 'New Channel',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [handleSubmit, isLoading] = useEventWithLoading(
|
||||||
|
async (values: FeedChannelEditFormValues) => {
|
||||||
|
await props.onSubmit(values);
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-8">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('Channel Name')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('Channel Name to Display')}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardFooter>
|
||||||
|
<Button type="submit" loading={isLoading}>
|
||||||
|
{props.defaultValues ? t('Update') : t('Create')}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FeedChannelEditForm.displayName = 'FeedChannelEditForm';
|
23
src/client/components/feed/FeedEventItem.tsx
Normal file
23
src/client/components/feed/FeedEventItem.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { AppRouterOutput } from '@/api/trpc';
|
||||||
|
import React from 'react';
|
||||||
|
import { Badge } from '../ui/badge';
|
||||||
|
|
||||||
|
type FeedEventItemType = AppRouterOutput['feed']['events'][number];
|
||||||
|
|
||||||
|
export const FeedEventItem: React.FC<{ event: FeedEventItemType }> = React.memo(
|
||||||
|
({ event }) => {
|
||||||
|
return (
|
||||||
|
<div className="border-muted rounded-lg border px-4 py-2">
|
||||||
|
<div className="mb-2">{event.eventName}</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Badge>{event.source}</Badge>
|
||||||
|
|
||||||
|
{event.tags.map((tag) => (
|
||||||
|
<Badge variant="secondary">{tag}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
FeedEventItem.displayName = 'FeedEventItem';
|
@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
|
LuActivitySquare,
|
||||||
LuAreaChart,
|
LuAreaChart,
|
||||||
LuFilePieChart,
|
LuFilePieChart,
|
||||||
LuMonitorDot,
|
LuMonitorDot,
|
||||||
@ -103,6 +104,12 @@ export const DesktopLayout: React.FC<LayoutProps> = React.memo((props) => {
|
|||||||
icon: RiSurveyLine,
|
icon: RiSurveyLine,
|
||||||
to: '/survey',
|
to: '/survey',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Feed'),
|
||||||
|
label: '',
|
||||||
|
icon: LuActivitySquare,
|
||||||
|
to: '/feed',
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
|
LuActivitySquare,
|
||||||
LuAreaChart,
|
LuAreaChart,
|
||||||
LuFilePieChart,
|
LuFilePieChart,
|
||||||
LuMonitorDot,
|
LuMonitorDot,
|
||||||
@ -79,6 +80,12 @@ export const MobileLayout: React.FC<LayoutProps> = React.memo((props) => {
|
|||||||
to="/survey"
|
to="/survey"
|
||||||
extraModal={true}
|
extraModal={true}
|
||||||
/>
|
/>
|
||||||
|
<MobileNavItem
|
||||||
|
title={t('Feed')}
|
||||||
|
icon={LuActivitySquare}
|
||||||
|
to="/feed"
|
||||||
|
extraModal={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@ -20,6 +20,7 @@ import { Route as RegisterImport } from './routes/register'
|
|||||||
import { Route as PageImport } from './routes/page'
|
import { Route as PageImport } from './routes/page'
|
||||||
import { Route as MonitorImport } from './routes/monitor'
|
import { Route as MonitorImport } from './routes/monitor'
|
||||||
import { Route as LoginImport } from './routes/login'
|
import { Route as LoginImport } from './routes/login'
|
||||||
|
import { Route as FeedImport } from './routes/feed'
|
||||||
import { Route as DashboardImport } from './routes/dashboard'
|
import { Route as DashboardImport } from './routes/dashboard'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as IndexImport } from './routes/index'
|
||||||
import { Route as WebsiteOverviewImport } from './routes/website/overview'
|
import { Route as WebsiteOverviewImport } from './routes/website/overview'
|
||||||
@ -35,12 +36,15 @@ import { Route as SettingsAuditLogImport } from './routes/settings/auditLog'
|
|||||||
import { Route as PageAddImport } from './routes/page/add'
|
import { Route as PageAddImport } from './routes/page/add'
|
||||||
import { Route as PageSlugImport } from './routes/page/$slug'
|
import { Route as PageSlugImport } from './routes/page/$slug'
|
||||||
import { Route as MonitorAddImport } from './routes/monitor/add'
|
import { Route as MonitorAddImport } from './routes/monitor/add'
|
||||||
|
import { Route as FeedAddImport } from './routes/feed/add'
|
||||||
import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
|
import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
|
||||||
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
|
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
|
||||||
import { Route as MonitorMonitorIdIndexImport } from './routes/monitor/$monitorId/index'
|
import { Route as MonitorMonitorIdIndexImport } from './routes/monitor/$monitorId/index'
|
||||||
|
import { Route as FeedChannelIdIndexImport } from './routes/feed/$channelId/index'
|
||||||
import { Route as WebsiteWebsiteIdConfigImport } from './routes/website/$websiteId/config'
|
import { Route as WebsiteWebsiteIdConfigImport } from './routes/website/$websiteId/config'
|
||||||
import { Route as SurveySurveyIdEditImport } from './routes/survey/$surveyId/edit'
|
import { Route as SurveySurveyIdEditImport } from './routes/survey/$surveyId/edit'
|
||||||
import { Route as MonitorMonitorIdEditImport } from './routes/monitor/$monitorId/edit'
|
import { Route as MonitorMonitorIdEditImport } from './routes/monitor/$monitorId/edit'
|
||||||
|
import { Route as FeedChannelIdEditImport } from './routes/feed/$channelId/edit'
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
@ -89,6 +93,11 @@ const LoginRoute = LoginImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const FeedRoute = FeedImport.update({
|
||||||
|
path: '/feed',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const DashboardRoute = DashboardImport.update({
|
const DashboardRoute = DashboardImport.update({
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@ -164,6 +173,11 @@ const MonitorAddRoute = MonitorAddImport.update({
|
|||||||
getParentRoute: () => MonitorRoute,
|
getParentRoute: () => MonitorRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const FeedAddRoute = FeedAddImport.update({
|
||||||
|
path: '/add',
|
||||||
|
getParentRoute: () => FeedRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const WebsiteWebsiteIdIndexRoute = WebsiteWebsiteIdIndexImport.update({
|
const WebsiteWebsiteIdIndexRoute = WebsiteWebsiteIdIndexImport.update({
|
||||||
path: '/$websiteId/',
|
path: '/$websiteId/',
|
||||||
getParentRoute: () => WebsiteRoute,
|
getParentRoute: () => WebsiteRoute,
|
||||||
@ -179,6 +193,11 @@ const MonitorMonitorIdIndexRoute = MonitorMonitorIdIndexImport.update({
|
|||||||
getParentRoute: () => MonitorRoute,
|
getParentRoute: () => MonitorRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const FeedChannelIdIndexRoute = FeedChannelIdIndexImport.update({
|
||||||
|
path: '/$channelId/',
|
||||||
|
getParentRoute: () => FeedRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const WebsiteWebsiteIdConfigRoute = WebsiteWebsiteIdConfigImport.update({
|
const WebsiteWebsiteIdConfigRoute = WebsiteWebsiteIdConfigImport.update({
|
||||||
path: '/$websiteId/config',
|
path: '/$websiteId/config',
|
||||||
getParentRoute: () => WebsiteRoute,
|
getParentRoute: () => WebsiteRoute,
|
||||||
@ -194,6 +213,11 @@ const MonitorMonitorIdEditRoute = MonitorMonitorIdEditImport.update({
|
|||||||
getParentRoute: () => MonitorRoute,
|
getParentRoute: () => MonitorRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const FeedChannelIdEditRoute = FeedChannelIdEditImport.update({
|
||||||
|
path: '/$channelId/edit',
|
||||||
|
getParentRoute: () => FeedRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
// Populate the FileRoutesByPath interface
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
@ -206,6 +230,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DashboardImport
|
preLoaderRoute: typeof DashboardImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/feed': {
|
||||||
|
preLoaderRoute: typeof FeedImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/login': {
|
'/login': {
|
||||||
preLoaderRoute: typeof LoginImport
|
preLoaderRoute: typeof LoginImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
@ -242,6 +270,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof WebsiteImport
|
preLoaderRoute: typeof WebsiteImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/feed/add': {
|
||||||
|
preLoaderRoute: typeof FeedAddImport
|
||||||
|
parentRoute: typeof FeedImport
|
||||||
|
}
|
||||||
'/monitor/add': {
|
'/monitor/add': {
|
||||||
preLoaderRoute: typeof MonitorAddImport
|
preLoaderRoute: typeof MonitorAddImport
|
||||||
parentRoute: typeof MonitorImport
|
parentRoute: typeof MonitorImport
|
||||||
@ -294,6 +326,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof WebsiteOverviewImport
|
preLoaderRoute: typeof WebsiteOverviewImport
|
||||||
parentRoute: typeof WebsiteImport
|
parentRoute: typeof WebsiteImport
|
||||||
}
|
}
|
||||||
|
'/feed/$channelId/edit': {
|
||||||
|
preLoaderRoute: typeof FeedChannelIdEditImport
|
||||||
|
parentRoute: typeof FeedImport
|
||||||
|
}
|
||||||
'/monitor/$monitorId/edit': {
|
'/monitor/$monitorId/edit': {
|
||||||
preLoaderRoute: typeof MonitorMonitorIdEditImport
|
preLoaderRoute: typeof MonitorMonitorIdEditImport
|
||||||
parentRoute: typeof MonitorImport
|
parentRoute: typeof MonitorImport
|
||||||
@ -306,6 +342,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof WebsiteWebsiteIdConfigImport
|
preLoaderRoute: typeof WebsiteWebsiteIdConfigImport
|
||||||
parentRoute: typeof WebsiteImport
|
parentRoute: typeof WebsiteImport
|
||||||
}
|
}
|
||||||
|
'/feed/$channelId/': {
|
||||||
|
preLoaderRoute: typeof FeedChannelIdIndexImport
|
||||||
|
parentRoute: typeof FeedImport
|
||||||
|
}
|
||||||
'/monitor/$monitorId/': {
|
'/monitor/$monitorId/': {
|
||||||
preLoaderRoute: typeof MonitorMonitorIdIndexImport
|
preLoaderRoute: typeof MonitorMonitorIdIndexImport
|
||||||
parentRoute: typeof MonitorImport
|
parentRoute: typeof MonitorImport
|
||||||
@ -326,6 +366,11 @@ declare module '@tanstack/react-router' {
|
|||||||
export const routeTree = rootRoute.addChildren([
|
export const routeTree = rootRoute.addChildren([
|
||||||
IndexRoute,
|
IndexRoute,
|
||||||
DashboardRoute,
|
DashboardRoute,
|
||||||
|
FeedRoute.addChildren([
|
||||||
|
FeedAddRoute,
|
||||||
|
FeedChannelIdEditRoute,
|
||||||
|
FeedChannelIdIndexRoute,
|
||||||
|
]),
|
||||||
LoginRoute,
|
LoginRoute,
|
||||||
MonitorRoute.addChildren([
|
MonitorRoute.addChildren([
|
||||||
MonitorAddRoute,
|
MonitorAddRoute,
|
||||||
|
88
src/client/routes/feed.tsx
Normal file
88
src/client/routes/feed.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { trpc } from '@/api/trpc';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { CommonList } from '@/components/CommonList';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useDataReady } from '@/hooks/useDataReady';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { Layout } from '@/components/layout';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { cn } from '@/utils/style';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import {
|
||||||
|
createFileRoute,
|
||||||
|
useNavigate,
|
||||||
|
useRouterState,
|
||||||
|
} from '@tanstack/react-router';
|
||||||
|
import { LuPlus } from 'react-icons/lu';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/feed')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: channels = [], isLoading } = trpc.feed.channels.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const pathname = useRouterState({
|
||||||
|
select: (state) => state.location.pathname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = channels.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.name,
|
||||||
|
number: item._count.events ?? 0,
|
||||||
|
href: `/feed/${item.id}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
useDataReady(
|
||||||
|
() => channels.length > 0,
|
||||||
|
() => {
|
||||||
|
if (pathname === Route.fullPath) {
|
||||||
|
navigate({
|
||||||
|
to: '/feed/$channelId',
|
||||||
|
params: {
|
||||||
|
channelId: channels[0].id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClickAdd = useEvent(() => {
|
||||||
|
navigate({
|
||||||
|
to: '/feed/add',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
list={
|
||||||
|
<CommonWrapper
|
||||||
|
header={
|
||||||
|
<CommonHeader
|
||||||
|
title={t('Feed')}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
className={cn(pathname === '/feed/add' && '!bg-muted')}
|
||||||
|
variant="outline"
|
||||||
|
Icon={LuPlus}
|
||||||
|
onClick={handleClickAdd}
|
||||||
|
>
|
||||||
|
{t('Add')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CommonList hasSearch={true} items={items} isLoading={isLoading} />
|
||||||
|
</CommonWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
110
src/client/routes/feed/$channelId/edit.tsx
Normal file
110
src/client/routes/feed/$channelId/edit.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { Loading } from '@/components/Loading';
|
||||||
|
import { ErrorTip } from '@/components/ErrorTip';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import {
|
||||||
|
FeedChannelEditForm,
|
||||||
|
FeedChannelEditFormValues,
|
||||||
|
} from '@/components/feed/FeedChannelEditForm';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/feed/$channelId/edit')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { channelId } = Route.useParams<{ channelId: string }>();
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const mutation = trpc.feed.updateChannelInfo.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const { data: channel, isLoading } = trpc.feed.channelInfo.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
channelId,
|
||||||
|
});
|
||||||
|
const trpcUtils = trpc.useUtils();
|
||||||
|
|
||||||
|
const handleSubmit = useEvent(async (values: FeedChannelEditFormValues) => {
|
||||||
|
const res = await mutation.mutateAsync({
|
||||||
|
...values,
|
||||||
|
channelId,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
trpcUtils.feed.channelInfo.setData(
|
||||||
|
{
|
||||||
|
channelId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
res
|
||||||
|
);
|
||||||
|
trpcUtils.feed.channels.setData(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
(prev) => {
|
||||||
|
if (prev) {
|
||||||
|
const index = prev.findIndex((item) => item.id === channelId);
|
||||||
|
if (index >= 0) {
|
||||||
|
prev[index] = {
|
||||||
|
...prev[index],
|
||||||
|
...res,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
navigate({
|
||||||
|
to: '/feed/$channelId',
|
||||||
|
params: {
|
||||||
|
channelId: res.id,
|
||||||
|
},
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate({
|
||||||
|
to: '/feed',
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
return <ErrorTip />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper
|
||||||
|
header={<CommonHeader title={channel.name} desc={t('Edit')} />}
|
||||||
|
>
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
<FeedChannelEditForm
|
||||||
|
defaultValues={channel}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</ScrollArea>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
103
src/client/routes/feed/$channelId/index.tsx
Normal file
103
src/client/routes/feed/$channelId/index.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
AppRouterOutput,
|
||||||
|
defaultErrorHandler,
|
||||||
|
defaultSuccessHandler,
|
||||||
|
trpc,
|
||||||
|
} from '@/api/trpc';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { AlertConfirm } from '@/components/AlertConfirm';
|
||||||
|
import { LuPencil, LuTrash } from 'react-icons/lu';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Scrollbar } from '@radix-ui/react-scroll-area';
|
||||||
|
import { Empty } from 'antd';
|
||||||
|
import { FeedApiGuide } from '@/components/feed/FeedApiGuide';
|
||||||
|
import { FeedEventItem } from '@/components/feed/FeedEventItem';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/feed/$channelId/')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const { channelId } = Route.useParams<{ channelId: string }>();
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: info } = trpc.feed.channelInfo.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
channelId,
|
||||||
|
});
|
||||||
|
const { data: events } = trpc.feed.events.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
channelId,
|
||||||
|
});
|
||||||
|
const deleteMutation = trpc.feed.deleteChannel.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const trpcUtils = trpc.useUtils();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleDelete = useEvent(async () => {
|
||||||
|
await deleteMutation.mutateAsync({ workspaceId, channelId });
|
||||||
|
trpcUtils.feed.channels.refetch();
|
||||||
|
navigate({
|
||||||
|
to: '/feed',
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper
|
||||||
|
header={
|
||||||
|
<CommonHeader
|
||||||
|
title={info?.name ?? ''}
|
||||||
|
actions={
|
||||||
|
<div className="space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
Icon={LuPencil}
|
||||||
|
onClick={() =>
|
||||||
|
navigate({
|
||||||
|
to: '/feed/$channelId/edit',
|
||||||
|
params: {
|
||||||
|
channelId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AlertConfirm
|
||||||
|
title={t('Confirm to delete this channel?')}
|
||||||
|
description={t('All feed will be remove')}
|
||||||
|
content={t('It will permanently delete the relevant data')}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
>
|
||||||
|
<Button variant="outline" size="icon" Icon={LuTrash} />
|
||||||
|
</AlertConfirm>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{events && events.length === 0 ? (
|
||||||
|
<div className="w-full overflow-hidden p-4">
|
||||||
|
<FeedApiGuide channelId={channelId} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
{(events ?? []).map((event) => (
|
||||||
|
<FeedEventItem key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
52
src/client/routes/feed/add.tsx
Normal file
52
src/client/routes/feed/add.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import {
|
||||||
|
FeedChannelEditForm,
|
||||||
|
FeedChannelEditFormValues,
|
||||||
|
} from '@/components/feed/FeedChannelEditForm';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/feed/add')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const createMutation = trpc.feed.createChannel.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const utils = trpc.useUtils();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onSubmit = useEvent(async (values: FeedChannelEditFormValues) => {
|
||||||
|
const res = await createMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
name: values.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
utils.feed.channels.refetch();
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: '/feed/$channelId',
|
||||||
|
params: {
|
||||||
|
channelId: res.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper
|
||||||
|
header={<h1 className="text-xl font-bold">{t('Add Channel')}</h1>}
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<FeedChannelEditForm onSubmit={onSubmit} />
|
||||||
|
</div>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -9,7 +9,7 @@ import { Layout } from '@/components/layout';
|
|||||||
import { useCurrentWorkspaceId } from '@/store/user';
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
import { Trans, useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import {
|
import {
|
||||||
createFileRoute,
|
createFileRoute,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc';
|
import {
|
||||||
|
OpenApiMetaInfo,
|
||||||
|
publicProcedure,
|
||||||
|
router,
|
||||||
|
workspaceOwnerProcedure,
|
||||||
|
workspaceProcedure,
|
||||||
|
} from '../trpc';
|
||||||
import { OPENAPI_TAG } from '../../utils/const';
|
import { OPENAPI_TAG } from '../../utils/const';
|
||||||
import { OpenApiMeta } from 'trpc-openapi';
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
import { FeedChannelModelSchema, FeedEventModelSchema } from '../../prisma/zod';
|
import { FeedChannelModelSchema, FeedEventModelSchema } from '../../prisma/zod';
|
||||||
@ -43,6 +49,65 @@ export const feedRouter = router({
|
|||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}),
|
}),
|
||||||
|
channelInfo: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/{channelId}/info',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(FeedChannelModelSchema.nullable())
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { channelId, workspaceId } = input;
|
||||||
|
|
||||||
|
const channel = prisma.feedChannel.findFirst({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
id: channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}),
|
||||||
|
updateChannelInfo: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedOpenapi({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/{channelId}/update',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
})
|
||||||
|
.merge(
|
||||||
|
FeedChannelModelSchema.pick({
|
||||||
|
name: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.output(FeedChannelModelSchema.nullable())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { channelId, workspaceId, name } = input;
|
||||||
|
|
||||||
|
const channel = prisma.feedChannel.update({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
id: channelId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}),
|
||||||
events: workspaceProcedure
|
events: workspaceProcedure
|
||||||
.meta(
|
.meta(
|
||||||
buildFeedOpenapi({
|
buildFeedOpenapi({
|
||||||
@ -67,6 +132,93 @@ export const feedRouter = router({
|
|||||||
|
|
||||||
return events;
|
return events;
|
||||||
}),
|
}),
|
||||||
|
createChannel: workspaceOwnerProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedOpenapi({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/createChannel',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
FeedChannelModelSchema.pick({
|
||||||
|
name: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(FeedChannelModelSchema)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { name, workspaceId } = input;
|
||||||
|
|
||||||
|
const channel = await prisma.feedChannel.create({
|
||||||
|
data: {
|
||||||
|
workspaceId,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}),
|
||||||
|
deleteChannel: workspaceOwnerProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedOpenapi({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/{channelId}',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(FeedChannelModelSchema)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { channelId, workspaceId } = input;
|
||||||
|
|
||||||
|
const channel = await prisma.feedChannel.delete({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
id: channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}),
|
||||||
|
sendEvent: publicProcedure
|
||||||
|
.meta({
|
||||||
|
openapi: {
|
||||||
|
tags: [OPENAPI_TAG.FEED],
|
||||||
|
protect: false,
|
||||||
|
method: 'POST',
|
||||||
|
path: '/feed/{channelId}/send',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.input(
|
||||||
|
FeedEventModelSchema.pick({
|
||||||
|
eventName: true,
|
||||||
|
eventContent: true,
|
||||||
|
tags: true,
|
||||||
|
source: true,
|
||||||
|
senderId: true,
|
||||||
|
senderName: true,
|
||||||
|
important: true,
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.output(FeedEventModelSchema)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { channelId, ...data } = input;
|
||||||
|
|
||||||
|
const event = await prisma.feedEvent.create({
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
channelId: channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildFeedOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
function buildFeedOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user