From 28232e371135661b87d1b2fb4989dd259be513a6 Mon Sep 17 00:00:00 2001 From: Lncvrt Date: Tue, 20 Jan 2026 21:15:24 -0700 Subject: [PATCH] Add swagger docs + icon marketplace get icons endpoint --- bun.lock | 57 ++-- package.json | 9 +- src/index.ts | 249 +++++++++++++++--- src/routes/berrydash/icon-marketplace/post.ts | 178 +++++++++++++ 4 files changed, 443 insertions(+), 50 deletions(-) create mode 100644 src/routes/berrydash/icon-marketplace/post.ts diff --git a/bun.lock b/bun.lock index df1ad90..c0fda45 100644 --- a/bun.lock +++ b/bun.lock @@ -5,33 +5,44 @@ "": { "name": "lncvrt-games-api", "dependencies": { - "@elysiajs/cors": "1.4.0", + "@elysiajs/cors": "1.4.1", + "@elysiajs/swagger": "1.3.1", "dotenv": "17.2.3", "drizzle-orm": "0.45.1", - "elysia": "1.4.19", - "mysql2": "3.16.0", + "elysia": "1.4.22", + "mysql2": "3.16.1", }, "devDependencies": { - "bun-types": "1.3.5", + "bun-types": "1.3.6", }, }, }, "packages": { - "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], - "@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="], + "@elysiajs/cors": ["@elysiajs/cors@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ=="], - "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], + "@elysiajs/swagger": ["@elysiajs/swagger@1.3.1", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-LcbLHa0zE6FJKWPWKsIC/f+62wbDv3aXydqcNPVPyqNcaUgwvCajIi+5kHEU6GO3oXUCpzKaMsb3gsjt8sLzFQ=="], + + "@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="], + + "@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="], + + "@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.47", "", {}, "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + + "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], - "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -43,17 +54,19 @@ "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], - "elysia": ["elysia@1.4.19", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-DZb9y8FnWyX5IuqY44SvqAV0DjJ15NeCWHrLdgXrKgTPDPsl3VNwWHqrEr9bmnOCpg1vh6QUvAX/tcxNj88jLA=="], + "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], - "exact-mirror": ["exact-mirror@0.2.5", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-u8Wu2lO8nio5lKSJubOydsdNtQmH8ENba5m0nbQYmTvsjksXKYIS1nSShdDlO8Uem+kbo+N6eD5I03cpZ+QsRQ=="], + "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], - "file-type": ["file-type@21.1.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg=="], + "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], - "iconv-lite": ["iconv-lite@0.7.1", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -67,12 +80,16 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mysql2": ["mysql2@3.16.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-AEGW7QLLSuSnjCS4pk3EIqOmogegmze9h8EyrndavUQnIUcfkVal/sK7QznE+a3bc6rzPbAiui9Jcb+96tPwYA=="], + "mysql2": ["mysql2@3.16.1", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.6", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-b75qsDB3ieYEzMsT1uRGsztM/sy6vWPY40uPZlVVl8eefAotFCoS7jaDB5DxDNtlW5kdVGd9jptSpkvujNxI2A=="], "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], @@ -81,10 +98,20 @@ "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], - "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="], + "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], + + "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], } } diff --git a/package.json b/package.json index a0e1e68..72a290b 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,15 @@ "dev": "bun run --watch src/index.ts" }, "dependencies": { - "@elysiajs/cors": "1.4.0", + "@elysiajs/cors": "1.4.1", + "@elysiajs/swagger": "1.3.1", "dotenv": "17.2.3", "drizzle-orm": "0.45.1", - "elysia": "1.4.19", - "mysql2": "3.16.0" + "elysia": "1.4.22", + "mysql2": "3.16.1" }, "devDependencies": { - "bun-types": "1.3.5" + "bun-types": "1.3.6" }, "module": "src/index.js" } diff --git a/src/index.ts b/src/index.ts index f23312d..042f7c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,63 +1,250 @@ -import { Elysia } from 'elysia' +import { Elysia, t } from 'elysia' import { cors } from '@elysiajs/cors' import { jsonResponse } from './lib/util' import dotenv from 'dotenv' +import swagger from '@elysiajs/swagger' import { handler as canLoadClientHandler } from './routes/can-load-client' + import { handler as launcherVersionsHandler } from './routes/launcher/versions' import { handler as launcherLatestHandler } from './routes/launcher/latest' import { handler as launcherLoaderLatestHandler } from './routes/launcher/loader/latest' import { handler as launcherLoaderUpdateDataHandler } from './routes/launcher/loader/update-data' + import { handler as berrydashLeaderboardsGetHandler } from './routes/berrydash/leaderboards/get' + import { handler as berrydashProfileGetHandler } from './routes/berrydash/profile/get' import { handler as berrydashProfilePostsDeleteHandler } from './routes/berrydash/profile/posts/delete' import { handler as berrydashProfilePostsGetHandler } from './routes/berrydash/profile/posts/get' import { handler as berrydashProfilePostsPostHandler } from './routes/berrydash/profile/posts/post' import { handler as berrydashProfilePostsPutHandler } from './routes/berrydash/profile/posts/put' +import { handler as berryDashIconMarketplacePostHandler } from './routes/berrydash/icon-marketplace/post' + dotenv.config() -const app = new Elysia().use( - cors({ - origin: '*', - methods: ['POST', 'GET'] - }) -) +const intNotStr = (name: string) => { + return ( + '\n\n**The type for parameter `' + + name + + '` is actually a `number`, but it shows as a `string` here.**' + ) +} + +const boolNotStr = (name: string) => { + return ( + '\n\n**The type for parameter `' + + name + + '` is actually a `boolean`, but it shows as a `string` here.**' + ) +} + +const app = new Elysia() + .use( + cors({ + origin: '*', + methods: ['POST', 'GET'] + }) + ) + .use( + swagger({ + path: '/docs', + documentation: { + info: { + title: 'Lncvrt Games API', + description: + 'This is the official documentation for the Lncvrt Games API!', + version: '1.0.0' + } + } + }) + ) app.get('/can-load-client', context => canLoadClientHandler(context)) -app.get('/launcher/versions', context => launcherVersionsHandler(context)) -app.get('/launcher/latest', launcherLatestHandler) -app.get('/launcher/loader/latest', launcherLoaderLatestHandler) -app.get('/launcher/loader/update-data', context => - launcherLoaderUpdateDataHandler(context) +app.get('/launcher/versions', context => launcherVersionsHandler(context), { + detail: { + description: + 'The endpoint for getting the launcher manifest.\n\nNote: if going to use the params, both must be provided at the same time.', + tags: ['Launcher'] + }, + params: t.Object({ + platform: t.Optional(t.String()), + arch: t.Optional(t.String()) + }) +}) +app.get('/launcher/latest', launcherLatestHandler, { + detail: { + description: 'The endpoint for getting the latest launcher version.', + tags: ['Launcher'] + } +}) +app.get('/launcher/loader/latest', launcherLoaderLatestHandler, { + detail: { + description: + 'The endpoint for getting the latest loader/auto updater version.', + tags: ['Launcher'] + } +}) +app.get( + '/launcher/loader/update-data', + context => launcherLoaderUpdateDataHandler(context), + { + detail: { + description: + 'The endpoint for getting Launcher Update data for when a new Update is released. It will be send & read by the updater.\n\nNote: if going to use the params, both must be provided at the same time.', + tags: ['Launcher'] + }, + params: t.Object({ + platform: t.Optional(t.String()), + arch: t.Optional(t.String()) + }) + } ) -app.get('/berrydash/leaderboards/score', context => - berrydashLeaderboardsGetHandler(context, 0) +app.get( + '/berrydash/leaderboards/score', + context => berrydashLeaderboardsGetHandler(context, 0), + { + detail: { + description: 'The endpoint for getting the score leaderboards.', + tags: ['Berry Dash', 'Leaderboards'] + } + } ) -app.get('/berrydash/leaderboards/berry', context => - berrydashLeaderboardsGetHandler(context, 1) +app.get( + '/berrydash/leaderboards/berry', + context => berrydashLeaderboardsGetHandler(context, 1), + { + detail: { + description: + 'The endpoint for getting the berry leaderboards.' + intNotStr('berry'), + tags: ['Berry Dash', 'Leaderboards'] + }, + params: t.Object({ + berry: t.String() + }) + } ) -app.get('/berrydash/leaderboards/coin', context => - berrydashLeaderboardsGetHandler(context, 2) +app.get( + '/berrydash/leaderboards/coin', + context => berrydashLeaderboardsGetHandler(context, 2), + { + detail: { + description: 'The endpoint for getting the coin leaderboards.', + tags: ['Berry Dash', 'Leaderboards'] + } + } ) -app.get('/berrydash/leaderboards/legacy', context => - berrydashLeaderboardsGetHandler(context, 3) +app.get( + '/berrydash/leaderboards/legacy', + context => berrydashLeaderboardsGetHandler(context, 3), + { + detail: { + description: 'The endpoint for getting the legacy leaderboards.', + tags: ['Berry Dash', 'Leaderboards'] + } + } ) -app.get('/berrydash/leaderboards/total', context => - berrydashLeaderboardsGetHandler(context, 4) +app.get( + '/berrydash/leaderboards/total', + context => berrydashLeaderboardsGetHandler(context, 4), + { + detail: { + description: 'The endpoint for getting the total leaderboards.', + tags: ['Berry Dash', 'Leaderboards'] + } + } ) -app.get('/berrydash/profile', context => berrydashProfileGetHandler(context)) -app.delete('/berrydash/profile/posts', context => - berrydashProfilePostsDeleteHandler(context) +app.get('/berrydash/profile', context => berrydashProfileGetHandler(context), { + detail: { + description: + "The endpoint for getting a user's profile." + intNotStr('userId'), + tags: ['Berry Dash', 'Profiles'] + }, + params: t.Object({ + userId: t.String() + }) +}) +app.delete( + '/berrydash/profile/posts', + context => berrydashProfilePostsDeleteHandler(context), + { + detail: { + description: 'This endpoint is for deleting a post.' + intNotStr('id'), + tags: ['Berry Dash', 'Profiles'] + }, + headers: t.Object({ + authorization: t.String() + }), + params: t.Object({ + id: t.String() + }) + } ) -app.get('/berrydash/profile/posts', context => - berrydashProfilePostsGetHandler(context) +app.get( + '/berrydash/profile/posts', + context => berrydashProfilePostsGetHandler(context), + { + detail: { + description: + 'This endpoint is for getting posts from a user.' + intNotStr('userId'), + tags: ['Berry Dash', 'Profiles'] + }, + params: t.Object({ + userId: t.String() + }) + } ) -app.post('/berrydash/profile/posts', context => - berrydashProfilePostsPostHandler(context) +app.post( + '/berrydash/profile/posts', + context => berrydashProfilePostsPostHandler(context), + { + detail: { + description: 'This endpoint is for uploading a new post.', + tags: ['Berry Dash', 'Profiles'] + }, + body: t.Object({ + content: t.String() + }) + } ) -app.put('/berrydash/profile/posts', context => - berrydashProfilePostsPutHandler(context) +app.put( + '/berrydash/profile/posts', + context => berrydashProfilePostsPutHandler(context), + { + detail: { + description: + 'This endpoint is for liking/disliking a post.' + + intNotStr('id') + + boolNotStr('likedQuery'), + tags: ['Berry Dash', 'Profiles'] + }, + params: t.Object({ + id: t.String(), + likedQuery: t.String() + }) + } +) +app.post( + '/berrydash/icon-marketplace', + context => berryDashIconMarketplacePostHandler(context), + { + detail: { + description: + 'The endpoint for getting the icon marketplace icons.\n\nPretty much none of the body types are correct.', + tags: ['Berry Dash', 'Icon Marketplace'] + }, + body: t.Object({ + sortBy: t.String(), + priceRangeEnabled: t.String(), + priceRangeMin: t.String(), + priceRangeMax: t.String(), + searchForEnabled: t.String(), + searchForValue: t.String(), + onlyShowEnabled: t.String(), + onlyShowValue: t.String(), + currentIcons: t.String() + }) + } ) app.all('*', () => jsonResponse( diff --git a/src/routes/berrydash/icon-marketplace/post.ts b/src/routes/berrydash/icon-marketplace/post.ts new file mode 100644 index 0000000..dd63fa9 --- /dev/null +++ b/src/routes/berrydash/icon-marketplace/post.ts @@ -0,0 +1,178 @@ +import { Context } from 'elysia' +import { getDatabaseConnection, jsonResponse } from '../../../lib/util' +import { + berryDashMarketplaceIcons, + berryDashUserData, + users +} from '../../../lib/tables' +import { and, eq, inArray, or, sql, not, like } from 'drizzle-orm' + +type Body = { + sortBy: number + priceRangeEnabled: boolean + priceRangeMin: number + priceRangeMax: number + searchForEnabled: boolean + searchForValue: string + onlyShowEnabled: boolean + onlyShowValue: number + currentIcons: string[] +} + +const requiredKeys = [ + 'sortBy', + 'priceRangeEnabled', + 'priceRangeMin', + 'priceRangeMax', + 'searchForEnabled', + 'searchForValue', + 'onlyShowEnabled', + 'onlyShowValue', + 'currentIcons' +] + +export async function handler (context: Context) { + const dbInfo0 = getDatabaseConnection(0) + const dbInfo1 = getDatabaseConnection(1) + + if (!dbInfo0 || !dbInfo1) + return jsonResponse( + { success: false, message: 'Failed to connect to database', data: null }, + 500 + ) + + const { connection: connection0, db: db0 } = dbInfo0 + const { connection: connection1, db: db1 } = dbInfo1 + + const body: { [key: string]: any } = context.body as any + + for (const key of requiredKeys) { + if (!(key in body)) { + return jsonResponse( + { success: false, message: 'Invalid POST data', data: null }, + 400 + ) + } + } + + let body2: { [key: string]: any } = {} + body2.sortBy = Number(body.sortBy) + body2.priceRangeEnabled = body.priceRangeEnabled.toLowerCase() === 'true' + body2.priceRangeMin = Number(body.priceRangeMin) + body2.priceRangeMax = Number(body.priceRangeMax) + body2.searchForEnabled = body.searchForEnabled.toLowerCase() === 'true' + body2.searchForValue = body.searchForValue as string + body2.onlyShowEnabled = body.onlyShowEnabled.toLowerCase() === 'true' + body2.onlyShowValue = Number(body.onlyShowValue) + body2.currentIcons = JSON.parse(atob(body.currentIcons)) + const body3: Body = body2 as Body + + const authorizationToken = context.headers.authorization + if (!authorizationToken) { + connection0.end() + connection1.end() + return jsonResponse( + { success: false, message: 'Unauthorized', data: null }, + 401 + ) + } + + const userData = await db1 + .select({ id: berryDashUserData.id }) + .from(berryDashUserData) + .where(eq(berryDashUserData.token, authorizationToken)) + .execute() + + if (!userData[0]) { + connection0.end() + connection1.end() + return jsonResponse( + { success: false, message: 'Unauthorized', data: null }, + 401 + ) + } + + const userId = userData[0].id + + const filters: any[] = [ + or( + eq(berryDashMarketplaceIcons.state, 1), + eq(berryDashMarketplaceIcons.state, 2) + ) + ] + + if (body3.priceRangeEnabled) { + filters.push( + sql`${berryDashMarketplaceIcons.price} >= ${body3.priceRangeMin}`, + sql`${berryDashMarketplaceIcons.price} <= ${body3.priceRangeMax}` + ) + } + + if (body3.searchForEnabled) { + filters.push( + sql`FROM_BASE64(${ + berryDashMarketplaceIcons.name + }) LIKE ${`%${body3.searchForValue}%`}` + ) + } + + if (body3.onlyShowEnabled) { + if (body3.onlyShowValue === 0) { + filters.push(eq(berryDashMarketplaceIcons.userId, userId)) + } else if (body3.onlyShowValue === 1) { + filters.push(sql`${berryDashMarketplaceIcons.userId} != ${userId}`) + } else if (body3.onlyShowValue === 2) { + filters.push(inArray(berryDashMarketplaceIcons.uuid, body3.currentIcons)) + } else if (body3.onlyShowValue === 3) { + filters.push( + not(inArray(berryDashMarketplaceIcons.uuid, body3.currentIcons)) + ) + } + } + + let orderBy: any + switch (body3.sortBy) { + case 1: + orderBy = sql`price ASC` + break + case 2: + orderBy = sql`id ASC` + break + case 3: + orderBy = sql`id DESC` + break + default: + orderBy = sql`price DESC` + break + } + + const icons = await db1 + .select() + .from(berryDashMarketplaceIcons) + .where(and(...filters)) + .orderBy(orderBy) + .execute() + + const userIds = Array.from(new Set(icons.map(i => i.userId))) + const usersData = await db0 + .select({ id: users.id, username: users.username }) + .from(users) + .where(inArray(users.id, userIds)) + .execute() + + const usersMap = Object.fromEntries(usersData.map(u => [u.id, u.username])) + + const result = icons.map(i => ({ + username: usersMap[i.userId] ?? 'Unknown', + userid: i.userId, + data: i.data, + uuid: i.uuid, + price: i.state === 2 ? 100000000 : i.price, + name: atob(i.name) + })) + + connection0.end() + connection1.end() + + return jsonResponse({ success: true, message: null, data: result }) +}