diff --git a/.husky/pre-commit b/.husky/pre-commit index 9c96ce9..58993aa 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -pnpm build +pnpm lint diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea363b5..17e9207 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,15 +71,15 @@ importers: jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + lodash: + specifier: ^4.17.21 + version: 4.17.21 pg: specifier: ^8.12.0 version: 8.12.0 socket.io: specifier: ^4.7.5 version: 4.7.5 - typescript: - specifier: ^5.2.2 - version: 5.4.5 devDependencies: '@eslint/js': specifier: ^9.6.0 @@ -109,14 +109,17 @@ importers: specifier: 9.x version: 9.6.0 globals: - specifier: ^15.6.0 - version: 15.6.0 + specifier: ^15.7.0 + version: 15.7.0 tsx: specifier: ^4.15.6 version: 4.15.6 + typescript: + specifier: ^5.5.3 + version: 5.5.3 typescript-eslint: - specifier: ^7.14.1 - version: 7.14.1(eslint@9.6.0)(typescript@5.4.5) + specifier: ^7.15.0 + version: 7.15.0(eslint@9.6.0)(typescript@5.5.3) web: dependencies: @@ -1093,8 +1096,8 @@ packages: typescript: optional: true - '@typescript-eslint/eslint-plugin@7.14.1': - resolution: {integrity: sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==} + '@typescript-eslint/eslint-plugin@7.15.0': + resolution: {integrity: sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -1114,8 +1117,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.14.1': - resolution: {integrity: sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==} + '@typescript-eslint/parser@7.15.0': + resolution: {integrity: sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1128,8 +1131,8 @@ packages: resolution: {integrity: sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@7.14.1': - resolution: {integrity: sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==} + '@typescript-eslint/scope-manager@7.15.0': + resolution: {integrity: sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==} engines: {node: ^18.18.0 || >=20.0.0} '@typescript-eslint/scope-manager@8.0.0-alpha.30': @@ -1146,8 +1149,8 @@ packages: typescript: optional: true - '@typescript-eslint/type-utils@7.14.1': - resolution: {integrity: sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==} + '@typescript-eslint/type-utils@7.15.0': + resolution: {integrity: sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1160,8 +1163,8 @@ packages: resolution: {integrity: sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@7.14.1': - resolution: {integrity: sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==} + '@typescript-eslint/types@7.15.0': + resolution: {integrity: sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==} engines: {node: ^18.18.0 || >=20.0.0} '@typescript-eslint/types@8.0.0-alpha.30': @@ -1177,8 +1180,8 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@7.14.1': - resolution: {integrity: sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==} + '@typescript-eslint/typescript-estree@7.15.0': + resolution: {integrity: sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -1201,8 +1204,8 @@ packages: peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/utils@7.14.1': - resolution: {integrity: sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==} + '@typescript-eslint/utils@7.15.0': + resolution: {integrity: sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -1217,8 +1220,8 @@ packages: resolution: {integrity: sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@7.14.1': - resolution: {integrity: sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==} + '@typescript-eslint/visitor-keys@7.15.0': + resolution: {integrity: sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==} engines: {node: ^18.18.0 || >=20.0.0} '@typescript-eslint/visitor-keys@8.0.0-alpha.30': @@ -1946,8 +1949,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.6.0: - resolution: {integrity: sha512-UzcJi88Hw//CurUIRa9Jxb0vgOCcuD/MNjwmXp633cyaRKkCWACkoqHCtfZv43b1kqXGg/fpOa8bwgacCeXsVg==} + globals@15.7.0: + resolution: {integrity: sha512-ivatRXWwKC6ImcdKO7dOwXuXR5XFrdwo45qFwD7D0qOkEPzzJdLXC3BHceBdyrPOD3p1suPaWi4Y4NMm2D++AQ==} engines: {node: '>=18'} globby@11.1.0: @@ -2203,6 +2206,9 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2937,8 +2943,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typescript-eslint@7.14.1: - resolution: {integrity: sha512-Eo1X+Y0JgGPspcANKjeR6nIqXl4VL5ldXLc15k4m9upq+eY5fhU2IueiEZL6jmHrKH8aCfbIvM/v3IrX5Hg99w==} + typescript-eslint@7.15.0: + resolution: {integrity: sha512-Ta40FhMXBCwHura4X4fncaCVkVcnJ9jnOq5+Lp4lN8F4DzHZtOwZdRvVBiNUGznUDHPwdGnrnwxmUOU2fFQqFA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -2952,6 +2958,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -3809,21 +3820,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.5.3))(eslint@9.6.0)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 7.14.1(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/type-utils': 7.14.1(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.14.1(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/parser': 7.15.0(eslint@9.6.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 7.15.0 + '@typescript-eslint/type-utils': 7.15.0(eslint@9.6.0)(typescript@5.5.3) + '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.15.0 eslint: 9.6.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3840,16 +3851,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.14.1(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/scope-manager': 7.15.0 + '@typescript-eslint/types': 7.15.0 + '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.15.0 debug: 4.3.5 eslint: 9.6.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3858,10 +3869,10 @@ snapshots: '@typescript-eslint/types': 7.13.1 '@typescript-eslint/visitor-keys': 7.13.1 - '@typescript-eslint/scope-manager@7.14.1': + '@typescript-eslint/scope-manager@7.15.0': dependencies: - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/types': 7.15.0 + '@typescript-eslint/visitor-keys': 7.15.0 '@typescript-eslint/scope-manager@8.0.0-alpha.30': dependencies: @@ -3880,21 +3891,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.14.1(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/type-utils@7.15.0(eslint@9.6.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.4.5) - '@typescript-eslint/utils': 7.14.1(eslint@9.6.0)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.5.3) + '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.5.3) debug: 4.3.5 eslint: 9.6.0 - ts-api-utils: 1.3.0(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.13.1': {} - '@typescript-eslint/types@7.14.1': {} + '@typescript-eslint/types@7.15.0': {} '@typescript-eslint/types@8.0.0-alpha.30': {} @@ -3913,18 +3924,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.14.1(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@7.15.0(typescript@5.5.3)': dependencies: - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/visitor-keys': 7.14.1 + '@typescript-eslint/types': 7.15.0 + '@typescript-eslint/visitor-keys': 7.15.0 debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.3 transitivePeerDependencies: - supports-color @@ -3954,12 +3965,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.14.1(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/utils@7.15.0(eslint@9.6.0)(typescript@5.5.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.15.0 + '@typescript-eslint/types': 7.15.0 + '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.5.3) eslint: 9.6.0 transitivePeerDependencies: - supports-color @@ -3981,9 +3992,9 @@ snapshots: '@typescript-eslint/types': 7.13.1 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@7.14.1': + '@typescript-eslint/visitor-keys@7.15.0': dependencies: - '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/types': 7.15.0 eslint-visitor-keys: 3.4.3 '@typescript-eslint/visitor-keys@8.0.0-alpha.30': @@ -4810,7 +4821,7 @@ snapshots: globals@14.0.0: {} - globals@15.6.0: {} + globals@15.7.0: {} globby@11.1.0: dependencies: @@ -5036,6 +5047,8 @@ snapshots: lodash.upperfirst@4.3.1: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -5673,6 +5686,10 @@ snapshots: dependencies: typescript: 5.4.5 + ts-api-utils@1.3.0(typescript@5.5.3): + dependencies: + typescript: 5.5.3 + ts-interface-checker@0.1.13: {} ts-node@10.9.1(@types/node@20.14.5)(typescript@5.4.5): @@ -5715,19 +5732,21 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typescript-eslint@7.14.1(eslint@9.6.0)(typescript@5.4.5): + typescript-eslint@7.15.0(eslint@9.6.0)(typescript@5.5.3): dependencies: - '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/parser': 7.14.1(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.14.1(eslint@9.6.0)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.5.3))(eslint@9.6.0)(typescript@5.5.3) + '@typescript-eslint/parser': 7.15.0(eslint@9.6.0)(typescript@5.5.3) + '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.5.3) eslint: 9.6.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.3 transitivePeerDependencies: - supports-color typescript@5.4.5: {} + typescript@5.5.3: {} + undici-types@5.26.5: {} unicorn-magic@0.1.0: {} diff --git a/server/eslint.config.js b/server/eslint.config.js deleted file mode 100644 index 5d267db..0000000 --- a/server/eslint.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const pluginJs = require('@eslint/js') -const globals = require('globals') -const tseslint = require('typescript-eslint') - -module.exports = [ - { files: ['src/**/*.ts'], ignores: ['dist/**', 'drizzle', 'node_modules'] }, - { languageOptions: { globals: globals.node } }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, -] diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs new file mode 100644 index 0000000..6d307cb --- /dev/null +++ b/server/eslint.config.mjs @@ -0,0 +1,10 @@ +import pluginJs from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default [ + { files: ['./src/**/*.ts'], ignores: ['./dist/**'] }, + { languageOptions: { globals: globals.node } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, +] diff --git a/server/package.json b/server/package.json index ae0f6fb..f68d747 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,7 @@ "start": "node dist/index.js", "dev": "tsx watch src/index.ts", "build": "tsc", - "lint": "tsc && eslint -c eslint.config.js", + "lint": "tsc && eslint src/**/*.ts", "migrate:gen": "drizzle-kit generate", "migrate:run": "tsx src/scripts/migrate.ts", "seed": "tsx src/scripts/seed.ts" @@ -18,7 +18,7 @@ "real time" ], "author": "Aseer KT", - "license": "UNLICENSED", + "license": "ISC", "dependencies": { "@socket.io/cluster-adapter": "^0.2.2", "@socket.io/sticky": "^1.0.4", @@ -30,9 +30,9 @@ "express": "^4.19.2", "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.2", + "lodash": "^4.17.21", "pg": "^8.12.0", - "socket.io": "^4.7.5", - "typescript": "^5.2.2" + "socket.io": "^4.7.5" }, "devDependencies": { "@eslint/js": "^9.6.0", @@ -44,8 +44,9 @@ "@types/pg": "^8.11.6", "drizzle-kit": "^0.22.8", "eslint": "9.x", - "globals": "^15.6.0", + "globals": "^15.7.0", "tsx": "^4.15.6", - "typescript-eslint": "^7.14.1" + "typescript": "^5.5.3", + "typescript-eslint": "^7.15.0" } } diff --git a/server/src/database/helpers.ts b/server/src/database/helpers.ts index 4e3fa9d..09f6ae0 100644 --- a/server/src/database/helpers.ts +++ b/server/src/database/helpers.ts @@ -1,33 +1,52 @@ -import { - AnyColumn, - SQL, - SQLWrapper, - and, - asc, - desc, - lt, - sql, -} from 'drizzle-orm' +import { isValidDate } from '@/utils/validations' +import { AnyColumn, SQL, and, asc, desc, gt, lt, sql } from 'drizzle-orm' import { PgSelect, bigserial, timestamp } from 'drizzle-orm/pg-core' +interface WithPaginateOptions { + query: Record + sortByColumn: AnyColumn + sortDirection?: 'asc' | 'desc' + where?: SQL +} + export const withPagination = async ( qb: T, - query: Record, - orderBy: SQLWrapper | AnyColumn, - order: 'asc' | 'desc' = 'desc', - where?: SQL, + { query, sortByColumn, sortDirection = 'desc', where }: WithPaginateOptions, ) => { const limit = Number(query.limit) > 20 ? 10 : Number(query.limit) - const cursor = Number(query.cursor) + let cursor + + switch (sortByColumn.dataType) { + case 'number': + cursor = Number(query.cursor) + break + case 'date': + cursor = isValidDate(query.cursor) + ? new Date(query.cursor as string) + : null + break + default: + cursor = query.cursor + } + + const orderWhere = + sortDirection === 'asc' + ? gt(sortByColumn, cursor) + : lt(sortByColumn, cursor) + const orderBy = + sortDirection === 'asc' ? asc(sortByColumn) : desc(sortByColumn) const result = await qb - .where(cursor ? and(lt(orderBy, cursor), where) : where) + .where(cursor ? and(orderWhere, where) : where) .limit(limit) - .orderBy(order === 'asc' ? asc(orderBy) : desc(orderBy)) + .orderBy(orderBy) return { data: result, - cursor: result.length === limit ? result[result.length - 1].id : null, + cursor: + result.length === limit + ? result[result.length - 1][sortByColumn.name] + : null, } } diff --git a/server/src/modules/groups/groups.controller.ts b/server/src/modules/groups/groups.controller.ts index 305f7c1..684917a 100644 --- a/server/src/modules/groups/groups.controller.ts +++ b/server/src/modules/groups/groups.controller.ts @@ -51,10 +51,11 @@ export const listGroups: RequestHandler = async (req, res, next) => { .from(groups) .innerJoin(members, eq(members.groupId, groups.id)) .$dynamic(), - req.query, - groups.id, - 'desc', - ne(members.userId, req.user!.id), + { + query: req.query, + where: ne(members.userId, req.user!.id), + sortByColumn: groups.id, + }, ) res.json(result) @@ -91,10 +92,11 @@ export const listUserGroups: RequestHandler = async (req, res, next) => { .from(groups) .innerJoin(members, eq(members.groupId, groups.id)) .$dynamic(), - req.query, - groups.id, - 'desc', - eq(members.userId, req.user!.id), + { + query: req.query, + where: eq(members.userId, req.user!.id), + sortByColumn: groups.id, + }, ) res.json(result) diff --git a/server/src/modules/groups/groups.routes.ts b/server/src/modules/groups/groups.routes.ts index bd76fda..a5ee1ae 100644 --- a/server/src/modules/groups/groups.routes.ts +++ b/server/src/modules/groups/groups.routes.ts @@ -1,6 +1,6 @@ import { hasRoomPermission } from '@/middlewares' import { Router } from 'express' -import { getRoomMembers } from '../members/members.controller' +import { getGroupMembers } from '../members/members.controller' import { createMessage, listMessages } from '../messages/messages.controller' import { createGroup, @@ -17,7 +17,7 @@ router.get('/', listGroups) router.get('/:groupId', hasRoomPermission('member'), getGroup) router.delete('/:groupId', hasRoomPermission('owner'), deleteGroup) -router.get('/:groupId/members', hasRoomPermission('member'), getRoomMembers) +router.get('/:groupId/members', hasRoomPermission('member'), getGroupMembers) router.get('/:groupId/messages', hasRoomPermission('member'), listMessages) router.post('/:groupId/messages', createMessage) diff --git a/server/src/modules/members/members.controller.ts b/server/src/modules/members/members.controller.ts index e43b8f0..ec5f89e 100644 --- a/server/src/modules/members/members.controller.ts +++ b/server/src/modules/members/members.controller.ts @@ -34,7 +34,7 @@ export const createMembers: RequestHandler = async (req, res, next) => { } } -export const getRoomMembers: RequestHandler = async (req, res, next) => { +export const getGroupMembers: RequestHandler = async (req, res, next) => { try { const result = await withPagination( db @@ -42,10 +42,12 @@ export const getRoomMembers: RequestHandler = async (req, res, next) => { .from(members) .innerJoin(users, eq(members.userId, users.id)) .$dynamic(), - req.query, - members.id, - 'asc', - eq(members.groupId, Number(req.params.groupId)), + { + query: req.query, + where: eq(members.groupId, Number(req.params.groupId)), + sortByColumn: users.username, + sortDirection: 'asc', + }, ) if (!result?.data.length) { diff --git a/server/src/modules/messages/messages.controller.ts b/server/src/modules/messages/messages.controller.ts index 26c3f02..e8235ca 100644 --- a/server/src/modules/messages/messages.controller.ts +++ b/server/src/modules/messages/messages.controller.ts @@ -29,10 +29,11 @@ export const listMessages: RequestHandler = async (req, res, next) => { .from(messages) .innerJoin(users, eq(messages.senderId, users.id)) .$dynamic(), - req.query, - messages.id, - 'desc', - eq(messages.groupId, Number(req.params.groupId)), + { + query: req.query, + where: eq(messages.groupId, Number(req.params.groupId)), + sortByColumn: messages.id, + }, ) res.json(result) diff --git a/server/src/socket/middlewares.ts b/server/src/socket/middlewares.ts index f5022dc..8ca1674 100644 --- a/server/src/socket/middlewares.ts +++ b/server/src/socket/middlewares.ts @@ -9,7 +9,7 @@ export const socketAuthMiddleware = ( try { const token = socket.handshake.auth.token const payload = verifyToken(token) - socket.data.user = payload as any + socket.data.user = payload as UserPayload next() } catch (error) { next(new Error('Socket: Not authenticated')) diff --git a/web/src/features/member/components/MemberList.tsx b/web/src/features/member/components/MemberList.tsx index eb0044e..3b2ed1f 100644 --- a/web/src/features/member/components/MemberList.tsx +++ b/web/src/features/member/components/MemberList.tsx @@ -34,7 +34,7 @@ export const MemberList = ({ groupId }: { groupId: number }) => { limit: 15, cursor: pageParam, }), - initialPageParam: null as null | number, + initialPageParam: null as string | null, getNextPageParam: lastPage => lastPage.cursor ? lastPage.cursor : undefined, }) diff --git a/web/src/features/member/member.interface.ts b/web/src/features/member/member.interface.ts index 67f8fdf..ce5691c 100644 --- a/web/src/features/member/member.interface.ts +++ b/web/src/features/member/member.interface.ts @@ -9,6 +9,6 @@ export interface IMember { online?: boolean } -export interface IGetRoomMembersArgs extends TPaginatedParams { +export interface IGetRoomMembersArgs extends TPaginatedParams { groupId: number } diff --git a/web/src/features/member/member.service.ts b/web/src/features/member/member.service.ts index 0b4feaa..376a041 100644 --- a/web/src/features/member/member.service.ts +++ b/web/src/features/member/member.service.ts @@ -5,5 +5,5 @@ import { IGetRoomMembersArgs, IMember } from './member.interface' export const fetchRoomMembers = async ({ groupId, ...params -}: IGetRoomMembersArgs): Promise> => +}: IGetRoomMembersArgs): Promise> => fetcher(`groups/${groupId}/members?${stringifyQueryParams(params)}`) diff --git a/web/src/interfaces/common.interface.ts b/web/src/interfaces/common.interface.ts index dc6499d..ce7b476 100644 --- a/web/src/interfaces/common.interface.ts +++ b/web/src/interfaces/common.interface.ts @@ -1,10 +1,13 @@ -export interface IPaginatedResult { +export interface IPaginatedResult< + TData, + TCursor extends number | string = number, +> { data: TData[] hasMore: boolean - cursor: number | null + cursor: TCursor | null } -export type TPaginatedParams = { +export type TPaginatedParams = { limit: number - cursor: number | null + cursor: TCursor | null }