From 1867aa33fb1c9dc1ec13a90d126400d790e061e0 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Thu, 12 Feb 2026 19:00:08 +0530 Subject: [PATCH 1/2] refactor: Large and Complex Route Files --- middleware/expenseValidator.js | 31 +++++ routes/expenseCreation.js | 64 ++++++++++ routes/expenseExport.js | 43 +++++++ routes/expenseUpdate.js | 51 ++++++++ routes/expenses.js | 221 --------------------------------- server.js | 6 + services/expenseService.js | 103 +++++++++++++++ utils/currencyUtils.js | 32 +++++ 8 files changed, 330 insertions(+), 221 deletions(-) create mode 100644 middleware/expenseValidator.js create mode 100644 routes/expenseCreation.js create mode 100644 routes/expenseExport.js create mode 100644 routes/expenseUpdate.js create mode 100644 services/expenseService.js create mode 100644 utils/currencyUtils.js diff --git a/middleware/expenseValidator.js b/middleware/expenseValidator.js new file mode 100644 index 00000000..30501768 --- /dev/null +++ b/middleware/expenseValidator.js @@ -0,0 +1,31 @@ +const Joi = require('joi'); +const currencyService = require('../services/currencyService'); + +const expenseSchema = Joi.object({ + description: Joi.string().trim().max(100).required(), + amount: Joi.number().min(0.01).required(), + currency: Joi.string().uppercase().optional(), + category: Joi.string().valid('food', 'transport', 'entertainment', 'utilities', 'healthcare', 'shopping', 'other').required(), + type: Joi.string().valid('income', 'expense').required(), + merchant: Joi.string().trim().max(50).optional(), + date: Joi.date().optional(), + workspaceId: Joi.string().hex().length(24).optional() +}); + +const expenseValidator = async (req, res, next) => { + const { error, value } = expenseSchema.validate(req.body); + if (error) return res.status(400).json({ error: error.details[0].message }); + + const user = req.user; // Assuming auth middleware sets req.user + const expenseCurrency = value.currency || user.preferredCurrency; + + if (!currencyService.isValidCurrency(expenseCurrency)) { + return res.status(400).json({ error: 'Invalid currency code' }); + } + + req.validatedExpense = value; + req.expenseCurrency = expenseCurrency; + next(); +}; + +module.exports = expenseValidator; diff --git a/routes/expenseCreation.js b/routes/expenseCreation.js new file mode 100644 index 00000000..f2120b7f --- /dev/null +++ b/routes/expenseCreation.js @@ -0,0 +1,64 @@ +const express = require('express'); +const Expense = require('../models/Expense'); +const User = require('../models/User'); +const expenseValidator = require('../middleware/expenseValidator'); +const auth = require('../middleware/auth'); +const expenseService = require('../services/expenseService'); +const { convertExpenseAmount } = require('../utils/currencyUtils'); + +const router = express.Router(); + +// POST new expense for authenticated user +router.post('/', auth, expenseValidator, async (req, res) => { + try { + const user = await User.findById(req.user._id); + const { validatedExpense: value, expenseCurrency } = req; + + // Handle auto-categorization + const { finalCategory, autoCategorized } = await expenseService.handleAutoCategorization(value, req.user._id); + + // Store original amount and currency + const expenseData = { + ...value, + category: finalCategory, + user: value.workspaceId ? req.user._id : req.user._id, + addedBy: req.user._id, + workspace: value.workspaceId || null, + originalAmount: value.amount, + originalCurrency: expenseCurrency, + amount: value.amount, + autoCategorized + }; + + // Handle currency conversion if needed + const conversionData = await expenseService.handleCurrencyConversion(value.amount, expenseCurrency, user.preferredCurrency); + Object.assign(expenseData, conversionData); + + const expense = new Expense(expenseData); + await expense.save(); + + // Handle approval submission + const { requiresApproval, workflow } = await expenseService.handleApprovalSubmission(expense, req.user._id); + + // Handle budget update + const amountForBudget = expenseData.convertedAmount || value.amount; + await expenseService.handleBudgetUpdate(req.user._id, value.type, amountForBudget, value.category); + + // Emit real-time update + const io = req.app.get('io'); + const expenseForSocket = expenseService.prepareExpenseResponse(expense, user.preferredCurrency); + expenseService.emitRealTimeUpdate(io, req.user._id, 'expense_created', expenseForSocket); + + const response = { + ...expenseService.prepareExpenseResponse(expense, user.preferredCurrency), + requiresApproval, + workflow: workflow ? { _id: workflow._id, status: workflow.status } : null + }; + + res.status(201).json(response); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/routes/expenseExport.js b/routes/expenseExport.js new file mode 100644 index 00000000..833dc831 --- /dev/null +++ b/routes/expenseExport.js @@ -0,0 +1,43 @@ +const express = require('express'); +const exportService = require('../services/exportService'); +const auth = require('../middleware/auth'); + +const router = express.Router(); + +// GET export expenses to CSV +router.get('/export', auth, async (req, res) => { + try { + const { format, startDate, endDate, category } = req.query; + + // Validate format + if (format && format !== 'csv') { + return res.status(400).json({ error: 'Only CSV format is supported' }); + } + + // Get expenses using export service + const expenses = await exportService.getExpensesForExport(req.user._id, { + startDate, + endDate, + category, + type: 'all' // Include both income and expenses + }); + + if (expenses.length === 0) { + return res.status(404).json({ error: 'No expenses found for the selected filters' }); + } + + // Generate CSV using ExportService + const csv = exportService.generateCSV(expenses); + + // Set CSV headers + res.setHeader('Content-Type', 'text/csv'); + res.setHeader('Content-Disposition', 'attachment; filename="expenses.csv"'); + + res.send(csv); + } catch (error) { + console.error('Export error:', error); + res.status(500).json({ error: 'Failed to export expenses' }); + } +}); + +module.exports = router; diff --git a/routes/expenseUpdate.js b/routes/expenseUpdate.js new file mode 100644 index 00000000..5671efd3 --- /dev/null +++ b/routes/expenseUpdate.js @@ -0,0 +1,51 @@ +const express = require('express'); +const Expense = require('../models/Expense'); +const User = require('../models/User'); +const expenseValidator = require('../middleware/expenseValidator'); +const auth = require('../middleware/auth'); +const expenseService = require('../services/expenseService'); + +const router = express.Router(); + +// PUT update expense for authenticated user +router.put('/:id', auth, expenseValidator, async (req, res) => { + try { + const user = await User.findById(req.user._id); + const { validatedExpense: value, expenseCurrency } = req; + + // Prepare update data + const updateData = { + ...value, + originalAmount: value.amount, + originalCurrency: expenseCurrency, + amount: value.amount + }; + + // Handle currency conversion if needed + const conversionData = await expenseService.handleCurrencyConversion(value.amount, expenseCurrency, user.preferredCurrency); + Object.assign(updateData, conversionData); + + const expense = await Expense.findOneAndUpdate( + { _id: req.params.id, user: req.user._id }, + updateData, + { new: true } + ); + if (!expense) return res.status(404).json({ error: 'Expense not found' }); + + // Handle budget update + await expenseService.handleBudgetUpdate(req.user._id, value.type, updateData.convertedAmount || value.amount, value.category); + + // Emit real-time update + const io = req.app.get('io'); + const expenseForSocket = expenseService.prepareExpenseResponse(expense, user.preferredCurrency); + expenseService.emitRealTimeUpdate(io, req.user._id, 'expense_updated', expenseForSocket); + + const response = expenseService.prepareExpenseResponse(expense, user.preferredCurrency); + + res.json(response); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/routes/expenses.js b/routes/expenses.js index 2770868d..b1050e4d 100644 --- a/routes/expenses.js +++ b/routes/expenses.js @@ -86,230 +86,9 @@ router.get('/', auth, async (req, res) => { } }); -// POST new expense for authenticated user -router.post('/', auth, async (req, res) => { - try { - const { error, value } = expenseSchema.validate(req.body); - if (error) return res.status(400).json({ error: error.details[0].message }); - - const user = await User.findById(req.user._id); - const expenseCurrency = value.currency || user.preferredCurrency; - - // Validate currency - if (!currencyService.isValidCurrency(expenseCurrency)) { - return res.status(400).json({ error: 'Invalid currency code' }); - } - - // Auto-categorize if category not provided or is 'other' - let finalCategory = value.category; - let autoCategorized = false; - let categorySuggestions = []; - - if (!value.category || value.category === 'other') { - try { - categorySuggestions = await categorizationService.suggestCategory( - req.user._id, - value.description, - value.amount - ); - - if (categorySuggestions.length > 0) { - finalCategory = categorySuggestions[0].category; - autoCategorized = true; - - // Save training data for future ML improvement - const CategoryTraining = require('../models/CategoryTraining'); - await CategoryTraining.create({ - user: req.user._id, - description: value.description, - amount: value.amount, - category: finalCategory, - merchant: value.merchant, - source: 'auto_categorized' - }); - } - } catch (categorizationError) { - console.error('Auto-categorization failed:', categorizationError); - // Continue with original category or 'other' - } - } - - // Store original amount and currency - const expenseData = { - ...value, - category: finalCategory, - user: value.workspaceId ? req.user._id : req.user._id, // User still relevant for reporting - addedBy: req.user._id, - workspace: value.workspaceId || null, - originalAmount: value.amount, - originalCurrency: expenseCurrency, - amount: value.amount, // Keep original as primary amount - autoCategorized - }; - - // If expense currency differs from user preference, add conversion info - if (expenseCurrency !== user.preferredCurrency) { - try { - const conversion = await currencyService.convertCurrency( - value.amount, - expenseCurrency, - user.preferredCurrency - ); - expenseData.convertedAmount = conversion.convertedAmount; - expenseData.convertedCurrency = user.preferredCurrency; - expenseData.exchangeRate = conversion.exchangeRate; - } catch (conversionError) { - console.error('Currency conversion failed:', conversionError.message); - // Continue without conversion data - } - } - - const expense = new Expense(expenseData); - await expense.save(); - - // Check if expense requires approval - const approvalService = require('../services/approvalService'); - let requiresApproval = false; - let workflow = null; - - if (expenseData.workspace) { - requiresApproval = await approvalService.requiresApproval(expenseData, expenseData.workspace); - } - - if (requiresApproval) { - try { - workflow = await approvalService.submitForApproval(expense._id, req.user._id); - expense.status = 'pending_approval'; - expense.approvalWorkflow = workflow._id; - await expense.save(); - } catch (approvalError) { - console.error('Failed to submit for approval:', approvalError.message); - // Continue with normal flow if approval submission fails - } - } - - // Update budget and goal progress using converted amount if available - const amountForBudget = expenseData.convertedAmount || value.amount; - if (value.type === 'expense') { - await budgetService.checkBudgetAlerts(req.user._id); - } - await budgetService.updateGoalProgress(req.user._id, value.type === 'expense' ? -amountForBudget : amountForBudget, value.category); - - // Emit real-time update to all user's connected devices - const io = req.app.get('io'); - - // Prepare the expense object with display amounts for socket emission - const expenseForSocket = expense.toObject(); - if (expenseCurrency !== user.preferredCurrency) { - expenseForSocket.displayAmount = expenseData.convertedAmount; - expenseForSocket.displayCurrency = user.preferredCurrency; - } else { - expenseForSocket.displayAmount = expense.amount; - expenseForSocket.displayCurrency = expenseCurrency; - } - - io.to(`user_${req.user._id}`).emit('expense_created', expenseForSocket); - - const response = { - ...expense.toObject(), - requiresApproval, - workflow: workflow ? { _id: workflow._id, status: workflow.status } : null - }; - // Add display amounts to response - if (expenseCurrency !== user.preferredCurrency) { - response.displayAmount = expenseData.convertedAmount; - response.displayCurrency = user.preferredCurrency; - } else { - response.displayAmount = expense.amount; - response.displayCurrency = expenseCurrency; - } - - res.status(201).json(response); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - -// PUT update expense for authenticated user -router.put('/:id', auth, async (req, res) => { - try { - const { error, value } = expenseSchema.validate(req.body); - if (error) return res.status(400).json({ error: error.details[0].message }); - - const user = await User.findById(req.user._id); - const expenseCurrency = value.currency || user.preferredCurrency; - - // Validate currency - if (!currencyService.isValidCurrency(expenseCurrency)) { - return res.status(400).json({ error: 'Invalid currency code' }); - } - - // Prepare update data - const updateData = { - ...value, - originalAmount: value.amount, - originalCurrency: expenseCurrency, - amount: value.amount - }; - - // If expense currency differs from user preference, add conversion info - if (expenseCurrency !== user.preferredCurrency) { - try { - const conversion = await currencyService.convertCurrency( - value.amount, - expenseCurrency, - user.preferredCurrency - ); - updateData.convertedAmount = conversion.convertedAmount; - updateData.convertedCurrency = user.preferredCurrency; - updateData.exchangeRate = conversion.exchangeRate; - } catch (conversionError) { - console.error('Currency conversion failed:', conversionError.message); - } - } - - const expense = await Expense.findOneAndUpdate( - { _id: req.params.id, user: req.user._id }, - updateData, - { new: true } - ); - if (!expense) return res.status(404).json({ error: 'Expense not found' }); - // Update budget calculations - await budgetService.checkBudgetAlerts(req.user._id); - // Emit real-time update - const io = req.app.get('io'); - - // Prepare the expense object with display amounts for socket emission - const expenseForSocket = expense.toObject(); - if (expenseCurrency !== user.preferredCurrency) { - expenseForSocket.displayAmount = updateData.convertedAmount || expense.amount; - expenseForSocket.displayCurrency = user.preferredCurrency; - } else { - expenseForSocket.displayAmount = expense.amount; - expenseForSocket.displayCurrency = expenseCurrency; - } - - io.to(`user_${req.user._id}`).emit('expense_updated', expenseForSocket); - - const response = expense.toObject(); - - // Add display amounts to response - if (expenseCurrency !== user.preferredCurrency) { - response.displayAmount = updateData.convertedAmount || expense.amount; - response.displayCurrency = user.preferredCurrency; - } else { - response.displayAmount = expense.amount; - response.displayCurrency = expenseCurrency; - } - - res.json(response); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); // DELETE expense for authenticated user router.delete('/:id', auth, async (req, res) => { diff --git a/server.js b/server.js index 7164ec8f..f8be8eb7 100644 --- a/server.js +++ b/server.js @@ -13,6 +13,9 @@ require('dotenv').config(); const authRoutes = require('./routes/auth'); const expenseRoutes = require('./routes/expenses'); +const expenseCreationRoutes = require('./routes/expenseCreation'); +const expenseUpdateRoutes = require('./routes/expenseUpdate'); +const expenseExportRoutes = require('./routes/expenseExport'); const syncRoutes = require('./routes/sync'); const splitsRoutes = require('./routes/splits'); const groupsRoutes = require('./routes/groups'); @@ -156,6 +159,9 @@ io.on('connection', (socket) => { // Routes app.use('/api/auth', require('./middleware/rateLimiter').authLimiter, authRoutes); app.use('/api/expenses', require('./middleware/rateLimiter').expenseLimiter, expenseRoutes); +app.use('/api/expenses', require('./middleware/rateLimiter').expenseLimiter, expenseCreationRoutes); +app.use('/api/expenses', require('./middleware/rateLimiter').expenseLimiter, expenseUpdateRoutes); +app.use('/api/expenses', require('./middleware/rateLimiter').expenseLimiter, expenseExportRoutes); app.use('/api/sync', syncRoutes); app.use('/api/notifications', require('./routes/notifications')); app.use('/api/receipts', require('./middleware/rateLimiter').uploadLimiter, require('./routes/receipts')); diff --git a/services/expenseService.js b/services/expenseService.js new file mode 100644 index 00000000..f1375dd8 --- /dev/null +++ b/services/expenseService.js @@ -0,0 +1,103 @@ +const Expense = require('../models/Expense'); +const User = require('../models/User'); +const CategoryTraining = require('../models/CategoryTraining'); +const categorizationService = require('./categorizationService'); +const approvalService = require('./approvalService'); +const budgetService = require('./budgetService'); +const { convertExpenseAmount, prepareExpenseWithDisplayAmounts } = require('../utils/currencyUtils'); + +const handleAutoCategorization = async (expenseData, userId) => { + let finalCategory = expenseData.category; + let autoCategorized = false; + let categorySuggestions = []; + + if (!expenseData.category || expenseData.category === 'other') { + try { + categorySuggestions = await categorizationService.suggestCategory( + userId, + expenseData.description, + expenseData.amount + ); + + if (categorySuggestions.length > 0) { + finalCategory = categorySuggestions[0].category; + autoCategorized = true; + + await CategoryTraining.create({ + user: userId, + description: expenseData.description, + amount: expenseData.amount, + category: finalCategory, + merchant: expenseData.merchant, + source: 'auto_categorized' + }); + } + } catch (error) { + console.error('Auto-categorization failed:', error); + } + } + + return { finalCategory, autoCategorized, categorySuggestions }; +}; + +const handleCurrencyConversion = async (amount, fromCurrency, toCurrency) => { + if (fromCurrency === toCurrency) { + return { convertedAmount: amount, convertedCurrency: toCurrency, exchangeRate: 1 }; + } + const conversion = await convertExpenseAmount(amount, fromCurrency, toCurrency); + if (conversion) { + return { + convertedAmount: conversion.convertedAmount, + convertedCurrency: toCurrency, + exchangeRate: conversion.exchangeRate + }; + } + return {}; +}; + +const handleApprovalSubmission = async (expense, userId) => { + let requiresApproval = false; + let workflow = null; + + if (expense.workspace) { + requiresApproval = await approvalService.requiresApproval(expense, expense.workspace); + } + + if (requiresApproval) { + try { + workflow = await approvalService.submitForApproval(expense._id, userId); + expense.status = 'pending_approval'; + expense.approvalWorkflow = workflow._id; + await expense.save(); + } catch (error) { + console.error('Failed to submit for approval:', error.message); + } + } + + return { requiresApproval, workflow }; +}; + +const handleBudgetUpdate = async (userId, type, amount, category) => { + if (type === 'expense') { + await budgetService.checkBudgetAlerts(userId); + } + await budgetService.updateGoalProgress(userId, type === 'expense' ? -amount : amount, category); +}; + +const emitRealTimeUpdate = (io, userId, event, data) => { + io.to(`user_${userId}`).emit(event, data); +}; + +const prepareExpenseResponse = (expense, userPreferredCurrency) => { + const response = prepareExpenseWithDisplayAmounts(expense, userPreferredCurrency); + return response; +}; + +module.exports = { + handleAutoCategorization, + handleCurrencyConversion, + handleApprovalSubmission, + handleBudgetUpdate, + emitRealTimeUpdate, + prepareExpenseResponse +}; diff --git a/utils/currencyUtils.js b/utils/currencyUtils.js new file mode 100644 index 00000000..9c0caca4 --- /dev/null +++ b/utils/currencyUtils.js @@ -0,0 +1,32 @@ +const currencyService = require('../services/currencyService'); + +const convertExpenseAmount = async (amount, fromCurrency, toCurrency) => { + if (fromCurrency === toCurrency) { + return { convertedAmount: amount, exchangeRate: 1 }; + } + try { + const conversion = await currencyService.convertCurrency(amount, fromCurrency, toCurrency); + return { convertedAmount: conversion.convertedAmount, exchangeRate: conversion.exchangeRate }; + } catch (error) { + console.error('Currency conversion failed:', error.message); + return null; // Or throw error, but for now return null to handle gracefully + } +}; + +const prepareExpenseWithDisplayAmounts = (expense, userPreferredCurrency) => { + const expenseObj = expense.toObject ? expense.toObject() : expense; + if (expenseObj.originalCurrency !== userPreferredCurrency) { + // Assuming convertedAmount is already set if conversion happened + expenseObj.displayAmount = expenseObj.convertedAmount || expenseObj.amount; + expenseObj.displayCurrency = userPreferredCurrency; + } else { + expenseObj.displayAmount = expenseObj.amount; + expenseObj.displayCurrency = expenseObj.originalCurrency; + } + return expenseObj; +}; + +module.exports = { + convertExpenseAmount, + prepareExpenseWithDisplayAmounts +}; From ecf7f203a343df4d6ba700bb01e66b6faf3f8321 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Thu, 12 Feb 2026 19:25:25 +0530 Subject: [PATCH 2/2] Refactor: Stub or Incomplete Service Implementations --- services/budgetService.js | 143 ++++++++++++++++++++++++++++-- services/categorizationService.js | 7 +- utils/logger.js | 30 +++++++ 3 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 utils/logger.js diff --git a/services/budgetService.js b/services/budgetService.js index 0ba2f2b9..d87a390f 100644 --- a/services/budgetService.js +++ b/services/budgetService.js @@ -1,14 +1,145 @@ -// Simple budget service +// Budget service with real implementation +const Budget = require('../models/Budget'); +const Goal = require('../models/Goal'); +const Expense = require('../models/Expense'); +const logger = require('../utils/logger'); + class BudgetService { async checkBudgetAlerts(userId) { - console.log(`Budget alerts checked for user ${userId}`); - return { alerts: [] }; + try { + logger.info(`Checking budget alerts for user ${userId}`); + + // Get all active budgets for the user + const budgets = await Budget.find({ + user: userId, + isActive: true, + endDate: { $gte: new Date() } + }); + + const alerts = []; + + for (const budget of budgets) { + // Calculate spent amount for this budget period + const spent = await this._calculateSpentAmount(userId, budget); + + // Update the spent field in the budget + budget.spent = spent; + budget.lastCalculated = new Date(); + await budget.save(); + + // Check if alert threshold is exceeded + const spentPercentage = (spent / budget.amount) * 100; + if (spentPercentage >= budget.alertThreshold) { + alerts.push({ + budgetId: budget._id, + name: budget.name, + category: budget.category, + spent: spent, + budgetAmount: budget.amount, + percentage: Math.round(spentPercentage), + threshold: budget.alertThreshold, + remaining: Math.max(0, budget.amount - spent), + period: budget.period + }); + } + } + + logger.info(`Found ${alerts.length} budget alerts for user ${userId}`); + return { alerts }; + } catch (error) { + logger.error(`Error checking budget alerts for user ${userId}:`, error); + throw error; + } } async updateGoalProgress(userId, amount, category) { - console.log(`Goal progress updated for user ${userId}: ${amount} in ${category}`); - return { success: true }; + try { + logger.info(`Updating goal progress for user ${userId}: ${amount} in ${category}`); + + // Find active goals that match the category or are general + const goals = await Goal.find({ + user: userId, + status: 'active', + isActive: true, + $or: [ + { category: category }, + { category: 'general' } + ] + }); + + const updates = []; + + for (const goal of goals) { + // Update current amount + const previousAmount = goal.currentAmount; + goal.currentAmount += amount; + + // Check milestones + const progressPercentage = (goal.currentAmount / goal.targetAmount) * 100; + const achievedMilestones = []; + + for (const milestone of goal.milestones) { + if (!milestone.achieved && progressPercentage >= milestone.percentage) { + milestone.achieved = true; + milestone.achievedDate = new Date(); + achievedMilestones.push(milestone); + } + } + + // Check if goal is completed + if (goal.currentAmount >= goal.targetAmount && goal.status === 'active') { + goal.status = 'completed'; + } + + await goal.save(); + + updates.push({ + goalId: goal._id, + title: goal.title, + previousAmount: previousAmount, + newAmount: goal.currentAmount, + targetAmount: goal.targetAmount, + progress: Math.round(progressPercentage), + achievedMilestones: achievedMilestones.length, + status: goal.status + }); + } + + logger.info(`Updated ${updates.length} goals for user ${userId}`); + return { success: true, updates }; + } catch (error) { + logger.error(`Error updating goal progress for user ${userId}:`, error); + throw error; + } + } + + // Helper method to calculate spent amount for a budget + async _calculateSpentAmount(userId, budget) { + try { + const matchConditions = { + user: userId, + date: { + $gte: budget.startDate, + $lte: budget.endDate + } + }; + + // Add category filter if not 'all' + if (budget.category !== 'all') { + matchConditions.category = budget.category; + } + + const result = await Expense.aggregate([ + { $match: matchConditions }, + { $group: { _id: null, total: { $sum: '$amount' } } } + ]); + + return result.length > 0 ? result[0].total : 0; + } catch (error) { + logger.error(`Error calculating spent amount for budget ${budget._id}:`, error); + return 0; + } } } -module.exports = new BudgetService(); \ No newline at end of file +module.exports = new BudgetService(); diff --git a/services/categorizationService.js b/services/categorizationService.js index 72cb878f..f92fe027 100644 --- a/services/categorizationService.js +++ b/services/categorizationService.js @@ -2,6 +2,7 @@ const tf = require('@tensorflow/tfjs'); const CategoryPattern = require('../models/CategoryPattern'); const CategoryTraining = require('../models/CategoryTraining'); const CategoryModel = require('../models/CategoryModel'); +const logger = require('../utils/logger'); class CategorizationService { constructor() { @@ -81,11 +82,11 @@ class CategorizationService { const trainingData = await CategoryTraining.getTrainingData(userId, 5000); if (trainingData.length < 10) { - console.log(`Not enough training data for user ${userId}`); + logger.warn(`Not enough training data for user ${userId}`); return false; } - console.log(`Training TensorFlow model for user ${userId} with ${trainingData.length} samples`); + logger.info(`Training TensorFlow model for user ${userId} with ${trainingData.length} samples`); // Prepare training data const inputs = []; @@ -160,7 +161,7 @@ class CategorizationService { xs.dispose(); ys.dispose(); - console.log(`TensorFlow model trained successfully for user ${userId} with ${finalAccuracy.toFixed(4)} accuracy`); + logger.info(`TensorFlow model trained successfully for user ${userId} with ${finalAccuracy.toFixed(4)} accuracy`); return true; } catch (error) { console.error('Error training TensorFlow model:', error); diff --git a/utils/logger.js b/utils/logger.js new file mode 100644 index 00000000..9ff034fd --- /dev/null +++ b/utils/logger.js @@ -0,0 +1,30 @@ +const winston = require('winston'); + +// Create logger instance +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { service: 'expense-flow' }, + transports: [ + // Write all logs with importance level of `error` or less to `error.log` + new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), + // Write all logs with importance level of `info` or less to `combined.log` + new winston.transports.File({ filename: 'logs/combined.log' }), + ], +}); + +// If we're not in production then log to the console with a simple format +if (process.env.NODE_ENV !== 'production') { + logger.add(new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ) + })); +} + +module.exports = logger;