Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10,055 changes: 10,055 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

43 changes: 27 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,55 @@
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"start:api": "node dist/index.js",
"dev": "ts-node src/index.ts",
"dev:api": "ts-node src/index.ts",
"test": "jest",
"lint": "eslint src/**/*.ts",
"start:business": "node dist/servers/business-operations/server.js",
"start:email": "node dist/servers/email-management/server.js",
"start:accounts": "node dist/servers/account-management/server.js"
},
"keywords": ["mcp", "claude", "whatsapp", "business", "booking", "payments"],
"keywords": [
"mcp",
"claude",
"whatsapp",
"business",
"booking",
"payments"
],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.27.0",
"@modelcontextprotocol/sdk": "^1.0.0",
"express": "^4.18.2",
"whatsapp-web.js": "^1.23.0",
"qrcode-terminal": "^0.12.0",
"nodemailer": "^6.9.7",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"helmet": "^7.1.0",
"rate-limiter-flexible": "^5.0.3",
"winston": "^3.11.0",
"joi": "^17.11.0",
"jsonwebtoken": "^9.0.2",
"moment": "^2.29.4",
"mongoose": "^8.0.0",
"nodemailer": "^6.9.7",
"qrcode-terminal": "^0.12.0",
"rate-limiter-flexible": "^5.0.3",
"stripe": "^14.5.0",
"uuid": "^9.0.1",
"moment": "^2.29.4"
"whatsapp-web.js": "^1.23.0",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/node": "^20.8.0",
"@types/express": "^4.17.20",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.5",
"@types/cors": "^2.8.15",
"@types/uuid": "^9.0.6",
"@types/express": "^4.17.20",
"@types/jest": "^29.5.7",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.8.0",
"@types/nodemailer": "^7.0.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/uuid": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"eslint": "^8.52.0",
Expand All @@ -52,4 +63,4 @@
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
}
77 changes: 77 additions & 0 deletions src/api/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { UserModel } from '../../shared/database/models';
import logger from '../../shared/utils/logger';

interface AuthenticatedRequest extends Request {
user?: any;
}

export const authMiddleware = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');

if (!token) {
res.status(401).json({
success: false,
error: 'Access denied. No token provided.'
});
return;
}

const secret = process.env.JWT_SECRET || 'fallback-secret';
const decoded = jwt.verify(token, secret) as any;
const user = await UserModel.findOne({ id: decoded.userId }).select('-password');

if (!user) {
res.status(401).json({
success: false,
error: 'Invalid token. User not found.'
});
return;
}

if (user.status !== 'active') {
res.status(401).json({
success: false,
error: 'Account is not active.'
});
return;
}

req.user = user;
next();
} catch (error) {
logger.error('Auth middleware error:', error);
res.status(401).json({
success: false,
error: 'Invalid token.'
});
}
};

export const requireRole = (roles: string[]) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
if (!req.user) {
res.status(401).json({
success: false,
error: 'Authentication required.'
});
return;
}

if (!roles.includes(req.user.role)) {
res.status(403).json({
success: false,
error: 'Insufficient permissions.'
});
return;
}

next();
};
};
52 changes: 52 additions & 0 deletions src/api/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Request, Response, NextFunction } from 'express';
import logger from '../../shared/utils/logger';

export const errorHandler = (
error: any,
req: Request,
res: Response,
next: NextFunction
): void => {
logger.error('Error occurred:', {
error: error.message,
stack: error.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});

// Default error
let statusCode = 500;
let message = 'Internal server error';

// Handle specific error types
if (error.name === 'ValidationError') {
statusCode = 400;
message = 'Validation failed';
} else if (error.name === 'CastError') {
statusCode = 400;
message = 'Invalid data format';
} else if (error.code === 11000) {
statusCode = 409;
message = 'Duplicate entry';
} else if (error.name === 'JsonWebTokenError') {
statusCode = 401;
message = 'Invalid token';
} else if (error.name === 'TokenExpiredError') {
statusCode = 401;
message = 'Token expired';
} else if (error.statusCode) {
statusCode = error.statusCode;
message = error.message;
}

res.status(statusCode).json({
success: false,
error: message,
...(process.env.NODE_ENV === 'development' && {
stack: error.stack,
details: error
})
});
};
104 changes: 104 additions & 0 deletions src/api/routes/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Router, Request, Response } from 'express';
import { BusinessOperationsService } from '../../servers/business-operations/business-service';
import { requireRole } from '../middleware/auth';
import logger from '../../shared/utils/logger';

const router = Router();
const businessService = new BusinessOperationsService();

// Get sales report
router.get('/sales', requireRole(['admin', 'manager']), async (req: Request, res: Response) => {
try {
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : new Date();

const salesReport = await businessService.getSalesReport(startDate, endDate);

res.json({
success: true,
data: salesReport
});
} catch (error) {
logger.error('Get sales report error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate sales report'
});
}
});

// Get booking analytics
router.get('/bookings', requireRole(['admin', 'manager']), async (req: Request, res: Response) => {
try {
const period = req.query.period as string || 'month';
const analytics = await businessService.getBookingAnalytics(period);

res.json({
success: true,
data: analytics
});
} catch (error) {
logger.error('Get booking analytics error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate booking analytics'
});
}
});

// Get revenue analytics
router.get('/revenue', requireRole(['admin', 'manager']), async (req: Request, res: Response) => {
try {
const period = req.query.period as string || 'month';
const revenue = await businessService.getRevenueAnalytics(period);

res.json({
success: true,
data: revenue
});
} catch (error) {
logger.error('Get revenue analytics error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate revenue analytics'
});
}
});

// Get customer analytics
router.get('/customers', requireRole(['admin', 'manager']), async (req: Request, res: Response) => {
try {
const analytics = await businessService.getCustomerAnalytics();

res.json({
success: true,
data: analytics
});
} catch (error) {
logger.error('Get customer analytics error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate customer analytics'
});
}
});

// Get dashboard summary
router.get('/dashboard', requireRole(['admin', 'manager', 'staff']), async (req: Request, res: Response) => {
try {
const summary = await businessService.getDashboardSummary();

res.json({
success: true,
data: summary
});
} catch (error) {
logger.error('Get dashboard summary error:', error);
res.status(500).json({
success: false,
error: 'Failed to generate dashboard summary'
});
}
});

export { router as analyticsRoutes };
Loading