diff --git a/src/client/api/model/website.ts b/src/client/api/model/website.ts index 939aff0..a498e48 100644 --- a/src/client/api/model/website.ts +++ b/src/client/api/model/website.ts @@ -3,9 +3,9 @@ import { DateUnit } from '../../utils/date'; import { queryClient } from '../cache'; import { request } from '../request'; import { getUserTimezone } from './user'; -import { Website as WebsiteInfo } from '@prisma/client'; +import { AppRouterOutput } from '../trpc'; -export type { WebsiteInfo }; +export type WebsiteInfo = NonNullable; export async function getWorkspaceWebsites( workspaceId: string diff --git a/src/client/components/monitor/MonitorInfo.tsx b/src/client/components/monitor/MonitorInfo.tsx index 7c6d9a1..7f499e7 100644 --- a/src/client/components/monitor/MonitorInfo.tsx +++ b/src/client/components/monitor/MonitorInfo.tsx @@ -31,7 +31,7 @@ export const MonitorInfo: React.FC = React.memo((props) => { isLoading, } = trpc.monitor.get.useQuery({ workspaceId, - id: monitorId, + monitorId, }); if (isInitialLoading) { diff --git a/src/client/pages/Monitor/Edit.tsx b/src/client/pages/Monitor/Edit.tsx index 7a5e6f0..8777397 100644 --- a/src/client/pages/Monitor/Edit.tsx +++ b/src/client/pages/Monitor/Edit.tsx @@ -12,10 +12,10 @@ import { useCurrentWorkspaceId } from '../../store/user'; export const MonitorEdit: React.FC = React.memo(() => { const { monitorId } = useParams<{ monitorId: string }>(); - const currentWorkspaceId = useCurrentWorkspaceId(); + const workspaceId = useCurrentWorkspaceId(); const { data: monitor, isLoading } = trpc.monitor.get.useQuery({ - id: monitorId!, - workspaceId: currentWorkspaceId, + monitorId: monitorId!, + workspaceId, }); const mutation = useMonitorUpsert(); const navigate = useNavigate(); @@ -40,7 +40,7 @@ export const MonitorEdit: React.FC = React.memo(() => { onSave={async (value) => { const monitor = await mutation.mutateAsync({ ...value, - workspaceId: currentWorkspaceId, + workspaceId, }); navigate(`/monitor/${monitor.id}`, { replace: true }); }} diff --git a/src/server/model/_schema/index.ts b/src/server/model/_schema/index.ts index 726d5b9..9cb8463 100644 --- a/src/server/model/_schema/index.ts +++ b/src/server/model/_schema/index.ts @@ -1,5 +1,15 @@ import { z } from 'zod'; +// Match prisma `JsonValue` +export const jsonFieldSchema = z.union([ + z.null(), + z.record(z.any()), + z.array(z.any()), + z.string(), + z.boolean(), + z.number(), +]); + export const workspaceSchema = z.object({ id: z.string(), name: z.string(), @@ -34,3 +44,36 @@ export const websiteInfoSchema = z.object({ updatedAt: z.date(), deletedAt: z.date().nullable(), }); + +export const monitorInfoSchema = z.object({ + id: z.string(), + workspaceId: z.string(), + name: z.string(), + type: z.string(), + active: z.boolean(), + interval: z.number(), + payload: jsonFieldSchema, + createdAt: z.date(), +}); + +export const monitorInfoWithNotificationIdSchema = monitorInfoSchema.and( + z.object({ + notifications: z.array(z.object({ id: z.string() })), + }) +); + +export const monitorEventSchema = z.object({ + id: z.string(), + message: z.string(), + monitorId: z.string(), + type: z.string(), + createdAt: z.date(), +}); + +export const monitorStatusSchema = z.object({ + monitorId: z.string(), + statusName: z.string(), + payload: jsonFieldSchema, + createdAt: z.date(), + updatedAt: z.date(), +}); diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts index 3a559b9..53b51d0 100644 --- a/src/server/trpc/routers/monitor.ts +++ b/src/server/trpc/routers/monitor.ts @@ -1,39 +1,36 @@ -import { router, workspaceOwnerProcedure, workspaceProcedure } from '../trpc'; +import { + OpenApiMetaInfo, + router, + workspaceOwnerProcedure, + workspaceProcedure, +} from '../trpc'; import { prisma } from '../../model/_client'; import { z } from 'zod'; import { monitorManager } from '../../model/monitor'; import { MonitorInfoWithNotificationIds } from '../../../types'; import dayjs from 'dayjs'; +import { + monitorEventSchema, + monitorInfoSchema, + monitorInfoWithNotificationIdSchema, + monitorStatusSchema, +} from '../../model/_schema'; +import { OPENAPI_TAG } from '../../utils/const'; +import { OpenApiMeta } from 'trpc-openapi'; export const monitorRouter = router({ - all: workspaceProcedure.query(async ({ input }) => { - const workspaceId = input.workspaceId; - const monitors = await prisma.monitor.findMany({ - where: { - workspaceId, - }, - include: { - notifications: { - select: { - id: true, - }, - }, - }, - }); - - return monitors as MonitorInfoWithNotificationIds[]; - }), - get: workspaceProcedure - .input( - z.object({ - id: z.string().cuid2(), + all: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/all', }) ) + .output(z.array(monitorInfoWithNotificationIdSchema)) .query(async ({ input }) => { - const { id, workspaceId } = input; - const monitor = await prisma.monitor.findUnique({ + const workspaceId = input.workspaceId; + const monitors = await prisma.monitor.findMany({ where: { - id, workspaceId, }, include: { @@ -45,9 +42,46 @@ export const monitorRouter = router({ }, }); - return monitor as MonitorInfoWithNotificationIds; + return monitors as MonitorInfoWithNotificationIds[]; + }), + get: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/{monitorId}', + }) + ) + .input( + z.object({ + monitorId: z.string().cuid2(), + }) + ) + .output(monitorInfoWithNotificationIdSchema.nullable()) + .query(async ({ input }) => { + const { monitorId, workspaceId } = input; + const monitor = await prisma.monitor.findUnique({ + where: { + id: monitorId, + workspaceId, + }, + include: { + notifications: { + select: { + id: true, + }, + }, + }, + }); + + return monitor; }), upsert: workspaceOwnerProcedure + .meta( + buildMonitorOpenapi({ + method: 'POST', + path: '/upsert', + }) + ) .input( z.object({ id: z.string().cuid2().optional(), @@ -59,6 +93,7 @@ export const monitorRouter = router({ payload: z.object({}).passthrough(), }) ) + .output(monitorInfoSchema) .mutation(async ({ input }) => { const { id, @@ -85,6 +120,12 @@ export const monitorRouter = router({ return monitor; }), data: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/{monitorId}/data', + }) + ) .input( z.object({ monitorId: z.string().cuid2(), @@ -92,6 +133,14 @@ export const monitorRouter = router({ endAt: z.number(), }) ) + .output( + z.array( + z.object({ + value: z.number(), + createdAt: z.date(), + }) + ) + ) .query(async ({ input }) => { const { monitorId, workspaceId, startAt, endAt } = input; @@ -113,12 +162,26 @@ export const monitorRouter = router({ }); }), recentData: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/{monitorId}/recentData', + }) + ) .input( z.object({ monitorId: z.string().cuid2(), take: z.number(), }) ) + .output( + z.array( + z.object({ + value: z.number(), + createdAt: z.date(), + }) + ) + ) .query(async ({ input }) => { const { monitorId, take } = input; @@ -134,11 +197,26 @@ export const monitorRouter = router({ }); }), dataMetrics: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/{monitorId}/dataMetrics', + }) + ) .input( z.object({ monitorId: z.string().cuid2(), }) ) + .output( + z.object({ + recent1DayAvg: z.number(), + recent1DayOnlineCount: z.number(), + recent1DayOfflineCount: z.number(), + recent30DayOnlineCount: z.number(), + recent30DayOfflineCount: z.number(), + }) + ) .query(async ({ input }) => { const { monitorId } = input; const now = dayjs(); @@ -219,11 +297,18 @@ export const monitorRouter = router({ }; }), events: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/events', + }) + ) .input( z.object({ monitorId: z.string().cuid2().optional(), }) ) + .output(z.array(monitorEventSchema)) .query(async ({ input }) => { const { monitorId } = input; @@ -240,12 +325,19 @@ export const monitorRouter = router({ return list; }), getStatus: workspaceProcedure + .meta( + buildMonitorOpenapi({ + method: 'GET', + path: '/{monitorId}/status', + }) + ) .input( z.object({ monitorId: z.string().cuid2(), statusName: z.string(), }) ) + .output(monitorStatusSchema.nullable()) .query(async ({ input }) => { const { monitorId, statusName } = input; @@ -259,3 +351,14 @@ export const monitorRouter = router({ }); }), }); + +function buildMonitorOpenapi(meta: OpenApiMetaInfo): OpenApiMeta { + return { + openapi: { + tags: [OPENAPI_TAG.MONITOR], + protect: true, + ...meta, + path: `/workspace/{workspaceId}/monitor${meta.path}`, + }, + }; +} diff --git a/src/server/utils/const.ts b/src/server/utils/const.ts index cbbc977..c744ace 100644 --- a/src/server/utils/const.ts +++ b/src/server/utils/const.ts @@ -118,6 +118,7 @@ export enum OPENAPI_TAG { GLOBAL = 'Global', USER = 'User', WEBSITE = 'Website', + MONITOR = 'Monitor', } export const hostnameRegex =