- Allow members to create todos, companies, projects - Auto-assign creator to resources (companyUsers, projectUsers, todoUsers) - Add public /api/users endpoint for all authenticated users - Make phone field optional in personal contacts (schema + validation) - Update todo routes to use checkTodoAccess for updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
4.4 KiB
JavaScript
142 lines
4.4 KiB
JavaScript
import express from 'express';
|
|
import morgan from 'morgan';
|
|
import helmet from 'helmet';
|
|
import cors from 'cors';
|
|
import cookieParser from 'cookie-parser';
|
|
import dotenv from 'dotenv';
|
|
dotenv.config();
|
|
|
|
import { validateBody } from './middlewares/global/validateBody.js';
|
|
import { notFound } from './middlewares/global/notFound.js';
|
|
import { errorHandler } from './middlewares/global/errorHandler.js';
|
|
import { apiRateLimiter } from './middlewares/security/rateLimiter.js';
|
|
import { logger } from './utils/logger.js';
|
|
|
|
// Import routes
|
|
import authRoutes from './routes/auth.routes.js';
|
|
import adminRoutes from './routes/admin.routes.js';
|
|
import contactRoutes from './routes/contact.routes.js';
|
|
import personalContactRoutes from './routes/personal-contact.routes.js';
|
|
import crmEmailRoutes from './routes/crm-email.routes.js';
|
|
import emailAccountRoutes from './routes/email-account.routes.js';
|
|
import timesheetRoutes from './routes/timesheet.routes.js';
|
|
import companyRoutes from './routes/company.routes.js';
|
|
import projectRoutes from './routes/project.routes.js';
|
|
import todoRoutes from './routes/todo.routes.js';
|
|
import timeTrackingRoutes from './routes/time-tracking.routes.js';
|
|
import noteRoutes from './routes/note.routes.js';
|
|
import auditRoutes from './routes/audit.routes.js';
|
|
import eventRoutes from './routes/event.routes.js';
|
|
import messageRoutes from './routes/message.routes.js';
|
|
import userRoutes from './routes/user.routes.js';
|
|
|
|
const app = express();
|
|
|
|
// HTTP request logging - only errors by default (LOG_LEVEL=debug shows all)
|
|
app.use(morgan((tokens, req, res) => {
|
|
const status = parseInt(tokens.status(req, res)) || 0;
|
|
const message = `${tokens.method(req, res)} ${tokens.url(req, res)} ${status} ${tokens['response-time'](req, res)} ms`;
|
|
logger.http(message, status);
|
|
return null; // Don't write to stdout, logger handles it
|
|
}));
|
|
app.use(
|
|
helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
},
|
|
},
|
|
hsts: {
|
|
maxAge: 31536000,
|
|
includeSubDomains: true,
|
|
preload: true,
|
|
},
|
|
})
|
|
);
|
|
|
|
// CORS configuration - allow local network access
|
|
const corsOptions = {
|
|
origin: (origin, callback) => {
|
|
// Allow requests with no origin (mobile apps, curl, etc.)
|
|
if (!origin) return callback(null, true);
|
|
|
|
// Allow localhost and local network IPs
|
|
const allowedPatterns = [
|
|
/^http:\/\/localhost(:\d+)?$/,
|
|
/^http:\/\/127\.0\.0\.1(:\d+)?$/,
|
|
/^http:\/\/192\.168\.\d{1,3}\.\d{1,3}(:\d+)?$/,
|
|
/^http:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/,
|
|
/^http:\/\/172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}(:\d+)?$/,
|
|
];
|
|
|
|
// Check if origin matches allowed patterns or CORS_ORIGIN env
|
|
const corsOrigin = process.env.CORS_ORIGIN;
|
|
if (corsOrigin && origin === corsOrigin) {
|
|
return callback(null, true);
|
|
}
|
|
|
|
if (allowedPatterns.some(pattern => pattern.test(origin))) {
|
|
return callback(null, true);
|
|
}
|
|
|
|
callback(new Error('Not allowed by CORS'));
|
|
},
|
|
credentials: true,
|
|
optionsSuccessStatus: 200,
|
|
};
|
|
app.use(cors(corsOptions));
|
|
|
|
// Body parsing middleware
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
app.use(cookieParser());
|
|
|
|
// Custom body validation middleware
|
|
app.use(validateBody);
|
|
|
|
// Rate limiting for all API routes
|
|
app.use('/api', apiRateLimiter);
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (req, res) => {
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'CRM API is running',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
});
|
|
|
|
// API Routes
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/admin', adminRoutes);
|
|
app.use('/api/contacts', contactRoutes);
|
|
app.use('/api/personal-contacts', personalContactRoutes);
|
|
app.use('/api/emails', crmEmailRoutes);
|
|
app.use('/api/email-accounts', emailAccountRoutes);
|
|
app.use('/api/timesheets', timesheetRoutes);
|
|
app.use('/api/companies', companyRoutes);
|
|
app.use('/api/projects', projectRoutes);
|
|
app.use('/api/todos', todoRoutes);
|
|
app.use('/api/time-tracking', timeTrackingRoutes);
|
|
app.use('/api/notes', noteRoutes);
|
|
app.use('/api/audit-logs', auditRoutes);
|
|
app.use('/api/events', eventRoutes);
|
|
app.use('/api/messages', messageRoutes);
|
|
app.use('/api/users', userRoutes);
|
|
|
|
// Basic route
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
message: 'CRM API Server',
|
|
version: '1.0.0',
|
|
});
|
|
});
|
|
|
|
// Global Middlewares (must be last)
|
|
app.use(notFound);
|
|
app.use(errorHandler);
|
|
|
|
export default app;
|