diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d41001 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +pem \ No newline at end of file diff --git a/Client/NewsFinance/.firebase/hosting.ZGlzdA.cache b/Client/NewsFinance/.firebase/hosting.ZGlzdA.cache new file mode 100644 index 0000000..aac55d8 --- /dev/null +++ b/Client/NewsFinance/.firebase/hosting.ZGlzdA.cache @@ -0,0 +1,4 @@ +index.html,1743073449066,e657da99db809c5b79082eb404c27532037ce49917a8a560eedd4a5190103299 +assets/index-CM8zwju8.css,1743073449067,3c880225f7ef155ebbc109e97728c2a0ff2a7f5103b777141429d83d5b09b247 +vite.svg,1742271844865,699a02e0e68a579f687d364bbbe7633161244f35af068220aee37b1b33dfb3c7 +assets/index-Dz-UR3KL.js,1743073449067,e24c7ca51969a8fcf0ef514983fae1da49c36b9bed31eea5a888aa1c5e1774c7 diff --git a/Client/NewsFinance/.firebaserc b/Client/NewsFinance/.firebaserc new file mode 100644 index 0000000..967165d --- /dev/null +++ b/Client/NewsFinance/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "brandon-finweb" + } +} diff --git a/Client/NewsFinance/.gitignore b/Client/NewsFinance/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/Client/NewsFinance/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Client/NewsFinance/README.md b/Client/NewsFinance/README.md new file mode 100644 index 0000000..fd3b758 --- /dev/null +++ b/Client/NewsFinance/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/Client/NewsFinance/eslint.config.js b/Client/NewsFinance/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/Client/NewsFinance/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/Client/NewsFinance/firebase.json b/Client/NewsFinance/firebase.json new file mode 100644 index 0000000..2c33c29 --- /dev/null +++ b/Client/NewsFinance/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "dist", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/Client/NewsFinance/index.html b/Client/NewsFinance/index.html new file mode 100644 index 0000000..820ae77 --- /dev/null +++ b/Client/NewsFinance/index.html @@ -0,0 +1,21 @@ + + +
+ + + +{response}
+Or Register, if you still doesn't have an account
+Or Login, if you already have an account
++ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 | 1x +1x + +1x +1x +1x + +1x +1x +1x + +1x + +1x | Eif(process.env.NODE_ENV !== 'production') {
+ require('dotenv').config();
+}
+const express = require('express');
+const app = express();
+const cors = require('cors');
+
+app.use(express.urlencoded({ extended: false }));
+app.use(express.json());
+app.use(cors())
+
+app.use('/', require('./router'));
+
+module.exports = app; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 | 1x +1x +1x + + + +1x +1x +1x + + +1x + + + + + + + + + + + + + + + + + +1x + + + + + +1x +1x + + + + +1x + + + + + + + +1x +1x +1x + + +1x +1x + + + + + +1x + + + + + +2x +2x +2x +1x + +1x +38x + + +1x + + +1x + +1x +1x + +1x +1x + +1x + +1x + + + + + +1x +1x + + + + + + + + + +1x +1x + + +1x + + + + + + + +1x +1x +1x +1x +5x + + + + + + + + + +5x +5x + + + +5x + + + + + +1x + + + + + + + + +1x +1x +1x +1x +5x + + + + + + + + +5x +5x + + + +5x + + +1x + + + + + +1x | const { Article } = require('../models');
+const { GoogleGenerativeAI } = require("@google/generative-ai");
+const http = require('../helpers/axios');
+
+class Controllers {
+ static async saveArticle(req, res, next) {
+ try {
+ const { userId, author, title, description, url, publishedAt, content, imageUrl } = req.body;
+ Iif (!userId && !author && !title && !description && !url && !publishedAt && !content && !imageUrl) {
+ throw {name: 'Not Found', message: 'Article not found'};
+ }
+ const checkArticle = await Article.findOne({ where: { url: url } });
+ if (checkArticle) {
+ throw {name: 'Bad Request', message: 'Article already saved'};
+ }
+ const newContent = content.split('[+')[0];
+
+ await Article.create({
+ userId : req.user.id,
+ author : author,
+ title : title,
+ description : description,
+ url : url,
+ publishedAt : publishedAt,
+ imageUrl : imageUrl,
+ content : newContent
+ });
+ res.status(200).json({message: 'Article saved successfully'});
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ //method to get all saved articles from loggedin user
+ static async getArticle (req, res, next) {
+ try {
+ const articles = await Article.findAll({
+ where: { userId: req.user.id },
+ include: 'User',
+ attributes: { exclude: ['password'] }
+ });
+ res.status(200).json(articles);
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ //method to delete saved article from loggedin user
+ static async deleteArticle (req, res, next) {
+ try {
+ const article = await Article.findOne({ where: { id: req.params.id } });
+ Iif (!article) {
+ throw {name: 'Not Found', message: 'Article not found'};
+ }
+ await article.destroy();
+ res.status(200).json({ message: 'Article deleted successfully' });
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ static isFinanceRelated = (text)=>{
+ return financeKeywords.some(keyword => text.toLowerCase().includes(keyword));
+ }
+
+ //handling GeminiAI API from user input
+ static async handleGemini (req, res, next) {
+ try {
+ const {question} = req.body;
+ if(!question){
+ throw {name: 'Bad Request', message: 'Please ask a question'};
+ }
+ const financeKeywords = ['stocks', 'investments', 'finance', 'economy', 'market', 'currency', 'tax', 'financial', 'trading', 'bonds', 'shares', 'dividends', 'capital', 'market', 'equity', 'portfolio', 'asset', 'liability', 'income', 'expense', 'profit', 'loss', 'revenue', 'debt', 'credit', 'loan', 'interest', 'rate', 'inflation', 'deflation', 'recession', 'depression', 'boom', 'bust', 'bull', 'bear', 'market', 'stock', 'exchange', 'bond', 'mutual', 'fund', 'index', 'option', 'future', 'commodity', 'derivative', 'hedge', 'fund', 'private', 'equity', 'venture', 'capital', 'angel', 'investor', 'crowdfunding', 'initial', 'public', 'offering', 'IPO', 'merger', 'acquisition', 'takeover', 'divestiture', 'spinoff', 'joint', 'venture', 'partnership', 'corporation', 'company', 'firm', 'business', 'enterprise', 'organization', 'institution', 'government', 'regulation', 'policy', 'law', 'rule', 'taxation', 'audit', 'accounting', 'financial', 'statement', 'balance', 'sheet', 'income', 'statement', 'cash', 'flow', 'statement', 'valuation', 'discounted', 'cash', 'flow', 'DCF', 'net', 'present', 'value', 'NPV', 'internal', 'rate', 'return', 'IRR', 'return', 'investment', 'ROI', 'risk', 'return', 'risk', 'free', 'rate', 'beta', 'alpha', 'standard', 'deviation', 'variance', 'correlation', 'covariance', 'diversification', 'hedging', 'arbitrage', 'speculation', 'technical', 'analysis', 'fundamental', 'analysis', 'efficient', 'market', 'hypothesis', 'random', 'walk', 'theory', 'behavioral', 'finance', 'value', 'investing', 'growth', 'investing', 'momentum', 'investing', 'contrarian', 'investing', 'income', 'investing', 'index', 'investing', 'passive', 'investing', 'active', 'investing', 'day', 'trading', 'swing', 'trading', 'price']
+ const isFinanceRelated = financeKeywords.some(keyword => question.toLowerCase().includes(keyword));
+ //check if the question is finance related
+
+ Iif(!isFinanceRelated){
+ throw {name: 'Bad Request', message: 'Please ask a finance related question'};
+ }
+ const prompt = `You are an assistant focused only on finance topics. Please provide answers related to finance, such as stocks, investments, economy, and market trends. Question: ${question}`
+
+ const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY)
+ const model = genAI.getGenerativeModel({model: 'gemini-2.0-flash'});
+
+ const result = await model.generateContent(prompt);
+ const message = typeof result.response.text === 'function' ? await result.response.text() : result.response.text;
+
+ res.status(200).json({ message });
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ //get the news data from NewsAPI
+ static async getNews (req, res, next) {
+ try {
+ const newsResponse = await http({
+ method: 'GET',
+ url: 'https://newsapi.org/v2/top-headlines',
+ params:{
+ q: 'stock',
+ pageSize: 5,
+ sortBy: 'popularity',
+ apiKey: process.env.NEWS_API_KEY
+ }
+ })
+ const newsData = newsResponse.data.articles;
+ Iif(newsData.length === 0){
+ throw {name: 'Not Found', message: 'News not found'};
+ }
+ res.status(200).json(newsData);
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ //get the TwelveData stocks data
+ static async getStock (req, res, next) {
+ try{
+ const StockCollection=[]
+ const STOCK_SYMBOL = ['AAPL', 'GOOGL', 'AMZN', 'MSFT', 'TSLA'];
+ for(let i = 0; i < STOCK_SYMBOL.length; i++){
+ const StockResponse = await http({
+ method: 'GET',
+ url: 'https://api.twelvedata.com/time_series',
+ params: {
+ symbol: STOCK_SYMBOL[i],
+ interval: '15min',
+ outputsize: 1,
+ apikey: process.env.TWELVE_DATA_API_KEY
+ }
+ })
+ const stockData = StockResponse.data;
+ Iif(stockData.code === 400){
+ throw {name: 'Bad Request', message: 'Invalid request'};
+ }
+ else{
+ StockCollection.push({
+ symbol: stockData.meta.symbol,
+ price: +stockData.values[0].close
+ });
+ }
+ }
+ res.status(200).json(StockCollection);
+ }
+ catch(error){
+ next(error);
+ }
+ }
+
+ //get the CryptoCompare cryptocurrency data
+ static async getCryptoCurrency (req, res, next) {
+ try {
+ const cryptoCollection = []
+ const CRYPTO_FSYM = ['BTC', 'ETH', 'LTC', 'XRP', 'DOGE'];
+ for(let i = 0; i < CRYPTO_FSYM.length; i++){
+ const cryptoResponse = await http({
+ method: 'GET',
+ url: 'https://min-api.cryptocompare.com/data/price',
+ params: {
+ fsym: CRYPTO_FSYM[i],
+ tsyms: 'USD',
+ apikey: process.env.CRYPTOCOMPARE_API_KEY
+ }
+ })
+ Eif(cryptoResponse.data.Response !== 'Error'){
+ const cryptoData = {
+ symbol: CRYPTO_FSYM[i],
+ price: cryptoResponse.data.USD
+ };
+ cryptoCollection.push(cryptoData);
+ }
+ }
+ res.status(200).json(cryptoCollection);
+ } catch (error) {
+ next(error);
+ }
+ }
+}
+module.exports = Controllers; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 | 1x +1x +1x +1x +1x +1x + + + + + +5x + +5x + +5x + +2x + +3x +3x +1x + +2x +2x +1x + +1x +1x + +4x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +4x + +4x + + +4x + +3x + +1x + + + + +1x + +3x + + + + + +1x +1x +1x +1x + + + + +1x + + +1x + +1x + + + + +1x + + + + + + + +1x + +1x +1x + + + +1x + + +1x + + + + + +1x + +1x +1x + + +1x + + + + + + +1x | require('dotenv').config();
+const { signToken } = require("../helpers/jwt");
+const {hashPassword, verifyPassword} = require('../helpers/bcrypt');
+const {User} = require('../models');
+const {OAuth2Client} = require('google-auth-library');
+const { defaults } = require('../helpers/axios');
+
+//class UserController
+class UserController {
+ //method to add user
+ static async login(req, res, next) {
+ try {
+ //get user data
+ const {email,password} = req.body;
+ //check if user exists
+ if (!email || !password) {
+ //if user does not exist, send error message}
+ throw {name: 'BadRequest', message: 'Email, and Password must be filled!'};
+ }
+ const user = await User.findOne({where: {email: email}});
+ if (!user) {
+ throw {name: 'Unauthorized', message: 'Invalid Email/Password'};
+ }
+ const pwValid = await verifyPassword(password, user.password);
+ if (!pwValid) {
+ throw {name: 'Unauthorized', message: 'Invalid Email/Password'};
+ }
+ const access_token = signToken({id: user.id});
+ res.status(200).json({access_token});
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ static async loginGoogle(req, res, next) {
+ try {
+ const {googleToken} = req.body;
+
+ const client = new OAuth2Client();
+ const ticket = await client.verifyIdToken({
+ idToken: googleToken,
+ audience: process.env.GOOGLE_API_KEY,
+ });
+ const payload = ticket.getPayload();
+ const [user] = await User.findOrCreate({
+ where: {
+ email: payload.email
+ },
+ defaults: {
+ username: payload.name,
+ email: payload.email,
+ password: Math.random().toString(36).slice(-8)
+ }
+ });
+ const access_token = signToken({id: user.id});
+ res.status(200).json({access_token})
+
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ static async register(req, res, next) {
+ try {
+ //get user data
+ const {username,email,password} = req.body;
+
+ //check if user exists
+ if (!username || !email || !password) {
+ //if user does not exist, send error message}
+ throw {name: 'BadRequest', message: 'Invalid email, username or password'};
+ }
+ const newUser = await User.create({
+ username:username,
+ email:email,
+ password:password
+ });
+ res.status(201).json(newUser);
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ //method to edit existing user data
+ static async updateUser(req, res, next) {
+ try {
+ const {username,email,password} = req.body;
+ const user = await User.findOne({where: {id: req.user.id}});
+ Iif (!user) {
+ throw {name: 'Forbidden', message: 'Forbidden access'};
+ }
+ //get updated user data from request.body
+
+ Iif (!email || !username || !password) {
+ return res.status(400).json({ message: 'All fields are required' });
+ }
+ const hashedPassword = hashPassword(password);
+
+ await user.update({
+ email: email,
+ username: username,
+ password: hashedPassword
+ });
+ res.status(200).json(user);
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ //method to delete loggedin user
+ static async deleteUser(req, res, next) {
+ try {
+ //get user data from request.user
+ const user = req.user;
+ Iif(!user) {
+ throw {name: 'Forbidden', message: 'Forbidden access'};
+ }
+ //delete user data
+ await user.destroy();
+ res.status(200).json({ message: 'User deleted successfully' });
+ } catch (error) {
+ next(error)
+ }
+ }
+
+ //method to get loggedin user data
+ static async getUser(req, res, next) {
+ try {
+ //get user data from request.user
+ const user = req.user;
+ Iif(!user) {
+ throw {name: 'Forbidden', message: 'Forbidden access'};
+ }
+ res.status(200).json(user);
+ } catch (error) {
+ next(error)
+ }
+ }
+}
+
+module.exports = UserController; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| Controller.js | +
+
+ |
+ 77.33% | +58/75 | +46.15% | +12/26 | +80% | +8/10 | +77.46% | +55/71 | +
| UserController.js | +
+
+ |
+ 73.33% | +44/60 | +83.33% | +20/24 | +83.33% | +5/6 | +73.33% | +44/60 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 | 1x + +1x + + + +1x | const axios = require('axios');
+
+const http = axios.create({
+ baseURL: 'http://localhost:3000'
+});
+
+module.exports = http; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 | 1x + +1x +1x +1x + +1x +2x + + +1x | const bcrypt = require('bcrypt')
+
+const hashPassword = (password) => {
+ const salt = bcrypt.genSaltSync(10);
+ return bcrypt.hashSync(password, salt);
+}
+const verifyPassword = (password, hash) => {
+ return bcrypt.compare(password, hash);
+}
+
+module.exports = { hashPassword, verifyPassword }; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| axios.js | +
+
+ |
+ 100% | +3/3 | +100% | +0/0 | +100% | +0/0 | +100% | +3/3 | +
| bcrypt.js | +
+
+ |
+ 100% | +7/7 | +100% | +0/0 | +100% | +2/2 | +100% | +7/7 | +
| jwt.js | +
+
+ |
+ 100% | +7/7 | +100% | +0/0 | +100% | +2/2 | +100% | +7/7 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 | 1x +1x + +1x +22x + + +1x +6x + + +1x | const jwt = require('jsonwebtoken');
+const JWT_SECRET = process.env.JWT_SECRET;
+
+const signToken = (payload) => {
+ return jwt.sign(payload, JWT_SECRET);
+}
+
+const verifyToken = (token) => {
+ return jwt.verify(token, JWT_SECRET);
+}
+
+module.exports = { signToken, verifyToken }; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| app.js | +
+
+ |
+ 100% | +10/10 | +50% | +1/2 | +100% | +0/0 | +100% | +10/10 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 | +1x +1x + + +6x + +6x + +6x + + + + +6x +6x + + +6x + +6x + +6x + + + + +6x +6x + + + + + +1x | //authentication middleware for verifying the token
+const {verifyToken} = require('../helpers/jwt');
+const {User} = require('../models');
+
+async function authentication(req, res, next) {
+ try {
+ //get authorization from request header
+ const {authorization} = req.headers;
+ //check if authorization exists
+ Iif (!authorization) {
+ //if authorization does not exist, send error message
+ throw {name: 'Unauthorized', message: 'Please login first'};
+ }
+ //split authorization to get token
+ const authorizationText = authorization.split(' ');
+ Iif(authorizationText[0] !== 'Bearer' || !authorizationText[1]) {
+ throw {name: 'Unauthorized', message: 'Please login first'};
+ }
+ const token = authorizationText[1];
+ //get user data from database
+ const user = await User.findOne({where: {id: verifyToken(token).id}});
+ //check if user exists
+ Iif (!user) {
+ //if user does not exist, send error message
+ throw {name: 'Unauthorized', message: 'Please login first'};
+ }
+ //set user data to request.user
+ req.user = user;
+ next();
+ } catch (error) {
+ next(error);
+ }
+}
+
+module.exports = authentication; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 | +1x + + +1x +1x +1x +1x + + +1x + + +1x + + + + + +1x | //authorization middleware for verifying if the user is authorized
+const {Article} = require('../models');
+
+async function authorization(req, res, next) {
+ try {
+ const id = +req.params.id;
+ const articles = await Article.findByPk(id);
+ Iif(!articles){
+ throw {name: 'Not Found', message: 'Article not found'};
+ }
+ Iif(articles.userId !== req.user.id){
+ throw {name: 'Forbidden', message: 'You are not authorized'};
+ }
+ next();
+ } catch (error) {
+ next(error);
+ }
+}
+
+module.exports = authorization; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 | + + +10x + + + +10x +2x + +10x + + +10x + + +10x +6x + + +4x + + + +1x | //function for error handling
+function errorHandler(err, req, res, next) {
+ //get error name and message
+ Iif(err.name === 'SequelizeValidationError' || err.name === 'SequelizeUniqueConstraintError') {
+ const errors = err.errors.map(e => e.message);
+ res.status(400).json({message: errors});
+ }
+ if(err.name === 'Unauthorized') {
+ res.status(401).json({message: err.message});
+ }
+ Iif(err.name === 'Not Found' || err.name === 'NotFound') {
+ res.status(404).json({message: err.message});
+ }
+ Iif(err.name === 'Forbidden') {
+ res.status(403).json({message: err.message});
+ }
+ if(err.name === 'Bad Request' || err.name === 'BadRequest') {
+ res.status(400).json({message: err.message});
+ }
+ else{
+ res.status(500).json({message:'Internal Server Error'});
+ }
+}
+
+module.exports = errorHandler; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| authentication.js | +
+
+ |
+ 76.47% | +13/17 | +62.5% | +5/8 | +100% | +1/1 | +76.47% | +13/17 | +
| authorization.js | +
+
+ |
+ 72.72% | +8/11 | +50% | +2/4 | +100% | +1/1 | +72.72% | +8/11 | +
| errorHandler.js | +
+
+ |
+ 64.28% | +9/14 | +81.25% | +13/16 | +50% | +1/2 | +69.23% | +9/13 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 | + + +1x +1x + + + + + + + + +1x + + + + +1x + + + + + + + + + + + + +1x + | 'use strict';
+const {
+ Model
+} = require('sequelize');
+module.exports = (sequelize, DataTypes) => {
+ class Article extends Model {
+ /**
+ * Helper method for defining associations.
+ * This method is not a part of Sequelize lifecycle.
+ * The `models/index` file will call this method automatically.
+ */
+ static associate(models) {
+ // define association here
+ Article.belongsTo(models.User, {
+ foreignKey: 'userId'
+ })
+ }
+ }
+ Article.init({
+ title: DataTypes.STRING,
+ author: DataTypes.STRING,
+ description: DataTypes.STRING,
+ content: DataTypes.STRING,
+ url: DataTypes.STRING,
+ publishedAt: DataTypes.DATE,
+ imageUrl: DataTypes.STRING,
+ userId: DataTypes.INTEGER
+ }, {
+ sequelize,
+ modelName: 'Article',
+ });
+ return Article;
+}; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| article.js | +
+
+ |
+ 100% | +5/5 | +100% | +0/0 | +100% | +2/2 | +100% | +5/5 | +
| index.js | +
+
+ |
+ 95.23% | +20/21 | +70% | +7/10 | +100% | +3/3 | +95.23% | +20/21 | +
| user.js | +
+
+ |
+ 100% | +9/9 | +100% | +0/0 | +100% | +3/3 | +100% | +9/9 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 | + +1x +1x +1x +1x +1x +1x +1x +1x + + +1x + + +1x + + +1x + + +3x + + + + + + + +2x +2x + + +1x +2x +2x + + + +1x +1x + +1x + | 'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const Sequelize = require('sequelize');
+const process = require('process');
+const basename = path.basename(__filename);
+const env = process.env.NODE_ENV || 'development';
+const config = require(__dirname + '/../config/config.json')[env];
+const db = {};
+
+let sequelize;
+Iif (config.use_env_variable) {
+ sequelize = new Sequelize(process.env[config.use_env_variable], config);
+} else {
+ sequelize = new Sequelize(config.database, config.username, config.password, config);
+}
+
+fs
+ .readdirSync(__dirname)
+ .filter(file => {
+ return (
+ file.indexOf('.') !== 0 &&
+ file !== basename &&
+ file.slice(-3) === '.js' &&
+ file.indexOf('.test.js') === -1
+ );
+ })
+ .forEach(file => {
+ const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
+ db[model.name] = model;
+ });
+
+Object.keys(db).forEach(modelName => {
+ Eif (db[modelName].associate) {
+ db[modelName].associate(db);
+ }
+});
+
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+
+module.exports = db;
+ |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 | + + +1x +1x +1x + + + + + + + + +1x + + + + +1x + + + + + + + + + +1x +1x +1x + + +1x + | 'use strict';
+const {
+ Model
+} = require('sequelize');
+const bcrypt = require('bcrypt');
+module.exports = (sequelize, DataTypes) => {
+ class User extends Model {
+ /**
+ * Helper method for defining associations.
+ * This method is not a part of Sequelize lifecycle.
+ * The `models/index` file will call this method automatically.
+ */
+ static associate(models) {
+ // define association here
+ User.hasMany(models.Article, {
+ foreignKey: 'userId'
+ })
+ }
+ }
+ User.init({
+ username: DataTypes.STRING,
+ email: DataTypes.STRING,
+ password: DataTypes.STRING
+ }, {
+ sequelize,
+ modelName: 'User',
+ });
+
+ //addHook bcrypt password
+ User.addHook('beforeCreate', (user, options) => {
+ const salt = bcrypt.genSaltSync(10);
+ user.password = bcrypt.hashSync(user.password, salt);
+ });
+
+ return User;
+}; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| index.js | +
+
+ |
+ 100% | +25/25 | +100% | +0/0 | +100% | +1/1 | +100% | +25/25 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| 1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 | +1x +1x +1x +1x +1x +1x +1x + + +1x +1x +1x +1x +1x +1x +1x + + +1x +1x + + +1x + + + +1x +1x +1x + + +1x +1x +1x + + +1x + +1x | //router for app.js
+const express = require('express');
+const UserController = require('../controllers/UserController');
+const Controllers = require('../controllers/Controller');
+const errorHandler = require('../middleware/errorHandler');
+const authentication = require('../middleware/authentication');
+const authorization = require('../middleware/authorization');
+const router = express.Router();
+
+//public routes
+router.get('/news', Controllers.getNews)
+router.post('/login', UserController.login)
+router.post('/google-login', UserController.loginGoogle)
+router.post('/register', UserController.register)
+router.post('/ask', Controllers.handleGemini)
+router.get('/stocks', Controllers.getStock)
+router.get('/crypto', Controllers.getCryptoCurrency)
+
+//get google API key
+router.get('/google', (req,res)=>{
+ res.json({key: process.env.GOOGLE_API_KEY})
+})
+//authentication middleware
+router.use(authentication)
+
+//need authorization to access these routes
+//profile routes
+router.get('/profile', UserController.getUser)
+router.put('/profile', UserController.updateUser)
+router.delete('/profile', UserController.deleteUser)
+
+//article routes
+router.post('/save', Controllers.saveArticle)
+router.get('/myArticle', Controllers.getArticle)
+router.delete('/myArticle/:id', authorization, Controllers.deleteArticle)
+
+//error handling middleware
+router.use(errorHandler)
+
+module.exports = router; |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +| File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
|---|---|---|---|---|---|---|---|---|---|
| Server | +
+
+ |
+ 100% | +10/10 | +50% | +1/2 | +100% | +0/0 | +100% | +10/10 | +
| Server/controllers | +
+
+ |
+ 75.55% | +102/135 | +64% | +32/50 | +81.25% | +13/16 | +75.57% | +99/131 | +
| Server/helpers | +
+
+ |
+ 100% | +17/17 | +100% | +0/0 | +100% | +4/4 | +100% | +17/17 | +
| Server/middleware | +
+
+ |
+ 71.42% | +30/42 | +71.42% | +20/28 | +75% | +3/4 | +73.17% | +30/41 | +
| Server/models | +
+
+ |
+ 97.14% | +34/35 | +70% | +7/10 | +100% | +8/8 | +97.14% | +34/35 | +
| Server/router | +
+
+ |
+ 100% | +25/25 | +100% | +0/0 | +100% | +1/1 | +100% | +25/25 | +