diff --git a/.gitignore b/.gitignore index 62226a4..44b1838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules -.env +.env.development +.env.test build coverage react-email-starter diff --git a/env.d.ts b/env.d.ts index 73a6543..04496ec 100644 --- a/env.d.ts +++ b/env.d.ts @@ -2,21 +2,30 @@ declare global { namespace NodeJS { interface ProcessEnv { NODE_ENV: 'development' | 'production' | 'test'; + TEST_MODEL: 'mock' | 'real'; PORT?: string; - HOSTNAME: string; + HOSTNAME?: string; - DB_HOST: string; - DB_PORT: string; - DB_NAME: string; - DB_USERNAME: string; - DB_PASSWORD: string; + DB_HOST?: string; + DB_PORT?: string; + DB_NAME?: string; + DB_USERNAME?: string; + DB_PASSWORD?: string; - REDIS_URL: string; + REDIS_URL?: string; + REDIS_HOST?: string; + REDIS_PORT?: string; - SESSION_SECRET: string; + SESSION_SECRET?: string; - HASHING_SALT: string; + JWT_SECRET_KEY?: string; + JWT_EXPIRES_IN?: string; + + COOKIES_SECRET_KEY?: string; + COOKIES_MAX_AGE?: string; + + HASHING_SALT?: string; // Add other environment variables here } diff --git a/jest.config.ts b/jest.config.ts index 688d760..1c079d8 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,6 +14,7 @@ const config: Config = { coverageProvider: 'v8', testEnvironment: 'node', roots: [''], + testTimeout: 15000, setupFilesAfterEnv: ['reflect-metadata'], modulePaths: [compilerOptions.baseUrl], coverageDirectory: './coverage', diff --git a/package.json b/package.json index 6a2f253..9e56ca2 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,18 @@ "db:create": "pnpm run build && NODE_ENV=development npx sequelize-cli db:create", "db:migrate:up": "pnpm run build && NODE_ENV=development npx sequelize-cli db:migrate", "db:migrate:undo": "pnpm run build && NODE_ENV=development npx sequelize-cli db:migrate:undo", - "test": "env NODE_ENV=test jest", - "test:watch": "env NODE_ENV=test jest --runInBand --watch ./src/tests/server.test.ts", + "test": "NODE_ENV=test TEST_MODEL=mock jest", + "test:watch": "NODE_ENV=test TEST_MODEL=real jest --runInBand --watch .", "lint": "eslint --ignore-path .eslintignore --ext .js,.ts" }, "author": "Chinedu", "license": "ISC", "dependencies": { + "@type-cacheable/core": "^14.0.1", + "@type-cacheable/ioredis-adapter": "^15.0.1", "bcrypt": "^5.1.1", "connect-redis": "^7.1.1", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -42,15 +45,17 @@ "@eslint/js": "^10.0.0", "@tsconfig/node16": "^16.1.3", "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", "@types/jest": "^29.5.12", + "@types/jsonwebtoken": "^9.0.6", "@types/node": "^22.0.0", "@types/safe-regex": "^1.1.6", "@types/supertest": "^6.0.2", "@types/validator": "^13.12.0", - "@typescript-eslint/eslint-plugin": "^7.16.1", - "@typescript-eslint/parser": "^7.16.1", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", @@ -66,6 +71,6 @@ "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.5.3", - "typescript-eslint": "^7.16.1" + "typescript-eslint": "^8.0.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5af50f9..7404d9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,12 +5,21 @@ settings: excludeLinksFromLockfile: false dependencies: + '@type-cacheable/core': + specifier: ^14.0.1 + version: 14.0.1 + '@type-cacheable/ioredis-adapter': + specifier: ^15.0.1 + version: 15.0.1(@type-cacheable/core@14.0.1)(ioredis@5.4.1) bcrypt: specifier: ^5.1.1 version: 5.1.1 connect-redis: specifier: ^7.1.1 version: 7.1.1(express-session@1.18.0) + cookie-parser: + specifier: ^1.4.6 + version: 1.4.6 cors: specifier: ^2.8.5 version: 2.8.5 @@ -59,9 +68,6 @@ dependencies: sequelize: specifier: ^6.37.3 version: 6.37.3(pg-hstore@2.3.4)(pg@8.12.0) - sequelize-typescript: - specifier: ^2.1.6 - version: 2.1.6(@types/node@22.0.0)(@types/validator@13.12.0)(reflect-metadata@0.2.2)(sequelize@6.37.3) zod: specifier: ^3.23.8 version: 3.23.8 @@ -79,6 +85,9 @@ devDependencies: '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 + '@types/cookie-parser': + specifier: ^1.4.7 + version: 1.4.7 '@types/express': specifier: ^4.17.21 version: 4.17.21 @@ -88,6 +97,9 @@ devDependencies: '@types/jest': specifier: ^29.5.12 version: 29.5.12 + '@types/jsonwebtoken': + specifier: ^9.0.6 + version: 9.0.6 '@types/node': specifier: ^22.0.0 version: 22.0.0 @@ -101,11 +113,11 @@ devDependencies: specifier: ^13.12.0 version: 13.12.0 '@typescript-eslint/eslint-plugin': - specifier: ^7.16.1 - version: 7.16.1(@typescript-eslint/parser@7.16.1)(eslint@9.7.0)(typescript@5.5.3) + specifier: ^8.0.0 + version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.7.0)(typescript@5.5.3) '@typescript-eslint/parser': - specifier: ^7.16.1 - version: 7.16.1(eslint@9.7.0)(typescript@5.5.3) + specifier: ^8.0.0 + version: 8.0.0(eslint@9.7.0)(typescript@5.5.3) eslint: specifier: ^9.7.0 version: 9.7.0 @@ -152,8 +164,8 @@ devDependencies: specifier: ^5.5.3 version: 5.5.3 typescript-eslint: - specifier: ^7.16.1 - version: 7.16.1(eslint@9.7.0)(typescript@5.5.3) + specifier: ^8.0.0 + version: 8.0.0(eslint@9.7.0)(typescript@5.5.3) packages: @@ -1114,6 +1126,25 @@ packages: resolution: {integrity: sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==} dev: true + /@type-cacheable/core@14.0.1: + resolution: {integrity: sha512-tJpUNQd3mkF1ZzjQvaA8lQYpLCBULzBBRmt4pvLGdK60J9d8SwhKVPcD9pG4ZPTpAItabnPz+097pHCXVs9lqA==} + dependencies: + blueimp-md5: 2.19.0 + reflect-metadata: 0.2.2 + serialize-javascript: 6.0.2 + dev: false + + /@type-cacheable/ioredis-adapter@15.0.1(@type-cacheable/core@14.0.1)(ioredis@5.4.1): + resolution: {integrity: sha512-j1WPz3c5h9VxymeWNz1x6ASo53if+A3NvMz4IW+vjIWDhlNWyaWHx+XQBP0dzrpxEek3tL8LDPiduODxQ5h4Sg==} + peerDependencies: + '@type-cacheable/core': ^14.0.0 + ioredis: ^5 + dependencies: + '@type-cacheable/core': 14.0.1 + compare-versions: 6.1.1 + ioredis: 5.4.1 + dev: false + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -1162,6 +1193,12 @@ packages: '@types/node': 22.0.0 dev: true + /@types/cookie-parser@1.4.7: + resolution: {integrity: sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==} + dependencies: + '@types/express': 4.17.21 + dev: true + /@types/cookiejar@2.1.5: resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} dev: true @@ -1229,6 +1266,12 @@ packages: pretty-format: 29.7.0 dev: true + /@types/jsonwebtoken@9.0.6: + resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} + dependencies: + '@types/node': 22.0.0 + dev: true + /@types/methods@1.1.4: resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} dev: true @@ -1306,23 +1349,23 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1)(eslint@9.7.0)(typescript@5.5.3): - resolution: {integrity: sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.7.0)(typescript@5.5.3): + resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.16.1(eslint@9.7.0)(typescript@5.5.3) - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/type-utils': 7.16.1(eslint@9.7.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/parser': 8.0.0(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/type-utils': 8.0.0(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.0.0(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 8.0.0 eslint: 9.7.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -1333,20 +1376,20 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@7.16.1(eslint@9.7.0)(typescript@5.5.3): - resolution: {integrity: sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/parser@8.0.0(eslint@9.7.0)(typescript@5.5.3): + resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.5(supports-color@5.5.0) eslint: 9.7.0 typescript: 5.5.3 @@ -1354,50 +1397,49 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager@7.16.1: - resolution: {integrity: sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/scope-manager@8.0.0: + resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 dev: true - /@typescript-eslint/type-utils@7.16.1(eslint@9.7.0)(typescript@5.5.3): - resolution: {integrity: sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/type-utils@8.0.0(eslint@9.7.0)(typescript@5.5.3): + resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.3) + '@typescript-eslint/utils': 8.0.0(eslint@9.7.0)(typescript@5.5.3) debug: 4.3.5(supports-color@5.5.0) - eslint: 9.7.0 ts-api-utils: 1.3.0(typescript@5.5.3) typescript: 5.5.3 transitivePeerDependencies: + - eslint - supports-color dev: true - /@typescript-eslint/types@7.16.1: - resolution: {integrity: sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/types@8.0.0: + resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.3): - resolution: {integrity: sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/typescript-estree@8.0.0(typescript@5.5.3): + resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.5(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 @@ -1409,27 +1451,27 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@7.16.1(eslint@9.7.0)(typescript@5.5.3): - resolution: {integrity: sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/utils@8.0.0(eslint@9.7.0)(typescript@5.5.3): + resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.7.0) - '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.3) eslint: 9.7.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@7.16.1: - resolution: {integrity: sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==} - engines: {node: ^18.18.0 || >=20.0.0} + /@typescript-eslint/visitor-keys@8.0.0: + resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 dev: true @@ -1686,6 +1728,10 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: true + /blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + dev: false + /body-parser@1.20.2: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1922,6 +1968,10 @@ packages: engines: {node: '>=14'} dev: true + /compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + dev: false + /component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} dev: true @@ -1965,6 +2015,14 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /cookie-parser@1.4.6: + resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==} + engines: {node: '>= 0.8.0'} + dependencies: + cookie: 0.4.1 + cookie-signature: 1.0.6 + dev: false + /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false @@ -1973,6 +2031,11 @@ packages: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} dev: false + /cookie@0.4.1: + resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} + engines: {node: '>= 0.6'} + dev: false + /cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -2777,18 +2840,6 @@ packages: path-scurry: 2.0.0 dev: true - /glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - deprecated: Glob versions prior to v9 are no longer supported - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -4408,6 +4459,12 @@ packages: engines: {node: '>= 0.8'} dev: false + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -4599,22 +4656,6 @@ packages: engines: {node: '>= 10.0.0'} dev: false - /sequelize-typescript@2.1.6(@types/node@22.0.0)(@types/validator@13.12.0)(reflect-metadata@0.2.2)(sequelize@6.37.3): - resolution: {integrity: sha512-Vc2N++3en346RsbGjL3h7tgAl2Y7V+2liYTAOZ8XL0KTw3ahFHsyAUzOwct51n+g70I1TOUDgs06Oh6+XGcFkQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - '@types/node': '*' - '@types/validator': '*' - reflect-metadata: '*' - sequelize: '>=6.20.1' - dependencies: - '@types/node': 22.0.0 - '@types/validator': 13.12.0 - glob: 7.2.0 - reflect-metadata: 0.2.2 - sequelize: 6.37.3(pg-hstore@2.3.4)(pg@8.12.0) - dev: false - /sequelize@6.37.3(pg-hstore@2.3.4)(pg@8.12.0): resolution: {integrity: sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==} engines: {node: '>=10.0.0'} @@ -4670,6 +4711,12 @@ packages: - supports-color dev: false + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + dependencies: + randombytes: 2.1.0 + dev: false + /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} @@ -5094,22 +5141,21 @@ packages: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} dev: true - /typescript-eslint@7.16.1(eslint@9.7.0)(typescript@5.5.3): - resolution: {integrity: sha512-889oE5qELj65q/tGeOSvlreNKhimitFwZqQ0o7PcWC7/lgRkAMknznsCsV8J8mZGTP/Z+cIbX8accf2DE33hrA==} - engines: {node: ^18.18.0 || >=20.0.0} + /typescript-eslint@8.0.0(eslint@9.7.0)(typescript@5.5.3): + resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 7.16.1(@typescript-eslint/parser@7.16.1)(eslint@9.7.0)(typescript@5.5.3) - '@typescript-eslint/parser': 7.16.1(eslint@9.7.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@5.5.3) - eslint: 9.7.0 + '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.0.0(eslint@9.7.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.0.0(eslint@9.7.0)(typescript@5.5.3) typescript: 5.5.3 transitivePeerDependencies: + - eslint - supports-color dev: true diff --git a/src/configs/env.ts b/src/configs/env.config.ts similarity index 57% rename from src/configs/env.ts rename to src/configs/env.config.ts index 479f227..ce9e21e 100644 --- a/src/configs/env.ts +++ b/src/configs/env.config.ts @@ -1,11 +1,14 @@ import dotenv from 'dotenv'; import { Options } from 'sequelize'; + dotenv.config({ path: `.env.${process.env.NODE_ENV}` }); export const isTest = process.env.NODE_ENV === 'test'; export const isDev = process.env.NODE_ENV === 'development'; export const isProd = process.env.NODE_ENV === 'production'; +export const TEST_MODEL = process.env.TEST_MODEL; + export const serverConfig = { host: process.env.HOSTNAME || 'localhost', port: process.env.PORT ? Number(process.env.PORT) : 8000, @@ -14,14 +17,30 @@ export const serverConfig = { export const dbConfig: Options = { logging: isDev, dialect: 'postgres', - host: process.env.DB_HOST, + host: process.env.DB_HOST!, + port: +process.env.DB_PORT!, database: process.env.DB_NAME!, - port: Number(process.env.DB_PORT), username: process.env.DB_USERNAME!, password: process.env.DB_PASSWORD!, }; -export const REDIS_URL = process.env.REDIS_URL!; +export const redisConfig = { + url: process.env.REDIS_URL!, + host: process.env.REDIS_HOST!, + port: +process.env.REDIS_PORT!, + username: process.env.REDIS_USER!, + password: process.env.REDIS_PASSWORD!, +}; + +export const jwtConfig = { + secretKey: process.env.JWT_SECRET_KEY!, + expiresIn: process.env.JWT_EXPIRES_IN!, +}; + +export const cookiesConfig = { + secretKey: process.env.COOKIES_SECRET_KEY!, + maxAge: +process.env.COOKIES_MAX_AGE!, +}; export const SESSION_SECRET = process.env.SESSION_SECRET!; diff --git a/src/configs/logger.ts b/src/configs/logger.config.ts similarity index 98% rename from src/configs/logger.ts rename to src/configs/logger.config.ts index 2caf1ca..1788984 100644 --- a/src/configs/logger.ts +++ b/src/configs/logger.config.ts @@ -1,4 +1,4 @@ -import { isTest } from './env'; +import { isTest } from './env.config'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type LogFunParams = [message?: any, ...optionalParams: any[]]; diff --git a/src/db/config.js b/src/db/config.js index cb8a96a..776131b 100644 --- a/src/db/config.js +++ b/src/db/config.js @@ -1,3 +1,3 @@ -import { dbConfig } from '../configs/env'; +import { dbConfig } from '../configs/env.config'; module.exports = dbConfig; diff --git a/src/db/migrations/20240723010553-create-user.ts b/src/db/migrations/20240723010553-create-user.ts index daaaebe..7be736f 100644 --- a/src/db/migrations/20240723010553-create-user.ts +++ b/src/db/migrations/20240723010553-create-user.ts @@ -12,23 +12,27 @@ export default { type: Sequelize.UUID, }, userType: { + allowNull: false, type: Sequelize.ENUM('0', '1', '2'), }, firstName: { + allowNull: false, type: Sequelize.STRING, }, lastName: { + allowNull: false, type: Sequelize.STRING, }, email: { + allowNull: false, type: Sequelize.STRING, unique: true, }, password: { + allowNull: false, type: Sequelize.STRING, }, dateOfBirth: { - allowNull: false, type: DataTypes.DATE, }, createdAt: { @@ -40,7 +44,6 @@ export default { type: Sequelize.DATE, }, deletedAt: { - allowNull: false, type: DataTypes.DATE, }, }); diff --git a/src/db/models/connection.ts b/src/db/models/connection.ts index f526b3e..2d3fdbe 100644 --- a/src/db/models/connection.ts +++ b/src/db/models/connection.ts @@ -1,5 +1,5 @@ import { Sequelize } from 'sequelize'; -import { dbConfig } from 'configs/env'; +import { dbConfig } from 'configs/env.config'; const sequelize = new Sequelize(dbConfig); diff --git a/src/db/models/user.model.ts b/src/db/models/user.model.ts index 7f5da10..9b3a738 100644 --- a/src/db/models/user.model.ts +++ b/src/db/models/user.model.ts @@ -5,8 +5,12 @@ import { InferAttributes, CreationOptional, } from 'sequelize'; +import bcrypt from 'bcrypt'; +import jwt from 'jsonwebtoken'; import { decorate, injectable } from 'inversify'; + import sequelize from './connection'; +import { HASHING_SALT, jwtConfig } from 'configs/env.config'; decorate(injectable(), Model); @@ -27,17 +31,31 @@ export class UserModel extends Model { } */ getFullname() { - return this.firstName + ' ' + this.lastName; + return this?.firstName + ' ' + this?.lastName; } getAge() { - if (!this.dateOfBirth) { + if (!this?.dateOfBirth) { return 0; } - const birthYear = new Date(this.dateOfBirth).getFullYear(); + const birthYear = new Date(this?.dateOfBirth).getFullYear(); const currentYear = new Date().getFullYear(); return currentYear - birthYear; } + + async isPasswordMatch(password: string) { + return await bcrypt.compare(password, this.password); + } + + generateAuthToken(type: 'auth' | 'reset' | 'verify') { + return jwt.sign( + { sub: this.id, email: this.email, type }, + jwtConfig.secretKey, + { + expiresIn: jwtConfig.expiresIn, + }, + ); + } } UserModel.init( @@ -62,14 +80,19 @@ UserModel.init( unique: true, }, userType: { + allowNull: false, type: DataTypes.ENUM('0', '1', '2'), }, password: { type: DataTypes.STRING, allowNull: false, + set(value: string) { + const salt = bcrypt.genSaltSync(+HASHING_SALT); + const hash = bcrypt.hashSync(value, salt + this.email); + this.setDataValue('password', hash); + }, }, dateOfBirth: { - allowNull: false, type: DataTypes.DATE, }, createdAt: { @@ -81,11 +104,10 @@ UserModel.init( type: DataTypes.DATE, }, deletedAt: { - allowNull: false, type: DataTypes.DATE, }, }, { sequelize, paranoid: true, freezeTableName: true, modelName: 'Users' }, ); -export interface UserModelDto extends InferAttributes {} +export type UserModelDto = InferAttributes; diff --git a/src/tests/configs/logger.test.ts b/src/tests/configs/logger.test.ts deleted file mode 100644 index c4841d2..0000000 --- a/src/tests/configs/logger.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('test', () => { - test('should first', () => { - expect(1 + 2).toEqual(3); - }); -}); diff --git a/src/tests/decorators/validate.test.ts b/src/tests/decorators/validate.test.ts deleted file mode 100644 index c4841d2..0000000 --- a/src/tests/decorators/validate.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('test', () => { - test('should first', () => { - expect(1 + 2).toEqual(3); - }); -}); diff --git a/src/tests/models/user.model.test.ts b/src/tests/models/user.model.test.ts index c4841d2..91cc932 100644 --- a/src/tests/models/user.model.test.ts +++ b/src/tests/models/user.model.test.ts @@ -1,5 +1,139 @@ -describe('test', () => { - test('should first', () => { - expect(1 + 2).toEqual(3); +import { TestContext } from 'tests/test.context'; + +import { UserModel } from 'db/models'; +import { mockModel } from 'tests/test.model'; + +import { TEST_MODEL } from 'configs/env.config'; + +describe('UserModel Test', () => { + let userModelMock: typeof UserModel; + const user = { + userType: '1', + lastName: 'Doe', + password: 'ABC', + firstName: 'John', + email: 'john@gmail.com', + } as const; + + beforeAll(() => { + const testContext = new TestContext(); + userModelMock = testContext.mock( + () => + TEST_MODEL === 'mock' + ? mockModel({ + getFullname: jest.fn(function (this) { + return this.firstName + ' ' + this.lastName; + }), + getAge: jest.fn(function (this) { + if (!this.dateOfBirth) { + return 0; + } + const birthYear = new Date(this.dateOfBirth).getFullYear(); + const currentYear = new Date().getFullYear(); + return currentYear - birthYear; + }), + isPasswordMatch: jest.fn(function (this, password) { + return Promise.resolve(this.password === password); + }), + }) + : UserModel, + typeof UserModel, + ); + }); + + it('shows that sequelized user model function exist', () => { + expect(userModelMock).toBeDefined(); + }); + + it('should resolve create function', async () => { + expect(userModelMock.create).toBeDefined(); + const dbUser = await userModelMock.create(user); + expect(dbUser.email).toEqual(user.email); + expect(dbUser.isPasswordMatch(user.password)).toBeTruthy(); + }); + + it('should resolve findAll function', async () => { + expect(userModelMock.findAll).toBeDefined(); + const users = await userModelMock.findAll(); + expect(users).toHaveLength(1); + expect(users[0].email).toEqual(user.email); + }); + + it('should resolve findOne function', async () => { + expect(userModelMock.findOne).toBeDefined(); + expect( + (await userModelMock.findOne({ where: { email: user.email } }))?.email, + ).toEqual(user.email); + expect( + await userModelMock.findOne({ + where: { email: 'email@notfound.com' }, + }), + ).toBeNull(); + }); + + it('should resolve findByPk function', async () => { + expect(userModelMock.findByPk).toBeDefined(); + const dbUser = await userModelMock.findOne({ + where: { email: user.email }, + }); + expect((await userModelMock.findByPk(dbUser?.id))?.email).toEqual( + user.email, + ); + expect( + await userModelMock.findByPk('fed6d510-5421-4a9a-80e0-ef1da43741b4'), + ).toBeNull(); + }); + + it('should resolve update function', async () => { + expect(userModelMock.update).toBeDefined(); + expect( + await userModelMock.update( + { firstName: 'James' }, + { where: { id: 'fed6d510-5421-4a9a-80e0-ef1da43741b4' } }, + ), + ).toEqual([0]); + const dbUser = await userModelMock.findOne({ + where: { email: user.email }, + }); + expect( + await userModelMock.update( + { firstName: 'James', dateOfBirth: new Date('1993-06-06') }, + { where: { id: dbUser?.id } }, + ), + ).toEqual([1]); + expect( + ( + await userModelMock.findOne({ + where: { email: user.email }, + }) + )?.firstName, + ).toEqual('James'); + }); + + it('should resolve static function', async () => { + const dbUser = await userModelMock.findOne({ + where: { email: user.email }, + }); + + expect(dbUser?.getFullname()).toEqual( + dbUser?.firstName + ' ' + dbUser?.lastName, + ); + + expect(dbUser?.getAge()).toBeGreaterThan(30); + }); + + it('should resolve destroy function', async () => { + expect(userModelMock.destroy).toBeDefined(); + const dbUser = await userModelMock.findOne({ + where: { email: user.email }, + }); + expect( + await userModelMock.destroy({ where: { id: dbUser?.id }, force: true }), + ).toEqual(1); + expect( + await userModelMock.findOne({ + where: { email: user.email }, + }), + ).toBeNull(); }); }); diff --git a/src/tests/repositories/auth.repository.test.ts b/src/tests/repositories/auth.repository.test.ts deleted file mode 100644 index c4841d2..0000000 --- a/src/tests/repositories/auth.repository.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('test', () => { - test('should first', () => { - expect(1 + 2).toEqual(3); - }); -}); diff --git a/src/tests/repositories/user.repository.test.ts b/src/tests/repositories/user.repository.test.ts index c4841d2..41e2c7a 100644 --- a/src/tests/repositories/user.repository.test.ts +++ b/src/tests/repositories/user.repository.test.ts @@ -1,5 +1,101 @@ -describe('test', () => { - test('should first', () => { - expect(1 + 2).toEqual(3); +import { UserRepository } from 'repositories'; +import { TestContext } from 'tests/test.context'; + +import { UserModel } from 'db/models'; +import { mockModel } from 'tests/test.model'; + +import { TEST_MODEL } from 'configs/env.config'; + +describe.only('User Repository Test', () => { + let userModelMock: typeof UserModel; + let userRepositoryMock: UserRepository; + const user = { + userType: '1', + lastName: 'Doe', + password: 'ABC', + firstName: 'John', + email: 'john@gmail.com', + } as const; + + beforeAll(() => { + const testContext = new TestContext(); + userModelMock = testContext.mock( + () => (TEST_MODEL === 'mock' ? mockModel() : UserModel), + typeof UserModel, + ); + + userRepositoryMock = testContext.mock( + () => new UserRepository(userModelMock), + UserRepository, + ); + }); + + it('shows that an instance of UserModel exist', async () => { + expect(userRepositoryMock).toBeDefined(); + }); + + it('shows that create method works as expected', async () => { + expect(userRepositoryMock.create).toBeDefined(); + expect((await userRepositoryMock.create(user)).email).toEqual(user.email); + }); + + it('shows that getAll method works as expected', async () => { + expect(userRepositoryMock.getAll).toBeDefined(); + const users = await userRepositoryMock.getAll(); + expect(users).toHaveLength(1); + expect(users[0].email).toEqual(user.email); + }); + + it('shows that getOne method works as expected', async () => { + expect(userRepositoryMock.getOne).toBeDefined(); + expect( + (await userRepositoryMock.getOne({ email: user.email }))?.email, + ).toEqual(user.email); + expect( + await userRepositoryMock.getOne({ email: 'email@notfound.com' }), + ).toBeNull(); + }); + + it('shows that getById method works as expected', async () => { + expect(userRepositoryMock.getById).toBeDefined(); + const dbUser = await userRepositoryMock.getOne({ email: user.email }); + if (dbUser?.id) { + expect((await userRepositoryMock.getById(dbUser?.id))?.email).toEqual( + user.email, + ); + } + expect( + await userRepositoryMock.getById('fed6d510-5421-4a9a-80e0-ef1da43741b4'), + ).toBeNull(); + }); + + it('shows that updateById method works as expected', async () => { + expect(userRepositoryMock.updateById).toBeDefined(); + expect( + await userRepositoryMock.updateById( + 'fed6d510-5421-4a9a-80e0-ef1da43741b4', + { firstName: 'James' }, + ), + ).toEqual([0]); + const dbUser = await userRepositoryMock.getOne({ email: user.email }); + if (dbUser?.id) { + expect( + await userRepositoryMock.updateById(dbUser.id, { + firstName: 'James', + }), + ).toEqual([1]); + } + expect( + (await userRepositoryMock.getOne({ email: user.email }))?.firstName, + ).toEqual('James'); + }); + + it('shows that deleteById method works as expected', async () => { + expect(userRepositoryMock.deleteById).toBeDefined(); + const dbUser = await userRepositoryMock.getOne({ email: user.email }); + if (dbUser?.id) { + expect(await userRepositoryMock.deleteById(dbUser.id, true)).toEqual(1); + } + expect(await userRepositoryMock.getOne({ email: user.email })).toBeNull(); }); }); diff --git a/src/tests/server.test.ts b/src/tests/server.test.ts index 9064d1e..fa96022 100644 --- a/src/tests/server.test.ts +++ b/src/tests/server.test.ts @@ -5,21 +5,30 @@ import { NOT_FOUND, OK } from 'http-status'; import { TestContext } from './test.context'; import { App } from 'app'; -import { AuthController, UserController } from 'controllers'; describe('Server Start', () => { - let app: Express; + let app: App; + let express: Express; + beforeAll(async () => { const testContext = new TestContext(); - testContext.mock(() => ({}), AuthController); - testContext.mock(() => ({}), UserController); - const _app = testContext.get(App); - await _app.initialize(); - app = _app.app; + app = testContext.get(App); + await app.initialize(); + express = app._express; + }); + + afterAll((done) => { + app.stop(done); + done(); + }); + + it('Starts and has the proper test environment', async () => { + expect(process.env.NODE_ENV).toBe('test'); + expect(express).toBeDefined(); }); it('responds with a not found message', async () => { - const response = await request(app) + const response = await request(express) .get('/not-found') .set('Accept', 'application/json') .expect(NOT_FOUND); @@ -30,7 +39,7 @@ describe('Server Start', () => { }); it('responds with a health status', async () => { - const response = await request(app) + const response = await request(express) .get('/health-check') .set('Accept', 'application/json') .expect(OK); diff --git a/src/tests/test.container.ts b/src/tests/test.container.ts index 78f4552..4363b05 100644 --- a/src/tests/test.container.ts +++ b/src/tests/test.container.ts @@ -1,15 +1,18 @@ import { interfaces } from 'inversify'; +import EventEmitter from 'node:events'; import { Container } from 'di/container'; +Object.getPrototypeOf(EventEmitter.prototype).constructor = Object; + export class TestContainer extends Container { public rebind( serviceIdentifier: interfaces.ServiceIdentifier, ): interfaces.BindingToSyntax { - return this.container.rebind(serviceIdentifier); + return this._container.bind(serviceIdentifier); } public get(serviceIdentifier: interfaces.ServiceIdentifier): T { - return this.container.get(serviceIdentifier); + return this._container.get(serviceIdentifier); } } diff --git a/src/tests/test.context.ts b/src/tests/test.context.ts index abfb14e..c8f3a91 100644 --- a/src/tests/test.context.ts +++ b/src/tests/test.context.ts @@ -22,4 +22,8 @@ export class TestContext { private mockClass(implementation: () => Partial): T { return jest.fn(implementation)() as T; } + + private mockFunc(implementation: () => Partial): T { + return jest.fn(implementation)() as T; + } } diff --git a/src/tests/test.model.ts b/src/tests/test.model.ts new file mode 100644 index 0000000..c2b4d8f --- /dev/null +++ b/src/tests/test.model.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +const table = new Map(); +const indexedTable = new Map(); + +type InstanceObj = { + [x in string]: jest.Mock; +}; + +export const mockModel = (instanceObj?: InstanceObj) => { + return { + create: jest.fn((payload: any) => { + const newData = { id: '1', ...payload }; + for (const key in newData) { + indexedTable.set(newData[key], newData); + } + table.set(newData.id, { ...newData, ...instanceObj }); + return Promise.resolve({ ...newData, ...instanceObj }); + }), + findAll: jest.fn(() => Promise.resolve(Array.from(table.values()))), + findByPk: jest.fn((id: string) => Promise.resolve(table.get(id) || null)), + findOne: jest.fn((query: any) => { + let result: any; + for (const key in query.where) { + result = indexedTable.get(query.where[key]); + if (result) { + break; + } + } + if (!result) return Promise.resolve(null); + return Promise.resolve({ ...result, ...instanceObj }); + }), + update: jest.fn((payload, query) => { + let result: any; + for (const key in query.where) { + result = indexedTable.get(query.where[key]); + if (result) { + break; + } + } + if (!result) return Promise.resolve<[affectedCount: number]>([0]); + for (const key in result) { + indexedTable.set(result[key], { ...result, ...payload }); + } + for (const key in query.where) { + result = indexedTable.get(query.where[key]); + if (result) { + table.set(result.id, { ...result, ...instanceObj }); + break; + } + } + return Promise.resolve<[affectedCount: number]>([1]); + }) as any, + destroy: jest.fn((query: any) => { + let result: any; + for (const key in query.where) { + result = indexedTable.get(query.where[key]); + if (result) { + break; + } + } + if (!result) return Promise.resolve(0); + for (const key in result) { + indexedTable.delete(result[key]); + } + table.delete(result.id); + return Promise.resolve(1); + }), + }; +}; diff --git a/tsconfig.json b/tsconfig.json index 1a8e817..a091aaf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,6 @@ "types": ["jest", "@types/jest", "reflect-metadata"], "forceConsistentCasingInFileNames": true }, - "include": ["src/**/*"], - "exclude": ["node_modules/", "build/", "src/tests/"] + "include": ["src/", "env.d.ts"], + "exclude": ["node_modules/", "build/"] }