diff --git a/bun.lockb b/bun.lockb index eb973bacbb595524c04f0abd0000e6b6fc16a0d4..b826abc55b56b5e0e1d717578c253b23310db7dc 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index a48e32787e71e333fbcc445633f981decbc48a56..8f328ebadf5c95b01d99cc274bec8e68d7044efd 100644 --- a/package.json +++ b/package.json @@ -10,20 +10,30 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.750.0", + "@hono/arktype-validator": "^2.0.0", "@hono/swagger-ui": "^0.5.0", + "@hono/typebox-validator": "^0.3.2", + "@hono/valibot-validator": "^0.5.2", "@hono/zod-openapi": "^0.18.3", "@hono/zod-validator": "^0.2.2", + "@scalar/hono-api-reference": "^0.7.2", + "@sinclair/typebox": "^0.34.31", + "@valibot/to-json-schema": "^1.0.0", "archiver": "^7.0.1", + "arktype": "^2.1.12", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "drizzle-orm": "^0.31.2", "drizzle-zod": "^0.5.1", "file-type": "^20.1.0", "hono": "^4.4.8", + "hono-openapi": "^0.4.6", "postgres": "^3.4.4", "reflect-metadata": "^0.2.2", "typedi": "^0.10.0", - "zod": "^3.23.8" + "valibot": "^1.0.0", + "zod": "^3.23.8", + "zod-openapi": "^4.2.3" }, "devDependencies": { "@eslint/js": "^9.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11a763d9f26b5e96d4c00114f11900ef8c4bea72..b9325c65d8830fb6e38f489d4b73b7afdbaf09d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,18 +11,39 @@ importers: '@aws-sdk/client-s3': specifier: ^3.750.0 version: 3.750.0 + '@hono/arktype-validator': + specifier: ^2.0.0 + version: 2.0.0(arktype@2.1.12)(hono@4.6.1) '@hono/swagger-ui': specifier: ^0.5.0 version: 0.5.0(hono@4.6.1) + '@hono/typebox-validator': + specifier: ^0.3.2 + version: 0.3.2(@sinclair/typebox@0.34.31)(hono@4.6.1) + '@hono/valibot-validator': + specifier: ^0.5.2 + version: 0.5.2(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2)) '@hono/zod-openapi': specifier: ^0.18.3 version: 0.18.3(hono@4.6.1)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 version: 0.2.2(hono@4.6.1)(zod@3.23.8) + '@scalar/hono-api-reference': + specifier: ^0.7.2 + version: 0.7.2(hono@4.6.1) + '@sinclair/typebox': + specifier: ^0.34.31 + version: 0.34.31 + '@valibot/to-json-schema': + specifier: ^1.0.0 + version: 1.0.0(valibot@1.0.0(typescript@5.6.2)) archiver: specifier: ^7.0.1 version: 7.0.1 + arktype: + specifier: ^2.1.12 + version: 2.1.12 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -41,6 +62,9 @@ importers: hono: specifier: ^4.4.8 version: 4.6.1 + hono-openapi: + specifier: ^0.4.6 + version: 0.4.6(@hono/arktype-validator@2.0.0(arktype@2.1.12)(hono@4.6.1))(@hono/typebox-validator@0.3.2(@sinclair/typebox@0.34.31)(hono@4.6.1))(@hono/valibot-validator@0.5.2(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2)))(@hono/zod-validator@0.2.2(hono@4.6.1)(zod@3.23.8))(@sinclair/typebox@0.34.31)(@valibot/to-json-schema@1.0.0(valibot@1.0.0(typescript@5.6.2)))(arktype@2.1.12)(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2))(zod-openapi@4.2.3(zod@3.23.8))(zod@3.23.8) postgres: specifier: ^3.4.4 version: 3.4.4 @@ -50,9 +74,15 @@ importers: typedi: specifier: ^0.10.0 version: 0.10.0 + valibot: + specifier: ^1.0.0 + version: 1.0.0(typescript@5.6.2) zod: specifier: ^3.23.8 version: 3.23.8 + zod-openapi: + specifier: ^4.2.3 + version: 4.2.3(zod@3.23.8) devDependencies: '@eslint/js': specifier: ^9.5.0 @@ -87,6 +117,16 @@ importers: packages: + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + + '@ark/schema@0.45.2': + resolution: {integrity: sha512-rMzFbO2RuwukEzdBq42D/WS0OS1McpRhaSJHdvuGc/Xfxwn6gw1GwG6+JDNsVuto0TMRwA+zteCDMBW0FXxAPw==} + + '@ark/util@0.45.2': + resolution: {integrity: sha512-atlBhyMEfEB8yFDmYh+m9aHdOwjOoEEOF76k1ZxVpQLUdpuiofBou4JtKBR1fojpnlNhevdlWg9KO/4eTjuRBA==} + '@asteasolutions/zod-to-openapi@7.3.0': resolution: {integrity: sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q==} peerDependencies: @@ -551,11 +591,29 @@ packages: resolution: {integrity: sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hono/arktype-validator@2.0.0': + resolution: {integrity: sha512-ICNZrK6Qcw6gyPfW53ONXI4JomRcks0fQhqzn9EWsfr6nlL6BNXQ96vIgVDU8qimcbJ2m3GJFAqgzOvWbZk3jw==} + peerDependencies: + arktype: ^2.0.0-dev.14 + hono: '*' + '@hono/swagger-ui@0.5.0': resolution: {integrity: sha512-MWYYSv9kC8IwFBLZdwgZZMT9zUq2C/4/ekuyEYOkHEgUMqu+FG3eebtBZ4ofMh60xYRxRR2BgQGoNIILys/PFg==} peerDependencies: hono: '*' + '@hono/typebox-validator@0.3.2': + resolution: {integrity: sha512-MIxYk80vtuFnkvbNreMubZ/vLoNCCQivLH8n3vNDY5dFNsZ12BFaZV3FmsLJHGibNMMpmkO6y4w5gNWY4KzSdg==} + peerDependencies: + '@sinclair/typebox': '>=0.31.15 <1' + hono: '>=3.9.0' + + '@hono/valibot-validator@0.5.2': + resolution: {integrity: sha512-WbTr8PCFNAME0ale62rmmq+E8bGp5a7VPiVNmNT+Ue12GJE3Ax+CWBGtHhR0TzAEiloPvJND3IbjHIgtNtsqhw==} + peerDependencies: + hono: '>=3.9.0' + valibot: ^1.0.0 || ^1.0.0-beta.4 || ^1.0.0-rc + '@hono/zod-openapi@0.18.3': resolution: {integrity: sha512-bNlRDODnp7P9Fs13ZPajEOt13G0XwXKfKRHMEFCphQsFiD1Y+twzHaglpNAhNcflzR1DQwHY92ZS06b4LTPbIQ==} engines: {node: '>=16.0.0'} @@ -587,6 +645,9 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -603,6 +664,27 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@scalar/core@0.2.2': + resolution: {integrity: sha512-jT6vfz37yQnqVjj8kXYEmV2cZvODW1A0PXjxZ9DzKqjm9tIssNwP4vvcdD1FSuiMcj+rgxAxOjIYMI+ybI/9RQ==} + engines: {node: '>=18'} + + '@scalar/hono-api-reference@0.7.2': + resolution: {integrity: sha512-CnxRjGfAWPGkV0D5TEwogvn7JSx/f9+ag6vQ6g25GigSDyj/UkxYbZqwe/QOV/+2EWruY3ypOvPuNMf7nEQhdQ==} + engines: {node: '>=18'} + peerDependencies: + hono: ^4.0.0 + + '@scalar/openapi-types@0.1.9': + resolution: {integrity: sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g==} + engines: {node: '>=18'} + + '@scalar/types@0.1.2': + resolution: {integrity: sha512-5kCLQRwAYWt1ds110EaUb9yonc3KoQYNyo4YUCigJLOnoNugbqkEX0zRudGevItiuk+xg4uOYd30r3C+6xAasA==} + engines: {node: '>=18'} + + '@sinclair/typebox@0.34.31': + resolution: {integrity: sha512-qQ71T9DsITbX3dVCrcBERbs11YuSMg3wZPnT472JhqhWGPdiLgyvihJXU8m+ADJtJvRdjATIiACJD22dEknBrQ==} + '@smithy/abort-controller@4.0.1': resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==} engines: {node: '>=18.0.0'} @@ -828,6 +910,9 @@ packages: '@types/bun@1.1.9': resolution: {integrity: sha512-SXJRejXpmAc3qxyN/YS4/JGWEzLf4dDBa5fLtRDipQXHqNccuMU4EUYCooXNTsylG0DmwFQsGgEDHxZF+3DqRw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@20.12.14': resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} @@ -895,6 +980,14 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} + '@unhead/schema@1.11.20': + resolution: {integrity: sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==} + + '@valibot/to-json-schema@1.0.0': + resolution: {integrity: sha512-/9crJgPptVsGCL6X+JPDQyaJwkalSZ/52WuF8DiRUxJgcmpNdzYRfZ+gqMEP8W3CTVfuMWPqqvIgfwJ97f9Etw==} + peerDependencies: + valibot: ^1.0.0 + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -939,6 +1032,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arktype@2.1.12: + resolution: {integrity: sha512-FdPoK/i+xQTehWfBBBSyCOZSN7ZMUjPdwkJR03/My+ckeuyaEGNi2tII1GIZ5WJep3i6vmFJ7PK1+4wNRDHKFw==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -992,6 +1088,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1337,10 +1437,60 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hono-openapi@0.4.6: + resolution: {integrity: sha512-wSDySp2cS5Zcf1OeLG7nCP3eMsCpcDomN137T9B6/Z5Qq3D0nWgMf0I3Gl41SE1rE37OBQ0Smqx3LOP9Hk//7A==} + peerDependencies: + '@hono/arktype-validator': ^2.0.0 + '@hono/effect-validator': ^1.2.0 + '@hono/typebox-validator': ^0.2.0 || ^0.3.0 + '@hono/valibot-validator': ^0.5.1 + '@hono/zod-validator': ^0.4.1 + '@sinclair/typebox': ^0.34.9 + '@valibot/to-json-schema': ^1.0.0-beta.3 + arktype: ^2.0.0-rc.25 + effect: ^3.11.3 + hono: ^4.6.13 + openapi-types: ^12.1.3 + valibot: ^1.0.0-beta.9 + zod: ^3.23.8 + zod-openapi: ^4.0.0 + peerDependenciesMeta: + '@hono/arktype-validator': + optional: true + '@hono/effect-validator': + optional: true + '@hono/typebox-validator': + optional: true + '@hono/valibot-validator': + optional: true + '@hono/zod-validator': + optional: true + '@sinclair/typebox': + optional: true + '@valibot/to-json-schema': + optional: true + arktype: + optional: true + effect: + optional: true + hono: + optional: true + openapi-types: + optional: true + valibot: + optional: true + zod: + optional: true + zod-openapi: + optional: true + hono@4.6.1: resolution: {integrity: sha512-6NGwvttY1+HAFii08VYiEKI6ETPAFbpLntpm2M/MogEsAFWdZV74UNT+2M4bmqX90cIQhjlpBSP+tO+CfB0uww==} engines: {node: '>=16.0.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1402,6 +1552,10 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-walker@2.0.0: + resolution: {integrity: sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw==} + engines: {node: '>=10'} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1701,6 +1855,14 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + valibot@1.0.0: + resolution: {integrity: sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1727,15 +1889,36 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zhead@2.2.4: + resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod-openapi@4.2.3: + resolution: {integrity: sha512-i0SqpcdXfsvVWTIY1Jl3Tk421s9fBIkpXvaA86zDas+8FjfZjm+GX6ot6SPB2SyuHwUNTN02gE5uIVlYXlyrDQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.21.4 + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} snapshots: + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + + '@ark/schema@0.45.2': + dependencies: + '@ark/util': 0.45.2 + + '@ark/util@0.45.2': {} + '@asteasolutions/zod-to-openapi@7.3.0(zod@3.23.8)': dependencies: openapi3-ts: 4.4.0 @@ -2381,10 +2564,25 @@ snapshots: dependencies: levn: 0.4.1 + '@hono/arktype-validator@2.0.0(arktype@2.1.12)(hono@4.6.1)': + dependencies: + arktype: 2.1.12 + hono: 4.6.1 + '@hono/swagger-ui@0.5.0(hono@4.6.1)': dependencies: hono: 4.6.1 + '@hono/typebox-validator@0.3.2(@sinclair/typebox@0.34.31)(hono@4.6.1)': + dependencies: + '@sinclair/typebox': 0.34.31 + hono: 4.6.1 + + '@hono/valibot-validator@0.5.2(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2))': + dependencies: + hono: 4.6.1 + valibot: 1.0.0(typescript@5.6.2) + '@hono/zod-openapi@0.18.3(hono@4.6.1)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.3.0(zod@3.23.8) @@ -2415,6 +2613,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jsdevtools/ono@7.1.3': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2430,6 +2630,25 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@scalar/core@0.2.2': + dependencies: + '@scalar/types': 0.1.2 + + '@scalar/hono-api-reference@0.7.2(hono@4.6.1)': + dependencies: + '@scalar/core': 0.2.2 + hono: 4.6.1 + + '@scalar/openapi-types@0.1.9': {} + + '@scalar/types@0.1.2': + dependencies: + '@scalar/openapi-types': 0.1.9 + '@unhead/schema': 1.11.20 + zod: 3.23.8 + + '@sinclair/typebox@0.34.31': {} + '@smithy/abort-controller@4.0.1': dependencies: '@smithy/types': 4.1.0 @@ -2779,6 +2998,8 @@ snapshots: dependencies: bun-types: 1.1.27 + '@types/json-schema@7.0.15': {} + '@types/node@20.12.14': dependencies: undici-types: 5.26.5 @@ -2872,6 +3093,15 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + '@unhead/schema@1.11.20': + dependencies: + hookable: 5.5.3 + zhead: 2.2.4 + + '@valibot/to-json-schema@1.0.0(valibot@1.0.0(typescript@5.6.2))': + dependencies: + valibot: 1.0.0(typescript@5.6.2) + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -2921,6 +3151,11 @@ snapshots: argparse@2.0.1: {} + arktype@2.1.12: + dependencies: + '@ark/schema': 0.45.2 + '@ark/util': 0.45.2 + array-union@2.1.0: {} async@3.2.6: {} @@ -2970,6 +3205,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + clone@2.1.2: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3289,8 +3526,26 @@ snapshots: has-flag@4.0.0: {} + hono-openapi@0.4.6(@hono/arktype-validator@2.0.0(arktype@2.1.12)(hono@4.6.1))(@hono/typebox-validator@0.3.2(@sinclair/typebox@0.34.31)(hono@4.6.1))(@hono/valibot-validator@0.5.2(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2)))(@hono/zod-validator@0.2.2(hono@4.6.1)(zod@3.23.8))(@sinclair/typebox@0.34.31)(@valibot/to-json-schema@1.0.0(valibot@1.0.0(typescript@5.6.2)))(arktype@2.1.12)(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2))(zod-openapi@4.2.3(zod@3.23.8))(zod@3.23.8): + dependencies: + json-schema-walker: 2.0.0 + optionalDependencies: + '@hono/arktype-validator': 2.0.0(arktype@2.1.12)(hono@4.6.1) + '@hono/typebox-validator': 0.3.2(@sinclair/typebox@0.34.31)(hono@4.6.1) + '@hono/valibot-validator': 0.5.2(hono@4.6.1)(valibot@1.0.0(typescript@5.6.2)) + '@hono/zod-validator': 0.2.2(hono@4.6.1)(zod@3.23.8) + '@sinclair/typebox': 0.34.31 + '@valibot/to-json-schema': 1.0.0(valibot@1.0.0(typescript@5.6.2)) + arktype: 2.1.12 + hono: 4.6.1 + valibot: 1.0.0(typescript@5.6.2) + zod: 3.23.8 + zod-openapi: 4.2.3(zod@3.23.8) + hono@4.6.1: {} + hookable@5.5.3: {} + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -3336,6 +3591,11 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-walker@2.0.0: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + clone: 2.1.2 + json-stable-stringify-without-jsonify@1.0.1: {} keyv@4.5.4: @@ -3607,6 +3867,10 @@ snapshots: uuid@9.0.1: {} + valibot@1.0.0(typescript@5.6.2): + optionalDependencies: + typescript: 5.6.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3629,10 +3893,16 @@ snapshots: yocto-queue@0.1.0: {} + zhead@2.2.4: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 compress-commons: 6.0.2 readable-stream: 4.7.0 + zod-openapi@4.2.3(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.23.8: {} diff --git a/src/documentation/achievementsDescribers.ts b/src/documentation/achievementsDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6a1ba8096494c28ffb6fb55d9133c4f80e52d88 --- /dev/null +++ b/src/documentation/achievementsDescribers.ts @@ -0,0 +1,301 @@ +import { describeRoute } from 'hono-openapi' + +const createRoute = describeRoute({ + method: 'POST', + path: '/create', + description: 'Create a new achievement', + tags: ['Achievements'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { type: 'string', maxLength: 255 }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string', enum: ['inactive', 'active'] }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + required: ['name', 'repeatable', 'is_resettable', 'state'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Achievement created successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not create achievement', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const updateRoute = describeRoute({ + method: 'POST', + path: '/update', + description: 'Update an existing achievement', + tags: ['Achievements'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string', maxLength: 255 }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string', enum: ['inactive', 'active'] }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + required: ['id'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Achievement updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update achievement', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const deleteRoute = describeRoute({ + method: 'POST', + path: '/delete/:id', + description: 'Delete an achievement by ID', + tags: ['Achievements'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Achievement deleted successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not delete achievement', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const getAchievementRoute = describeRoute({ + method: 'GET', + path: '/:id', + description: 'Get a specific achievement by ID', + tags: ['Achievements'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Achievement found successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '404': { + description: 'Achievement not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const getAchievementsRoute = describeRoute({ + method: 'GET', + path: '/', + description: 'Get all achievements', + tags: ['Achievements'], + responses: { + '200': { + description: 'Achievements found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + }, + '404': { + description: 'Achievements not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +export { + createRoute, + updateRoute, + deleteRoute, + getAchievementRoute, + getAchievementsRoute, +} diff --git a/src/documentation/actionsDescribers.ts b/src/documentation/actionsDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..9942707c24e2c15ccc676447c534bb8407947874 --- /dev/null +++ b/src/documentation/actionsDescribers.ts @@ -0,0 +1,302 @@ +import { describeRoute } from 'hono-openapi' + +const createActionRoute = describeRoute({ + method: 'POST', + path: '/create', + description: 'Create a new action', + tags: ['Actions'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { type: 'string', maxLength: 255 }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string', enum: ['inactive', 'active'] }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + required: ['name', 'repeatable', 'is_resettable', 'state'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Action created successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not create action', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }) + + const getActionsRoute = describeRoute({ + method: 'GET', + path: '/actions', + description: 'Get all actions', + tags: ['Actions'], + responses: { + '200': { + description: 'Actions found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + }, + '404': { + description: 'Actions not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }) + + const getActionByNameRoute = describeRoute({ + method: 'GET', + path: '/:name', + description: 'Get an action by name', + tags: ['Actions'], + parameters: [ + { + name: 'name', + in: 'path', + required: true, + schema: { + type: 'string', + }, + }, + ], + responses: { + '200': { + description: 'Action found successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '404': { + description: 'Action not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }) + + const updateActionRoute = describeRoute({ + method: 'POST', + path: '/update', + description: 'Update an existing action', + tags: ['Actions'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string', maxLength: 255 }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string', enum: ['inactive', 'active'] }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + required: ['id'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Action updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update action', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }) + + const deleteActionRoute = describeRoute({ + method: 'POST', + path: '/delete/:id', + description: 'Delete an action by ID', + tags: ['Actions'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Action deleted successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + reward_experience: { type: 'number' }, + reward_points: { type: 'number' }, + state: { type: 'string' }, + repeatable: { type: 'integer' }, + is_resettable: { type: 'boolean' } + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not delete action', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }) + + export { + createActionRoute, + getActionsRoute, + getActionByNameRoute, + updateActionRoute, + deleteActionRoute, + } + \ No newline at end of file diff --git a/src/documentation/authDescribers.ts b/src/documentation/authDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..694b3cd20b93dbbb3654ad19b529e2cc5968d001 --- /dev/null +++ b/src/documentation/authDescribers.ts @@ -0,0 +1,204 @@ +import { describeRoute } from 'hono-openapi' +import { HttpStatus } from '@/services/error.service' + +const signinRoute = describeRoute({ + method: 'POST', + path: '/signin', + description: 'Sign in the user with email and password', + tags: ['Auth'], // Tag adicionada + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/AuthInput', // Referência ao schema de autenticação + }, + }, + }, + }, + responses: { + [HttpStatus.OK]: { + description: 'User signed in successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + token: { type: 'string' }, + }, + }, + }, + }, + }, + [HttpStatus.NOT_FOUND]: { + description: 'Invalid or non-existent user', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'error' }, + code: { type: 'integer', example: HttpStatus.NOT_FOUND }, + message: { type: 'string', example: 'Invalid or inexistent user' }, + }, + }, + }, + }, + }, + }, +}) + +const signupRoute = describeRoute({ + method: 'POST', + path: '/signup', + description: 'Sign up a new user and create corresponding user stats', + tags: ['Auth'], // Tag adicionada + requestBody: { + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/UserInput', // Referência ao schema de entrada do usuário + }, + }, + }, + }, + responses: { + [HttpStatus.OK]: { + description: 'User signed up successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + user: { + $ref: '#/components/schemas/UserDto', // Referência ao schema de usuário + }, + userStats: { + $ref: '#/components/schemas/UserStatsDto', // Referência ao schema de estatísticas do usuário + }, + }, + }, + }, + }, + }, + [HttpStatus.BAD_REQUEST]: { + description: 'Error while creating user', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'error' }, + message: { type: 'string', example: 'could not create' }, + }, + }, + }, + }, + }, + }, +}) + +const requestPasswordResetRoute = describeRoute({ + method: 'POST', + path: '/request/:email', + description: 'Request password reset for the given email', + tags: ['Auth'], // Tag adicionada + parameters: [ + { + name: 'email', + in: 'path', + required: true, + schema: { type: 'string', format: 'email' }, + }, + ], + responses: { + [HttpStatus.OK]: { + description: 'Password reset ticket created successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'string' }, + tokenHash: { type: 'string' }, + createdAt: { type: 'string', format: 'date-time' }, + }, + }, + }, + }, + }, + [HttpStatus.BAD_REQUEST]: { + description: 'Could not send password recovery email', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'error' }, + message: { type: 'string', example: 'could not send recovery password email' }, + }, + }, + }, + }, + }, + }, +}) + +const resetPasswordRoute = describeRoute({ + method: 'POST', + path: '/reset', + description: 'Reset the password using the provided email, token, and new password', + tags: ['Auth'], // Tag adicionada + parameters: [ + { + name: 'email', + in: 'query', + required: true, + schema: { type: 'string', format: 'email' }, + }, + { + name: 'token', + in: 'query', + required: true, + schema: { type: 'string' }, + }, + { + name: 'password', + in: 'query', + required: true, + schema: { type: 'string', minLength: 8 }, + }, + ], + responses: { + [HttpStatus.OK]: { + description: 'Password reset successfully', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/UserDto', // Referência ao schema de usuário + }, + }, + }, + }, + [HttpStatus.BAD_REQUEST]: { + description: 'Could not reset password', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'error' }, + message: { type: 'string', example: 'could not send recovery password email' }, + }, + }, + }, + }, + }, + }, +}) + +export { + signinRoute, + signupRoute, + requestPasswordResetRoute, + resetPasswordRoute, +} diff --git a/src/documentation/collectionLikesDescribers.ts b/src/documentation/collectionLikesDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..1364ee71d0586a7e9c6a15f463afd025eb2112ef --- /dev/null +++ b/src/documentation/collectionLikesDescribers.ts @@ -0,0 +1,334 @@ +import { describeRoute } from 'hono-openapi' + +const associateRoute = describeRoute({ + method: 'POST', + path: '/associate', + description: 'Associate likes to a collection for multiple users', + tags: ['Collection Likes'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + collectionId: { type: 'integer' }, + userIds: { type: 'array', items: { type: 'integer' } } + }, + required: ['collectionId', 'userIds'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Likes associated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not associate likes', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const removeLikesRoute = describeRoute({ + method: 'POST', + path: '/:collectionId/delete/:userId', + description: 'Remove likes from a collection for a user', + tags: ['Collection Likes'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + { + name: 'userId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Likes removed successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not remove likes', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const updateLikesRoute = describeRoute({ + method: 'POST', + path: '/update/likes', + description: 'Update likes for a collection for multiple users', + tags: ['Collection Likes'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + collectionId: { type: 'integer' }, + userIds: { type: 'array', items: { type: 'integer' } } + }, + required: ['collectionId', 'userIds'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Likes updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update likes', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const getLikesByCollectionRoute = describeRoute({ + method: 'GET', + path: '/:collectionId/likes', + description: 'Get all likes for a specific collection', + tags: ['Collection Likes'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Likes fetched successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'integer', + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not fetch likes', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const checkLikeExistsRoute = describeRoute({ + method: 'GET', + path: '/:collectionId/likes/:userId/exists', + description: 'Check if a like exists for a specific collection and user', + tags: ['Collection Likes'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + { + name: 'userId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Like exists check completed successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + exists: { type: 'boolean' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not check if like exists', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +const getCollectionsByUserRoute = describeRoute({ + method: 'GET', + path: '/user/:userId/collections', + description: 'Get all collections for a specific user', + tags: ['Collection Likes'], + parameters: [ + { + name: 'userId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collections fetched successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + likesCount: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not fetch collections', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +export { + associateRoute, + removeLikesRoute, + updateLikesRoute, + getLikesByCollectionRoute, + checkLikeExistsRoute, + getCollectionsByUserRoute, +} diff --git a/src/documentation/collectionResourcesDescribers.ts b/src/documentation/collectionResourcesDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa01b5085aca2491ee6118583bf7110600451bb9 --- /dev/null +++ b/src/documentation/collectionResourcesDescribers.ts @@ -0,0 +1,280 @@ +import { describeRoute } from 'hono-openapi' + +// Descrição para a rota POST /associate +const associateRoute = describeRoute({ + method: 'POST', + path: '/associate', + description: 'Associate resources with a collection', + tags: ['Collection Resources'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + collectionId: { type: 'integer' }, + resourceIds: { + type: 'array', + items: { type: 'integer' }, + }, + }, + required: ['collectionId', 'resourceIds'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Resources associated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + '400': { + description: 'Failed to associate resources', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Descrição para a rota POST /:collectionId/delete/:resourceId +const deleteResourceRoute = describeRoute({ + method: 'POST', + path: '/:collectionId/delete/:resourceId', + description: 'Remove resources from a collection', + tags: ['Collection Resources'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + { + name: 'resourceId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + ], + responses: { + '200': { + description: 'Resource removed successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + '400': { + description: 'Failed to remove resource', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Descrição para a rota GET /:collectionId/resources +const getResourcesRoute = describeRoute({ + method: 'GET', + path: '/:collectionId/resources', + description: 'Get all resources of a collection', + tags: ['Collection Resources'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + ], + responses: { + '200': { + description: 'Resources fetched successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + resourceId: { type: 'integer' }, + resourceName: { type: 'string' }, + // Add other properties based on the resource object + }, + }, + }, + }, + }, + }, + '400': { + description: 'Failed to fetch resources', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Descrição para a rota GET /:collectionId/resources/:resourceId/exists +const checkResourceExistsRoute = describeRoute({ + method: 'GET', + path: '/:collectionId/resources/:resourceId/exists', + description: 'Check if a resource is associated with a collection', + tags: ['Collection Resources'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + { + name: 'resourceId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + ], + responses: { + '200': { + description: 'Resource association exists or not', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + exists: { type: 'boolean' }, + }, + }, + }, + }, + }, + '400': { + description: 'Failed to check resource association', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Descrição para a rota GET /resource/:resourceId/collections +const getCollectionsForResourceRoute = describeRoute({ + method: 'GET', + path: '/resource/:resourceId/collections', + description: 'Get collections associated with a resource', + tags: ['Collection Resources'], + parameters: [ + { + name: 'resourceId', + in: 'path', + required: true, + schema: { type: 'integer' }, + }, + ], + responses: { + '200': { + description: 'Collections fetched successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + collectionId: { type: 'integer' }, + collectionName: { type: 'string' }, + // Add other properties based on the collection object + }, + }, + }, + }, + }, + }, + '400': { + description: 'Failed to fetch collections', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +export { + associateRoute, + deleteResourceRoute, + getResourcesRoute, + checkResourceExistsRoute, + getCollectionsForResourceRoute, +} diff --git a/src/documentation/collectionStatsDescribers.ts b/src/documentation/collectionStatsDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..963325cf1d2ccc9611eec08dffbb9f2d419edf4c --- /dev/null +++ b/src/documentation/collectionStatsDescribers.ts @@ -0,0 +1,439 @@ +import { describeRoute } from 'hono-openapi'; + +const createRoute = describeRoute({ + method: 'POST', + path: '/create', + description: 'Create new collection stats', + tags: ['CollectionStats'], + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' } + }, + required: ['views', 'downloads', 'likes', 'shares', 'score', 'followers'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Collection stats created successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not create collection stats', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateViewsRoute = describeRoute({ + method: 'POST', + path: '/update-views/:id', + description: 'Update views of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats views updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update views', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateDownloadsRoute = describeRoute({ + method: 'POST', + path: '/update-downloads/:id', + description: 'Update downloads of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats downloads updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update downloads', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateLikesRoute = describeRoute({ + method: 'POST', + path: '/update-likes/:id', + description: 'Update likes of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats likes updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update likes', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateSharesRoute = describeRoute({ + method: 'POST', + path: '/update-shares/:id', + description: 'Update shares of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats shares updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update shares', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateScoreRoute = describeRoute({ + method: 'POST', + path: '/update-score/:id', + description: 'Update score of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats score updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update score', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const updateFollowersRoute = describeRoute({ + method: 'POST', + path: '/update-followers/:id', + description: 'Update followers of collection stats by ID', + tags: ['CollectionStats'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection stats followers updated successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'number' }, + followers: { type: 'integer' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request. Could not update followers', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}); + +const deleteCollectionStatsRoute = describeRoute({ + method: 'POST', + path: '/delete/:id', + description: 'Deletes collection stats by ID.', + tags: ['CollectionStats'], + params: { + id: 'The ID of the collection stats to delete.', + }, + responses: { + 200: { + description: 'Successfully deleted collection stats.', + content: { collectionStats: 'The deleted collection stats data.' }, + }, + 400: { + description: 'Failed to delete collection stats.', + content: { status: 'error', message: 'could not delete collection stats', code: 400 }, + }, + }, +}) + + const getCollectionStatsRoute = describeRoute({ + method: 'GET', + path: '/:id', + description: 'Retrieves collection stats by ID.', + tags: ['CollectionStats'], + params: { + id: 'The ID of the collection stats to retrieve.', + }, + responses: { + 200: { + description: 'Successfully retrieved collection stats.', + content: { collectionStats: 'The retrieved collection stats data.' }, + }, + 404: { + description: 'Collection stats not found.', + content: { status: 'error', message: 'could not find collection stats', code: 404 }, + }, + }, +}) + +export { + createRoute, + updateViewsRoute, + updateDownloadsRoute, + updateLikesRoute, + updateSharesRoute, + updateScoreRoute, + updateFollowersRoute, + deleteCollectionStatsRoute, + getCollectionStatsRoute +} diff --git a/src/documentation/collectionsDescribers.ts b/src/documentation/collectionsDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..35043fe6127fa16f69f2e47e780fe1e069b906be --- /dev/null +++ b/src/documentation/collectionsDescribers.ts @@ -0,0 +1,617 @@ +import { describeRoute } from 'hono-openapi' +const createCollectionRoute = describeRoute({ + method: 'POST', + path: '/create', + description: 'Creates a new collection, which includes generating associated statistics.', + request: { + body: { + collection_stats_id: { + type: 'number', + description: 'The ID of the collection statistics associated with this collection.', + example: 1 + }, + name: { + type: 'string', + description: 'The name of the collection being created.', + example: 'Art Collection' + }, + description: { + type: 'string', + description: 'A brief description of the collection.', + example: 'A collection of abstract art pieces.' + }, + created_by: { + type: 'string', + description: 'The user ID or name who is creating the collection.', + example: 'user123' + } + } + }, + response: { + status: 200, + body: { + collection: { + id: { + type: 'number', + description: 'The ID of the newly created collection.', + example: 123 + }, + name: { + type: 'string', + description: 'The name of the newly created collection.', + example: 'Art Collection' + }, + description: { + type: 'string', + description: 'The description of the newly created collection.', + example: 'A collection of abstract art pieces.' + }, + created_at: { + type: 'string', + description: 'The date and time when the collection was created.', + example: '2025-03-26T12:34:56Z' + }, + updated_at: { + type: 'string', + description: 'The date and time when the collection was last updated.', + example: '2025-03-26T12:34:56Z' + } + }, + collectionStats: { + id: { + type: 'number', + description: 'The ID of the collection statistics.', + example: 1 + }, + created_at: { + type: 'string', + description: 'The date and time when the statistics were generated.', + example: '2025-03-26T12:30:00Z' + } + } + } + } +}); +const updateCollectionRoute = describeRoute({ + method: 'POST', + path: '/update', + description: 'Updates an existing collection.', + request: { + body: { + id: { + type: 'number', + description: 'The ID of the collection to update.', + example: 123 + }, + name: { + type: 'string', + description: 'The new name of the collection.', + example: 'Modern Art Collection' + }, + description: { + type: 'string', + description: 'The updated description of the collection.', + example: 'A collection of modern and contemporary art pieces.' + }, + updated_by: { + type: 'string', + description: 'The user ID or name who is updating the collection.', + example: 'user123' + } + } + }, + response: { + status: 200, + body: { + collection: { + id: { + type: 'number', + description: 'The ID of the updated collection.', + example: 123 + }, + name: { + type: 'string', + description: 'The updated name of the collection.', + example: 'Modern Art Collection' + }, + description: { + type: 'string', + description: 'The updated description of the collection.', + example: 'A collection of modern and contemporary art pieces.' + }, + updated_at: { + type: 'string', + description: 'The date and time when the collection was last updated.', + example: '2025-03-26T14:00:00Z' + } + } + } + } +}); + +const deleteCollectionRoute = describeRoute({ + method: 'POST', + path: '/delete/:id', + description: 'Deletes a collection by ID.', + request: { + params: { + id: { + type: 'number', + description: 'The ID of the collection to delete.', + example: 123 + } + } + }, + response: { + status: 200, + body: { + collection: { + id: { + type: 'number', + description: 'The ID of the deleted collection.', + example: 123 + }, + name: { + type: 'string', + description: 'The name of the deleted collection.', + example: 'Art Collection' + }, + deleted_at: { + type: 'string', + description: 'The date and time when the collection was deleted.', + example: '2025-03-26T14:30:00Z' + } + } + } + } +}); + +const deletePermanentlyCollectionRoute = describeRoute({ + method: 'POST', + path: '/delete-permanently/:id', + description: 'Permanently deletes a collection by ID.', + request: { + params: { + id: { + type: 'number', + description: 'The ID of the collection to permanently delete.', + example: 123 + } + } + }, + response: { + status: 200, + body: { + collection: { + id: { + type: 'number', + description: 'The ID of the permanently deleted collection.', + example: 123 + }, + name: { + type: 'string', + description: 'The name of the permanently deleted collection.', + example: 'Art Collection' + }, + permanently_deleted_at: { + type: 'string', + description: 'The date and time when the collection was permanently deleted.', + example: '2025-03-26T14:45:00Z' + } + } + } + } +}); + +const restoreCollectionRoute = describeRoute({ + method: 'POST', + path: '/restore/:id', + description: 'Restores a deleted collection by ID.', + request: { + params: { + id: { + type: 'number', + description: 'The ID of the collection to restore.', + example: 123 + } + } + }, + response: { + status: 200, + body: { + collection: { + id: { + type: 'number', + description: 'The ID of the restored collection.', + example: 123 + }, + name: { + type: 'string', + description: 'The name of the restored collection.', + example: 'Art Collection' + }, + restored_at: { + type: 'string', + description: 'The date and time when the collection was restored.', + example: '2025-03-26T15:00:00Z' + } + } + } + } +}); + + +const getAllCollectionsRoute = describeRoute({ + method: 'GET', + path: '/all', + description: 'Get all collections', + tags: ['Collections'], + responses: { + '200': { + description: 'Collections found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + }, + '404': { + description: 'Collections not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para obter uma coleção específica por ID +const getCollectionRoute = describeRoute({ + method: 'GET', + path: '/:id', + description: 'Get a specific collection by ID', + tags: ['Collections'], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Collection found successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + '404': { + description: 'Collection not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para obter coleções ativas de um usuário +const getActiveCollectionsByUserRoute = describeRoute({ + method: 'GET', + path: '/getActiveCollectionsByUsers/:id_user', + description: 'Get active collections for a user by ID', + tags: ['Collections'], + parameters: [ + { + name: 'id_user', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Active collections found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + }, + '404': { + description: 'Active collections not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para obter todas as coleções de um usuário +const getAllCollectionsByUserRoute = describeRoute({ + method: 'GET', + path: '/getAllCollectionsByUsers/:id_user', + description: 'Get all collections for a user by ID', + tags: ['Collections'], + parameters: [ + { + name: 'id_user', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'All collections found for the user', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + }, + '404': { + description: 'Collections not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para obter coleções públicas de um usuário +const getPublicCollectionsByUserRoute = describeRoute({ + method: 'GET', + path: '/getPublicCollectionsByUser/:id_user', + description: 'Get public collections for a user by ID', + tags: ['Collections'], + parameters: [ + { + name: 'id_user', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Public collections found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + }, + '404': { + description: 'Public collections not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para obter coleções privadas de um usuário +const getPrivateCollectionsByUserRoute = describeRoute({ + method: 'GET', + path: '/getPrivateCollectionsByUser/:id_user', + description: 'Get private collections for a user by ID', + tags: ['Collections'], + parameters: [ + { + name: 'id_user', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Private collections found successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + }, + }, + }, + '404': { + description: 'Private collections not found', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string' }, + message: { type: 'string' }, + code: { type: 'integer' }, + path: { type: 'string' }, + suggestion: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +// Rota para download dos recursos de uma coleção +const downloadCollectionRoute = describeRoute({ + method: 'GET', + path: '/:collectionId/download', + description: 'Download resources from a collection as a ZIP file', + tags: ['Collections'], + parameters: [ + { + name: 'collectionId', + in: 'path', + required: true, + schema: { + type: 'integer', + }, + }, + ], + responses: { + '200': { + description: 'Resources downloaded successfully', + content: { + 'application/zip': { + schema: { + type: 'string', + format: 'binary', + }, + }, + }, + }, + '404': { + description: 'No resources found for the collection', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + }, + }, + }, + }, + }, +}) + +export { + createCollectionRoute, + updateCollectionRoute, + deleteCollectionRoute, + deletePermanentlyCollectionRoute, + restoreCollectionRoute, + getAllCollectionsRoute, + getCollectionRoute, + getActiveCollectionsByUserRoute, + getAllCollectionsByUserRoute, + getPublicCollectionsByUserRoute, + getPrivateCollectionsByUserRoute, + downloadCollectionRoute, +} diff --git a/src/documentation/userDescribers.ts b/src/documentation/userDescribers.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/index.ts b/src/index.ts index 97b4c89fd35e6cf8d1303e3d42e29d3cdae494ba..b39fd2c4b0bcb84076707b9edd47bcb05739c30d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,8 +38,8 @@ import { publicUserAchievementsRoute, userAchievementsRouter } from './routes/us import { actionRouter } from './routes/action.route' import { notificationRouter } from './routes/notification.route' import { userItemRouter } from './routes/user-item.route' -import { swaggerUI } from '@hono/swagger-ui' -import { OpenAPIHono } from "@hono/zod-openapi"; +import { openAPISpecs } from 'hono-openapi' +import { apiReference } from '@scalar/hono-api-reference' import { commentsRouter, publicCommentsRoute } from './routes/comments.route' import { publicCommentsReplyRoute, commentReplyRouter } from './routes/comment-reply.route' import { publicUserCollectionsRoutes, userCollectionsRoute } from './routes/user-collection.route' @@ -47,7 +47,7 @@ import { publicS3, s3Routes } from './routes/s3.route' -const app = new OpenAPIHono(); +const app = new Hono(); app.use('*', logger()) app.use('*', prettyJSON()) @@ -59,39 +59,36 @@ app.use( }) ) -// The openapi.json will be available at /doc -app.doc("/doc", { - openapi: "3.0.0", - info: { - version: "1.0.0", - title: "My API", - }, -}); // swagger ui doc will be available at {server url}/ui // fell free to change the url // swaggerUI url must have same path as openapi.json -app.get("/ui", swaggerUI({ url: "/doc" })); - -app.use( - '/api/upload', - bodyLimit({ - maxSize: 50 * 1024 * 1024, // 50mb - onError(c) { - return c.json( - createApexError({ - status: 'error', - message: 'File is too big. Current limit is 50mb', - code: HttpStatus.PAYLOAD_TOO_LARGE, - path: c.req.routePath, - suggestion: 'Reduce the size of your file and try again', - }), - 413 - ) +app.get( + '/openapi', + openAPISpecs(app, { + documentation: { + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + }, + security: [{ bearerAuth: [] }], }, }) ) +app.get( + '/docs', + apiReference({ + theme: 'saturn', + spec: { url: '/openapi' }, + }) +) + app.get('/', (c) => c.json({ message: 'sv running on /api' })) diff --git a/src/routes/achievement.route.ts b/src/routes/achievement.route.ts index 57180c4352771206fcec35cf1bec7ce8cba2721c..f7fe316b7f7c31feef9058cf24210758cea7e2b6 100644 --- a/src/routes/achievement.route.ts +++ b/src/routes/achievement.route.ts @@ -5,11 +5,14 @@ import { zValidator } from "@hono/zod-validator"; import { achievementSchemas } from "@/db/schema/achievement.schema"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; +import { createRoute, updateRoute, deleteRoute, getAchievementRoute, getAchievementsRoute } from '../documentation/achievementsDescribers' const service = Container.get(AchievementService) + export const achievementRouter = honoWithJwt() - .post('/create', zValidator('json', achievementSchemas.achievementInputSchema), + .post('/create', createRoute, + zValidator('json', achievementSchemas.achievementInputSchema), async (c) => { try { const input = await c.req.valid('json') @@ -33,7 +36,8 @@ export const achievementRouter = honoWithJwt() } } ) - .post('/update', zValidator('json', achievementSchemas.achievementUpdateSchema), + .post('/update', updateRoute, + zValidator('json', achievementSchemas.achievementUpdateSchema), async (c) => { try { const input = await c.req.valid('json') @@ -59,7 +63,8 @@ export const achievementRouter = honoWithJwt() } } ) - .post('/delete/:id', async (c) => { + .post('/delete/:id', deleteRoute, + async (c) => { try { const id = +c.req.param('id') @@ -83,7 +88,8 @@ export const achievementRouter = honoWithJwt() }) export const publicAchievementRouter = new Hono() - .get('/:id', async (c) => { + .get('/:id', getAchievementRoute, + async (c) => { try { const id = +c.req.param('id') @@ -104,7 +110,8 @@ export const achievementRouter = honoWithJwt() ) } }) - .get('/', async (c) => { + .get('/', getAchievementsRoute, + async (c) => { try { const achievements = achievementSchemas.achievementDtoSchema.array().parse( await service.findMany() diff --git a/src/routes/action.route.ts b/src/routes/action.route.ts index bc8582f149f0da429855a950e28ff5e423eb6bd6..0dd50cb1afecf98a27283ac5520b2c8f7ffec0f8 100644 --- a/src/routes/action.route.ts +++ b/src/routes/action.route.ts @@ -6,10 +6,18 @@ import { actionSchemas } from "@/db/schema/action.schema" import { createApexError, HttpStatus } from "@/services/error.service" import { z } from "zod" +import { + createActionRoute, + getActionsRoute, + getActionByNameRoute, + updateActionRoute, + deleteActionRoute +} from "../documentation/actionsDescribers" + const service = Container.get(ActionService) export const actionRouter = honoWithJwt() -.post('/create', zValidator('json', actionSchemas.input), +.post('/create', createActionRoute, zValidator('json', actionSchemas.input), async (c) => { try{ const input = await c.req.valid('json') @@ -32,7 +40,7 @@ export const actionRouter = honoWithJwt() ) } }) -.get('/actions', +.get('/actions', getActionsRoute, async (c) => { try { const actions = z.array(actionSchemas.dto).parse(await service.findMany()) @@ -42,7 +50,7 @@ export const actionRouter = honoWithJwt() return c.notFound() } }) -.get('/:name', +.get('/:name', getActionByNameRoute, async (c) => { try { const name = c.req.param('name') @@ -54,7 +62,7 @@ export const actionRouter = honoWithJwt() return c.notFound() } }) -.post('/update', +.post('/update', updateActionRoute, zValidator('json', actionSchemas.update), async (c) => { try { @@ -76,7 +84,7 @@ export const actionRouter = honoWithJwt() } } ) -.delete('/delete/:id', +.delete('/delete/:id', deleteActionRoute, async (c) =>{ try{ const id: number = +c.req.param('id') diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index ce9b45b747d1b6e2dbc63aeb434385df61dbaffc..f4687e7aa20d81cc7c1b8de3a711fcdb0574fc0d 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -10,6 +10,7 @@ import { UserService } from '@/services/user.service' import { zValidator } from '@hono/zod-validator' import { Hono } from 'hono' import Container from 'typedi' +import { signinRoute, signupRoute, requestPasswordResetRoute, resetPasswordRoute } from '../documentation/authDescribers' const authService = Container.get(AuthService) const userService = Container.get(UserService) @@ -17,7 +18,7 @@ const userStatsService = Container.get(UserStatsService) const passwordRecoveryService = Container.get(PasswordRecoveryService) export const authRouter = new Hono().post( - '/signin', + '/signin', signinRoute, zValidator('json', authSchema), async (c) => { try { @@ -40,7 +41,7 @@ export const authRouter = new Hono().post( } } ) -.post('/signup', +.post('/signup', signupRoute, zValidator('json', userSchemas.userInputSchema), async (c) => { try { @@ -57,7 +58,7 @@ export const authRouter = new Hono().post( return c.text('could not create') } }) -.post('/request/:email', +.post('/request/:email', requestPasswordResetRoute, async (c) => { try{ const email: string = c.req.param('email') @@ -107,7 +108,7 @@ export const authRouter = new Hono().post( } }) .post( - '/reset', + '/reset', resetPasswordRoute, //url preview: reset?email=admin%40admin.com&token=xEakn2HpEaH8Xvyz8fsntYvmHip8IVP0NZu7bWpjkEY&password= async (c) => { try { diff --git a/src/routes/collection-likes.route.ts b/src/routes/collection-likes.route.ts index bb38aee61303fc20f225305aeee732fa6eebfd98..2ad50b98e15ffcb0feb4592be138092b8f7a98cf 100644 --- a/src/routes/collection-likes.route.ts +++ b/src/routes/collection-likes.route.ts @@ -5,6 +5,15 @@ import { honoWithJwt } from ".."; import { zValidator } from "@hono/zod-validator"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; +import { + associateRoute, + removeLikesRoute, + updateLikesRoute, + getLikesByCollectionRoute, + checkLikeExistsRoute, + getCollectionsByUserRoute, + } from '../documentation/collectionLikesDescribers'; + const associateSchema = z.object({ collectionId: z.number(), @@ -14,7 +23,7 @@ const associateSchema = z.object({ const service = Container.get(CollectionLikesService); export const collectionLikesRoutes = honoWithJwt() - .post('/associate', zValidator('json', associateSchema), + .post('/associate', associateRoute, zValidator('json', associateSchema), async (c) => { try { const { collectionId, userIds } = await c.req.valid('json'); @@ -34,7 +43,7 @@ export const collectionLikesRoutes = honoWithJwt() } } ) - .post('/:collectionId/delete/:userId', + .post('/:collectionId/delete/:userId', removeLikesRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -55,7 +64,7 @@ export const collectionLikesRoutes = honoWithJwt() } } ) - .post('/update/likes', zValidator('json', associateSchema), + .post('/update/likes', updateLikesRoute, zValidator('json', associateSchema), async (c) => { try { const { collectionId, userIds } = await collectionId.req.valid('json'); @@ -77,7 +86,7 @@ export const collectionLikesRoutes = honoWithJwt() ) export const publicCollectionLikesRoutes = new Hono() - .get('/:collectionId/likes', + .get('/:collectionId/likes', getLikesByCollectionRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -97,7 +106,7 @@ export const collectionLikesRoutes = honoWithJwt() } } ) - .get('/:collectionId/likes/:userId/exists', + .get('/:collectionId/likes/:userId/exists', checkLikeExistsRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -118,7 +127,7 @@ export const collectionLikesRoutes = honoWithJwt() } } ) - .get('/user/:userId/collections', + .get('/user/:userId/collections', getCollectionsByUserRoute, async (c) => { try { const userId = +c.req.param('userId'); diff --git a/src/routes/collection-resources.route.ts b/src/routes/collection-resources.route.ts index 83b349406e1bc5d4807450b562c6787fa2aec945..a5a637d12eb87c2355b53faaca24607ecd48a079 100644 --- a/src/routes/collection-resources.route.ts +++ b/src/routes/collection-resources.route.ts @@ -5,6 +5,13 @@ import { honoWithJwt } from ".."; import { zValidator } from "@hono/zod-validator"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; +import { + associateRoute, + deleteResourceRoute, + getResourcesRoute, + checkResourceExistsRoute, + getCollectionsForResourceRoute, +} from "../documentation/collectionResourcesDescribers" const associateSchema = z.object({ @@ -16,7 +23,7 @@ const service = Container.get(CollectionResourcesService); export const collectionResourcesRoutes = honoWithJwt() // associate resources with collection - .post('/associate', zValidator('json', associateSchema), + .post('/associate', associateRoute, zValidator('json', associateSchema), async (c) => { try { const { collectionId, resourceIds } = await c.req.valid('json'); @@ -36,7 +43,7 @@ export const collectionResourcesRoutes = honoWithJwt() } } ) - .post('/:collectionId/delete/:resourceId', + .post('/:collectionId/delete/:resourceId', deleteResourceRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -61,7 +68,7 @@ export const collectionResourcesRoutes = honoWithJwt() export const publicCollectionResourcesRoutes = new Hono() // get all resources of a collection - .get('/:collectionId/resources', + .get('/:collectionId/resources', getResourcesRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -82,7 +89,7 @@ export const publicCollectionResourcesRoutes = new Hono() } ) // check if resource is associated with collection - .get('/:collectionId/resources/:resourceId/exists', + .get('/:collectionId/resources/:resourceId/exists', checkResourceExistsRoute, async (c) => { try { const collectionId = +c.req.param('collectionId'); @@ -104,7 +111,7 @@ export const publicCollectionResourcesRoutes = new Hono() } ) // get collections of a resource - .get('/resource/:resourceId/collections', + .get('/resource/:resourceId/collections', getCollectionsForResourceRoute, async (c) => { try { const resourceId = +c.req.param('resourceId'); diff --git a/src/routes/collection-stats.route.ts b/src/routes/collection-stats.route.ts index d1e88d84f709ee6a87273c30dff0707388b74282..3741f74593d7fe7e62bd9387068e3758b9c34b80 100644 --- a/src/routes/collection-stats.route.ts +++ b/src/routes/collection-stats.route.ts @@ -6,11 +6,23 @@ import { collectionStatsSchemas } from "@/db/schema/collection-stats.schema"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; +import { + createRoute, + updateViewsRoute, + updateDownloadsRoute, + updateLikesRoute, + updateSharesRoute, + updateScoreRoute, + updateFollowersRoute, + deleteCollectionStatsRoute, + getCollectionStatsRoute +} from "../documentation/collectionStatsDescribers" + const service = Container.get(CollectionStatsService); export const collectionsStatsRouter = honoWithJwt() .post( - '/create', + '/create', createRoute, zValidator('json', collectionStatsSchemas.collectionStatsInputSchema), async (c) => { try { @@ -35,7 +47,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-views/:id', async (c) => { + '/update-views/:id', updateViewsRoute, async (c) => { try { const id = +c.req.param('id') @@ -59,7 +71,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-downloads/:id', async (c) => { + '/update-downloads/:id', updateDownloadsRoute, async (c) => { try { const id = +c.req.param('id') @@ -83,7 +95,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-likes/:id', async (c) => { + '/update-likes/:id', updateLikesRoute, async (c) => { try { const id = +c.req.param('id') @@ -107,7 +119,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-shares/:id', async (c) => { + '/update-shares/:id', updateSharesRoute, async (c) => { try { const id = +c.req.param('id') @@ -131,7 +143,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-score/:id', async (c) => { + '/update-score/:id', updateScoreRoute, async (c) => { try { const id = +c.req.param('id') @@ -155,7 +167,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/update-followers/:id', async (c) => { + '/update-followers/:id', updateFollowersRoute,async (c) => { try { const id = +c.req.param('id') @@ -179,7 +191,7 @@ export const collectionsStatsRouter = honoWithJwt() } ) .post( - '/delete/:id', async (c) => { + '/delete/:id', deleteCollectionStatsRoute, async (c) => { try { const id = +c.req.param('id') @@ -204,7 +216,7 @@ export const collectionsStatsRouter = honoWithJwt() ) export const getCollectionsStats = new Hono() - .get('/:id', async (c) => { + .get('/:id', getCollectionStatsRoute, async (c) => { try { const id = +c.req.param('id') diff --git a/src/routes/collections.route.ts b/src/routes/collections.route.ts index 1aacb154b668b1d71f857205488b961140910c53..33d4e644c991c75f98ea694f813bfb30f55b8436 100644 --- a/src/routes/collections.route.ts +++ b/src/routes/collections.route.ts @@ -10,7 +10,20 @@ import { CollectionStatsService } from "@/services/collection-stats.service"; import { getFile } from "@/services/s3.service"; import archiver from "archiver"; // Biblioteca para criar ZIPs import { CollectionResourcesService } from "@/services/collection-resources.service"; - +import { + createCollectionRoute, + updateCollectionRoute, + deleteCollectionRoute, + deletePermanentlyCollectionRoute, + restoreCollectionRoute, + getAllCollectionsRoute, + getCollectionRoute, + getActiveCollectionsByUserRoute, + getAllCollectionsByUserRoute, + getPublicCollectionsByUserRoute, + getPrivateCollectionsByUserRoute, + downloadCollectionRoute, +} from "../documentation/collectionsDescribers" const service = Container.get(CollectionsService); const serviceStats = Container.get(CollectionStatsService); @@ -18,7 +31,7 @@ const serviceResourceCollection = Container.get(CollectionResourcesService) ; export const collectionsRouter = honoWithJwt() .post( - '/create', + '/create', createCollectionRoute, zValidator('json', collectionSchemas.collectionInputSchema), async (c) => { try { @@ -49,7 +62,7 @@ export const collectionsRouter = honoWithJwt() } ) .post( - '/update', + '/update', updateCollectionRoute, zValidator('json', collectionSchemas.collectionUpdateSchema), async (c) => { try { @@ -76,7 +89,7 @@ export const collectionsRouter = honoWithJwt() } } ) - .post('/delete/:id', async (c) => { + .post('/delete/:id', deleteCollectionRoute, async (c) => { try { const id = +c.req.param('id') @@ -98,7 +111,7 @@ export const collectionsRouter = honoWithJwt() ) } }) - .post('/delete-permanently/:id', async (c) => { + .post('/delete-permanently/:id', deletePermanentlyCollectionRoute, async (c) => { try { const id = +c.req.param('id') @@ -120,7 +133,7 @@ export const collectionsRouter = honoWithJwt() ) } }) - .post('/restore/:id', async (c) => { + .post('/restore/:id', restoreCollectionRoute, async (c) => { try { const id = +c.req.param('id') @@ -144,7 +157,7 @@ export const collectionsRouter = honoWithJwt() }) export const getCollections = new Hono() - .get('/all', async (c) => { + .get('/all', getCollectionRoute, async (c) => { try { const collections = collectionSchemas.collectionDtoSchema.array().parse( await service.findMany() @@ -164,7 +177,7 @@ export const getCollections = new Hono() ) } }) - .get('/:id', async (c) => { + .get('/:id', getAllCollectionsRoute, async (c) => { try { const id = +c.req.param('id') @@ -190,7 +203,7 @@ export const getCollections = new Hono() - .get('getActiveCollectionsByUsers/:id_user', + .get('getActiveCollectionsByUsers/:id_user', getActiveCollectionsByUserRoute, async (c) => { try { const id_user = +c.req.param('id_user') @@ -213,7 +226,7 @@ export const getCollections = new Hono() } }) - .get('getAllCollectionsByUsers/:id_user', async (c) => { + .get('getAllCollectionsByUsers/:id_user', getAllCollectionsByUserRoute, async (c) => { try { const id = +c.req.param('id_user') @@ -236,7 +249,7 @@ export const getCollections = new Hono() } }) - .get('getPublicCollectionsByUser/:id_user', async (c) => { + .get('getPublicCollectionsByUser/:id_user', getPublicCollectionsByUserRoute, async (c) => { try { const id = +c.req.param('id_user') @@ -259,7 +272,7 @@ export const getCollections = new Hono() } }) - .get('getPrivateCollectionsByUser/:id_user', async (c) => { + .get('getPrivateCollectionsByUser/:id_user', getPrivateCollectionsByUserRoute, async (c) => { try { const id = +c.req.param('id_user') @@ -282,7 +295,7 @@ export const getCollections = new Hono() } }) - .get("/:collectionId/download", async (c) => { + .get("/:collectionId/download", downloadCollectionRoute, async (c) => { try { const collectionId = +c.req.param("collectionId");