docs: add openapi for global and website

This commit is contained in:
moonrailgun 2023-10-23 01:44:08 +08:00
parent 243b1dad81
commit 594f3124ef
10 changed files with 96 additions and 11 deletions

View File

@ -47,6 +47,7 @@ app.use('/telemetry', telemetryRouter);
if (env.allowOpenapi) { if (env.allowOpenapi) {
app.use('/open/_ui', swaggerUI.serve, swaggerUI.setup(trpcOpenapiDocument)); app.use('/open/_ui', swaggerUI.serve, swaggerUI.setup(trpcOpenapiDocument));
app.use('/open/_document', (req, res) => res.send(trpcOpenapiDocument));
app.use('/open', trpcOpenapiHttpHandler); app.use('/open', trpcOpenapiHttpHandler);
} }
app.use('/trpc', trpcExpressMiddleware); app.use('/trpc', trpcExpressMiddleware);

View File

@ -21,3 +21,16 @@ export const userInfoSchema = z.object({
}) })
), ),
}); });
export const websiteInfoSchema = z.object({
id: z.string(),
workspaceId: z.string(),
name: z.string(),
domain: z.string().nullable(),
shareId: z.string().nullable(),
resetAt: z.date().nullable(),
monitorId: z.string().nullable(),
createdAt: z.date(),
updatedAt: z.date(),
deletedAt: z.date().nullable(),
});

View File

@ -273,7 +273,7 @@ export async function getWebsiteOnlineUserCount(
Ret[] Ret[]
>`SELECT count(distinct "sessionId") x FROM "WebsiteEvent" where "websiteId" = ${websiteId} AND "createdAt" >= ${startAt}`; >`SELECT count(distinct "sessionId") x FROM "WebsiteEvent" where "websiteId" = ${websiteId} AND "createdAt" >= ${startAt}`;
return res?.[0].x ?? 0; return Number(res?.[0].x ?? 0);
} }
export async function getSessionMetrics( export async function getSessionMetrics(

View File

@ -19,9 +19,13 @@ export const trpcOpenapiHttpHandler = createOpenApiHttpHandler({
createContext, createContext,
}); });
const description = `
<h3>Insight into everything</h3>
<p>Github: <a href="https://github.com/msgbyte/tianji" target="_blank">https://github.com/msgbyte/tianji</a></p>
`.trim();
export const trpcOpenapiDocument = generateOpenApiDocument(appRouter, { export const trpcOpenapiDocument = generateOpenApiDocument(appRouter, {
title: 'Tianji OpenAPI', title: 'Tianji OpenAPI',
description: 'Insight into everything', description,
version: '1.0.0', version: '1.0.0',
baseUrl: '/open', baseUrl: '/open',
}); });

View File

@ -1,7 +1,24 @@
import { z } from 'zod';
import { publicProcedure, router } from '../trpc'; import { publicProcedure, router } from '../trpc';
import { OPENAPI_TAG } from '../../utils/const';
export const globalRouter = router({ export const globalRouter = router({
config: publicProcedure.query(async ({ input }) => { config: publicProcedure
.meta({
openapi: {
method: 'GET',
path: '/global/config',
tags: [OPENAPI_TAG.GLOBAL],
},
})
.input(z.void())
.output(
z.object({
allowRegister: z.boolean(),
websiteId: z.string().optional(),
})
)
.query(async ({ input }) => {
return { return {
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER), allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
websiteId: process.env.WEBSITE_ID, websiteId: process.env.WEBSITE_ID,

View File

@ -1,23 +1,38 @@
import { router, workspaceOwnerProcedure, workspaceProcedure } from '../trpc'; import {
OpenApiMetaInfo,
router,
workspaceOwnerProcedure,
workspaceProcedure,
} from '../trpc';
import { z } from 'zod'; import { z } from 'zod';
import { getWebsiteOnlineUserCount } from '../../model/website'; import { getWebsiteOnlineUserCount } from '../../model/website';
import { prisma } from '../../model/_client'; import { prisma } from '../../model/_client';
import { import {
EVENT_COLUMNS, EVENT_COLUMNS,
FILTER_COLUMNS, FILTER_COLUMNS,
OPENAPI_TAG,
SESSION_COLUMNS, SESSION_COLUMNS,
hostnameRegex, hostnameRegex,
} from '../../utils/const'; } from '../../utils/const';
import { parseDateRange } from '../../utils/common'; import { parseDateRange } from '../../utils/common';
import { getSessionMetrics, getPageviewMetrics } from '../../model/website'; import { getSessionMetrics, getPageviewMetrics } from '../../model/website';
import { websiteInfoSchema } from '../../model/_schema';
import { OpenApiMeta } from 'trpc-openapi';
export const websiteRouter = router({ export const websiteRouter = router({
onlineCount: workspaceProcedure onlineCount: workspaceProcedure
.meta(
buildWebsiteOpenapi({
method: 'GET',
path: '/onlineCount',
})
)
.input( .input(
z.object({ z.object({
websiteId: z.string(), websiteId: z.string(),
}) })
) )
.output(z.number())
.query(async ({ input }) => { .query(async ({ input }) => {
const websiteId = input.websiteId; const websiteId = input.websiteId;
@ -26,11 +41,18 @@ export const websiteRouter = router({
return count; return count;
}), }),
info: workspaceProcedure info: workspaceProcedure
.meta(
buildWebsiteOpenapi({
method: 'GET',
path: '/info',
})
)
.input( .input(
z.object({ z.object({
websiteId: z.string(), websiteId: z.string(),
}) })
) )
.output(websiteInfoSchema.nullable())
.query(async ({ input }) => { .query(async ({ input }) => {
const { workspaceId, websiteId } = input; const { workspaceId, websiteId } = input;
@ -44,6 +66,12 @@ export const websiteRouter = router({
return website; return website;
}), }),
metrics: workspaceProcedure metrics: workspaceProcedure
.meta(
buildWebsiteOpenapi({
method: 'GET',
path: '/metrics',
})
)
.input( .input(
z.object({ z.object({
websiteId: z.string(), websiteId: z.string(),
@ -157,6 +185,12 @@ export const websiteRouter = router({
return []; return [];
}), }),
updateInfo: workspaceOwnerProcedure updateInfo: workspaceOwnerProcedure
.meta(
buildWebsiteOpenapi({
method: 'PUT',
path: '/update',
})
)
.input( .input(
z.object({ z.object({
websiteId: z.string().cuid2(), websiteId: z.string().cuid2(),
@ -168,6 +202,7 @@ export const websiteRouter = router({
monitorId: z.string().cuid2().nullish(), monitorId: z.string().cuid2().nullish(),
}) })
) )
.output(websiteInfoSchema)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const { workspaceId, websiteId, name, domain, monitorId } = input; const { workspaceId, websiteId, name, domain, monitorId } = input;
@ -186,3 +221,14 @@ export const websiteRouter = router({
return websiteInfo; return websiteInfo;
}), }),
}); });
function buildWebsiteOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
return {
openapi: {
tags: [OPENAPI_TAG.WEBSITE],
protect: true,
...meta,
path: `/workspace/{workspaceId}/website/{websiteId}${meta.path}`,
},
};
}

View File

@ -17,6 +17,8 @@ export function createContext({ req }: { req: IncomingMessage }) {
type Context = inferAsyncReturnType<typeof createContext>; type Context = inferAsyncReturnType<typeof createContext>;
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create(); const t = initTRPC.context<Context>().meta<OpenApiMeta>().create();
export type OpenApiMetaInfo = NonNullable<OpenApiMeta['openapi']>;
export const middleware = t.middleware; export const middleware = t.middleware;
export const router = t.router; export const router = t.router;
export const publicProcedure = t.procedure; export const publicProcedure = t.procedure;

View File

@ -115,7 +115,9 @@ export const FILTER_COLUMNS = {
export const DEFAULT_RESET_DATE = '2000-01-01'; export const DEFAULT_RESET_DATE = '2000-01-01';
export enum OPENAPI_TAG { export enum OPENAPI_TAG {
GLOBAL = 'Global',
USER = 'User', USER = 'User',
WEBSITE = 'Website',
} }
export const hostnameRegex = export const hostnameRegex =

View File

@ -1,5 +1,3 @@
export * from './server'; export * from './server';
export * from './monitor'; export * from './monitor';
export * from './utils'; export * from './utils';
export type { MaybePromise } from '@trpc/server';

View File

@ -2,3 +2,5 @@ export type ExactType<T, U extends Partial<T>> = Omit<T, keyof U> & U;
export type PickRequired<T, U extends keyof T> = Omit<T, keyof U> & export type PickRequired<T, U extends keyof T> = Omit<T, keyof U> &
Required<Pick<T, U>>; Required<Pick<T, U>>;
export type { MaybePromise } from '@trpc/server';