Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3e455b8
testing e2e
BasmaO9 Dec 22, 2024
f48816c
Income Barchart Backend
BasmaO9 Apr 11, 2025
713a42a
add transaction fix
BasmaO9 Apr 11, 2025
0bdca38
change connection string
BasmaO9 Apr 11, 2025
34cb739
Income barchart frontend
BasmaO9 Apr 11, 2025
3f55f85
Merge pull request #39 from MariamEwas/Basma
BasmaO9 Apr 11, 2025
bdda670
Merge pull request #1 from BasmaO9/Basma
BasmaO9 Apr 11, 2025
460c138
Merge pull request #40 from MariamEwas/Basma
BasmaO9 Apr 11, 2025
b3d9ee3
Merge pull request #2 from BasmaO9/Basma
BasmaO9 Apr 12, 2025
d519c1f
Merge pull request #3 from MariamEwas/Basma
BasmaO9 Apr 12, 2025
31767af
filter
Reemyasser603 Apr 19, 2025
a0d8da6
Merge pull request #4 from MariamEwas/Reem
MariamEwas Apr 19, 2025
32228af
fix frontend budges
MariamEwas Apr 19, 2025
2190376
Added receipt upload and OCR extraction feature
nadaelnegihi May 13, 2025
8fa28e1
Merge branch 'main' of https://github.com/MariamEwas/Testing_Project_…
nadaelnegihi May 13, 2025
526e45a
Merge pull request #5 from MariamEwas/nada
MariamEwas May 17, 2025
0355f21
frontend receipt detector
Reemyasser603 May 17, 2025
2ec100f
dependancies
BasmaO9 May 20, 2025
bc87dd2
chatbot backend
BasmaO9 May 20, 2025
66bc8c0
chatbot frontend
BasmaO9 May 20, 2025
6337565
Merge pull request #6 from MariamEwas/Reem
Fatma-Fouad May 20, 2025
39c4b85
Merge branch 'main' of https://github.com/MariamEwas/Testing_Project_…
BasmaO9 May 20, 2025
64ea4a3
Merge pull request #7 from MariamEwas/Basma
Fatma-Fouad May 20, 2025
aa27839
MFA DONE
Fatma-Fouad May 22, 2025
676e458
Merge pull request #8 from MariamEwas/Fatma
MariamEwas May 24, 2025
df21bae
fix frontend
MariamEwas May 24, 2025
30a6941
Merge branch 'main' of https://github.com/MariamEwas/Testing_Project_…
MariamEwas May 24, 2025
fa8cccf
Merge pull request #9 from MariamEwas/Mariam
MariamEwas May 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Backend/eng.traineddata
Binary file not shown.
417 changes: 339 additions & 78 deletions Backend/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
"scripts": {
"start": "tsc index.ts",
"start:dev": "npx tsc & nodemon",
"test":"jest"
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@google/generative-ai": "^0.24.1",
"axios": "^1.7.7",
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
Expand All @@ -28,9 +29,11 @@
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"mongoose": "^8.8.2",
"multer": "^2.0.0",
"path": "^0.12.7",
"swagger": "^0.0.1",
"swagger-ui-express": "^5.0.1",
"tesseract.js": "^6.0.1",
"yaml": "^2.6.1",
"yamljs": "^0.3.0"
},
Expand All @@ -44,6 +47,7 @@
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.7",
"@types/mongoose": "^5.11.97",
"@types/multer": "^1.4.12",
"@types/node": "^22.9.1",
"@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.7",
Expand Down
39 changes: 39 additions & 0 deletions Backend/src/BusinessLogic_Layer/config/dotenvConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const JWT_SECRET = process.env.JWT_SECRET as string;
export const JWT_EXPIRATION = process.env.JWT_EXPIRATION ;
export const COOKIE_NAME = process.env.COOKIE_NAME ;
export const NODE_ENV = process.env.NODE_ENV ;
export const geminiApiKey = process.env.GEMINI_API_KEY || "";
export const PORT = process.env.PORT || 5000;

// Validate all required environment variables

Expand All @@ -30,3 +32,40 @@ if (!process.env.COOKIE_NAME) {
if (!process.env.NODE_ENV) {
console.warn("NODE_ENV is not set. Using default value 'development'.");
}


// import * as dotenv from "dotenv";

// // Load environment variables from .env file
// dotenv.config();

// // Structured configuration object
// export const config = {
// port: process.env.PORT || 5000,
// geminiApiKey: process.env.GEMINI_API_KEY || "",
// jwtSecret: process.env.JWT_SECRET as string,
// jwtExpiration: process.env.JWT_EXPIRATION || "1h",
// cookieName: process.env.COOKIE_NAME || "authToken",
// nodeEnv: process.env.NODE_ENV || "development",
// };

// // Validate required environment variables
// if (!config.jwtSecret) {
// throw new Error("JWT_SECRET is not set in .env");
// }

// if (!process.env.JWT_EXPIRATION) {
// console.warn("JWT_EXPIRATION is not set. Using default value '1h'.");
// }

// if (!process.env.COOKIE_NAME) {
// console.warn("COOKIE_NAME is not set. Using default value 'authToken'.");
// }

// if (!process.env.NODE_ENV) {
// console.warn("NODE_ENV is not set. Using default value 'development'.");
// }

// if (!config.geminiApiKey) {
// console.warn("GEMINI_API_KEY is not set. Gemini services will not function correctly.");
// }
26 changes: 26 additions & 0 deletions Backend/src/BusinessLogic_Layer/controllers/chatbot.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Request, Response } from "express";
import { JwtPayload } from "jsonwebtoken";
import GeminiService from "../services/chatbot.services";

class ChatController {
constructor(private geminiService: GeminiService) {}

async chatWithAI(req: Request & { user?: JwtPayload }, res: Response): Promise<void> {
try {
const { message } = req.body;

if (!message) {
res.status(400).json({ error: "Message is required" });
return;
}

const aiResponse = await this.geminiService.generateAIResponse(message);
res.status(200).json({ response: aiResponse });
} catch (error: any) {
console.error("ChatWithAI Error:", error);
res.status(500).json({ error: "Internal Server Error" });
}
}
}

export default ChatController;
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import regService from '../services/Reg.services';
class regController {
static async register (req:Request,res:Response)
{
const {name ,email,phone ,password}=req.body;
const {name ,email,phone ,password, securityQuestion, securityAnswer}=req.body;
try {
console.log("here ");
const user =await regService.registerUser(name ,email,phone,password);
const user =await regService.registerUser(name ,email,phone,password, securityQuestion, securityAnswer);
res.status(201).json({message:'User registered successfully!',user});
}
catch (error:any) {
Expand Down
69 changes: 69 additions & 0 deletions Backend/src/BusinessLogic_Layer/controllers/security.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Request, Response } from 'express';
import User from '../../Database_Layer/models/user.schema';
import jwt from 'jsonwebtoken';
import { JWT_SECRET } from '../config/dotenvConfig';

class SecurityController {
// Get the security question for the logged-in user
static async getSecurityQuestion(req: Request, res: Response) {
try {
const token = req.cookies[process.env.COOKIE_NAME as string];
if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}

const decoded = jwt.verify(token, JWT_SECRET as string) as { id: string };
const user = await User.findById(decoded.id);

if (!user) {
return res.status(404).json({ error: 'User not found' });
}

res.json({ question: user.securityQuestion });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}

// Verify the security answer
static async verifySecurityAnswer(req: Request, res: Response) {
try {
const { answer } = req.body;
const token = req.cookies[process.env.COOKIE_NAME as string];

if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}

const decoded = jwt.verify(token, JWT_SECRET as string) as { id: string };
const user = await User.findById(decoded.id);

if (!user) {
return res.status(404).json({ error: 'User not found' });
}

const isAnswerCorrect = await user.compareSecurityAnswer(answer);
if (!isAnswerCorrect) {
return res.status(400).json({ error: 'Incorrect security answer' });
}

// Generate a new token for full authentication
const newToken = jwt.sign({ id: user._id, verified: true }, JWT_SECRET as string, {
expiresIn: '1h',
});

// Update the cookie with the new token
res.cookie(process.env.COOKIE_NAME as string, newToken, {
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000,
});

res.json({ verified: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
}

export default SecurityController;
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class TransactionController {
const transaction = await this.transactionService.addTransaction({ ...req.body, userId: user.id });
res.status(201).json(transaction);
} catch (err: unknown) {
console.error("Error adding transaction:", (err as Error).message); // ← add this

res.status(500).json({ error: (err as Error).message });
}
}
Expand All @@ -86,6 +88,26 @@ class TransactionController {
}
}



async scanReceipt(req: Request & { user?: JwtPayload }, res: Response) {
try {
const user = req.user;
if (!user || !user.id) {
res.status(401).json({ error: "User not authenticated or invalid token" });
return;
}

if (!req.file) {
res.status(400).json({ error: "No file uploaded" });
return;
}

const result = await this.transactionService.processReceipt(req.file.path, user.id);
res.status(200).json(result);
} catch (err: unknown) {
res.status(500).json({ error: (err as Error).message });
}}
}

export default TransactionController;
17 changes: 17 additions & 0 deletions Backend/src/BusinessLogic_Layer/controllers/vis.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,23 @@ class VisController {
}
}
//=================================================================================================
//updates

async getIncomeBySource(req: Request & { user?: JwtPayload }, res: Response): Promise<void> {
try {
const userId = req.user?.id;
if (!userId) {
res.status(401).json({ error: "Sorry Unauthorized :(" });
return;
}

//Pass query params to support month/year filter
const incomeSources = await this.visService.getIncomeBySource(userId, req.query);
res.status(200).json({ incomeSources });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}

}
export default VisController;
19 changes: 19 additions & 0 deletions Backend/src/BusinessLogic_Layer/routes/chatbot.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import express from "express";
import ChatController from "../controllers/chatbot.controllers";
import GeminiService from "../services/chatbot.services";

const router = express.Router();

// Instantiate service and controller
const geminiService = new GeminiService();
const chatController = new ChatController(geminiService);

// POST: Handle chat interaction
router.post("/", (req, res) => chatController.chatWithAI(req, res));

// Optional GET route to help testers
router.get("/", (req, res) => {
res.send("This is a POST API. Use Thunder Client or Postman to test it.");
});

export default router;
17 changes: 17 additions & 0 deletions Backend/src/BusinessLogic_Layer/routes/security.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import express from 'express';
import SecurityController from '../controllers/security.controller';
import authenticateToken from '../Middleware/AuthMiddleware';

const router = express.Router();

// Get security question
router.get('/security-question', authenticateToken, (req, res) => {
SecurityController.getSecurityQuestion(req, res);
});

// Verify security answer
router.post('/verify-security-question', authenticateToken, (req, res) => {
SecurityController.verifySecurityAnswer(req, res);
});

export default router;
7 changes: 5 additions & 2 deletions Backend/src/BusinessLogic_Layer/routes/transaction.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import express from 'express'
import TransactionController from "../controllers/transaction.controller";
import TransactionService from "../services/transaction.service";
import authenticateToken from '../Middleware/AuthMiddleware';

import multer from 'multer';
import * as path from 'path';
const upload = multer({ dest: path.join(__dirname, "../../uploads/") });
const router = express.Router();

const transactionService = new TransactionService();
Expand All @@ -21,5 +23,6 @@ router.post("/", authenticateToken, (req,res)=> transactionController.addTransac
// Route to delete a transaction by ID for the authenticated user
router.delete("/:id", authenticateToken, (req,res)=> transactionController.deleteTransaction(req,res));


// Existing transaction routes...
router.post("/scan", authenticateToken, upload.single("receipt"), (req, res) => transactionController.scanReceipt(req, res));
export default router;
2 changes: 2 additions & 0 deletions Backend/src/BusinessLogic_Layer/routes/vis.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ router.get('/spent-in-last-30-days', authenticateToken, (req, res) => visControl
//=================================================================================================
router.get('/spent-in-last-12-months', authenticateToken, (req, res) => visController.getSpentLast12Months(req, res));
//=================================================================================================
//updates
router.get('/income-by-source', authenticateToken, (req, res) => visController.getIncomeBySource(req, res));

export default router;

28 changes: 19 additions & 9 deletions Backend/src/BusinessLogic_Layer/services/Reg.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@ import jwt from 'jsonwebtoken';

class regServies {
constructor(){};
static async registerUser(name:string ,email:string ,phone:string,password:string )
{
static async registerUser(
name: string,
email: string,
phone: string,
password: string,
securityQuestion: string,
securityAnswer: string
) {
const user = await User.findOne({email});
if (user)
{
throw new Error("Email already in use ")
if (user) {
throw new Error("Email already in use");
}
const newUser = new User ({name,email,phone,password});
const newUser = new User({
name,
email,
phone,
password,
securityQuestion,
securityAnswerHash: securityAnswer // Will be hashed by the pre-save hook
});
return await newUser.save();

}


}

export default regServies;
Loading