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) {
app.use('/open/_ui', swaggerUI.serve, swaggerUI.setup(trpcOpenapiDocument));
app.use('/open/_document', (req, res) => res.send(trpcOpenapiDocument));
app.use('/open', trpcOpenapiHttpHandler);
}
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[]
>`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(

View File

@ -19,9 +19,13 @@ export const trpcOpenapiHttpHandler = createOpenApiHttpHandler({
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, {
title: 'Tianji OpenAPI',
description: 'Insight into everything',
description,
version: '1.0.0',
baseUrl: '/open',
});

View File

@ -1,12 +1,29 @@
import { z } from 'zod';
import { publicProcedure, router } from '../trpc';
import { OPENAPI_TAG } from '../../utils/const';
export const globalRouter = router({
config: publicProcedure.query(async ({ input }) => {
return {
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
websiteId: process.env.WEBSITE_ID,
};
}),
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 {
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
websiteId: process.env.WEBSITE_ID,
};
}),
});
function checkEnvTrusty(env: string | undefined): boolean {

View File

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

View File

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

View File

@ -1,5 +1,3 @@
export * from './server';
export * from './monitor';
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> &
Required<Pick<T, U>>;
export type { MaybePromise } from '@trpc/server';