From 7ba12b7542f90722b25f6051157f23c925135a37 Mon Sep 17 00:00:00 2001 From: EduardoFariaKruger <efk23@inf.ufpr.br> Date: Wed, 26 Mar 2025 15:23:35 -0300 Subject: [PATCH] a --- bun.lockb | Bin 76818 -> 157113 bytes package.json | 12 +- pnpm-lock.yaml | 270 ++++++++ src/documentation/achievementsDescribers.ts | 301 +++++++++ src/documentation/actionsDescribers.ts | 302 +++++++++ src/documentation/authDescribers.ts | 204 ++++++ .../collectionLikesDescribers.ts | 334 ++++++++++ .../collectionResourcesDescribers.ts | 280 ++++++++ .../collectionStatsDescribers.ts | 439 +++++++++++++ src/documentation/collectionsDescribers.ts | 617 ++++++++++++++++++ src/documentation/userDescribers.ts | 0 src/index.ts | 53 +- src/routes/achievement.route.ts | 17 +- src/routes/action.route.ts | 18 +- src/routes/auth.route.ts | 9 +- src/routes/collection-likes.route.ts | 21 +- src/routes/collection-resources.route.ts | 17 +- src/routes/collection-stats.route.ts | 30 +- src/routes/collections.route.ts | 39 +- 19 files changed, 2887 insertions(+), 76 deletions(-) create mode 100644 src/documentation/achievementsDescribers.ts create mode 100644 src/documentation/actionsDescribers.ts create mode 100644 src/documentation/authDescribers.ts create mode 100644 src/documentation/collectionLikesDescribers.ts create mode 100644 src/documentation/collectionResourcesDescribers.ts create mode 100644 src/documentation/collectionStatsDescribers.ts create mode 100644 src/documentation/collectionsDescribers.ts create mode 100644 src/documentation/userDescribers.ts diff --git a/bun.lockb b/bun.lockb index eb973bacbb595524c04f0abd0000e6b6fc16a0d4..b826abc55b56b5e0e1d717578c253b23310db7dc 100755 GIT binary patch literal 157113 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g8O-8SB)GjMF_#KkuHu*gQ&(wX3i(`gK$r@_gY< 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 delta 14325 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 diff --git a/package.json b/package.json index a48e327..8f328eb 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 11a763d..b9325c6 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 0000000..b6a1ba8 --- /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 0000000..9942707 --- /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 0000000..694b3cd --- /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 0000000..1364ee7 --- /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 0000000..fa01b50 --- /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 0000000..963325c --- /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 0000000..35043fe --- /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 0000000..e69de29 diff --git a/src/index.ts b/src/index.ts index 97b4c89..b39fd2c 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 57180c4..f7fe316 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 bc8582f..0dd50cb 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 ce9b45b..f4687e7 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 bb38aee..2ad50b9 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 83b3494..a5a637d 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 d1e88d8..3741f74 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 1aacb15..33d4e64 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"); -- GitLab