diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e88703d..b2cb6e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -404,6 +404,9 @@ importers: '@types/request-ip': specifier: ^0.0.38 version: 0.0.38 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 '@types/swagger-ui-express': specifier: ^4.1.5 version: 4.1.5 @@ -431,6 +434,9 @@ importers: prisma-zod-generator: specifier: 0.8.13 version: 0.8.13(prisma@5.4.2) + supertest: + specifier: ^6.3.4 + version: 6.3.4 tailwindcss: specifier: ^3.3.5 version: 3.3.5(ts-node@10.9.1) @@ -9533,6 +9539,10 @@ packages: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: false + /@types/cookiejar@2.1.5: + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + dev: true + /@types/cors@2.8.15: resolution: {integrity: sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==} dependencies: @@ -9757,6 +9767,10 @@ packages: resolution: {integrity: sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==} dev: true + /@types/methods@1.1.4: + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + dev: true + /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} @@ -9963,12 +9977,27 @@ packages: dependencies: '@types/node': 18.17.12 + /@types/superagent@8.1.6: + resolution: {integrity: sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ==} + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 18.17.12 + dev: true + /@types/supercluster@7.1.3: resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} dependencies: '@types/geojson': 7946.0.13 dev: false + /@types/supertest@6.0.2: + resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.6 + dev: true + /@types/swagger-ui-express@4.1.5: resolution: {integrity: sha512-MRvm1OCzIR321glc/4tP34wRVmsupgLzs6XIq50CFp0CJUzxbpDsrhJxEBMQfoO46ixrlCiw3QXxEs5HHxYI8Q==} dependencies: @@ -10696,7 +10725,7 @@ packages: /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 is-array-buffer: 3.0.2 /array-flatten@1.1.1: @@ -10732,7 +10761,7 @@ packages: resolution: {integrity: sha512-nK1psgF2cXqP3wSyCSq0Hc7zwNq3sfljQqaG27r/7a7ooNUnn5nGq6yYWyks9jMO5EoFQ0ax80hSg6oXSRNXaw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 es-array-method-boxes-properly: 1.0.0 @@ -10744,9 +10773,9 @@ packages: engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 is-array-buffer: 3.0.2 is-shared-array-buffer: 1.0.2 @@ -10756,7 +10785,6 @@ packages: /asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - dev: false /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -10806,7 +10834,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} @@ -11347,7 +11374,8 @@ packages: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.2 - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 + dev: true /call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} @@ -11857,7 +11885,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: false /comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} @@ -11912,7 +11939,6 @@ packages: /component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - dev: false /compose-middleware@5.0.1: resolution: {integrity: sha512-Rcv19QgPOtYHu8wDJsu4ehSfkqSXjQLwKRXhIy9TFiIijSZz330ORyLCeirb4sPuBBbDNC5lUvQLuM72vWjKSQ==} @@ -12137,7 +12163,7 @@ packages: handlebars: 4.7.8 json-stringify-safe: 5.0.1 meow: 12.1.1 - semver: 7.5.4 + semver: 7.6.0 split2: 4.2.0 dev: true @@ -12211,7 +12237,6 @@ packages: /cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - dev: false /copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} @@ -12533,7 +12558,7 @@ packages: postcss-modules-scope: 3.1.0(postcss@8.4.33) postcss-modules-values: 4.0.0(postcss@8.4.33) postcss-value-parser: 4.2.0 - semver: 7.5.4 + semver: 7.6.0 webpack: 5.89.0 dev: false @@ -13133,7 +13158,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: false /density-clustering@1.3.0: resolution: {integrity: sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ==} @@ -13216,7 +13240,6 @@ packages: dependencies: asap: 2.0.6 wrappy: 1.0.2 - dev: false /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -13699,11 +13722,11 @@ packages: array-buffer-byte-length: 1.0.0 arraybuffer.prototype.slice: 1.0.1 available-typed-arrays: 1.0.5 - call-bind: 1.0.2 + call-bind: 1.0.7 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 get-symbol-description: 1.0.0 globalthis: 1.0.3 gopd: 1.0.1 @@ -13753,8 +13776,8 @@ packages: /es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 is-arguments: 1.1.1 is-map: 2.0.2 @@ -13771,7 +13794,7 @@ packages: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 has: 1.0.3 has-tostringtag: 1.0.0 @@ -14188,7 +14211,6 @@ packages: /fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - dev: false /fast-shallow-equal@1.0.0: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} @@ -14471,7 +14493,7 @@ packages: memfs: 3.5.3 minimatch: 3.1.2 schema-utils: 2.7.0 - semver: 7.5.4 + semver: 7.6.0 tapable: 1.1.3 typescript: 4.7.4 webpack: 5.89.0 @@ -14502,7 +14524,7 @@ packages: memfs: 3.5.3 minimatch: 3.1.2 schema-utils: 2.7.0 - semver: 7.5.4 + semver: 7.6.0 tapable: 1.1.3 typescript: 4.7.4 webpack: 5.90.3 @@ -14520,7 +14542,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: false /format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} @@ -14541,7 +14562,6 @@ packages: hexoid: 1.0.0 once: 1.4.0 qs: 6.11.2 - dev: false /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -14653,7 +14673,7 @@ packages: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 functions-have-names: 1.2.3 @@ -14700,6 +14720,7 @@ packages: has: 1.0.3 has-proto: 1.0.3 has-symbols: 1.0.3 + dev: true /get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} @@ -14746,8 +14767,8 @@ packages: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 /get-uri@6.0.2: resolution: {integrity: sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==} @@ -14781,7 +14802,7 @@ packages: hasBin: true dependencies: meow: 12.1.1 - semver: 7.5.4 + semver: 7.6.0 dev: true /git-up@7.0.0: @@ -15354,7 +15375,6 @@ packages: /hexoid@1.0.0: resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} engines: {node: '>=8'} - dev: false /history@4.10.1: resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} @@ -15862,7 +15882,7 @@ packages: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 has: 1.0.3 side-channel: 1.0.4 @@ -15943,14 +15963,14 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-tostringtag: 1.0.0 /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 is-typed-array: 1.1.12 /is-arrayish@0.2.1: @@ -15975,7 +15995,7 @@ packages: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-tostringtag: 1.0.0 /is-buffer@1.1.6: @@ -16193,7 +16213,7 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-tostringtag: 1.0.0 /is-regexp@1.0.0: @@ -16218,7 +16238,7 @@ packages: /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 /is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -16292,7 +16312,7 @@ packages: /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 /is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} @@ -17846,7 +17866,6 @@ packages: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} hasBin: true - dev: false /mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} @@ -18186,7 +18205,7 @@ packages: resolution: {integrity: sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ==} engines: {node: '>=10'} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: false /node-domexception@1.0.0: @@ -18321,7 +18340,7 @@ packages: dependencies: hosted-git-info: 7.0.1 is-core-module: 2.13.0 - semver: 7.5.4 + semver: 7.6.0 validate-npm-package-license: 3.0.4 dev: true @@ -18741,7 +18760,7 @@ packages: got: 12.6.1 registry-auth-token: 5.0.2 registry-url: 6.0.1 - semver: 7.5.4 + semver: 7.6.0 dev: true /param-case@3.0.4: @@ -19272,7 +19291,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.33 - ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.3.3) yaml: 2.3.2 dev: true @@ -19286,7 +19305,7 @@ packages: cosmiconfig: 8.3.6(typescript@4.7.4) jiti: 1.21.0 postcss: 8.4.33 - semver: 7.5.4 + semver: 7.6.0 webpack: 5.89.0 transitivePeerDependencies: - typescript @@ -19302,7 +19321,7 @@ packages: cosmiconfig: 8.3.6(typescript@4.7.4) jiti: 1.21.0 postcss: 8.4.35 - semver: 7.5.4 + semver: 7.6.0 webpack: 5.90.3 transitivePeerDependencies: - typescript @@ -22282,7 +22301,7 @@ packages: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 set-function-name: 2.0.1 @@ -22839,8 +22858,8 @@ packages: resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 @@ -22853,8 +22872,8 @@ packages: /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 is-regex: 1.1.4 /safe-stable-stringify@2.4.3: @@ -22972,7 +22991,7 @@ packages: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: true /semver@5.7.2: @@ -23010,7 +23029,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -23630,21 +23648,21 @@ packages: resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 /string.prototype.trimstart@1.0.6: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 @@ -23798,11 +23816,29 @@ packages: mime: 2.6.0 qs: 6.11.2 readable-stream: 3.6.2 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color dev: false + /superagent@8.1.2: + resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} + engines: {node: '>=6.4.0 <13 || >=14'} + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.4(supports-color@5.5.0) + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.11.2 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + /supercluster@7.1.5: resolution: {integrity: sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==} dependencies: @@ -23815,6 +23851,16 @@ packages: kdbush: 4.0.2 dev: false + /supertest@6.3.4: + resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==} + engines: {node: '>=6.4.0'} + dependencies: + methods: 1.1.2 + superagent: 8.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -24466,7 +24512,6 @@ packages: typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: false /ts-pattern@4.3.0: resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==} @@ -24553,15 +24598,15 @@ packages: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 is-typed-array: 1.1.12 /typed-array-byte-length@1.0.0: resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.3 is-typed-array: 1.1.12 @@ -24571,7 +24616,7 @@ packages: engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.5 - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.3 is-typed-array: 1.1.12 @@ -24579,7 +24624,7 @@ packages: /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 is-typed-array: 1.1.12 @@ -24657,7 +24702,7 @@ packages: /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 @@ -24957,7 +25002,7 @@ packages: is-yarn-global: 0.3.0 latest-version: 5.1.0 pupa: 2.1.1 - semver: 7.5.4 + semver: 7.6.0 semver-diff: 3.1.1 xdg-basedir: 4.0.0 dev: false @@ -25172,7 +25217,7 @@ packages: engines: {node: '>= 12'} dependencies: resolve-package-path: 4.0.3 - semver: 7.5.4 + semver: 7.6.0 dev: true /validator@13.11.0: @@ -25836,7 +25881,7 @@ packages: engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.5 - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 diff --git a/src/server/app.ts b/src/server/app.ts new file mode 100644 index 0000000..cf735af --- /dev/null +++ b/src/server/app.ts @@ -0,0 +1,75 @@ +import express from 'express'; +import 'express-async-errors'; +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, + trpcOpenapiDocument, + trpcOpenapiHttpHandler, +} from './trpc'; +import { env } from './utils/env'; +import cors from 'cors'; +import { serverStatusRouter } from './router/serverStatus'; +import { logger } from './utils/logger'; +import { monitorRouter } from './router/monitor'; +import { healthRouter } from './router/health'; +import path from 'path'; + +const app = express(); + +app.use(compression()); +app.use(express.json()); +app.use(passport.initialize()); +app.use(morgan('tiny')); +app.use(cors()); +app.use(express.static('public')); + +// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header +app.disable('x-powered-by'); + +app.use( + '/tracker.js', + express.static('./public/tracker.js', { + maxAge: '7d', + }) +); + +app.use('/health', healthRouter); +app.use('/api/website', websiteRouter); +app.use('/api/workspace', workspaceRouter); +app.use('/monitor', monitorRouter); +app.use('/telemetry', telemetryRouter); +app.use('/serverStatus', serverStatusRouter); + +app.use('/trpc', trpcExpressMiddleware); + +if (env.customTrackerScriptName) { + app.get(`/${env.customTrackerScriptName}`, (req, res) => + res.sendFile(path.resolve(__dirname, './public/tracker.js')) + ); +} + +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); +} + +// fallback +app.use('/*', (req, res) => { + if (req.method === 'GET' && req.accepts('html')) { + res.sendFile(path.join(process.cwd(), 'public', 'index.html')); + } +}); + +app.use((err: any, req: any, res: any, next: any) => { + logger.error('[express]', err); + res.status(500).json({ message: err.message }); +}); + +export { app }; diff --git a/src/server/main.ts b/src/server/main.ts index fd4f605..f9ec172 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -1,35 +1,16 @@ import 'dotenv/config'; import './init'; -import express from 'express'; -import 'express-async-errors'; -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, - trpcOpenapiDocument, - trpcOpenapiHttpHandler, -} from './trpc'; import { initUdpServer } from './udp/server'; import { createServer } from 'http'; import { initSocketio } from './ws'; import { monitorManager } from './model/monitor'; import { env } from './utils/env'; -import cors from 'cors'; -import { serverStatusRouter } from './router/serverStatus'; import { initCronjob } from './cronjob'; import { logger } from './utils/logger'; -import { monitorRouter } from './router/monitor'; -import { healthRouter } from './router/health'; -import path from 'path'; +import { app } from './app'; const port = env.port; -const app = express(); const httpServer = createServer(app); initUdpServer(port); @@ -40,56 +21,6 @@ initCronjob(); monitorManager.startAll(); -app.use(compression()); -app.use(express.json()); -app.use(passport.initialize()); -app.use(morgan('tiny')); -app.use(cors()); -app.use(express.static('public')); - -// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header -app.disable('x-powered-by'); - -app.use( - '/tracker.js', - express.static('./public/tracker.js', { - maxAge: '7d', - }) -); - -app.use('/health', healthRouter); -app.use('/api/website', websiteRouter); -app.use('/api/workspace', workspaceRouter); -app.use('/monitor', monitorRouter); -app.use('/telemetry', telemetryRouter); -app.use('/serverStatus', serverStatusRouter); - -app.use('/trpc', trpcExpressMiddleware); - -if (env.customTrackerScriptName) { - app.get(`/${env.customTrackerScriptName}`, (req, res) => - res.sendFile(path.resolve(__dirname, './public/tracker.js')) - ); -} - -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); -} - -// fallback -app.use('/*', (req, res) => { - if (req.method === 'GET' && req.accepts('html')) { - res.sendFile(path.join(process.cwd(), 'public', 'index.html')); - } -}); - -app.use((err: any, req: any, res: any, next: any) => { - logger.error('[express]', err); - res.status(500).json({ message: err.message }); -}); - httpServer.listen(port, () => { logger.info(`Server is listening on port ${port}...`); if (env.allowOpenapi) { diff --git a/src/server/model/telemetry.ts b/src/server/model/telemetry.ts index 9f80b5a..94a132b 100644 --- a/src/server/model/telemetry.ts +++ b/src/server/model/telemetry.ts @@ -12,7 +12,14 @@ import { import { SESSION_COLUMNS } from '../utils/const'; export async function recordTelemetryEvent(req: Request) { - const { url = req.headers.referer, name, ...others } = req.query; + const { + url = req.headers.referer, + name, + title, + start, + fullNum, + ...others + } = req.query; if (!(url && typeof url === 'string')) { return; @@ -272,8 +279,8 @@ export async function getTelemetrySessionMetrics( where "TelemetryEvent"."telemetryId" = ${telemetryId} and "TelemetryEvent"."createdAt" between ${params.startDate}::timestamptz and ${ - params.endDate - }::timestamptz + params.endDate + }::timestamptz ${filterQuery} group by 1 ${includeCountry ? Prisma.sql([', 3']) : Prisma.empty} @@ -301,8 +308,8 @@ export async function getTelemetryPageviewMetrics( where "TelemetryEvent"."telemetryId" = ${telemetryId} and "TelemetryEvent"."createdAt" between ${params.startDate}::timestamptz and ${ - params.endDate - }::timestamptz + params.endDate + }::timestamptz ${filterQuery} group by 1 order by 2 desc diff --git a/src/server/package.json b/src/server/package.json index c37f331..a075dec 100644 --- a/src/server/package.json +++ b/src/server/package.json @@ -79,6 +79,7 @@ "@types/passport-jwt": "^3.0.9", "@types/ping": "^0.4.2", "@types/request-ip": "^0.0.38", + "@types/supertest": "^6.0.2", "@types/swagger-ui-express": "^4.1.5", "@types/tcp-ping": "^0.1.5", "@types/uuid": "^9.0.7", @@ -88,6 +89,7 @@ "prisma": "5.4.2", "prisma-json-types-generator": "3.0.3", "prisma-zod-generator": "0.8.13", + "supertest": "^6.3.4", "tailwindcss": "^3.3.5", "vite": "^5.0.12", "vitest": "^1.2.1" diff --git a/src/server/router/__test__/telemetry.test.ts b/src/server/router/__test__/telemetry.test.ts new file mode 100644 index 0000000..401ddfa --- /dev/null +++ b/src/server/router/__test__/telemetry.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'vitest'; +import { createTestContext } from '../../tests/utils'; +import { generateETag } from '../../utils/common'; + +describe('telemetry router', () => { + const { app, createTestUser, createTestTelemetry } = createTestContext(); + + describe('/badge', () => { + test('check header', async () => { + const { workspace } = await createTestUser(); + const telemetry = await createTestTelemetry(workspace.id); + + const { header, status } = await app.get( + `/telemetry/${workspace.id}/${telemetry.id}/badge.svg` + ); + + expect(status).toBe(200); + expect(header['content-type']).toBe('image/svg+xml; charset=utf-8'); + expect(header['cache-control']).toBe( + 'no-cache,max-age=0,no-store,s-maxage=0,proxy-revalidate' + ); + expect(header['etag']).toBe('"f2a1eaa06e04bf61f14a783fdc8ac38c"'); + expect(header['etag']).toBe(generateETag('visitor|0')); + }); + + test('check header change with count', async () => { + const { workspace } = await createTestUser(); + const telemetry = await createTestTelemetry(workspace.id); + + const { header, status } = await app.get( + `/telemetry/${workspace.id}/${telemetry.id}/badge.svg?url=https://www.google.com/` + ); + + expect(status).toBe(200); + expect(header['etag']).toBe('"988dd9b8dab74e167225028b4b19faf7"'); + expect(header['etag']).toBe(generateETag('visitor|1')); + }); + }); +}); diff --git a/src/server/router/telemetry.ts b/src/server/router/telemetry.ts index a753c47..f8722d3 100644 --- a/src/server/router/telemetry.ts +++ b/src/server/router/telemetry.ts @@ -3,6 +3,7 @@ import { query, validate } from '../middleware/validate'; import { recordTelemetryEvent, sumTelemetryEvent } from '../model/telemetry'; import { generateETag, numify } from '../utils/common'; import { makeBadge } from 'badge-maker'; +import { env } from '../utils/env'; export const telemetryRouter = Router(); @@ -60,7 +61,11 @@ telemetryRouter.get( query('url').optional().isURL() ), async (req, res) => { - recordTelemetryEvent(req); + if (env.isTest) { + await recordTelemetryEvent(req); + } else { + recordTelemetryEvent(req); + } res .header('Content-Type', 'image/gif') @@ -87,7 +92,11 @@ telemetryRouter.get( const start = req.query.start ? Number(req.query.start) : 0; const fullNum = req.query.fullNum === 'true'; - recordTelemetryEvent(req); + if (env.isTest) { + await recordTelemetryEvent(req); + } else { + recordTelemetryEvent(req); + } const num = await sumTelemetryEvent(req); const count = num + start; diff --git a/src/server/tests/utils.ts b/src/server/tests/utils.ts new file mode 100644 index 0000000..f9f4aa7 --- /dev/null +++ b/src/server/tests/utils.ts @@ -0,0 +1,76 @@ +import supertest from 'supertest'; +import { afterAll } from 'vitest'; +import { app } from '../app'; +import { prisma } from '../model/_client'; +import { PrismaPromise } from '@prisma/client'; +import { createUser } from '../model/user'; +import { nanoid } from 'nanoid'; + +export function createTestContext() { + const testDataCallback: (() => PrismaPromise | Promise)[] = []; + + afterAll(async () => { + for (const cb of testDataCallback.reverse()) { + await cb(); + } + }); + + const createTestUser = async () => { + const data = await createUser(nanoid(), ''); + + testDataCallback.push(async () => { + await prisma.user.delete({ + where: { id: data.id }, + }); + + await prisma.workspace.delete({ + where: { id: data.currentWorkspace.id }, + }); + }); + + return { + user: data, + workspace: data.currentWorkspace, + }; + }; + + const createTestWorkspace = async () => { + const data = await prisma.workspace.create({ + data: { + name: 'Test Workspace', + }, + }); + + testDataCallback.push(() => + prisma.workspace.delete({ + where: { id: data.id }, + }) + ); + + return data; + }; + + const createTestTelemetry = async (workspaceId: string) => { + const data = await prisma.telemetry.create({ + data: { + name: 'Test Telemetry', + workspaceId, + }, + }); + + testDataCallback.push(() => + prisma.telemetry.delete({ + where: { id: data.id }, + }) + ); + + return data; + }; + + return { + app: supertest(app), + createTestUser, + createTestWorkspace, + createTestTelemetry, + }; +} diff --git a/src/server/utils/env.ts b/src/server/utils/env.ts index 1959a12..9f04644 100644 --- a/src/server/utils/env.ts +++ b/src/server/utils/env.ts @@ -1,5 +1,6 @@ export const env = { isProd: process.env.NODE_ENV === 'production', + isTest: process.env.NODE_ENV === 'test', port: Number(process.env.PORT || 12345), allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER), allowOpenapi: checkEnvTrusty(process.env.ALLOW_OPENAPI),