From cf5b2f34775e126e370e2fc7f3747c57121ac20f Mon Sep 17 00:00:00 2001 From: Deedee Date: Sun, 21 Apr 2024 17:10:27 +0100 Subject: [PATCH 1/9] Installed passport to generate Api-key --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 45 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5fa9e43..c5ace7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "express": "^4.19.2", "inversify": "^6.0.2", "morgan": "^1.10.0", + "passport": "^0.7.0", + "passport-custom": "^1.1.1", "pg": "^8.11.5", "reflect-metadata": "^0.2.2" }, @@ -4168,6 +4170,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-custom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz", + "integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4206,6 +4244,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pg": { "version": "8.11.5", "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", diff --git a/package.json b/package.json index 133a7da..877d401 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "express": "^4.19.2", "inversify": "^6.0.2", "morgan": "^1.10.0", + "passport": "^0.7.0", + "passport-custom": "^1.1.1", "pg": "^8.11.5", "reflect-metadata": "^0.2.2" }, From 0f52d97c598f43f218d83203220430281f723f19 Mon Sep 17 00:00:00 2001 From: Deedee Date: Fri, 26 Apr 2024 01:22:40 +0100 Subject: [PATCH 2/9] chore: Hash user password --- db_script/database.sql | 2 +- package-lock.json | 442 +++++++++++++++++++++++++++-- package.json | 2 + src/controllers/passport-custom.ts | 109 +++++-- 4 files changed, 497 insertions(+), 58 deletions(-) diff --git a/db_script/database.sql b/db_script/database.sql index 29a23a4..8098f5a 100644 --- a/db_script/database.sql +++ b/db_script/database.sql @@ -11,7 +11,7 @@ CREATE SCHEMA IF NOT EXISTS celebration AUTHORIZATION devdeedee; CREATE TABLE IF NOT EXISTS celebration.user( id SERIAL PRIMARY KEY, phone_number varchar(32) NOT NULL UNIQUE, - password varchar(32) NOT NULL, + password varchar(128) NOT NULL, api_key varchar(255) NOT NULL, is_admin BOOLEAN DEFAULT FALSE, created_at DATE DEFAULT CURRENT_DATE, diff --git a/package-lock.json b/package-lock.json index 739e4f4..1bb1234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -23,6 +24,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", @@ -1087,6 +1089,61 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1176,6 +1233,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -1483,8 +1549,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { "version": "1.3.8", @@ -1519,6 +1584,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1538,7 +1635,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1571,6 +1667,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1722,8 +1835,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/basic-auth": { "version": "2.0.1", @@ -1741,6 +1853,19 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1780,7 +1905,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1991,6 +2115,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2060,6 +2192,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2084,8 +2224,12 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -2240,6 +2384,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2257,6 +2406,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2331,8 +2488,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -2634,11 +2790,32 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2662,6 +2839,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2723,7 +2919,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2819,6 +3014,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2860,6 +3060,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2918,7 +3151,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2985,7 +3217,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3807,7 +4038,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3928,7 +4158,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3936,6 +4165,48 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -3981,6 +4252,30 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4080,6 +4375,17 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4125,7 +4431,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -4271,7 +4576,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4594,6 +4898,19 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4667,6 +4984,20 @@ "node": ">=10" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4695,7 +5026,6 @@ "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4748,6 +5078,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4810,8 +5145,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -4893,6 +5227,14 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4910,7 +5252,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4924,7 +5265,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5055,6 +5395,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5116,6 +5472,11 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -5309,6 +5670,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5376,6 +5742,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5391,6 +5771,14 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5411,8 +5799,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -5447,8 +5834,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "17.7.2", diff --git a/package.json b/package.json index 153ce4b..a0eb5b1 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -27,6 +28,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", diff --git a/src/controllers/passport-custom.ts b/src/controllers/passport-custom.ts index 472ed10..f8be2f1 100644 --- a/src/controllers/passport-custom.ts +++ b/src/controllers/passport-custom.ts @@ -1,45 +1,96 @@ import passport from "passport"; import passportCustom from "passport-custom"; +import bcrypt from "bcrypt"; const CustomStrategy = passportCustom.Strategy; import { v4 as uuidv4 } from "uuid"; import { pool } from "../config/database"; +async function hashPassword(password: string) { + const saltRounds = 10; + return await bcrypt.hash(password, saltRounds); +} + passport.use( "custom-api-key", - new CustomStrategy((req, done) => { - const phone_number = req.body.phone_number; - const password = req.body.password; - - // Check if the user exists - pool.query( - "SELECT * FROM celebration.user WHERE phone_number = $1 AND password = $2", - [phone_number, password], - (err, result) => { - if (err) { - return done(err.message); - } - if (result.rows.length > 0) { - // User exists, return the user info with existing API key - const user = result.rows[0]; + new CustomStrategy(async (req, done) => { + try { + const phone_number = req.body.phone_number; + const password = req.body.password; + + // Check if the user exists + const query = { + text: "SELECT * FROM celebration.user WHERE phone_number = $1", + values: [phone_number], + }; + const result = await pool.query(query); + + if (result.rows.length > 0) { + const user = result.rows[0]; + const match = await bcrypt.compare(password, user.password); + + if (match) { + // Passwords match, return the user info return done(null, user); } else { - // User doesn't exist, create a new user with API key - const api_key = uuidv4(); // Generate a new UUID for API key - pool.query( - "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", - [phone_number, password, api_key], - (err, result) => { - if (err) { - return done(err); - } - const newUser = result.rows[0]; - return done(null, newUser); - } - ); + // Passwords don't match + return done(null, { message: "Incorrect password" }); } + } else { + // User doesn't exist, create a new user with API key + const hashedPassword = await hashPassword(password); + const api_key = uuidv4(); // Generate a new UUID for API key + + const insertQuery = { + text: "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", + values: [phone_number, hashedPassword, api_key], + }; + const newUserResult = await pool.query(insertQuery); + const newUser = newUserResult.rows[0]; + + return done(null, newUser); } - ); + } catch (error) { + return done(error); + } }) ); export default passport; + +// Check if the user exists +// pool.query( +// "SELECT * FROM celebration.user WHERE phone_number = $1 AND password = $2", +// [phone_number, password], +// (err, result) => { +// if (err) { +// return done(err.message); +// } +// if (result.rows.length > 0) { +// // User exists, return the user info with existing API key +// const user = result.rows[0]; +// const match = await bcrypt.compare(password, user.password); +// if (match) { +// // Passwords match, return the user info +// return done(null, user); +// } else { +// // Passwords don't match +// return done(null, { message: "Incorrect password" }); +// } +// } else { +// // User doesn't exist, create a new user with API key +// const hashedPassword = await hashPassword(password); +// const api_key = uuidv4(); // Generate a new UUID for API key +// pool.query( +// "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", +// [phone_number, hashedPassword, api_key], +// (err, result) => { +// if (err) { +// return done(err); +// } +// const newUser = result.rows[0]; +// return done(null, newUser); +// } +// ); +// } +// } +// ); From a77dbc4de6856726113634a3b73d1578c3f17245 Mon Sep 17 00:00:00 2001 From: Deedee Date: Fri, 26 Apr 2024 16:29:11 +0100 Subject: [PATCH 3/9] chore: make index file an export file for controller folder --- src/controllers/index.ts | 3 + src/controllers/passport-custom.ts | 96 ------------------------------ src/routes/auth.route.ts | 21 ------- src/routes/index.ts | 2 +- 4 files changed, 4 insertions(+), 118 deletions(-) delete mode 100644 src/controllers/passport-custom.ts delete mode 100644 src/routes/auth.route.ts diff --git a/src/controllers/index.ts b/src/controllers/index.ts index e69de29..2bf4237 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -0,0 +1,3 @@ +import passport from "./apiAuth.controller"; + +export default passport; diff --git a/src/controllers/passport-custom.ts b/src/controllers/passport-custom.ts deleted file mode 100644 index f8be2f1..0000000 --- a/src/controllers/passport-custom.ts +++ /dev/null @@ -1,96 +0,0 @@ -import passport from "passport"; -import passportCustom from "passport-custom"; -import bcrypt from "bcrypt"; -const CustomStrategy = passportCustom.Strategy; -import { v4 as uuidv4 } from "uuid"; -import { pool } from "../config/database"; - -async function hashPassword(password: string) { - const saltRounds = 10; - return await bcrypt.hash(password, saltRounds); -} - -passport.use( - "custom-api-key", - new CustomStrategy(async (req, done) => { - try { - const phone_number = req.body.phone_number; - const password = req.body.password; - - // Check if the user exists - const query = { - text: "SELECT * FROM celebration.user WHERE phone_number = $1", - values: [phone_number], - }; - const result = await pool.query(query); - - if (result.rows.length > 0) { - const user = result.rows[0]; - const match = await bcrypt.compare(password, user.password); - - if (match) { - // Passwords match, return the user info - return done(null, user); - } else { - // Passwords don't match - return done(null, { message: "Incorrect password" }); - } - } else { - // User doesn't exist, create a new user with API key - const hashedPassword = await hashPassword(password); - const api_key = uuidv4(); // Generate a new UUID for API key - - const insertQuery = { - text: "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", - values: [phone_number, hashedPassword, api_key], - }; - const newUserResult = await pool.query(insertQuery); - const newUser = newUserResult.rows[0]; - - return done(null, newUser); - } - } catch (error) { - return done(error); - } - }) -); - -export default passport; - -// Check if the user exists -// pool.query( -// "SELECT * FROM celebration.user WHERE phone_number = $1 AND password = $2", -// [phone_number, password], -// (err, result) => { -// if (err) { -// return done(err.message); -// } -// if (result.rows.length > 0) { -// // User exists, return the user info with existing API key -// const user = result.rows[0]; -// const match = await bcrypt.compare(password, user.password); -// if (match) { -// // Passwords match, return the user info -// return done(null, user); -// } else { -// // Passwords don't match -// return done(null, { message: "Incorrect password" }); -// } -// } else { -// // User doesn't exist, create a new user with API key -// const hashedPassword = await hashPassword(password); -// const api_key = uuidv4(); // Generate a new UUID for API key -// pool.query( -// "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", -// [phone_number, hashedPassword, api_key], -// (err, result) => { -// if (err) { -// return done(err); -// } -// const newUser = result.rows[0]; -// return done(null, newUser); -// } -// ); -// } -// } -// ); diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts deleted file mode 100644 index 49e1fc0..0000000 --- a/src/routes/auth.route.ts +++ /dev/null @@ -1,21 +0,0 @@ -import express, { Request, Response } from "express"; -import passport from "../controllers/passport-custom"; - -const router = express.Router(); - -// Define a route to handle authentication -router.post("/auth", (req: Request, res: Response, next) => { - // Use passport.authenticate middleware with your custom strategy - passport.authenticate("custom-api-key", (err: String, user: Object) => { - if (err) { - return next(err); // Pass error to Express error handler - } - if (!user) { - return res.status(401).json({ message: "Authentication failed" }); - } - // If authentication succeeds, you can handle the user data as needed - return res.json({ user }); - })(req, res, next); // Call the authenticate middleware with req, res, and next -}); - -export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 66441ad..978b60b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,7 +1,7 @@ import express from "express"; import router from "./serverRoute"; -import authRouter from "./auth.route"; +import authRouter from "./apiAuth.route"; const routes = express(); From aae83301e0745d4e97a834d1d7aba9cbf1761c71 Mon Sep 17 00:00:00 2001 From: Deedee Date: Fri, 26 Apr 2024 16:29:33 +0100 Subject: [PATCH 4/9] chore: make index file an export file for controller folder --- src/controllers/apiAuth.controller.ts | 58 +++++++++++++++++++++++++++ src/routes/apiAuth.route.ts | 25 ++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/controllers/apiAuth.controller.ts create mode 100644 src/routes/apiAuth.route.ts diff --git a/src/controllers/apiAuth.controller.ts b/src/controllers/apiAuth.controller.ts new file mode 100644 index 0000000..3194fc5 --- /dev/null +++ b/src/controllers/apiAuth.controller.ts @@ -0,0 +1,58 @@ +import passport from "passport"; +import passportCustom from "passport-custom"; +import bcrypt from "bcrypt"; +const CustomStrategy = passportCustom.Strategy; +import { v4 as uuidv4 } from "uuid"; +import { pool } from "../config/database"; + +async function hashPassword(password: string) { + const saltRounds = 10; + return await bcrypt.hash(password, saltRounds); +} + +passport.use( + "custom-api-key", + new CustomStrategy(async (req, done) => { + try { + const phone_number = req.body.phone_number; + const password = req.body.password; + + // Check if the user exists + const query = { + text: "SELECT * FROM celebration.user WHERE phone_number = $1", + values: [phone_number], + }; + const result = await pool.query(query); + + if (result.rows.length > 0) { + const user = result.rows[0]; + const match = await bcrypt.compare(password, user.password); + + if (match) { + // Passwords match, return the user info + return done(null, user); + } else { + // Passwords don't match + return done(null, { message: "Incorrect password" }); + } + } else { + // User doesn't exist, create a new user with API key + const hashedPassword = await hashPassword(password); + const api_key = uuidv4(); // Generate a new UUID for API key + + const insertQuery = { + text: "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", + values: [phone_number, hashedPassword, api_key], + }; + const newUserResult = await pool.query(insertQuery); + const newUser = newUserResult.rows[0]; + + return done(null, newUser); + } + } catch (error) { + return done(error); + } + }) +); + +export default passport; diff --git a/src/routes/apiAuth.route.ts b/src/routes/apiAuth.route.ts new file mode 100644 index 0000000..1ea561d --- /dev/null +++ b/src/routes/apiAuth.route.ts @@ -0,0 +1,25 @@ +import express, { Request, Response } from "express"; +import passport from "../controllers/index"; + +const router = express.Router(); + +// Define a route to handle authentication +router.post("/auth", (req: Request, res: Response, next) => { + // Use passport.authenticate middleware with your custom strategy + passport.authenticate("custom-api-key", (err: string, user: object) => { + if (err) { + return next(err); // Pass error to Express error handler + } + if (!user) { + return res.status(401).json({ message: "Authentication failed" }); + } + // If authentication succeeds, you can handle the user data as needed + return res.status(200).json({ + message: "Successfully Signed-Up/Signed-In with API Key", + success: true, + user: user, + }); + })(req, res, next); // Call the authenticate middleware with req, res, next +}); + +export default router; From 760288756048f50aa8b770f5642ddbbc2e986539 Mon Sep 17 00:00:00 2001 From: Deedee Date: Sat, 27 Apr 2024 21:10:59 +0100 Subject: [PATCH 5/9] feat: Created celebrant --- db_script/database.sql | 24 +++---- src/controllers/auth.controller.ts | 3 - src/controllers/celebrant/create-celebrant.ts | 35 ++++++++++ src/controllers/celebrant/get-a-celebrant.ts | 52 +++++++++++++++ .../celebrant/get-all-celebrants.ts | 66 +++++++++++++++++++ src/controllers/index.ts | 4 ++ src/middleware/authenticateAPI.ts | 34 ++++++++++ src/middleware/index.ts | 1 + src/routes/celebrant.route.ts | 15 +++++ src/routes/index.ts | 2 + 10 files changed, 219 insertions(+), 17 deletions(-) delete mode 100644 src/controllers/auth.controller.ts create mode 100644 src/controllers/celebrant/create-celebrant.ts create mode 100644 src/controllers/celebrant/get-a-celebrant.ts create mode 100644 src/controllers/celebrant/get-all-celebrants.ts create mode 100644 src/middleware/authenticateAPI.ts create mode 100644 src/routes/celebrant.route.ts diff --git a/db_script/database.sql b/db_script/database.sql index 8098f5a..b63ea70 100644 --- a/db_script/database.sql +++ b/db_script/database.sql @@ -26,24 +26,17 @@ CREATE TABLE IF NOT EXISTS celebration.celebrants ( email VARCHAR(255), phone_number VARCHAR(32), birthdate DATE NOT NULL, - channel_id INTEGER NOT NULL, + channel VARCHAR(50) NOT NULL CHECK (channel IN ('SMS', 'AUTOMATED_CALL', 'EMAIL'));, + channel_id INTEGER NOT NULL REFERENCES celebration.channels(id), is_active BOOLEAN DEFAULT FALSE, created_at DATE DEFAULT CURRENT_DATE, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); --- Create a table called channels for storing the channels through which birthday wishes are sent to the celebrants -CREATE TABLE IF NOT EXISTS celebration.channels ( - id SERIAL PRIMARY KEY, - mode_name INTEGER NOT NULL, - is_disabled Boolean DEFAULT FALSE, - descriiption TEXT DEFAULT NULL -); - -- Create a table called birthday_wishes for storing scheduled birthday wishes CREATE TABLE IF NOT EXISTS celebration.birthday_wishes ( id SERIAL PRIMARY KEY, - celebrant_id INTEGER NOT NULL, + celebrant_id INTEGER NOT NULL REFERENCES celebration.celebrants(id), message Varchar(3000) NOT NULL, scheduled_time TIMESTAMP NOT NULL ); @@ -51,7 +44,7 @@ CREATE TABLE IF NOT EXISTS celebration.birthday_wishes ( -- Create a table called birthday_wish_logs for storing logs of sent birthday wishes CREATE TABLE IF NOT EXISTS celebration.birthday_wish_logs ( id SERIAL PRIMARY KEY, - birthday_wishes_id INTEGER NOT NULL, + birthday_wishes_id INTEGER NOT NULL REFERENCES celebration.birthday_wishes(id), time_sent TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status VARCHAR(32) CHECK (status IN ('Successful', 'Pending', 'Failed')) ); @@ -72,7 +65,6 @@ SELECT table_name FROM information_schema.tables WHERE table_schema = 'celebrati -- Add Foreign Key Constraints to Celebrant Table ALTER TABLE celebration.celebrants - ADD FOREIGN KEY (birthdate_id) REFERENCES celebration.birthdates(id), ADD FOREIGN KEY (channel_id) REFERENCES celebration.channels(id); -- Add Foreign Key Constraints to Birthday_wishes Table @@ -110,6 +102,10 @@ ALTER TABLE celebration.celebrants ALTER TABLE celebration.celebrants DROP COLUMN birthdate_old; --- Set the birthdate column as not null +-- Set the birthdate column as not null in celebrants table +ALTER TABLE celebration.celebrants + ALTER COLUMN birthdate SET NOT NULL; + +-- Add channel column to celebrants table ALTER TABLE celebration.celebrants - ALTER COLUMN birthdate SET NOT NULL; \ No newline at end of file + ADD COLUMN channel VARCHAR(50) NOT NULL CHECK (channel IN ('SMS', 'AUTOMATED_CALL', 'EMAIL')); \ No newline at end of file diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts deleted file mode 100644 index 2a127a5..0000000 --- a/src/controllers/auth.controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Request, Response } from "express"; -import { QueryResult } from "pg"; -import { pool } from "../config/database"; diff --git a/src/controllers/celebrant/create-celebrant.ts b/src/controllers/celebrant/create-celebrant.ts new file mode 100644 index 0000000..969cc9b --- /dev/null +++ b/src/controllers/celebrant/create-celebrant.ts @@ -0,0 +1,35 @@ +import { Request, Response } from "express"; +import { pool } from "../../config/database"; + +const createCelebrant = async (req: Request, res: Response) => { + const { username, gender, email, phone_number, birthdate, channel } = + req.body; + + if (username) { + return res.status(400).json({ message: "Username already exist!" }); + } + + try { + const client = await pool.connect(); + const result = await client.query( + "INSERT INTO celebration.celebrants (username, gender, email, phone_number, birthdate, channel, is_active) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", + [username, gender, email, phone_number, birthdate, channel, true] + ); + + client.release(); + const newCelebrant = result.rows[0]; + + return res.status(200).json({ + success: true, + message: "New Celebbrant has been added successfully.", + celebrant: newCelebrant, + }); + } catch (error) { + console.error("Error executing query", error); + res.status(500).json({ + error: "An error occurred while creating the celebrant details!", + }); + } +}; + +export default createCelebrant; diff --git a/src/controllers/celebrant/get-a-celebrant.ts b/src/controllers/celebrant/get-a-celebrant.ts new file mode 100644 index 0000000..d248caa --- /dev/null +++ b/src/controllers/celebrant/get-a-celebrant.ts @@ -0,0 +1,52 @@ +import { Request, Response } from "express"; +import { pool } from "../../config/database"; + +const getACelebrant = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const { fields } = req.query; + + let queryString = "SELECT "; + const queryParams = [id]; + + // Check if fields parameter is provided and construct SELECT query accordingly + if (fields) { + const selectedFields = + typeof fields === "string" + ? fields + .split(",") + .map((field) => field.trim()) + .filter((field) => field !== "id") + : []; + + if (selectedFields.length === 0) { + queryString += "*"; + } else { + queryString += selectedFields.join(","); + } + } else { + queryString += "*"; + } + + queryString += " FROM celebration.celebrants WHERE id = $1"; + + // Execute the query + const result = await pool.query(queryString, queryParams); + const celebrant = result.rows[0]; + + if (!celebrant) { + return res.status(404).json({ error: "Celebrant not found" }); + } + + res.status(200).json({ + success: true, + message: "Celebrant Id fetched successfully", + celebrant: celebrant, + }); + } catch (error) { + console.error("Error fetching celebrant:", error); + res.status(500).json({ error: "Internal server error" }); + } +}; + +export default getACelebrant; diff --git a/src/controllers/celebrant/get-all-celebrants.ts b/src/controllers/celebrant/get-all-celebrants.ts new file mode 100644 index 0000000..c4a637a --- /dev/null +++ b/src/controllers/celebrant/get-all-celebrants.ts @@ -0,0 +1,66 @@ +import { Request, Response } from "express"; +import { pool } from "../../config/database"; + +const allCelebrants = async (req: Request, res: Response) => { + try { + const { q, channel, birthdate, active, gender, sort, order_by } = req.query; + + let queryString = "SELECT * FROM celebration.celebrants WHERE 1=1"; + const queryParams = []; + + if (q) { + queryString += ` AND username ~* $${queryParams.length + 1}`; + queryParams.push(q); + } + + if (channel) { + queryString += ` AND channel = $${queryParams.length + 1}`; + queryParams.push(channel); + } + + if (birthdate) { + const birthdateString = String(birthdate); // Convert birthdate to string + const [month, day] = birthdateString.split("/"); // Split birthdate string + queryString += ` AND EXTRACT(MONTH FROM birthdate) = EXTRACT(MONTH FROM $${ + queryParams.length + 1 + }) AND EXTRACT(DAY FROM birthdate) = EXTRACT(DAY FROM $${ + queryParams.length + 2 + })`; + queryParams.push(month); + queryParams.push(day); + } + + if (active !== undefined) { + queryString += ` AND is_active = $${queryParams.length + 1}`; + queryParams.push(active); + } + + if (gender) { + queryString += ` AND gender = $${queryParams.length + 1}`; + queryParams.push(gender); + } + + if (sort && order_by) { + if (typeof sort === "string" && typeof order_by === "string") { + queryString += ` ORDER BY ${order_by} ${sort.toUpperCase()}`; + } + } + + // Execute the query + const result = await pool.query(queryString, queryParams); + const celebrants = result.rows; + + res + .status(200) + .json({ + success: true, + message: "Successfully fetched all Celebrants", + All_Celebrants: celebrants, + }); + } catch (error) { + console.error("Error fetching celebrants:", error); + res.status(500).json({ error: "Internal server error" }); + } +}; + +export default allCelebrants; diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 2bf4237..2ed80ac 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,3 +1,7 @@ import passport from "./apiAuth.controller"; export default passport; + +export { default as createCelebrant } from "./celebrant/create-celebrant"; +export { default as allCelebrants } from "./celebrant/get-all-celebrants"; +export { default as getACelebrant } from "./celebrant/get-a-celebrant"; diff --git a/src/middleware/authenticateAPI.ts b/src/middleware/authenticateAPI.ts new file mode 100644 index 0000000..4ee469e --- /dev/null +++ b/src/middleware/authenticateAPI.ts @@ -0,0 +1,34 @@ +import { Request, Response, NextFunction } from "express"; +import { pool } from "../config/database"; + +const authenticateApiKey = async ( + req: Request, + res: Response, + next: NextFunction +) => { + const apiKey = req.headers["api_key"]; + + if (!apiKey) { + return res.status(401).json({ message: "API key is required" }); + } + + try { + const client = await pool.connect(); + const result = await client.query( + "SELECT id FROM celebration.user WHERE api_key = $1", + [apiKey] + ); + client.release(); + + if (result.rows.length === 0) { + return res.status(401).json({ message: "Invalid API key" }); + } + + next(); + } catch (error) { + console.error("Error authenticating user:", error); + res.status(500).json({ message: "Internal server error" }); + } +}; + +export default authenticateApiKey; diff --git a/src/middleware/index.ts b/src/middleware/index.ts index e69de29..65e5625 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -0,0 +1 @@ +export { default as authenticateApiKey } from "./authenticateAPI"; diff --git a/src/routes/celebrant.route.ts b/src/routes/celebrant.route.ts new file mode 100644 index 0000000..0f58887 --- /dev/null +++ b/src/routes/celebrant.route.ts @@ -0,0 +1,15 @@ +import express from "express"; +import { authenticateApiKey } from "../middleware/index"; +import { + allCelebrants, + createCelebrant, + getACelebrant, +} from "../controllers/index"; + +const router = express.Router(); + +router.post("/", authenticateApiKey, createCelebrant); +router.get("/", authenticateApiKey, allCelebrants); +router.get("/:id", authenticateApiKey, getACelebrant); + +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index 978b60b..a6ca2ea 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,10 +2,12 @@ import express from "express"; import router from "./serverRoute"; import authRouter from "./apiAuth.route"; +import celebrantRouter from "./celebrant.route"; const routes = express(); routes.use("/api/v1", router); routes.use("/api/v1", authRouter); +routes.use("/api/v1/celebrants", celebrantRouter); export default routes; From 527b6e5f7a3b814f8778827a9dcb1ec3d210cb05 Mon Sep 17 00:00:00 2001 From: Deedee Date: Tue, 30 Apr 2024 21:06:10 +0100 Subject: [PATCH 6/9] chore: update and delete celebrate data --- package-lock.json | 44 ++++++++++ package.json | 1 + src/__test__/database.test.ts | 2 +- src/{config => configs}/app.ts | 0 src/{config => configs}/database.ts | 0 src/{config => configs}/inversify.config.ts | 0 src/{config => configs}/types.ts | 0 src/controllers/apiAuth.controller.ts | 6 +- .../create.ts} | 37 +++++++-- src/controllers/celebrants/delete.ts | 34 ++++++++ .../get-all.ts} | 14 ++-- .../get-a-celebrant.ts => celebrants/get.ts} | 2 +- src/controllers/celebrants/update.ts | 81 +++++++++++++++++++ src/controllers/index.ts | 8 +- .../authenticateAPI.ts | 2 +- src/{middleware => middlewares}/index.ts | 0 src/routes/apiAuth.route.ts | 4 +- src/routes/celebrant.route.ts | 6 +- src/server.ts | 2 +- src/validators/celebrant.validate.ts | 12 +++ src/validators/index.ts | 2 + src/validators/update.validate.ts | 11 +++ 22 files changed, 241 insertions(+), 27 deletions(-) rename src/{config => configs}/app.ts (100%) rename src/{config => configs}/database.ts (100%) rename src/{config => configs}/inversify.config.ts (100%) rename src/{config => configs}/types.ts (100%) rename src/controllers/{celebrant/create-celebrant.ts => celebrants/create.ts} (50%) create mode 100644 src/controllers/celebrants/delete.ts rename src/controllers/{celebrant/get-all-celebrants.ts => celebrants/get-all.ts} (89%) rename src/controllers/{celebrant/get-a-celebrant.ts => celebrants/get.ts} (96%) create mode 100644 src/controllers/celebrants/update.ts rename src/{middleware => middlewares}/authenticateAPI.ts (94%) rename src/{middleware => middlewares}/index.ts (100%) create mode 100644 src/validators/celebrant.validate.ts create mode 100644 src/validators/index.ts create mode 100644 src/validators/update.validate.ts diff --git a/package-lock.json b/package-lock.json index 1bb1234..bfaee31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "express": "^4.19.2", "express-session": "^1.18.0", "inversify": "^6.0.2", + "joi": "^17.13.0", "morgan": "^1.10.0", "passport": "^0.7.0", "passport-custom": "^1.1.1", @@ -698,6 +699,19 @@ "node": ">=12" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1144,6 +1158,24 @@ "node": ">=6" } }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3943,6 +3975,18 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joi": { + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.0.tgz", + "integrity": "sha512-9qcrTyoBmFZRNHeVP4edKqIUEgFzq7MHvTNSDuHSqkpOPtiBkgNgcmTSqmiw1kw9tdKaiddvIDv/eCJDxmqWCA==", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index a0eb5b1..90196be 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "^4.19.2", "express-session": "^1.18.0", "inversify": "^6.0.2", + "joi": "^17.13.0", "morgan": "^1.10.0", "passport": "^0.7.0", "passport-custom": "^1.1.1", diff --git a/src/__test__/database.test.ts b/src/__test__/database.test.ts index 859db62..88d87d2 100644 --- a/src/__test__/database.test.ts +++ b/src/__test__/database.test.ts @@ -1,4 +1,4 @@ -import { pool } from "../config/database"; +import { pool } from "../configs/database"; describe("Database connection", () => { it("should connect to the database successfully", () => { diff --git a/src/config/app.ts b/src/configs/app.ts similarity index 100% rename from src/config/app.ts rename to src/configs/app.ts diff --git a/src/config/database.ts b/src/configs/database.ts similarity index 100% rename from src/config/database.ts rename to src/configs/database.ts diff --git a/src/config/inversify.config.ts b/src/configs/inversify.config.ts similarity index 100% rename from src/config/inversify.config.ts rename to src/configs/inversify.config.ts diff --git a/src/config/types.ts b/src/configs/types.ts similarity index 100% rename from src/config/types.ts rename to src/configs/types.ts diff --git a/src/controllers/apiAuth.controller.ts b/src/controllers/apiAuth.controller.ts index 3194fc5..7aa1e07 100644 --- a/src/controllers/apiAuth.controller.ts +++ b/src/controllers/apiAuth.controller.ts @@ -3,7 +3,7 @@ import passportCustom from "passport-custom"; import bcrypt from "bcrypt"; const CustomStrategy = passportCustom.Strategy; import { v4 as uuidv4 } from "uuid"; -import { pool } from "../config/database"; +import { pool } from "../configs/database"; async function hashPassword(password: string) { const saltRounds = 10; @@ -41,8 +41,8 @@ passport.use( const api_key = uuidv4(); // Generate a new UUID for API key const insertQuery = { - text: "INSERT INTO celebration.user (phone_number, password, api_key) VALUES ($1, $2, $3) RETURNING *", - values: [phone_number, hashedPassword, api_key], + text: "INSERT INTO celebration.user (phone_number, password, api_key, is_admin) VALUES ($1, $2, $3, $4) RETURNING *", + values: [phone_number, hashedPassword, api_key, true], }; const newUserResult = await pool.query(insertQuery); const newUser = newUserResult.rows[0]; diff --git a/src/controllers/celebrant/create-celebrant.ts b/src/controllers/celebrants/create.ts similarity index 50% rename from src/controllers/celebrant/create-celebrant.ts rename to src/controllers/celebrants/create.ts index 969cc9b..49d7ca8 100644 --- a/src/controllers/celebrant/create-celebrant.ts +++ b/src/controllers/celebrants/create.ts @@ -1,15 +1,40 @@ import { Request, Response } from "express"; -import { pool } from "../../config/database"; +import { pool } from "../../configs/database"; +import { celebrantSchema } from "../../validators/index"; + +async function checkUsernameExists(username: string) { + try { + const client = await pool.connect(); + const result = await client.query( + "SELECT COUNT(*) as count FROM celebration.celebrants WHERE username = $1", + [username] + ); + client.release(); + + return result.rows[0].count > 0; + } catch (error) { + console.error("Error checking username existence:", error); + throw error; + } +} const createCelebrant = async (req: Request, res: Response) => { - const { username, gender, email, phone_number, birthdate, channel } = - req.body; + const { error } = celebrantSchema.validate(req.body); - if (username) { - return res.status(400).json({ message: "Username already exist!" }); + if (error) { + return res.status(400).json({ message: error.details[0].message }); } + const { username, gender, email, phone_number, birthdate, channel } = + req.body; + try { + const usernameExists = await checkUsernameExists(username); + + if (usernameExists) { + return res.status(400).json({ message: "Username already exists" }); + } + const client = await pool.connect(); const result = await client.query( "INSERT INTO celebration.celebrants (username, gender, email, phone_number, birthdate, channel, is_active) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", @@ -21,7 +46,7 @@ const createCelebrant = async (req: Request, res: Response) => { return res.status(200).json({ success: true, - message: "New Celebbrant has been added successfully.", + message: "New Celebrant has been added successfully.", celebrant: newCelebrant, }); } catch (error) { diff --git a/src/controllers/celebrants/delete.ts b/src/controllers/celebrants/delete.ts new file mode 100644 index 0000000..83fe3dc --- /dev/null +++ b/src/controllers/celebrants/delete.ts @@ -0,0 +1,34 @@ +import { Request, Response } from "express"; +import { v4 as uuidv4 } from "uuid"; +import { pool } from "../../configs/database"; + +const deleteCelebrant = async (req: Request, res: Response) => { + const { id } = req.params; + + const result = await pool.query( + `UPDATE celebration.celebrants + SET is_active = false, + username = FLOOR(10000000 + random() * 90000000), + updated_at = NOW() + WHERE id = $1 + RETURNING *`, + [id] + ); + + const updatedCelebrant = result.rows[0]; + + if (!updatedCelebrant) { + return res.status(404).json({ error: "Celebrant not found" }); + } + + return res.status(200).json({ + success: true, + message: "Celebrant deleted successfully", + data: result.rows[0], + }); +}; + +export default deleteCelebrant; + +// (username = Math.floor(1000 + Math.random() * 9000)), +// (added_by = "system"); diff --git a/src/controllers/celebrant/get-all-celebrants.ts b/src/controllers/celebrants/get-all.ts similarity index 89% rename from src/controllers/celebrant/get-all-celebrants.ts rename to src/controllers/celebrants/get-all.ts index c4a637a..7f9763b 100644 --- a/src/controllers/celebrant/get-all-celebrants.ts +++ b/src/controllers/celebrants/get-all.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { pool } from "../../config/database"; +import { pool } from "../../configs/database"; const allCelebrants = async (req: Request, res: Response) => { try { @@ -50,13 +50,11 @@ const allCelebrants = async (req: Request, res: Response) => { const result = await pool.query(queryString, queryParams); const celebrants = result.rows; - res - .status(200) - .json({ - success: true, - message: "Successfully fetched all Celebrants", - All_Celebrants: celebrants, - }); + res.status(200).json({ + success: true, + message: "Successfully fetched all Celebrants", + All_Celebrants: celebrants, + }); } catch (error) { console.error("Error fetching celebrants:", error); res.status(500).json({ error: "Internal server error" }); diff --git a/src/controllers/celebrant/get-a-celebrant.ts b/src/controllers/celebrants/get.ts similarity index 96% rename from src/controllers/celebrant/get-a-celebrant.ts rename to src/controllers/celebrants/get.ts index d248caa..bbb4e42 100644 --- a/src/controllers/celebrant/get-a-celebrant.ts +++ b/src/controllers/celebrants/get.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { pool } from "../../config/database"; +import { pool } from "../../configs/database"; const getACelebrant = async (req: Request, res: Response) => { try { diff --git a/src/controllers/celebrants/update.ts b/src/controllers/celebrants/update.ts new file mode 100644 index 0000000..9121551 --- /dev/null +++ b/src/controllers/celebrants/update.ts @@ -0,0 +1,81 @@ +import { Request, Response } from "express"; +import { pool } from "../../configs/database"; +import { updateCelebrantValidator } from "../../validators/index"; + +const updateCelebrant = async (req: Request, res: Response) => { + const { id } = req.params; + + const { error } = updateCelebrantValidator.validate(req.body); + + if (error) { + return res.status(400).json({ + message: "Validation error", + error, + }); + } + + const { gender, phone_number, email, birthdate, channel } = req.body; + + try { + const client = await pool.connect(); + + const updates = []; + const values = []; + + if (gender !== undefined) { + updates.push("gender = $1"); + values.push(gender); + } + + if (phone_number !== undefined) { + updates.push("phone_number = $2"); + values.push(phone_number); + } + + if (email !== undefined) { + updates.push("email = $3"); + values.push(email); + } + + if (birthdate !== undefined) { + updates.push("birthdate = $4"); + values.push(birthdate); + } + + if (channel !== undefined) { + updates.push("channel = $5"); + values.push(channel); + } + + const updateQuery = `UPDATE celebration.celebrants SET ${updates.join( + "," + )} WHERE id = $${values.length + 1} RETURNING *;`; + values.push(id); + + const result = await client.query(updateQuery, values); + + client.release(); + + const updatedCelebrant = result.rows[0]; + + if (!updatedCelebrant) { + return res.status(404).json({ + message: "Celebrant not found", + }); + } + + return res.status(200).json({ + success: "true", + message: "Celebrant updated successfully", + data: updatedCelebrant, + }); + } catch (error) { + console.error("Error fetching celebrant:", error); + res.status(500).json({ + message: "Internal server error", + error, + }); + } +}; + +export default updateCelebrant; diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 2ed80ac..8e879e0 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -2,6 +2,8 @@ import passport from "./apiAuth.controller"; export default passport; -export { default as createCelebrant } from "./celebrant/create-celebrant"; -export { default as allCelebrants } from "./celebrant/get-all-celebrants"; -export { default as getACelebrant } from "./celebrant/get-a-celebrant"; +export { default as createCelebrant } from "./celebrants/create"; +export { default as allCelebrants } from "./celebrants/get-all"; +export { default as getACelebrant } from "./celebrants/get"; +export { default as updateCelebrant } from "./celebrants/update"; +export { default as deleteCelebrant } from "./celebrants/delete"; diff --git a/src/middleware/authenticateAPI.ts b/src/middlewares/authenticateAPI.ts similarity index 94% rename from src/middleware/authenticateAPI.ts rename to src/middlewares/authenticateAPI.ts index 4ee469e..50d847d 100644 --- a/src/middleware/authenticateAPI.ts +++ b/src/middlewares/authenticateAPI.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from "express"; -import { pool } from "../config/database"; +import { pool } from "../configs/database"; const authenticateApiKey = async ( req: Request, diff --git a/src/middleware/index.ts b/src/middlewares/index.ts similarity index 100% rename from src/middleware/index.ts rename to src/middlewares/index.ts diff --git a/src/routes/apiAuth.route.ts b/src/routes/apiAuth.route.ts index 1ea561d..dbe417e 100644 --- a/src/routes/apiAuth.route.ts +++ b/src/routes/apiAuth.route.ts @@ -4,9 +4,9 @@ import passport from "../controllers/index"; const router = express.Router(); // Define a route to handle authentication -router.post("/auth", (req: Request, res: Response, next) => { +router.post("/auth", async (req: Request, res: Response, next) => { // Use passport.authenticate middleware with your custom strategy - passport.authenticate("custom-api-key", (err: string, user: object) => { + await passport.authenticate("custom-api-key", (err: string, user: object) => { if (err) { return next(err); // Pass error to Express error handler } diff --git a/src/routes/celebrant.route.ts b/src/routes/celebrant.route.ts index 0f58887..6455fa5 100644 --- a/src/routes/celebrant.route.ts +++ b/src/routes/celebrant.route.ts @@ -1,9 +1,11 @@ import express from "express"; -import { authenticateApiKey } from "../middleware/index"; +import { authenticateApiKey } from "../middlewares/index"; import { allCelebrants, createCelebrant, + deleteCelebrant, getACelebrant, + updateCelebrant, } from "../controllers/index"; const router = express.Router(); @@ -11,5 +13,7 @@ const router = express.Router(); router.post("/", authenticateApiKey, createCelebrant); router.get("/", authenticateApiKey, allCelebrants); router.get("/:id", authenticateApiKey, getACelebrant); +router.patch("/:id", authenticateApiKey, updateCelebrant); +router.delete("/:id", authenticateApiKey, deleteCelebrant); export default router; diff --git a/src/server.ts b/src/server.ts index 0bd9318..f454307 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,4 @@ -import server from "./config/app"; +import server from "./configs/app"; const port = process.env.PORT || 3000; diff --git a/src/validators/celebrant.validate.ts b/src/validators/celebrant.validate.ts new file mode 100644 index 0000000..bec3243 --- /dev/null +++ b/src/validators/celebrant.validate.ts @@ -0,0 +1,12 @@ +import joi from "joi"; + +const createCelebrantSchema = joi.object({ + username: joi.string().required(), + gender: joi.string().valid("M", "F", "O").required(), + email: joi.string().email(), + phone_number: joi.string(), + birthdate: joi.date().iso().required(), + channel: joi.string().required(), +}); + +export default createCelebrantSchema; diff --git a/src/validators/index.ts b/src/validators/index.ts new file mode 100644 index 0000000..0cd4607 --- /dev/null +++ b/src/validators/index.ts @@ -0,0 +1,2 @@ +export { default as celebrantSchema } from "./celebrant.validate"; +export { default as updateCelebrantValidator } from "./update.validate"; diff --git a/src/validators/update.validate.ts b/src/validators/update.validate.ts new file mode 100644 index 0000000..be0049e --- /dev/null +++ b/src/validators/update.validate.ts @@ -0,0 +1,11 @@ +import joi from "joi"; + +const updateCelebrantValidator = joi.object({ + gender: joi.string().valid("M", "F", "O"), + email: joi.string().email(), + phone_number: joi.string(), + birthdate: joi.date().iso(), + channel: joi.string(), +}); + +export default updateCelebrantValidator; From 9a36d5dec4e890db50e74a3f3c6574b1d42995dd Mon Sep 17 00:00:00 2001 From: Deedee Date: Tue, 7 May 2024 23:34:31 +0100 Subject: [PATCH 7/9] chore: create birthday wishes for celebrant --- src/controllers/BirthdayWishes/create.ts | 48 ++++++++++++++++++++++++ src/controllers/BirthdayWishes/get.ts | 32 ++++++++++++++++ src/controllers/apiAuth.controller.ts | 21 ++++++++--- src/controllers/celebrants/create.ts | 2 +- src/controllers/celebrants/delete.ts | 7 +--- src/controllers/celebrants/get-all.ts | 2 +- src/controllers/celebrants/get.ts | 2 +- src/controllers/index.ts | 2 + src/middlewares/authenticateAPI.ts | 4 +- src/routes/celebrant.route.ts | 8 ++++ src/validators/birthdayWish.validate.ts | 9 +++++ src/validators/index.ts | 1 + 12 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/controllers/BirthdayWishes/create.ts create mode 100644 src/controllers/BirthdayWishes/get.ts create mode 100644 src/validators/birthdayWish.validate.ts diff --git a/src/controllers/BirthdayWishes/create.ts b/src/controllers/BirthdayWishes/create.ts new file mode 100644 index 0000000..a7b6e74 --- /dev/null +++ b/src/controllers/BirthdayWishes/create.ts @@ -0,0 +1,48 @@ +import { Request, Response } from "express"; +import { pool } from "../../configs/database"; +import { postBirthdayWishValidator } from "../../validators/index"; + +const postWishes = async (req: Request, res: Response) => { + const { error } = postBirthdayWishValidator.validate(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + + const { celebrant_id, message, scheduled_time } = req.body; + + try { + const client = await pool.connect(); + + // Check if the celebrant_id exists in the celebrants table + const celebrantExists = await client.query( + "SELECT COUNT(*) FROM celebration.celebrants WHERE id = $1", + [celebrant_id] + ); + + if (celebrantExists.rows[0].count === "0") { + return res.status(404).json({ error: "Celebrant not found" }); + } + + const result = await pool.query( + "INSERT INTO celebration.birthday_wishes (celebrant_id, message, scheduled_time) VALUES ($1, $2, $3) RETURNING *", + [celebrant_id, message, scheduled_time] + ); + + client.release(); + const createWish = result.rows[0]; + + return res.status(201).json({ + success: true, + message: "BirthdayWish created successfully", + data: createWish, + }); + } catch (error) { + console.error("Error executing query", error); + res.status(500).json({ + success: false, + error: "An error occurred while creating the celebrant details!", + }); + } +}; + +export default postWishes; diff --git a/src/controllers/BirthdayWishes/get.ts b/src/controllers/BirthdayWishes/get.ts new file mode 100644 index 0000000..5c4913d --- /dev/null +++ b/src/controllers/BirthdayWishes/get.ts @@ -0,0 +1,32 @@ +import { Request, Response } from "express"; +import { pool } from "../../configs/database"; + +const getCelebrantBirthdayWish = async (req: Request, res: Response) => { + const celebrantId = req.params.celebrantId; + + try { + const client = await pool.connect(); + const result = await client.query( + "SELECT * FROM celebration.birthday_wishes WHERE celebrant_id = $1", + [celebrantId] + ); + + client.release(); + + const celebrantWishes = result.rows; + + res.status(200).json({ + success: true, + message: "Birthday wishes retrieved successfully!", + data: celebrantWishes, + }); + } catch (error) { + console.error("Error executing query", error); + res.status(500).json({ + success: false, + error: "An error occurred while creating the celebrant details!", + }); + } +}; + +export default getCelebrantBirthdayWish; diff --git a/src/controllers/apiAuth.controller.ts b/src/controllers/apiAuth.controller.ts index 7aa1e07..9022b55 100644 --- a/src/controllers/apiAuth.controller.ts +++ b/src/controllers/apiAuth.controller.ts @@ -10,6 +10,13 @@ async function hashPassword(password: string) { return await bcrypt.hash(password, saltRounds); } +async function generateApiKey(phone_number: string) { + const hashedPhoneNumber = await bcrypt.hash(phone_number, 10); + const uuid = uuidv4(); + const combinedString = `${uuid}${hashedPhoneNumber}`; + return await bcrypt.hash(combinedString, 10); +} + passport.use( "custom-api-key", new CustomStrategy(async (req, done) => { @@ -25,12 +32,12 @@ passport.use( const result = await pool.query(query); if (result.rows.length > 0) { - const user = result.rows[0]; - const match = await bcrypt.compare(password, user.password); + const data = result.rows[0]; + const match = await bcrypt.compare(password, data.password); if (match) { // Passwords match, return the user info - return done(null, user); + return done(null, data); } else { // Passwords don't match return done(null, { message: "Incorrect password" }); @@ -38,16 +45,18 @@ passport.use( } else { // User doesn't exist, create a new user with API key const hashedPassword = await hashPassword(password); - const api_key = uuidv4(); // Generate a new UUID for API key + const api_key = await generateApiKey(phone_number); + + // const api_key = uuidv4(); // Generate a new UUID for API key const insertQuery = { text: "INSERT INTO celebration.user (phone_number, password, api_key, is_admin) VALUES ($1, $2, $3, $4) RETURNING *", values: [phone_number, hashedPassword, api_key, true], }; const newUserResult = await pool.query(insertQuery); - const newUser = newUserResult.rows[0]; + const data = newUserResult.rows[0]; - return done(null, newUser); + return done(null, data); } } catch (error) { return done(error); diff --git a/src/controllers/celebrants/create.ts b/src/controllers/celebrants/create.ts index 49d7ca8..2755833 100644 --- a/src/controllers/celebrants/create.ts +++ b/src/controllers/celebrants/create.ts @@ -47,7 +47,7 @@ const createCelebrant = async (req: Request, res: Response) => { return res.status(200).json({ success: true, message: "New Celebrant has been added successfully.", - celebrant: newCelebrant, + data: newCelebrant, }); } catch (error) { console.error("Error executing query", error); diff --git a/src/controllers/celebrants/delete.ts b/src/controllers/celebrants/delete.ts index 83fe3dc..b29be4d 100644 --- a/src/controllers/celebrants/delete.ts +++ b/src/controllers/celebrants/delete.ts @@ -1,5 +1,4 @@ import { Request, Response } from "express"; -import { v4 as uuidv4 } from "uuid"; import { pool } from "../../configs/database"; const deleteCelebrant = async (req: Request, res: Response) => { @@ -21,14 +20,10 @@ const deleteCelebrant = async (req: Request, res: Response) => { return res.status(404).json({ error: "Celebrant not found" }); } - return res.status(200).json({ + return res.status(204).json({ success: true, message: "Celebrant deleted successfully", - data: result.rows[0], }); }; export default deleteCelebrant; - -// (username = Math.floor(1000 + Math.random() * 9000)), -// (added_by = "system"); diff --git a/src/controllers/celebrants/get-all.ts b/src/controllers/celebrants/get-all.ts index 7f9763b..43b8e79 100644 --- a/src/controllers/celebrants/get-all.ts +++ b/src/controllers/celebrants/get-all.ts @@ -53,7 +53,7 @@ const allCelebrants = async (req: Request, res: Response) => { res.status(200).json({ success: true, message: "Successfully fetched all Celebrants", - All_Celebrants: celebrants, + data: celebrants, }); } catch (error) { console.error("Error fetching celebrants:", error); diff --git a/src/controllers/celebrants/get.ts b/src/controllers/celebrants/get.ts index bbb4e42..064cd26 100644 --- a/src/controllers/celebrants/get.ts +++ b/src/controllers/celebrants/get.ts @@ -41,7 +41,7 @@ const getACelebrant = async (req: Request, res: Response) => { res.status(200).json({ success: true, message: "Celebrant Id fetched successfully", - celebrant: celebrant, + data: celebrant, }); } catch (error) { console.error("Error fetching celebrant:", error); diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 8e879e0..c61c322 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -7,3 +7,5 @@ export { default as allCelebrants } from "./celebrants/get-all"; export { default as getACelebrant } from "./celebrants/get"; export { default as updateCelebrant } from "./celebrants/update"; export { default as deleteCelebrant } from "./celebrants/delete"; +export { default as postWishes } from "./BirthdayWishes/create"; +export { default as getCelebrantBirthdayWish } from "./BirthdayWishes/get"; diff --git a/src/middlewares/authenticateAPI.ts b/src/middlewares/authenticateAPI.ts index 50d847d..97699b9 100644 --- a/src/middlewares/authenticateAPI.ts +++ b/src/middlewares/authenticateAPI.ts @@ -9,7 +9,9 @@ const authenticateApiKey = async ( const apiKey = req.headers["api_key"]; if (!apiKey) { - return res.status(401).json({ message: "API key is required" }); + return res + .status(401) + .json({ success: false, message: "API key is required" }); } try { diff --git a/src/routes/celebrant.route.ts b/src/routes/celebrant.route.ts index 6455fa5..15f4c77 100644 --- a/src/routes/celebrant.route.ts +++ b/src/routes/celebrant.route.ts @@ -5,6 +5,8 @@ import { createCelebrant, deleteCelebrant, getACelebrant, + getCelebrantBirthdayWish, + postWishes, updateCelebrant, } from "../controllers/index"; @@ -15,5 +17,11 @@ router.get("/", authenticateApiKey, allCelebrants); router.get("/:id", authenticateApiKey, getACelebrant); router.patch("/:id", authenticateApiKey, updateCelebrant); router.delete("/:id", authenticateApiKey, deleteCelebrant); +router.post("/birthday-wish", authenticateApiKey, postWishes); +router.get( + "/birthday-wish/:celebrantId", + authenticateApiKey, + getCelebrantBirthdayWish +); export default router; diff --git a/src/validators/birthdayWish.validate.ts b/src/validators/birthdayWish.validate.ts new file mode 100644 index 0000000..e1a4051 --- /dev/null +++ b/src/validators/birthdayWish.validate.ts @@ -0,0 +1,9 @@ +import joi from "joi"; + +const postBirthdayWishValidator = joi.object({ + celebrant_id: joi.number().integer().required(), + message: joi.string().required(), + scheduled_time: joi.date().iso().required(), +}); + +export default postBirthdayWishValidator; diff --git a/src/validators/index.ts b/src/validators/index.ts index 0cd4607..6c3a3f2 100644 --- a/src/validators/index.ts +++ b/src/validators/index.ts @@ -1,2 +1,3 @@ export { default as celebrantSchema } from "./celebrant.validate"; export { default as updateCelebrantValidator } from "./update.validate"; +export { default as postBirthdayWishValidator } from "./birthdayWish.validate"; From 1693a89af4c4edc611db29fbf1e21fe7914fe1f5 Mon Sep 17 00:00:00 2001 From: Deedee Date: Sun, 12 May 2024 21:39:20 +0100 Subject: [PATCH 8/9] feat: Background task to send birthday wishes --- package-lock.json | 27 +++++++++++++++ package.json | 2 ++ src/configs/app.ts | 7 ++++ src/configs/cronJobTask.ts | 56 +++++++++++++++++++++++++++++++ src/configs/types.ts | 20 +++++++++-- src/helpers/utils/birthdayWish.ts | 53 +++++++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/configs/cronJobTask.ts create mode 100644 src/helpers/utils/birthdayWish.ts diff --git a/package-lock.json b/package-lock.json index bfaee31..adc02f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "inversify": "^6.0.2", "joi": "^17.13.0", "morgan": "^1.10.0", + "node-cron": "^3.0.3", "passport": "^0.7.0", "passport-custom": "^1.1.1", "pg": "^8.11.5", @@ -32,6 +33,7 @@ "@types/jest": "^29.5.12", "@types/morgan": "^1.9.9", "@types/node": "^20.12.5", + "@types/node-cron": "^3.0.11", "@types/passport": "^1.0.16", "@types/pg": "^8.11.5", "@types/supertest": "^6.0.2", @@ -1420,6 +1422,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true + }, "node_modules/@types/passport": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz", @@ -4301,6 +4309,25 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index 90196be..7744266 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "inversify": "^6.0.2", "joi": "^17.13.0", "morgan": "^1.10.0", + "node-cron": "^3.0.3", "passport": "^0.7.0", "passport-custom": "^1.1.1", "pg": "^8.11.5", @@ -36,6 +37,7 @@ "@types/jest": "^29.5.12", "@types/morgan": "^1.9.9", "@types/node": "^20.12.5", + "@types/node-cron": "^3.0.11", "@types/passport": "^1.0.16", "@types/pg": "^8.11.5", "@types/supertest": "^6.0.2", diff --git a/src/configs/app.ts b/src/configs/app.ts index 9fccefb..c8ef5fe 100644 --- a/src/configs/app.ts +++ b/src/configs/app.ts @@ -7,6 +7,7 @@ import cors from "cors"; import session from "express-session"; import serverRoute from "../routes/index"; import { pool } from "./database"; +import scheduleBirthdayWishes from "./cronJobTask"; dotenv.config(); @@ -20,6 +21,7 @@ export default class App { this.config(); this.routes(); this.database(); + this.scheduleBirthdayWishes(); } public config() { @@ -60,6 +62,11 @@ export default class App { }); } + public scheduleBirthdayWishes() { + scheduleBirthdayWishes.start(); + console.log("Birthday wishes cron job scheduled."); + } + public start(port: number) { this.server.listen(port, () => { console.log(`Server is running on port: ${port}`); diff --git a/src/configs/cronJobTask.ts b/src/configs/cronJobTask.ts new file mode 100644 index 0000000..636a421 --- /dev/null +++ b/src/configs/cronJobTask.ts @@ -0,0 +1,56 @@ +import cron from "node-cron"; +import { pool } from "./database"; +import { BirthdayWish } from "./types"; +import { sendWishToCelebrant } from "../helpers/utils/birthdayWish"; + +const scheduleBirthdayWishes = cron.schedule("0 0 * * *", async () => { + const client = await pool.connect(); + + // Schedule the job to run every day at midnight + try { + await client.query("BEGIN"); + // Query birthday wishes for today + const result = await client.query(` + SELECT bw.id, bw.celebrant_id, bw.message, cw.username + FROM celebration.birthday_wishes bw + JOIN celebration.celebrants cw ON bw.celebrant_id = cw.id + WHERE DATE_TRUNC('day', bw.scheduled_time) = DATE_TRUNC('day', CURRENT_TIMESTAMP) + ORDER BY bw.scheduled_time ASC; + `); + + const birthdayWishes: BirthdayWish[] = result.rows; + + // Group wishes by celebrants + const groupedWishes: { [celebrantId: number]: BirthdayWish[] } = {}; + birthdayWishes.forEach((wish) => { + if (!groupedWishes[wish.celebrant_id]) { + groupedWishes[wish.celebrant_id] = []; + } + groupedWishes[wish.celebrant_id].push(wish); + }); + + // Send wishes for each celebrant + for (const celebrantId of Object.keys(groupedWishes)) { + const wishes = groupedWishes[parseInt(celebrantId)]; + console.log(`Sending birthday wishes for celebrant ${celebrantId}`); + for (const wish of birthdayWishes) { + await sendWishToCelebrant(wish); + } + } + + await client.query( + `INSERT INTO celebration.bg_jobs_status (job_name, status) VALUES ('Send Birthday Wishes', 'Completed');` + ); + + await client.query("COMMIT"); + } catch (error) { + console.error("Error executing cron job:", error); + await client.query("ROLLBACK"); + } finally { + client.release(); + } +}); + +scheduleBirthdayWishes.start(); + +export default scheduleBirthdayWishes; diff --git a/src/configs/types.ts b/src/configs/types.ts index 4c83626..18e46c1 100644 --- a/src/configs/types.ts +++ b/src/configs/types.ts @@ -1,3 +1,19 @@ -// const TYPES = {}; +export interface BirthdayWish { + id: number; + celebrant_id: number; + message: string; + username: string; +} -// export default TYPES; +export interface Wish { + id: number; + celebrant_id: number; + message: string; +} + +export interface CelebrantDetails { + id: number; + username: string; + email: string; + phone_number: string; +} diff --git a/src/helpers/utils/birthdayWish.ts b/src/helpers/utils/birthdayWish.ts new file mode 100644 index 0000000..a48a346 --- /dev/null +++ b/src/helpers/utils/birthdayWish.ts @@ -0,0 +1,53 @@ +import { pool } from "../../configs/database"; +import { Wish, CelebrantDetails } from "../../configs/types"; + +async function fetchCelebrantDetails( + celebrantId: number +): Promise { + // Query the database to fetch celebrant details based on celebrant_id + const client = await pool.connect(); + const result = await client.query( + ` + SELECT username, email, phone_number + FROM celebration.celebrants + WHERE id = $1 + `, + [celebrantId] + ); + client.release(); + + if (result.rows.length > 0) { + return result.rows[0]; + } else { + throw new Error(`Celebrant with id ${celebrantId} not found`); + } +} + +async function sendMessage( + celebrantDetails: CelebrantDetails, + message: string +) { + // A function to send a message through the internal messaging system + console.log( + `Sending wish to celebrant ${celebrantDetails.username} through birthmark messaging system: ${message}` + ); +} + +async function sendWishToCelebrant(wish: Wish) { + try { + // A function to fetch celebrant details based on celebrant_id + const celebrantDetails = await fetchCelebrantDetails(wish.celebrant_id); + + // A function to send a message through the messaging system + await sendMessage(celebrantDetails, wish.message); + + console.log(`Wish sent to celebrant ${wish.celebrant_id}`); + } catch (error) { + console.error( + `Error sending wish to celebrant ${wish.celebrant_id}:`, + error + ); + } +} + +export { sendWishToCelebrant }; From 6fdb3f979a2022a1ab2046559b7b5be5a73902fe Mon Sep 17 00:00:00 2001 From: Deedee Date: Fri, 17 May 2024 01:27:07 +0100 Subject: [PATCH 9/9] (docs): create Readme file --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..053725c --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Birthmark + +Birthmark is a web automation tool designed to automatically send birthday wishes to your loved ones. This tool leverages background jobs to identify active birthdays for the upcoming day and send personalized messages accordingly, ensuring you never miss a chance to celebrate the special moments of those who matter most to you. + +## Installation + +- Clone the repository: `git clone https://github.com/deedee-code/Birthmark.git` +- Navigate to the project directory: `cd Birthmark` +- Install Dependencies: `npm install` +- Configure the environment variables: Create a .env file in the root directory and add the necessary environment variables. Example: + +``` +PORT=your_port_number +SESSION_SECRET=your_session_secret +POSTGRES_USER=your_postgres_username +POSTGRES_PASSWORD=your_postgres_password +POSTGRES_HOST=your_postgres_host +POSTGRES_DATABASE=your_postgres_database +``` + +- Run the server: `npm run dev` + +## Usage + +- User Signup/Signin: Users registers or login with their generated API key. +- Create Celebrant: Users can create celebrants with their birthday details and their preferred mode of communication (Email, SMS, Automated_Call). +- Create Wishes: Users create and save birthday wishes for different celebrants. +- Create Background Job: A background job runs daily to check to check for birthdays happening he next day. +- Send Wishes: The system automatically sends the birthday wishes through the specified communication method. + +## API Documentation + +https://documenter.getpostman.com/view/26786258/2sA3JT1xKi + +### Support + +For any feedback or collaboration, please contact me on [LinkedIn](https://www.linkedin.com/in/doris-oladotun-owoeye-84a38014b/)