diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 896d3ea..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# MarketStack API key for external service -MARKETSTACK_API_KEY=your_marketstack_api_key_here - -# MongoDB database password -DB_PASSWORD=your_mongodb_password_here - -MONGODB_CONNECTION_STRING=mongodb://admin:${DB_PASSWORD}@mongo:27017/?authSource=admin diff --git a/backend/package-lock.json b/backend/package-lock.json index b9969a7..017435a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -12,6 +12,7 @@ "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "cheerio": "^1.0.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.4", @@ -235,6 +236,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -293,6 +300,48 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -410,6 +459,34 @@ "node": ">= 0.10" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -469,6 +546,61 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.4", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", @@ -520,6 +652,43 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -858,6 +1027,25 @@ "node": ">= 0.4" } }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1417,6 +1605,18 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1452,6 +1652,43 @@ "wrappy": "1" } }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1825,6 +2062,15 @@ "node": ">= 0.6" } }, + "node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -1879,6 +2125,39 @@ "node": ">=12" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", @@ -2085,6 +2364,11 @@ "unpipe": "1.0.0" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2128,6 +2412,37 @@ "supports-color": "^7.1.0" } }, + "cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -2217,6 +2532,23 @@ "vary": "^1" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2260,6 +2592,39 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dotenv": { "version": "16.4.4", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", @@ -2296,6 +2661,30 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2553,6 +2942,17 @@ "function-bind": "^1.1.2" } }, + "htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2618,6 +3018,7 @@ "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "cheerio": "^1.0.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.4", @@ -2806,6 +3207,11 @@ "unpipe": "1.0.0" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2849,6 +3255,37 @@ "supports-color": "^7.1.0" } }, + "cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -2938,6 +3375,23 @@ "vary": "^1" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2981,6 +3435,39 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dotenv": { "version": "16.4.4", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", @@ -3017,6 +3504,30 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3274,6 +3785,17 @@ "function-bind": "^1.1.2" } }, + "htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3682,6 +4204,14 @@ "set-blocking": "^2.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3708,6 +4238,31 @@ "wrappy": "1" } }, + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "requires": { + "entities": "^4.5.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "requires": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "requires": { + "parse5": "^7.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3983,6 +4538,11 @@ "mime-types": "~2.1.24" } }, + "undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==" + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -4018,6 +4578,29 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, "whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", @@ -4397,6 +4980,14 @@ "set-blocking": "^2.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4423,6 +5014,31 @@ "wrappy": "1" } }, + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "requires": { + "entities": "^4.5.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "requires": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "requires": { + "parse5": "^7.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4698,6 +5314,11 @@ "mime-types": "~2.1.24" } }, + "undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==" + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -4733,6 +5354,29 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, "whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 1c9a668..6797171 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,6 +13,7 @@ "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", + "cheerio": "^1.0.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.4", diff --git a/backend/server.js b/backend/server.js index d89290c..5097376 100644 --- a/backend/server.js +++ b/backend/server.js @@ -434,7 +434,242 @@ app.post('/stocks/get', isAuth, async function(req,res){ res.send(ownedStocks); }) -app.listen(process.env.PORT); +app.get("/stock/data",async function(req,res,next){ + try { + const query = req.query.q; + + console.log(query); + // if (!query) { + // return res.status(400).json({ message: 'Query parameter "q" is required' }); + // } + + // const response = await axios.get(`https://query1.finance.yahoo.com/v1/finance/search`, { + // params: { + // q: query, + // quotesCount: 5, + // newsCount: 0, + // enableFuzzyQuery: false, + // quotesQueryId: 'tss_match_phrase_query', + // multiQuoteQueryId: 'multi_quote_single_token_query', + // region: 'US', + // lang: 'en-US', + // }, + // }) + + // console.log(response.data); + + // const symbol = response.data.quotes[0].symbol.slice(0,-3); + + // console.log(symbol); + // // Fetch the stock list + // const resp = await axios.get('https://eodhistoricaldata.com/api/exchange-symbol-list/NSE', { + // params: { + // api_token: '67140e4f624e22.73357466', + // }, + // responseType: 'text', + // }); + + // // Split the response text by new lines to get each row (CSV format) + // const lines = resp.data.split('\n'); + let isin; + + // Parse each line and look for the specific symbol + const stocksWithISIN = [ + { name: 'Reliance', isin: 'INE002A01018' }, + { name: 'Tata', isin: 'INE155A01022' }, + { name: 'Infosys', isin: 'INE009A01021' }, + { name: 'HDFC', isin: 'INE001A01036' }, + { name: 'Wipro', isin: 'INE075A01022' }, + { name: 'ICICI', isin: 'INE185A01016' }, + { name: 'Adani', isin: 'INE523A01024' }, + { name: 'ITC', isin: 'INE154A01025' }, + { name: 'Bharti Airtel', isin: 'INE397D01024' }, + { name: 'Bajaj Auto', isin: 'INE235A01016' }, + { name: 'Larsen & Toubro', isin: 'INE018A01030' }, + { name: 'Maruti Suzuki', isin: 'INE585B01010' }, + { name: 'HCL Technologies', isin: 'INE868A01027' }, + { name: 'Mahindra & Mahindra', isin: 'INE101A01026' }, + { name: 'Tata Steel', isin: 'INE081A01012' }, + { name: 'ONGC', isin: 'INE213A01029' }, + { name: 'Hindalco', isin: 'INE038A01015' }, + { name: 'Axis Bank', isin: 'INE238A01034' }, + { name: 'SBI', isin: 'INE062A01020' }, + { name: 'Kotak Mahindra', isin: 'INE237A01024' }, + { name: 'Asian Paints', isin: 'INE021A01026' }, + { name: 'Sun Pharma', isin: 'INE044A01036' }, + { name: 'Tech Mahindra', isin: 'INE769A01024' }, + { name: 'JSW Steel', isin: 'INE019A01038' }, + { name: 'Titan Company', isin: 'INE280A01028' }, + { name: 'UltraTech Cement', isin: 'INE481G01011' }, + { name: 'Grasim Industries', isin: 'INE046A01013' }, + { name: 'Power Grid Corporation', isin: 'INE752E01010' }, + { name: 'NTPC', isin: 'INE733E01010' }, + { name: 'Coal India', isin: 'INE522F01014' }, + { name: 'BPCL', isin: 'INE029A01011' }, + { name: 'Hero MotoCorp', isin: 'INE158A01026' }, + { name: 'Nestle India', isin: 'INE239A01016' }, + { name: 'IndusInd Bank', isin: 'INE095A01016' }, + { name: 'Divi’s Laboratories', isin: 'INE361B01024' }, + { name: 'Bajaj Finserv', isin: 'INE918I01018' }, + { name: 'Zee Entertainment', isin: 'INE256A01026' }, + { name: 'Apollo Hospitals', isin: 'INE437A01024' }, + { name: 'Godrej Consumer Products', isin: 'INE102D01025' }, + { name: 'PVR', isin: 'INE191E01014' }, + { name: 'Tata Motors', isin: 'INE155A01027' }, + { name: 'Eicher Motors', isin: 'INE066A01022' }, + { name: 'Dabur', isin: 'INE016A01026' }, + { name: 'Bata India', isin: 'INE176A01029' }, + { name: 'Dr. Reddy’s Laboratories', isin: 'INE089A01023' }, + { name: 'Motherson Sumi', isin: 'INE102A01010' }, + { name: 'Siemens', isin: 'INE003A01024' }, + { name: 'ABB India', isin: 'INE117A01014' }, + { name: 'GAIL', isin: 'INE129A01019' }, + { name: 'Bharat Forge', isin: 'INE464A01016' }, + { name: 'Pidilite Industries', isin: 'INE234A01025' }, + { name: 'Muthoot Finance', isin: 'INE414F01012' }, + { name: 'IRCTC', isin: 'INE532E01030' }, + { name: 'Shree Cement', isin: 'INE070A01018' }, + { name: 'Cipla', isin: 'INE059A01026' }, + { name: 'Biocon', isin: 'INE376G01013' }, + { name: 'Havells India', isin: 'INE176B01024' }, + { name: 'Ashok Leyland', isin: 'INE158A01026' }, + { name: 'Tata Power', isin: 'INE245A01021' }, + { name: 'Exide Industries', isin: 'INE302A01020' }, + { name: 'Voltas', isin: 'INE226A01013' }, + { name: 'Colgate-Palmolive', isin: 'INE259A01012' }, + { name: 'Britannia', isin: 'INE221A01026' }, + { name: 'Federal Bank', isin: 'INE171A01013' }, + { name: 'Jubilant FoodWorks', isin: 'INE797F01012' }, + { name: 'InterGlobe Aviation', isin: 'INE742F01027' }, + { name: 'Hindustan Unilever', isin: 'INE030A01027' }, + { name: 'Godrej Properties', isin: 'INE484J01027' }, + { name: 'Glenmark Pharma', isin: 'INE935A01032' }, + { name: 'Mangalore Refinery', isin: 'INE169A01014' }, + { name: 'Aurobindo Pharma', isin: 'INE406A01037' }, + { name: 'Crompton Greaves', isin: 'INE399D01026' }, + { name: 'Hindustan Zinc', isin: 'INE267A01025' }, + { name: 'Torrent Power', isin: 'INE195G01027' }, + { name: 'TVS Motor', isin: 'INE494B01023' }, + { name: 'Ambuja Cements', isin: 'INE079A01024' }, + { name: 'Pfizer', isin: 'INE529A01025' }, + { name: 'Sun TV Network', isin: 'INE949D01010' }, + { name: 'Escorts', isin: 'INE600A01024' }, + { name: 'Yes Bank', isin: 'INE528G01011' }, + { name: 'MindTree', isin: 'INE018T01012' }, + { name: 'Max Healthcare', isin: 'INE221B01019' }, + { name: 'Aditya Birla Fashion', isin: 'INE100A01025' }, + { name: 'Avenue Supermarts', isin: 'INE192R01014' }, + { name: 'Zydus Wellness', isin: 'INE803A01022' }, + { name: 'IDFC First Bank', isin: 'INE092T01018' }, + { name: 'Page Industries', isin: 'INE761H01025' }, + { name: 'Blue Star', isin: 'INE189A01023' }, + { name: 'Bajaj Holdings', isin: 'INE118A01026' }, + { name: 'Lupin', isin: 'INE326D01028' }, + { name: 'Emami', isin: 'INE496B01020' }, + { name: 'Kajaria Ceramics', isin: 'INE195J01027' }, + { name: 'Berger Paints', isin: 'INE463A01022' }, + { name: 'L&T Finance', isin: 'INE147A01013' }, + { name: 'Mphasis', isin: 'INE356A01019' }, + { name: 'Quess Corp', isin: 'INE102T01011' }, + { name: 'Syngene International', isin: 'INE134Z01025' }, + { name: 'Manappuram Finance', isin: 'INE926D01022' }, + { name: 'SpiceJet', isin: 'INE800A01014' }, + { name: 'Indiabulls Housing Finance', isin: 'INE148D01024' }, + { name: 'Sunteck Realty', isin: 'INE121I01027' }, + { name: 'NMDC', isin: 'INE586A01011' }, + { name: 'Bombay Dyeing', isin: 'INE081A01023' }, + { name: 'Ramco Cements', isin: 'INE172C01039' }, + { name: 'ICICI Prudential', isin: 'INE765G01015' } + ] + + isin = stocksWithISIN.find(stock => stock.name.includes(query))?.isin; + + console.log(isin) + + // If ISIN is not found, return a 404 error + if (!isin) { + return res.status(404).json({ message: 'Stock not found' }); + } + + // Helper function to check if a given date is a holiday + const isHoliday = async (date) => { + try { + const holidayResponse = await axios.get(`https://api.upstox.com/v2/market/holidays/${date}`); + return holidayResponse.data.isHoliday; + } catch (error) { + console.error('Error checking holiday status:', error); + return false; // Default to not a holiday in case of API error + } + }; + + // Helper function to get formatted date string (YYYY-MM-DD) + const formatDate = (date) => date.toISOString().split('T')[0]; + + // Get the current date and time + const currentDate = new Date(); + let currentTime = currentDate.getHours() * 60 + currentDate.getMinutes(); // Convert time to minutes + let stockDetails; + + // Define the time range for intraday data (9:15 AM to 3:30 PM) + const startTime = 9 * 60 + 15; // 9:15 AM in minutes + const endTime = 15 * 60 + 30; // 3:30 PM in minutes + + // Set up date for API calls, starting with today + let dateToFetch = formatDate(currentDate); + let count = 0; + + // Retry logic in case of holiday or empty data + const retryFetchStockDetails = async () => { + while (true && count < 5) { + if (await isHoliday(dateToFetch)) { + console.log(`Market holiday on ${dateToFetch}, retrying with previous day`); + currentDate.setDate(currentDate.getDate() - 1); + dateToFetch = formatDate(currentDate); + continue; + } + + if (currentTime >= startTime && currentTime <= endTime) { + // Fetch intraday data if within market hours + stockDetails = await axios.get(`https://api.upstox.com/v2/historical-candle/intraday/NSE_EQ|${isin}/1minute/`, { + headers: { "Accept": "application/json" }, + }); + } else { + // Fetch historical data for the day + console.log(`Fetching historical data for ${dateToFetch}`); + stockDetails = await axios.get(`https://api.upstox.com/v2/historical-candle/NSE_EQ|${isin}/1minute/${dateToFetch}/${dateToFetch}`, { + headers: { "Accept": "application/json" }, + }); + } + + // If stock data is available, return it + if (stockDetails?.data?.data?.candles?.length > 0) { + count++; + return stockDetails; + + } else { + count++; + console.log(`No data for ${dateToFetch}, retrying with previous day`); + currentDate.setDate(currentDate.getDate() - 1); + dateToFetch = formatDate(currentDate); + } + } + }; + + stockDetails = await retryFetchStockDetails(); + + return res.json(stockDetails?.data); + } catch (error) { + console.error('Error fetching stock data:', error); + return res.status(500).json({ message: 'An error occurred' }); + } + +}) + + + +app.listen(process.env.PORT,()=>{ + console.log(`Server started on port ${process.env.PORT}`); +}); export default app; // export async function handler(event, context) { diff --git a/client/app/Dash/page.js b/client/app/Dash/page.js index 7056e8a..41cd8e6 100644 --- a/client/app/Dash/page.js +++ b/client/app/Dash/page.js @@ -13,6 +13,7 @@ import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import ChatbotEmbed from '../chatBot.jsx' +import SearchStocks from '@/components/SearchComponent' const availableStocks = ['Reliance', 'Tata', 'Infosys', 'HDFC', 'Wipro', 'ICICI', 'Adani', 'ITC', @@ -189,7 +190,7 @@ const Dash = () => { const { name, value } = e.target; - if (name!=undefined && value.length()!=0) { + if (name!=undefined && value?.length()!=0) { setPurchaseData({ ...PurchaseData, [name]: value, @@ -290,9 +291,13 @@ const Dash = () => { pauseOnHover theme="dark" /> +
-

Hey, {Username}

+
+

Hey, {Username}

+ +
-
- - ) diff --git a/client/app/globals.css b/client/app/globals.css index b395029..3d484c5 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -492,4 +492,67 @@ button { grid-template-columns: repeat(1, 1fr); place-content: center; } +} +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } } \ No newline at end of file diff --git a/client/app/stock/[stockName]/page.jsx b/client/app/stock/[stockName]/page.jsx new file mode 100644 index 0000000..3316cd4 --- /dev/null +++ b/client/app/stock/[stockName]/page.jsx @@ -0,0 +1,54 @@ +"use client"; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import FlexibleCandlestickChart from '@/components/CandleStickGraph'; + +export default function StockPage({ params }) { + const router = useRouter(); + const { stockName } = params; + const decodedStockName = decodeURIComponent(stockName); + const [stockData, setStockData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (decodedStockName) { + fetch(`https://investra-26xe.vercel.app/stock/data?q=${decodedStockName}`) + .then((response) => response.json()) + .then((data) => { + console.log("Fetched stock data:", data); + setStockData(data); + setLoading(false); + }) + .catch((error) => { + console.error("Error fetching stock data:", error); + setLoading(false); + }); + } + }, [decodedStockName]); + + return ( +
+
+

{decodedStockName}

+ + {loading ? ( +

Loading...

+ ) : stockData ? ( +
+ +
+ ) : ( +

No data available for {decodedStockName}.

+ )} + + +
+ +
+ ); +} diff --git a/client/components.json b/client/components.json new file mode 100644 index 0000000..47abe04 --- /dev/null +++ b/client/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} \ No newline at end of file diff --git a/client/components/CandleStickGraph.jsx b/client/components/CandleStickGraph.jsx new file mode 100644 index 0000000..da4bd1b --- /dev/null +++ b/client/components/CandleStickGraph.jsx @@ -0,0 +1,131 @@ +"use client" + +import React, { useMemo } from "react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { ChartContainer } from "@/components/ui/chart" +import { ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, ResponsiveContainer } from "recharts" + +// Helper function to format the date +const formatDate = (dateString) => { + const date = new Date(dateString) + return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` +} + +// Helper function to parse different data formats +const parseData = (inputData) => { + if (inputData?.status && inputData?.data && Array.isArray(inputData?.data?.candles[0])) { + // First format: { status: "success", data: { candles: [...] } } + const data = inputData?.data?.candles.map(candle => ({ + time: new Date(candle[0]).getTime(), + open: candle[1], + high: candle[2], + low: candle[3], + close: candle[4], + volume: candle[5], + color: candle[4] > candle[1] ? "var(--chart-green)" : "var(--chart-red)" + })); + console.log(data) + return data.reverse() + } else if (Array.isArray(inputData?.data?.candles.length > 0)) { + // Second format: { candles: [...] } + const data = inputData?.candles.map((candle) => ({ + time: formatDate(candle[0]), + open: candle[1], + high: candle[2], + low: candle[3], + close: candle[4], + volume: candle[5], + color: candle[4] > candle[1] ? "var(--chart-green)" : "var(--chart-red)" + })) + + console.log(data) + return data + } else { + console.error("Unsupported data format") + return [] + } +} + +export default function FlexibleCandlestickChart({ inputData }) { + const data = useMemo(() => parseData(inputData), [inputData]) + + return ( + + + Stock Price Candlestick Chart + 1-minute interval data + + + + + + value} /> + + + { + if (payload && payload.length) { + const data = payload[0].payload + return ( +
+

Time: {data.time}

+

Open: {data.open}

+

High: {data.high}

+

Low: {data.low}

+

Close: {data.close}

+

Volume: {data.volume}

+
+ ) + } + return null + }} + /> + { + const { cx, payload, yAxis } = props + const { open, close, high, low } = payload + const candleWidth = 8 + const isUp = close > open + const yHigh = yAxis.scale(high) + const yLow = yAxis.scale(low) + const yOpen = yAxis.scale(open) + const yClose = yAxis.scale(close) + + return ( + + + + + ) + }} + /> +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/client/components/SearchComponent.jsx b/client/components/SearchComponent.jsx new file mode 100644 index 0000000..5447a30 --- /dev/null +++ b/client/components/SearchComponent.jsx @@ -0,0 +1,55 @@ +"use client"; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; + +export default function SearchStocks({ availableStocks }) { + const router = useRouter(); + const [query, setQuery] = useState(""); + const [filteredStocks, setFilteredStocks] = useState(availableStocks); + const [showList, setShowList] = useState(false); + + const handleSearchChange = (e) => { + const value = e.target.value; + setQuery(value); + setFilteredStocks( + availableStocks.filter((stock) => + stock.toLowerCase().includes(value.toLowerCase()) + ) + ); + setShowList(true); + }; + + const handleStockClick = (stockName) => { + router.push(`/stock/${stockName}`); + setShowList(false); + }; + + const handleBlur = () => setTimeout(() => setShowList(false), 150); + + return ( +
+ setShowList(true)} + onBlur={handleBlur} + placeholder="Search stocks..." + className="border border-darkgray bg-gainsboro rounded w-full mb-2 text-darkslategray font-spartan text-17xl focus:outline-none" + /> + {query && showList && ( + + )} +
+ ); + } \ No newline at end of file diff --git a/client/components/ui/card.jsx b/client/components/ui/card.jsx new file mode 100644 index 0000000..4af349a --- /dev/null +++ b/client/components/ui/card.jsx @@ -0,0 +1,50 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/client/components/ui/chart.jsx b/client/components/ui/chart.jsx new file mode 100644 index 0000000..de4fe82 --- /dev/null +++ b/client/components/ui/chart.jsx @@ -0,0 +1,309 @@ +"use client"; +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { + light: "", + dark: ".dark" +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + ( +
+ + + {children} + +
+
) + ); +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ + id, + config +}) => { + const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color) + + if (!colorConfig.length) { + return null + } + + return ( + (