feat: add trpc openapi and swagger ui

This commit is contained in:
moonrailgun 2023-10-22 00:26:13 +08:00
parent 4f58c7f5eb
commit d183b8ca6f
11 changed files with 289 additions and 4 deletions

View File

@ -1,8 +1,9 @@
# postgresql url
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
# Whether allow register user
# Whether allow feature
ALLOW_REGISTER=
ALLOW_OPENAPI=
# For analyze tianji self
WEBSITE_ID=

View File

@ -8,6 +8,7 @@ services:
DATABASE_URL: postgresql://tianji:tianji@db:5432/tianji
JWT_SECRET: replace-me-with-a-random-string
ALLOW_REGISTER: "false"
ALLOW_OPENAPI: "true"
depends_on:
- postgres
restart: always

View File

@ -11,6 +11,7 @@
"build:server": "tsc -p tsconfig.server.json",
"build:tracker": "ts-node scripts/build-tracker.ts",
"build:geo": "ts-node scripts/build-geo.ts",
"postinstall": "pnpm db:generate",
"check:type": "tsc --noEmit --skipLibCheck --module esnext",
"db:push": "prisma db push",
"db:generate": "prisma generate",
@ -69,6 +70,8 @@
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"str2int": "^1.1.0",
"swagger-ui-express": "^5.0.0",
"trpc-openapi": "^1.2.0",
"ts-node": "^10.9.1",
"uuid": "^9.0.0",
"vite-express": "^0.10.0",
@ -92,6 +95,7 @@
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/request-ip": "^0.0.38",
"@types/swagger-ui-express": "^4.1.5",
"@types/tar": "^6.1.5",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.15",

View File

@ -151,6 +151,12 @@ dependencies:
str2int:
specifier: ^1.1.0
version: 1.1.0
swagger-ui-express:
specifier: ^5.0.0
version: 5.0.0(express@4.18.2)
trpc-openapi:
specifier: ^1.2.0
version: 1.2.0(@trpc/server@10.38.4)(zod@3.22.2)
ts-node:
specifier: ^10.9.1
version: 10.9.1(@types/node@18.17.12)(typescript@5.2.2)
@ -216,6 +222,9 @@ devDependencies:
'@types/request-ip':
specifier: ^0.0.38
version: 0.0.38
'@types/swagger-ui-express':
specifier: ^4.1.5
version: 4.1.5
'@types/tar':
specifier: ^6.1.5
version: 6.1.5
@ -2181,6 +2190,13 @@ packages:
'@types/node': 18.17.12
dev: true
/@types/swagger-ui-express@4.1.5:
resolution: {integrity: sha512-MRvm1OCzIR321glc/4tP34wRVmsupgLzs6XIq50CFp0CJUzxbpDsrhJxEBMQfoO46ixrlCiw3QXxEs5HHxYI8Q==}
dependencies:
'@types/express': 4.17.17
'@types/serve-static': 1.15.2
dev: true
/@types/tar@6.1.5:
resolution: {integrity: sha512-qm2I/RlZij5RofuY7vohTpYNaYcrSQlN2MyjucQc7ZweDwaEWkdN/EeNh6e9zjK6uEm6PwjdMXkcj05BxZdX1Q==}
dependencies:
@ -2739,6 +2755,15 @@ packages:
engines: {node: '>=6'}
dev: false
/co-body@6.1.0:
resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==}
dependencies:
inflation: 2.1.0
qs: 6.11.0
raw-body: 2.5.1
type-is: 1.6.18
dev: false
/color-convert@0.5.3:
resolution: {integrity: sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==}
dev: false
@ -2835,6 +2860,11 @@ packages:
/concat-map@0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/consola@3.2.3:
resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
engines: {node: ^14.18.0 || >=16.10.0}
dev: false
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
@ -2855,6 +2885,10 @@ packages:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: true
/cookie-es@1.0.0:
resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==}
dev: false
/cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
dev: false
@ -3162,6 +3196,10 @@ packages:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
dev: false
/defu@6.1.2:
resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
dev: false
/degenerator@5.0.1:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
@ -3176,11 +3214,20 @@ packages:
engines: {node: '>=0.4.0'}
dev: false
/depd@1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
engines: {node: '>= 0.6'}
dev: false
/depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dev: false
/destr@2.0.1:
resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
@ -3843,6 +3890,19 @@ packages:
resolution: {integrity: sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==}
dev: false
/h3@1.8.2:
resolution: {integrity: sha512-1Ca0orJJlCaiFY68BvzQtP2lKLk46kcLAxVM8JgYbtm2cUg6IY7pjpYgWMwUvDO9QI30N5JAukOKoT8KD3Q0PQ==}
dependencies:
cookie-es: 1.0.0
defu: 6.1.2
destr: 2.0.1
iron-webcrypto: 0.10.1
radix3: 1.1.0
ufo: 1.3.1
uncrypto: 0.1.3
unenv: 1.7.4
dev: false
/hammerjs@2.0.8:
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
engines: {node: '>=0.8.0'}
@ -3954,6 +4014,11 @@ packages:
resolve-from: 4.0.0
dev: false
/inflation@2.1.0:
resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
engines: {node: '>= 0.8.0'}
dev: false
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
@ -4004,6 +4069,10 @@ packages:
engines: {node: '>= 0.10'}
dev: false
/iron-webcrypto@0.10.1:
resolution: {integrity: sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==}
dev: false
/is-any-array@2.0.1:
resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==}
dev: false
@ -4316,6 +4385,10 @@ packages:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
dev: false
/lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
dev: false
/lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
dev: false
@ -4487,6 +4560,12 @@ packages:
hasBin: true
dev: false
/mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
dev: false
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
@ -4643,6 +4722,10 @@ packages:
engines: {node: '>= 0.4.0'}
dev: false
/node-fetch-native@1.4.0:
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
dev: false
/node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
@ -4655,6 +4738,22 @@ packages:
whatwg-url: 5.0.0
dev: false
/node-mocks-http@1.13.0:
resolution: {integrity: sha512-lArD6sJMPJ53WF50GX0nJ89B1nkV1TdMvNwq8WXXFrUXF80ujSyye1T30mgiHh4h2It0/svpF3C4kZ2OAONVlg==}
engines: {node: '>=14'}
dependencies:
accepts: 1.3.8
content-disposition: 0.5.4
depd: 1.1.2
fresh: 0.5.2
merge-descriptors: 1.0.1
methods: 1.1.2
mime: 1.6.0
parseurl: 1.3.3
range-parser: 1.2.1
type-is: 1.6.18
dev: false
/node-releases@2.0.13:
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
dev: true
@ -4758,6 +4857,10 @@ packages:
dependencies:
wrappy: 1.0.2
/openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
dev: false
/pac-proxy-agent@7.0.1:
resolution: {integrity: sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==}
engines: {node: '>= 14'}
@ -4852,6 +4955,10 @@ packages:
engines: {node: '>=8'}
dev: false
/pathe@1.1.1:
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
dev: false
/pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
dev: false
@ -5137,6 +5244,10 @@ packages:
resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==}
dev: false
/radix3@1.1.0:
resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==}
dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@ -6433,6 +6544,20 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
/swagger-ui-dist@5.9.0:
resolution: {integrity: sha512-NUHSYoe5XRTk/Are8jPJ6phzBh3l9l33nEyXosM17QInoV95/jng8+PuSGtbD407QoPf93MH3Bkh773OgesJpA==}
dev: false
/swagger-ui-express@5.0.0(express@4.18.2):
resolution: {integrity: sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==}
engines: {node: '>= v0.10.32'}
peerDependencies:
express: '>=4.0.0 || >=5.0.0-beta'
dependencies:
express: 4.18.2
swagger-ui-dist: 5.9.0
dev: false
/tailwindcss@3.3.3(ts-node@10.9.1):
resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==}
engines: {node: '>=14.0.0'}
@ -6600,6 +6725,22 @@ packages:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/trpc-openapi@1.2.0(@trpc/server@10.38.4)(zod@3.22.2):
resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==}
peerDependencies:
'@trpc/server': ^10.0.0
zod: ^3.14.4
dependencies:
'@trpc/server': 10.38.4
co-body: 6.1.0
h3: 1.8.2
lodash.clonedeep: 4.5.0
node-mocks-http: 1.13.0
openapi-types: 12.1.3
zod: 3.22.2
zod-to-json-schema: 3.21.4(zod@3.22.2)
dev: false
/ts-easing@0.2.0:
resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==}
dev: false
@ -6710,6 +6851,10 @@ packages:
resolution: {integrity: sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==}
dev: false
/ufo@1.3.1:
resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==}
dev: false
/uglify-js@2.8.29:
resolution: {integrity: sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==}
engines: {node: '>=0.8.0'}
@ -6743,10 +6888,24 @@ packages:
through: 2.3.8
dev: false
/uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
dev: false
/undefsafe@2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
dev: true
/unenv@1.7.4:
resolution: {integrity: sha512-fjYsXYi30It0YCQYqLOcT6fHfMXsBr2hw9XC7ycf8rTG7Xxpe3ZssiqUnD0khrjiZEmkBXWLwm42yCSCH46fMw==}
dependencies:
consola: 3.2.3
defu: 6.1.2
mime: 3.0.0
node-fetch-native: 1.4.0
pathe: 1.1.1
dev: false
/universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
@ -7049,6 +7208,14 @@ packages:
type-fest: 2.19.0
dev: false
/zod-to-json-schema@3.21.4(zod@3.22.2):
resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==}
peerDependencies:
zod: ^3.21.4
dependencies:
zod: 3.22.2
dev: false
/zod@3.22.2:
resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
dev: false

View File

@ -4,17 +4,23 @@ import express from 'express';
import 'express-async-errors';
import ViteExpress from 'vite-express';
import compression from 'compression';
import swaggerUI from 'swagger-ui-express';
import passport from 'passport';
import morgan from 'morgan';
import { websiteRouter } from './router/website';
import { workspaceRouter } from './router/workspace';
import { telemetryRouter } from './router/telemetry';
import { trpcExpressMiddleware } from './trpc';
import {
trpcExpressMiddleware,
trpcOpenapiDocument,
trpcOpenapiHttpHandler,
} from './trpc';
import { initUdpServer } from './udp/server';
import { createServer } from 'http';
import { initSocketio } from './ws';
import { monitorManager } from './model/monitor';
import { settings } from './utils/settings';
import { env } from './utils/env';
const port = settings.port;
@ -39,6 +45,10 @@ app.use('/api/website', websiteRouter);
app.use('/api/workspace', workspaceRouter);
app.use('/telemetry', telemetryRouter);
if (env.allowOpenapi) {
app.use('/open/_ui', swaggerUI.serve, swaggerUI.setup(trpcOpenapiDocument));
app.use('/open', trpcOpenapiHttpHandler);
}
app.use('/trpc', trpcExpressMiddleware);
app.use((err: any, req: any, res: any, next: any) => {
@ -49,6 +59,9 @@ app.use((err: any, req: any, res: any, next: any) => {
httpServer.listen(port, () => {
ViteExpress.bind(app, httpServer, () => {
console.log(`Server is listening on port ${port}...`);
if (env.allowOpenapi) {
console.log(`Openapi UI: http://127.0.0.1:${port}/open/_ui`);
}
console.log(`Website: http://127.0.0.1:${port}`);
});
});

View File

@ -0,0 +1,23 @@
import { z } from 'zod';
export const workspaceSchema = z.object({
id: z.string(),
name: z.string(),
dashboardOrder: z.array(z.string()),
});
export const userInfoSchema = z.object({
username: z.string(),
id: z.string(),
role: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
deletedAt: z.date().nullable(),
currentWorkspace: workspaceSchema.nullable(),
workspaces: z.array(
z.object({
role: z.string(),
workspace: workspaceSchema,
})
),
});

View File

@ -1,6 +1,10 @@
import * as trpcExpress from '@trpc/server/adapters/express';
import { createContext, router } from './trpc';
import { createContext } from './trpc';
import { appRouter } from './routers';
import {
createOpenApiHttpHandler,
generateOpenApiDocument,
} from 'trpc-openapi';
export const trpcExpressMiddleware = trpcExpress.createExpressMiddleware({
router: appRouter,
@ -9,3 +13,15 @@ export const trpcExpressMiddleware = trpcExpress.createExpressMiddleware({
console.error('Error:', path, error);
},
});
export const trpcOpenapiHttpHandler = createOpenApiHttpHandler({
router: appRouter,
createContext,
});
export const trpcOpenapiDocument = generateOpenApiDocument(appRouter, {
title: 'Tianji OpenAPI',
description: 'Insight into everything',
version: '1.0.0',
baseUrl: '/open',
});

View File

@ -10,15 +10,27 @@ import {
} from '../../model/user';
import { jwtSign } from '../../middleware/auth';
import { TRPCError } from '@trpc/server';
import { env } from '../../utils/env';
import { userInfoSchema } from '../../model/_schema/index';
import { OPENAPI_TAG } from '../../utils/const';
export const userRouter = router({
login: publicProcedure
.meta({
openapi: { method: 'POST', path: '/login', tags: [OPENAPI_TAG.USER] },
})
.input(
z.object({
username: z.string(),
password: z.string(),
})
)
.output(
z.object({
info: userInfoSchema,
token: z.string(),
})
)
.mutation(async ({ input }) => {
const { username, password } = input;
const user = await authUser(username, password);
@ -28,11 +40,24 @@ export const userRouter = router({
return { info: user, token };
}),
loginWithToken: publicProcedure
.meta({
openapi: {
method: 'POST',
path: '/loginWithToken',
tags: [OPENAPI_TAG.USER],
},
})
.input(
z.object({
token: z.string(),
})
)
.output(
z.object({
info: userInfoSchema,
token: z.string(),
})
)
.mutation(async ({ input }) => {
const { token } = input;
@ -51,13 +76,34 @@ export const userRouter = router({
}
}),
register: publicProcedure
.meta({
openapi: {
enabled: env.allowRegister,
method: 'POST',
path: '/register',
tags: [OPENAPI_TAG.USER],
},
})
.input(
z.object({
username: z.string(),
password: z.string(),
})
)
.output(
z.object({
info: userInfoSchema,
token: z.string(),
})
)
.mutation(async ({ input }) => {
if (!env.allowRegister) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Not allow register',
});
}
const { username, password } = input;
const userCount = await getUserCount();

View File

@ -5,6 +5,7 @@ import { jwtVerify } from '../middleware/auth';
import { getWorkspaceUser } from '../model/workspace';
import { ROLES, SYSTEM_ROLES } from '../utils/const';
import type { IncomingMessage } from 'http';
import { OpenApiMeta } from 'trpc-openapi';
export function createContext({ req }: { req: IncomingMessage }) {
const authorization = req.headers['authorization'] ?? '';
@ -14,7 +15,7 @@ export function createContext({ req }: { req: IncomingMessage }) {
}
type Context = inferAsyncReturnType<typeof createContext>;
const t = initTRPC.context<Context>().create();
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create();
export const middleware = t.middleware;
export const router = t.router;

View File

@ -113,3 +113,7 @@ export const FILTER_COLUMNS = {
};
export const DEFAULT_RESET_DATE = '2000-01-01';
export enum OPENAPI_TAG {
USER = 'User',
}

9
src/server/utils/env.ts Normal file
View File

@ -0,0 +1,9 @@
export const env = {
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
allowOpenapi: checkEnvTrusty(process.env.ALLOW_OPENAPI),
websiteId: process.env.WEBSITE_ID,
};
export function checkEnvTrusty(env: string | undefined): boolean {
return env === '1' || env === 'true';
}