1773 lines
48 KiB
Markdown
1773 lines
48 KiB
Markdown
# RE Workflow Management System - Backend Setup Guide
|
|
**Version:** 1.0
|
|
**Date:** October 16, 2025
|
|
**Technology Stack:** Node.js 22 LTS + TypeScript 5.7 + Express.js 4.21 + PostgreSQL 16
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
1. [Backend Architecture Overview](#backend-architecture-overview)
|
|
2. [Technology Stack](#technology-stack)
|
|
3. [Project Folder Structure](#project-folder-structure)
|
|
4. [Database Schema Design](#database-schema-design)
|
|
5. [API Endpoints Structure](#api-endpoints-structure)
|
|
6. [Authentication & Security](#authentication-security)
|
|
7. [Configuration Management](#configuration-management)
|
|
8. [Deployment Architecture](#deployment-architecture)
|
|
9. [Development Setup Instructions](#development-setup-instructions)
|
|
|
|
---
|
|
|
|
## 1. Backend Architecture Overview
|
|
|
|
### High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ APPLICATION LAYER (Node.js) │
|
|
│ - Express.js REST API Server │
|
|
│ - Business Logic & Controllers │
|
|
│ - Service Layer (Workflow, Notification, Document) │
|
|
│ - AI Integration (Conclusion Generation) │
|
|
│ - Authentication Middleware (JWT + SSO) │
|
|
│ - Validation Layer (Zod) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↕
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ DATA LAYER (PostgreSQL) │
|
|
│ - Relational Database │
|
|
│ - Stored Procedures & Functions │
|
|
│ - Triggers for Activity Logging │
|
|
│ - Indexes for Performance │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↕
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ STORAGE LAYER │
|
|
│ - Cloud Storage (GCP Cloud Storage) │
|
|
│ - Document Repository │
|
|
│ - Backup & Recovery System │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↕
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ EXTERNAL SERVICES │
|
|
│ - SSO Authentication Provider │
|
|
│ - Email Service (SMTP) │
|
|
│ - AI Service (OpenAI) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Technology Stack
|
|
|
|
| Component | Technology | Version | Purpose |
|
|
|-----------|-----------|---------|---------|
|
|
| **Backend Runtime** | Node.js | 22.x LTS | Server-side JavaScript runtime |
|
|
| **Backend Language** | TypeScript | 5.7.x | Type-safe backend development |
|
|
| **Backend Framework** | Express.js | 4.21.x | RESTful API framework |
|
|
| **Database** | PostgreSQL | 16.x | Primary relational database |
|
|
| **ORM** | Sequelize + TypeScript | 6.37.x | Database ORM with TypeScript support |
|
|
| **Authentication** | Passport.js | 0.7.x | SSO integration middleware |
|
|
| **Validation** | Zod | 3.24.x | TypeScript-first schema validation |
|
|
| **File Upload** | Multer | 1.4.x | Multipart file handling |
|
|
| **Logging** | Winston | 3.17.x | Application logging |
|
|
| **API Documentation** | Swagger/OpenAPI | 3.1 | API documentation |
|
|
| **Testing** | Jest + Supertest + ts-jest | 29.x | Unit & integration testing |
|
|
| **Process Manager** | PM2 | 5.4.x | Production process management |
|
|
| **Code Quality** | ESLint + Prettier + TypeScript ESLint | 9.x/3.x | Code linting & formatting |
|
|
|
|
---
|
|
|
|
## 3. Project Folder Structure
|
|
|
|
### Backend Repository (`re-workflow-backend`)
|
|
|
|
```
|
|
Re_Backend/
|
|
│
|
|
├── src/ # Source code
|
|
│ ├── app.ts
|
|
│ ├── server.ts
|
|
│ ├── types/ # TypeScript type definitions
|
|
│ │ ├── index.ts
|
|
│ │ ├── express.d.ts
|
|
│ │ ├── user.types.ts
|
|
│ │ ├── workflow.types.ts
|
|
│ │ ├── approval.types.ts
|
|
│ │ ├── document.types.ts
|
|
│ │ ├── notification.types.ts
|
|
│ │ └── common.types.ts
|
|
│ │
|
|
│ ├── config/ # Configuration files
|
|
│ │ ├── database.ts
|
|
│ │ ├── jwt.ts
|
|
│ │ ├── sso.ts
|
|
│ │ ├── storage.ts
|
|
│ │ ├── email.ts
|
|
│ │ └── constants.ts
|
|
│ │
|
|
│ ├── controllers/ # Request handlers
|
|
│ │ ├── auth.controller.ts
|
|
│ │ ├── workflow.controller.ts
|
|
│ │ ├── approval.controller.ts
|
|
│ │ ├── document.controller.ts
|
|
│ │ ├── notification.controller.ts
|
|
│ │ ├── workNote.controller.ts
|
|
│ │ ├── participant.controller.ts
|
|
│ │ ├── dashboard.controller.ts
|
|
│ │ └── user.controller.ts
|
|
│ │
|
|
│ ├── services/ # Business logic layer
|
|
│ │ ├── auth.service.ts
|
|
│ │ ├── workflow.service.ts
|
|
│ │ ├── approval.service.ts
|
|
│ │ ├── tat.service.ts
|
|
│ │ ├── notification.service.ts
|
|
│ │ ├── document.service.ts
|
|
│ │ ├── workNote.service.ts
|
|
│ │ ├── participant.service.ts
|
|
│ │ ├── activity.service.ts
|
|
│ │ ├── ai.service.ts
|
|
│ │ ├── email.service.ts
|
|
│ │ └── storage.service.ts
|
|
│ │
|
|
│ ├── models/ # Sequelize models (ORM)
|
|
│ │ ├── index.ts
|
|
│ │ ├── User.ts
|
|
│ │ ├── WorkflowRequest.ts
|
|
│ │ ├── ApprovalLevel.ts
|
|
│ │ ├── Approver.ts
|
|
│ │ ├── Participant.ts
|
|
│ │ ├── Spectator.ts
|
|
│ │ ├── Document.ts
|
|
│ │ ├── WorkNote.ts
|
|
│ │ ├── Activity.ts
|
|
│ │ ├── Notification.ts
|
|
│ │ ├── TATTracking.ts
|
|
│ │ └── ConclusionRemark.ts
|
|
│ │
|
|
│ ├── routes/ # API route definitions
|
|
│ │ ├── index.ts
|
|
│ │ ├── auth.routes.ts
|
|
│ │ ├── workflow.routes.ts
|
|
│ │ ├── approval.routes.ts
|
|
│ │ ├── document.routes.ts
|
|
│ │ ├── notification.routes.ts
|
|
│ │ ├── workNote.routes.ts
|
|
│ │ ├── participant.routes.ts
|
|
│ │ ├── dashboard.routes.ts
|
|
│ │ └── user.routes.ts
|
|
│ │
|
|
│ ├── middlewares/ # Express middlewares
|
|
│ │ ├── auth.middleware.ts
|
|
│ │ ├── sso.middleware.ts
|
|
│ │ ├── validate.middleware.ts
|
|
│ │ ├── upload.middleware.ts
|
|
│ │ ├── rateLimiter.middleware.ts
|
|
│ │ ├── errorHandler.middleware.ts
|
|
│ │ ├── logger.middleware.ts
|
|
│ │ └── cors.middleware.ts
|
|
│ │
|
|
│ ├── validators/ # Request validation schemas (Zod)
|
|
│ │ ├── auth.validator.ts
|
|
│ │ ├── workflow.validator.ts
|
|
│ │ ├── approval.validator.ts
|
|
│ │ ├── document.validator.ts
|
|
│ │ ├── workNote.validator.ts
|
|
│ │ └── participant.validator.ts
|
|
│ │
|
|
│ ├── utils/ # Utility functions
|
|
│ │ ├── logger.ts
|
|
│ │ ├── responseHandler.ts
|
|
│ │ ├── errorHandler.ts
|
|
│ │ ├── dateCalculator.ts
|
|
│ │ ├── fileValidator.ts
|
|
│ │ ├── emailTemplate.ts
|
|
│ │ └── helpers.ts
|
|
│ │
|
|
│ ├── jobs/ # Background jobs & schedulers
|
|
│ │ ├── tatMonitor.job.ts
|
|
│ │ ├── reminderSender.job.ts
|
|
│ │ ├── dataCleanup.job.ts
|
|
│ │ └── reportGenerator.job.ts
|
|
│ │
|
|
│ └── seeders/ # Database seed data
|
|
│ ├── admin.seeder.ts
|
|
│ └── testData.seeder.ts
|
|
│
|
|
├── dist/ # Compiled JavaScript output
|
|
│
|
|
├── tests/ # Test suites
|
|
│ ├── unit/
|
|
│ │ ├── services/
|
|
│ │ ├── controllers/
|
|
│ │ └── utils/
|
|
│ ├── integration/
|
|
│ │ ├── api/
|
|
│ │ └── database/
|
|
│ └── setup.js
|
|
│
|
|
├── database/ # Database Scripts & Migrations
|
|
│ ├── migrations/
|
|
│ ├── seeders/
|
|
│ ├── schema/
|
|
│ │ └── schema.sql
|
|
│ └── README.md
|
|
│
|
|
├── logs/ # Application logs
|
|
│ ├── error.log
|
|
│ ├── combined.log
|
|
│ └── access.log
|
|
│
|
|
├── uploads/ # Temporary upload directory
|
|
│ └── .gitkeep
|
|
│
|
|
├── docs/ # API Documentation
|
|
│ ├── swagger/
|
|
│ └── postman/
|
|
│
|
|
├── scripts/ # Deployment & Utility Scripts
|
|
│ ├── deploy.sh
|
|
│ ├── backup.sh
|
|
│ ├── setup.sh
|
|
│ └── seed-db.sh
|
|
│
|
|
├── .env.example
|
|
├── .env.development
|
|
├── .env.production
|
|
├── .eslintrc.json
|
|
├── .prettierrc
|
|
├── .gitignore
|
|
├── .dockerignore
|
|
├── Dockerfile
|
|
├── docker-compose.yml
|
|
├── jest.config.js
|
|
├── tsconfig.json
|
|
├── nodemon.json
|
|
├── package.json
|
|
├── package-lock.json
|
|
└── README.md
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Key Backend Files
|
|
|
|
### 4.1 TypeScript Configuration
|
|
|
|
#### `tsconfig.json`
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2021",
|
|
"module": "commonjs",
|
|
"lib": ["ES2021"],
|
|
"outDir": "./dist",
|
|
"rootDir": "./src",
|
|
"strict": true,
|
|
"esModuleInterop": true,
|
|
"skipLibCheck": true,
|
|
"forceConsistentCasingInFileNames": true,
|
|
"resolveJsonModule": true,
|
|
"moduleResolution": "node",
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true,
|
|
"noImplicitAny": true,
|
|
"strictNullChecks": true,
|
|
"strictFunctionTypes": true,
|
|
"noUnusedLocals": true,
|
|
"noUnusedParameters": true,
|
|
"noImplicitReturns": true,
|
|
"noFallthroughCasesInSwitch": true,
|
|
"types": ["node", "jest"],
|
|
"typeRoots": ["./node_modules/@types", "./src/types"],
|
|
"baseUrl": "./src",
|
|
"paths": {
|
|
"@/*": ["./*"],
|
|
"@controllers/*": ["controllers/*"],
|
|
"@services/*": ["services/*"],
|
|
"@models/*": ["models/*"],
|
|
"@middlewares/*": ["middlewares/*"],
|
|
"@utils/*": ["utils/*"],
|
|
"@types/*": ["types/*"],
|
|
"@config/*": ["config/*"]
|
|
}
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["node_modules", "dist", "tests"]
|
|
}
|
|
```
|
|
|
|
### 4.2 Express Application
|
|
|
|
#### `src/app.ts`
|
|
```typescript
|
|
import express, { Application, Request, Response } from 'express';
|
|
import helmet from 'helmet';
|
|
import cors from 'cors';
|
|
import morgan from 'morgan';
|
|
import routes from './routes';
|
|
import errorHandler from './middlewares/errorHandler.middleware';
|
|
import logger from './utils/logger';
|
|
|
|
const app: Application = express();
|
|
|
|
// Security middleware
|
|
app.use(helmet());
|
|
app.use(cors());
|
|
|
|
// Body parsing middleware
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// Logging middleware
|
|
app.use(morgan('combined', { stream: logger.stream }));
|
|
|
|
// API routes
|
|
app.use('/api/v1', routes);
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (req: Request, res: Response) => {
|
|
res.status(200).json({ status: 'OK', timestamp: new Date() });
|
|
});
|
|
|
|
// Error handling middleware (must be last)
|
|
app.use(errorHandler);
|
|
|
|
export default app;
|
|
```
|
|
|
|
### 4.3 Server Entry Point
|
|
|
|
#### `src/server.ts`
|
|
```typescript
|
|
import app from './app';
|
|
import { sequelize } from './models';
|
|
import logger from './utils/logger';
|
|
|
|
const PORT: number = parseInt(process.env.PORT || '5000', 10);
|
|
|
|
// Database connection and server start
|
|
const startServer = async (): Promise<void> => {
|
|
try {
|
|
// Test database connection
|
|
await sequelize.authenticate();
|
|
logger.info('Database connection established successfully');
|
|
|
|
// Sync models (in development only)
|
|
if (process.env.NODE_ENV === 'development') {
|
|
await sequelize.sync({ alter: true });
|
|
logger.info('Database models synchronized');
|
|
}
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
logger.info(`Server running on port ${PORT}`);
|
|
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
|
});
|
|
} catch (error) {
|
|
logger.error('Unable to start server:', error);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGTERM', async () => {
|
|
logger.info('SIGTERM signal received: closing HTTP server');
|
|
await sequelize.close();
|
|
process.exit(0);
|
|
});
|
|
|
|
startServer();
|
|
```
|
|
|
|
### 4.4 User Model Implementation
|
|
|
|
#### `src/models/User.ts`
|
|
```typescript
|
|
import { DataTypes, Model, Optional } from 'sequelize';
|
|
import { sequelize } from './index';
|
|
|
|
interface UserAttributes {
|
|
userId: string;
|
|
employeeId: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
displayName: string;
|
|
department?: string;
|
|
designation?: string;
|
|
phone?: string;
|
|
reportingManagerId?: string;
|
|
isActive: boolean;
|
|
isAdmin: boolean;
|
|
lastLogin?: Date;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
interface UserCreationAttributes extends Optional<UserAttributes, 'userId' | 'department' | 'designation' | 'phone' | 'reportingManagerId' | 'lastLogin' | 'createdAt' | 'updatedAt'> {}
|
|
|
|
class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
|
|
public userId!: string;
|
|
public employeeId!: string;
|
|
public email!: string;
|
|
public firstName!: string;
|
|
public lastName!: string;
|
|
public displayName!: string;
|
|
public department?: string;
|
|
public designation?: string;
|
|
public phone?: string;
|
|
public reportingManagerId?: string;
|
|
public isActive!: boolean;
|
|
public isAdmin!: boolean;
|
|
public lastLogin?: Date;
|
|
public createdAt!: Date;
|
|
public updatedAt!: Date;
|
|
|
|
// Associations
|
|
public reportingManager?: User;
|
|
public subordinates?: User[];
|
|
}
|
|
|
|
User.init(
|
|
{
|
|
userId: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
employeeId: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
unique: true,
|
|
comment: 'HR System Employee ID'
|
|
},
|
|
email: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
unique: true,
|
|
validate: {
|
|
isEmail: true
|
|
}
|
|
},
|
|
firstName: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false
|
|
},
|
|
lastName: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false
|
|
},
|
|
displayName: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
comment: 'Full Name for display'
|
|
},
|
|
department: {
|
|
type: DataTypes.STRING,
|
|
allowNull: true
|
|
},
|
|
designation: {
|
|
type: DataTypes.STRING,
|
|
allowNull: true
|
|
},
|
|
phone: {
|
|
type: DataTypes.STRING,
|
|
allowNull: true
|
|
},
|
|
reportingManagerId: {
|
|
type: DataTypes.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'users',
|
|
key: 'userId'
|
|
},
|
|
comment: 'Self-reference to reporting manager'
|
|
},
|
|
isActive: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: true,
|
|
comment: 'Account status'
|
|
},
|
|
isAdmin: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false,
|
|
comment: 'Super user flag'
|
|
},
|
|
lastLogin: {
|
|
type: DataTypes.DATE,
|
|
allowNull: true
|
|
},
|
|
createdAt: {
|
|
type: DataTypes.DATE,
|
|
allowNull: false,
|
|
defaultValue: DataTypes.NOW
|
|
},
|
|
updatedAt: {
|
|
type: DataTypes.DATE,
|
|
allowNull: false,
|
|
defaultValue: DataTypes.NOW
|
|
}
|
|
},
|
|
{
|
|
sequelize,
|
|
modelName: 'User',
|
|
tableName: 'users',
|
|
timestamps: true,
|
|
indexes: [
|
|
{
|
|
unique: true,
|
|
fields: ['employeeId']
|
|
},
|
|
{
|
|
unique: true,
|
|
fields: ['email']
|
|
},
|
|
{
|
|
fields: ['reportingManagerId']
|
|
},
|
|
{
|
|
fields: ['department']
|
|
},
|
|
{
|
|
fields: ['isActive']
|
|
}
|
|
]
|
|
}
|
|
);
|
|
|
|
// Self-referencing association for reporting manager
|
|
User.belongsTo(User, {
|
|
as: 'reportingManager',
|
|
foreignKey: 'reportingManagerId',
|
|
targetKey: 'userId'
|
|
});
|
|
|
|
User.hasMany(User, {
|
|
as: 'subordinates',
|
|
foreignKey: 'reportingManagerId',
|
|
sourceKey: 'userId'
|
|
});
|
|
|
|
export { User };
|
|
```
|
|
|
|
#### Authentication Middleware
|
|
|
|
```typescript
|
|
// src/middlewares/auth.middleware.ts
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import { User } from '@models/User';
|
|
import { ssoConfig } from '@config/sso';
|
|
|
|
interface JwtPayload {
|
|
userId: string;
|
|
employeeId: string;
|
|
email: string;
|
|
role: string;
|
|
iat: number;
|
|
exp: number;
|
|
}
|
|
|
|
export const authenticateToken = async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<void> => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
|
|
if (!token) {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'Access token is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Verify JWT token
|
|
const decoded = jwt.verify(token, ssoConfig.jwtSecret) as JwtPayload;
|
|
|
|
// Fetch user from database to ensure they still exist and are active
|
|
const user = await User.findByPk(decoded.userId);
|
|
|
|
if (!user || !user.isActive) {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'User not found or inactive'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Attach user info to request object
|
|
req.user = {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
employeeId: user.employeeId,
|
|
role: user.isAdmin ? 'admin' : 'user'
|
|
};
|
|
|
|
next();
|
|
} catch (error) {
|
|
if (error.name === 'TokenExpiredError') {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'Token has expired'
|
|
});
|
|
} else if (error.name === 'JsonWebTokenError') {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid token'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Authentication error',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
export const requireAdmin = (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void => {
|
|
if (req.user?.role !== 'admin') {
|
|
res.status(403).json({
|
|
success: false,
|
|
message: 'Admin access required'
|
|
});
|
|
return;
|
|
}
|
|
next();
|
|
};
|
|
```
|
|
|
|
#### Authentication Routes
|
|
|
|
```typescript
|
|
// src/routes/auth.routes.ts
|
|
import { Router } from 'express';
|
|
import { AuthController } from '@controllers/auth.controller';
|
|
import { authenticateToken } from '@middlewares/auth.middleware';
|
|
import { validateSSOCallback } from '@validators/auth.validator';
|
|
|
|
const router = Router();
|
|
const authController = new AuthController();
|
|
|
|
// SSO callback endpoint (no authentication required)
|
|
router.post('/sso-callback', authController.handleSSOCallback);
|
|
|
|
// Protected routes (require authentication)
|
|
router.get('/me', authenticateToken, authController.getCurrentUser);
|
|
router.post('/refresh', authController.refreshToken);
|
|
router.post('/logout', authenticateToken, authController.logout);
|
|
|
|
export default router;
|
|
```
|
|
|
|
### 4.5 Type Definitions
|
|
|
|
#### `src/types/express.d.ts`
|
|
```typescript
|
|
import { JwtPayload } from 'jsonwebtoken';
|
|
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
user?: {
|
|
userId: string;
|
|
email: string;
|
|
employeeId: string;
|
|
role?: string;
|
|
};
|
|
file?: Express.Multer.File;
|
|
files?: Express.Multer.File[];
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### `src/types/common.types.ts`
|
|
```typescript
|
|
export enum Priority {
|
|
STANDARD = 'STANDARD',
|
|
EXPRESS = 'EXPRESS'
|
|
}
|
|
|
|
export enum WorkflowStatus {
|
|
DRAFT = 'DRAFT',
|
|
PENDING = 'PENDING',
|
|
IN_PROGRESS = 'IN_PROGRESS',
|
|
APPROVED = 'APPROVED',
|
|
REJECTED = 'REJECTED',
|
|
CLOSED = 'CLOSED'
|
|
}
|
|
|
|
export enum ApprovalStatus {
|
|
PENDING = 'PENDING',
|
|
IN_PROGRESS = 'IN_PROGRESS',
|
|
APPROVED = 'APPROVED',
|
|
REJECTED = 'REJECTED',
|
|
SKIPPED = 'SKIPPED'
|
|
}
|
|
|
|
export enum ParticipantType {
|
|
SPECTATOR = 'SPECTATOR',
|
|
INITIATOR = 'INITIATOR',
|
|
APPROVER = 'APPROVER',
|
|
CONSULTATION = 'CONSULTATION'
|
|
}
|
|
|
|
export enum TATStatus {
|
|
ON_TRACK = 'ON_TRACK',
|
|
APPROACHING = 'APPROACHING',
|
|
BREACHED = 'BREACHED'
|
|
}
|
|
|
|
export interface ApiResponse<T = any> {
|
|
success: boolean;
|
|
message: string;
|
|
data?: T;
|
|
error?: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
export interface PaginationParams {
|
|
page: number;
|
|
limit: number;
|
|
sortBy?: string;
|
|
sortOrder?: 'ASC' | 'DESC';
|
|
}
|
|
|
|
export interface PaginatedResponse<T> {
|
|
data: T[];
|
|
pagination: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Database Schema Design
|
|
|
|
[See Full Database Schema Section from Original Document]
|
|
|
|
The database schema includes:
|
|
- `users` - User information from SSO
|
|
- `workflow_requests` - Main workflow requests
|
|
- `approval_levels` - Approval hierarchy
|
|
- `participants` - Spectators and participants
|
|
- `documents` - Document metadata
|
|
- `work_notes` - Communication within workflow
|
|
- `activities` - Activity log
|
|
- `notifications` - User notifications
|
|
- `tat_tracking` - TAT monitoring
|
|
- `conclusion_remarks` - AI-generated conclusions
|
|
- `audit_logs` - System audit trail
|
|
- `system_settings` - Application configuration
|
|
|
|
---
|
|
|
|
## 6. API Endpoints Structure
|
|
|
|
### Base URL: `http://api.re-workflow.com/api/v1`
|
|
|
|
### 6.1 Authentication APIs
|
|
```
|
|
POST /auth/sso-callback # Frontend sends SSO user data
|
|
POST /auth/logout # Logout and invalidate session
|
|
GET /auth/me # Get current user profile
|
|
POST /auth/refresh # Refresh JWT token
|
|
GET /auth/validate # Validate JWT token
|
|
```
|
|
|
|
### 6.2 Workflow Management APIs
|
|
```
|
|
GET /workflows
|
|
POST /workflows
|
|
GET /workflows/:id
|
|
PUT /workflows/:id
|
|
DELETE /workflows/:id
|
|
PATCH /workflows/:id/submit
|
|
PATCH /workflows/:id/close
|
|
GET /workflows/my-requests
|
|
GET /workflows/open
|
|
GET /workflows/closed
|
|
GET /workflows/assigned-to-me
|
|
```
|
|
|
|
### 6.3 Approval APIs
|
|
```
|
|
GET /workflows/:id/approvals
|
|
POST /workflows/:id/approvals
|
|
PATCH /workflows/:id/approvals/:levelId/approve
|
|
PATCH /workflows/:id/approvals/:levelId/reject
|
|
GET /workflows/:id/approvals/current
|
|
```
|
|
|
|
### 6.4 Participant APIs
|
|
```
|
|
GET /workflows/:id/participants
|
|
POST /workflows/:id/participants
|
|
DELETE /workflows/:id/participants/:participantId
|
|
GET /workflows/:id/spectators
|
|
```
|
|
|
|
### 6.5 Document APIs
|
|
```
|
|
GET /workflows/:id/documents
|
|
POST /workflows/:id/documents
|
|
GET /documents/:documentId
|
|
GET /documents/:documentId/download
|
|
DELETE /documents/:documentId
|
|
GET /documents/:documentId/preview
|
|
```
|
|
|
|
### 6.6 Work Notes APIs
|
|
```
|
|
GET /workflows/:id/work-notes
|
|
POST /workflows/:id/work-notes
|
|
PUT /work-notes/:noteId
|
|
DELETE /work-notes/:noteId
|
|
POST /work-notes/:noteId/reactions
|
|
POST /work-notes/:noteId/attachments
|
|
```
|
|
|
|
### 6.7 Activity Log APIs
|
|
```
|
|
GET /workflows/:id/activities
|
|
GET /workflows/:id/activities/:type
|
|
```
|
|
|
|
### 6.8 Notification APIs
|
|
```
|
|
GET /notifications
|
|
GET /notifications/unread
|
|
PATCH /notifications/:id/read
|
|
PATCH /notifications/mark-all-read
|
|
DELETE /notifications/:id
|
|
```
|
|
|
|
### 6.9 TAT Tracking APIs
|
|
```
|
|
GET /workflows/:id/tat
|
|
GET /tat/breached
|
|
GET /tat/approaching
|
|
```
|
|
|
|
### 6.10 Dashboard APIs
|
|
```
|
|
GET /dashboard/stats
|
|
GET /dashboard/recent
|
|
GET /dashboard/pending-actions
|
|
```
|
|
|
|
### 6.11 User Management APIs
|
|
```
|
|
GET /users
|
|
GET /users/search
|
|
GET /users/:id
|
|
GET /users/:id/requests
|
|
```
|
|
|
|
### 6.12 Conclusion APIs
|
|
```
|
|
POST /workflows/:id/conclusion/generate
|
|
PUT /workflows/:id/conclusion
|
|
GET /workflows/:id/conclusion
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Authentication & Security
|
|
|
|
### 7.1 SSO Integration Flow (Frontend-Initiated)
|
|
|
|
The authentication flow is handled entirely on the frontend side. The backend receives user information from the frontend after successful SSO authentication and either creates a new user record or updates an existing one.
|
|
|
|
#### SSO Flow Architecture
|
|
|
|
```
|
|
┌─────────────────┐ SSO Auth ┌─────────────────┐ User Data ┌─────────────────┐
|
|
│ Frontend │ ────────────► │ SSO Provider │ ──────────────► │ Frontend │
|
|
│ Application │ │ (RE SSO) │ │ Application │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
│ │
|
|
│ │
|
|
│ User Info + JWT Token │
|
|
│ ──────────────────────────────────────────────────────────────────────► │
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────┐ POST /api/v1/auth/sso-callback ┌─────────────────┐
|
|
│ Backend API │ ◄─────────────────────────────────── │ Frontend │
|
|
│ Server │ │ Application │
|
|
└─────────────────┘ └─────────────────┘
|
|
│
|
|
│ Create/Update User Record
|
|
▼
|
|
┌─────────────────┐
|
|
│ PostgreSQL │
|
|
│ Database │
|
|
└─────────────────┘
|
|
```
|
|
|
|
#### SSO Configuration
|
|
|
|
```typescript
|
|
// src/config/sso.ts
|
|
export interface SSOUserData {
|
|
employeeId: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
displayName: string;
|
|
department?: string;
|
|
designation?: string;
|
|
phone?: string;
|
|
reportingManagerId?: string;
|
|
}
|
|
|
|
export interface SSOConfig {
|
|
jwtSecret: string;
|
|
jwtExpiry: string;
|
|
refreshTokenExpiry: string;
|
|
sessionSecret: string;
|
|
allowedOrigins: string[];
|
|
}
|
|
|
|
const ssoConfig: SSOConfig = {
|
|
jwtSecret: process.env.JWT_SECRET || '',
|
|
jwtExpiry: '24h',
|
|
refreshTokenExpiry: '7d',
|
|
sessionSecret: process.env.SESSION_SECRET || '',
|
|
allowedOrigins: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000']
|
|
};
|
|
|
|
export default ssoConfig;
|
|
```
|
|
|
|
### 7.2 Authentication Service Implementation
|
|
|
|
#### SSO Callback Handler
|
|
|
|
```typescript
|
|
// src/services/auth.service.ts
|
|
import { User } from '@models/User';
|
|
import { SSOUserData } from '@config/sso';
|
|
import jwt from 'jsonwebtoken';
|
|
import { ssoConfig } from '@config/sso';
|
|
|
|
export class AuthService {
|
|
/**
|
|
* Handle SSO callback from frontend
|
|
* Creates new user or updates existing user based on employeeId
|
|
*/
|
|
async handleSSOCallback(userData: SSOUserData): Promise<{
|
|
user: User;
|
|
accessToken: string;
|
|
refreshToken: string;
|
|
}> {
|
|
try {
|
|
// Check if user exists by employeeId
|
|
let user = await User.findOne({
|
|
where: { employeeId: userData.employeeId }
|
|
});
|
|
|
|
if (user) {
|
|
// Update existing user
|
|
user = await user.update({
|
|
email: userData.email,
|
|
firstName: userData.firstName,
|
|
lastName: userData.lastName,
|
|
displayName: userData.displayName,
|
|
department: userData.department,
|
|
designation: userData.designation,
|
|
phone: userData.phone,
|
|
reportingManagerId: userData.reportingManagerId,
|
|
lastLogin: new Date(),
|
|
isActive: true
|
|
});
|
|
} else {
|
|
// Create new user
|
|
user = await User.create({
|
|
employeeId: userData.employeeId,
|
|
email: userData.email,
|
|
firstName: userData.firstName,
|
|
lastName: userData.lastName,
|
|
displayName: userData.displayName,
|
|
department: userData.department,
|
|
designation: userData.designation,
|
|
phone: userData.phone,
|
|
reportingManagerId: userData.reportingManagerId,
|
|
isActive: true,
|
|
isAdmin: false,
|
|
lastLogin: new Date()
|
|
});
|
|
}
|
|
|
|
// Generate JWT tokens
|
|
const accessToken = this.generateAccessToken(user);
|
|
const refreshToken = this.generateRefreshToken(user);
|
|
|
|
return {
|
|
user,
|
|
accessToken,
|
|
refreshToken
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`SSO callback failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate JWT access token
|
|
*/
|
|
private generateAccessToken(user: User): string {
|
|
const payload = {
|
|
userId: user.userId,
|
|
employeeId: user.employeeId,
|
|
email: user.email,
|
|
role: user.isAdmin ? 'admin' : 'user'
|
|
};
|
|
|
|
return jwt.sign(payload, ssoConfig.jwtSecret, {
|
|
expiresIn: ssoConfig.jwtExpiry
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate JWT refresh token
|
|
*/
|
|
private generateRefreshToken(user: User): string {
|
|
const payload = {
|
|
userId: user.userId,
|
|
type: 'refresh'
|
|
};
|
|
|
|
return jwt.sign(payload, ssoConfig.jwtSecret, {
|
|
expiresIn: ssoConfig.refreshTokenExpiry
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate JWT token
|
|
*/
|
|
async validateToken(token: string): Promise<any> {
|
|
try {
|
|
return jwt.verify(token, ssoConfig.jwtSecret);
|
|
} catch (error) {
|
|
throw new Error('Invalid token');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh access token using refresh token
|
|
*/
|
|
async refreshAccessToken(refreshToken: string): Promise<string> {
|
|
try {
|
|
const decoded = jwt.verify(refreshToken, ssoConfig.jwtSecret) as any;
|
|
|
|
if (decoded.type !== 'refresh') {
|
|
throw new Error('Invalid refresh token');
|
|
}
|
|
|
|
const user = await User.findByPk(decoded.userId);
|
|
if (!user || !user.isActive) {
|
|
throw new Error('User not found or inactive');
|
|
}
|
|
|
|
return this.generateAccessToken(user);
|
|
} catch (error) {
|
|
throw new Error('Token refresh failed');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### SSO Callback Controller
|
|
|
|
```typescript
|
|
// src/controllers/auth.controller.ts
|
|
import { Request, Response } from 'express';
|
|
import { AuthService } from '@services/auth.service';
|
|
import { SSOUserData } from '@config/sso';
|
|
import { validateSSOCallback } from '@validators/auth.validator';
|
|
|
|
export class AuthController {
|
|
private authService: AuthService;
|
|
|
|
constructor() {
|
|
this.authService = new AuthService();
|
|
}
|
|
|
|
/**
|
|
* Handle SSO callback from frontend
|
|
* POST /api/v1/auth/sso-callback
|
|
*/
|
|
async handleSSOCallback(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
// Validate request body
|
|
const validatedData = validateSSOCallback(req.body);
|
|
|
|
const result = await this.authService.handleSSOCallback(validatedData);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Authentication successful',
|
|
data: {
|
|
user: {
|
|
userId: result.user.userId,
|
|
employeeId: result.user.employeeId,
|
|
email: result.user.email,
|
|
firstName: result.user.firstName,
|
|
lastName: result.user.lastName,
|
|
displayName: result.user.displayName,
|
|
department: result.user.department,
|
|
designation: result.user.designation,
|
|
isAdmin: result.user.isAdmin
|
|
},
|
|
accessToken: result.accessToken,
|
|
refreshToken: result.refreshToken
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'Authentication failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current user profile
|
|
* GET /api/v1/auth/me
|
|
*/
|
|
async getCurrentUser(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const user = req.user;
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
userId: user.userId,
|
|
employeeId: user.employeeId,
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
displayName: user.displayName,
|
|
department: user.department,
|
|
designation: user.designation,
|
|
isAdmin: user.isAdmin,
|
|
lastLogin: user.lastLogin
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to get user profile',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh access token
|
|
* POST /api/v1/auth/refresh
|
|
*/
|
|
async refreshToken(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { refreshToken } = req.body;
|
|
|
|
if (!refreshToken) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'Refresh token is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newAccessToken = await this.authService.refreshAccessToken(refreshToken);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
accessToken: newAccessToken
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'Token refresh failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout user
|
|
* POST /api/v1/auth/logout
|
|
*/
|
|
async logout(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
// In a more sophisticated implementation, you might want to:
|
|
// 1. Add the token to a blacklist
|
|
// 2. Update user's last logout time
|
|
// 3. Clear any server-side sessions
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Logout successful'
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Logout failed',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### SSO Validation Schema
|
|
|
|
```typescript
|
|
// src/validators/auth.validator.ts
|
|
import { z } from 'zod';
|
|
|
|
export const ssoCallbackSchema = z.object({
|
|
employeeId: z.string().min(1, 'Employee ID is required'),
|
|
email: z.string().email('Valid email is required'),
|
|
firstName: z.string().min(1, 'First name is required'),
|
|
lastName: z.string().min(1, 'Last name is required'),
|
|
displayName: z.string().min(1, 'Display name is required'),
|
|
department: z.string().optional(),
|
|
designation: z.string().optional(),
|
|
phone: z.string().optional(),
|
|
reportingManagerId: z.string().uuid().optional()
|
|
});
|
|
|
|
export const validateSSOCallback = (data: any) => {
|
|
return ssoCallbackSchema.parse(data);
|
|
};
|
|
```
|
|
|
|
### 7.3 Security Measures
|
|
|
|
| Security Layer | Implementation |
|
|
|----------------|----------------|
|
|
| **Transport Security** | HTTPS/TLS 1.3 |
|
|
| **Authentication** | Frontend SSO + JWT tokens |
|
|
| **Authorization** | Role-based access control (RBAC) |
|
|
| **Token Storage** | HTTP-only cookies + Local storage |
|
|
| **CSRF Protection** | CSRF tokens on state-changing operations |
|
|
| **XSS Protection** | Input sanitization + Content Security Policy |
|
|
| **SQL Injection** | Parameterized queries (Sequelize ORM) |
|
|
| **Rate Limiting** | Express rate limiter (100 req/15min) |
|
|
| **File Upload Security** | Type validation, size limits, virus scanning |
|
|
| **Audit Logging** | All actions logged with user context |
|
|
| **Data Encryption** | At-rest (database) and in-transit (TLS) |
|
|
| **User Data Validation** | Zod schema validation for SSO user data |
|
|
| **Session Management** | JWT with refresh token rotation |
|
|
|
|
---
|
|
|
|
## 8. Configuration Management
|
|
|
|
### 8.1 Environment Variables
|
|
|
|
```bash
|
|
# backend/.env.example
|
|
|
|
# Application
|
|
NODE_ENV=development
|
|
PORT=5000
|
|
API_VERSION=v1
|
|
BASE_URL=http://localhost:5000
|
|
|
|
# Database
|
|
DB_HOST=localhost
|
|
DB_PORT=5432
|
|
DB_NAME=re_workflow_db
|
|
DB_USER=postgres
|
|
DB_PASSWORD=postgres
|
|
DB_SSL=false
|
|
DB_POOL_MIN=2
|
|
DB_POOL_MAX=10
|
|
|
|
# SSO Configuration (Frontend-handled)
|
|
# Backend only needs JWT secrets for token validation
|
|
JWT_SECRET=your_jwt_secret_key_here_min_32_chars
|
|
JWT_EXPIRY=24h
|
|
REFRESH_TOKEN_SECRET=your_refresh_token_secret_here
|
|
REFRESH_TOKEN_EXPIRY=7d
|
|
|
|
# Session
|
|
SESSION_SECRET=your_session_secret_here_min_32_chars
|
|
|
|
# Cloud Storage (GCP)
|
|
GCP_PROJECT_ID=re-workflow-project
|
|
GCP_BUCKET_NAME=re-workflow-documents
|
|
GCP_KEY_FILE=./config/gcp-key.json
|
|
|
|
# Email Service (Optional)
|
|
SMTP_HOST=smtp.gmail.com
|
|
SMTP_PORT=587
|
|
SMTP_SECURE=false
|
|
SMTP_USER=notifications@royalenfield.com
|
|
SMTP_PASSWORD=your_smtp_password
|
|
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
|
|
|
|
# AI Service (for conclusion generation)
|
|
AI_API_KEY=your_ai_api_key
|
|
AI_MODEL=gpt-4
|
|
AI_MAX_TOKENS=500
|
|
|
|
# Logging
|
|
LOG_LEVEL=info
|
|
LOG_FILE_PATH=./logs
|
|
|
|
# CORS
|
|
CORS_ORIGIN=http://localhost:3000
|
|
|
|
# Rate Limiting
|
|
RATE_LIMIT_WINDOW_MS=900000
|
|
RATE_LIMIT_MAX_REQUESTS=100
|
|
|
|
# File Upload
|
|
MAX_FILE_SIZE_MB=10
|
|
ALLOWED_FILE_TYPES=pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif
|
|
|
|
# TAT Monitoring
|
|
TAT_CHECK_INTERVAL_MINUTES=30
|
|
TAT_REMINDER_THRESHOLD_1=50
|
|
TAT_REMINDER_THRESHOLD_2=80
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Deployment Architecture
|
|
|
|
### 9.1 Docker Compose for Local Development
|
|
|
|
```yaml
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
container_name: re_workflow_db
|
|
environment:
|
|
POSTGRES_USER: ${DB_USER:-workflow_user}
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-secure_password}
|
|
POSTGRES_DB: ${DB_NAME:-re_workflow_db}
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- ./database/schema:/docker-entrypoint-initdb.d
|
|
networks:
|
|
- re_workflow_network
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-workflow_user}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
backend:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: re_workflow_backend
|
|
environment:
|
|
NODE_ENV: development
|
|
DB_HOST: postgres
|
|
DB_PORT: 5432
|
|
DB_USER: ${DB_USER:-workflow_user}
|
|
DB_PASSWORD: ${DB_PASSWORD:-secure_password}
|
|
DB_NAME: ${DB_NAME:-re_workflow_db}
|
|
PORT: 5000
|
|
ports:
|
|
- "5000:5000"
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
volumes:
|
|
- ./logs:/app/logs
|
|
- ./uploads:/app/uploads
|
|
networks:
|
|
- re_workflow_network
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
postgres_data:
|
|
|
|
networks:
|
|
re_workflow_network:
|
|
driver: bridge
|
|
```
|
|
|
|
### 9.2 Dockerfile
|
|
|
|
```dockerfile
|
|
# Dockerfile
|
|
|
|
FROM node:22-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy package files
|
|
COPY package*.json ./
|
|
COPY tsconfig.json ./
|
|
|
|
# Install all dependencies (including devDependencies for build)
|
|
RUN npm ci
|
|
|
|
# Copy source code
|
|
COPY src ./src
|
|
|
|
# Build TypeScript to JavaScript
|
|
RUN npm run build
|
|
|
|
# =====================================
|
|
# Production Image
|
|
# =====================================
|
|
FROM node:22-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
# Install PM2 globally
|
|
RUN npm install -g pm2
|
|
|
|
# Copy package files
|
|
COPY package*.json ./
|
|
|
|
# Install only production dependencies
|
|
RUN npm ci --only=production
|
|
|
|
# Copy compiled JavaScript from builder
|
|
COPY --from=builder /app/dist ./dist
|
|
|
|
# Create logs and uploads directories
|
|
RUN mkdir -p logs uploads
|
|
|
|
# Expose port
|
|
EXPOSE 5000
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
|
CMD node -e "require('http').get('http://localhost:5000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
|
|
|
# Start with PM2
|
|
CMD ["pm2-runtime", "start", "dist/server.js", "--name", "re-workflow-api"]
|
|
```
|
|
|
|
### 9.3 CI/CD Pipeline (GitHub Actions)
|
|
|
|
```yaml
|
|
# .github/workflows/backend-deploy.yml
|
|
|
|
name: Backend CI/CD
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
test-and-build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run linter
|
|
run: npm run lint
|
|
|
|
- name: Run tests
|
|
run: npm test
|
|
|
|
- name: Build TypeScript
|
|
run: npm run build
|
|
|
|
- name: Build Docker image
|
|
run: docker build -t gcr.io/re-project/re-workflow-backend:${{ github.sha }} .
|
|
|
|
- name: Push to GCR
|
|
run: docker push gcr.io/re-project/re-workflow-backend:${{ github.sha }}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Development Setup Instructions
|
|
|
|
### 10.1 Prerequisites
|
|
|
|
- **Node.js:** v22.x LTS
|
|
- **PostgreSQL:** v16.x
|
|
- **npm:** v10.x or higher
|
|
- **TypeScript:** v5.7.x (installed globally or as dev dependency)
|
|
- **Git:** Latest version
|
|
- **Docker & Docker Compose** (Optional)
|
|
- **GCP Account** (for cloud storage in production)
|
|
|
|
### 10.2 Local Development Setup
|
|
|
|
#### Step 1: Clone Repository
|
|
|
|
```bash
|
|
git clone https://github.com/royalenfield/re-workflow-backend.git
|
|
cd re-workflow-backend
|
|
```
|
|
|
|
#### Step 2: Setup Database
|
|
|
|
```bash
|
|
# Install PostgreSQL (if not installed)
|
|
# For Ubuntu/Debian:
|
|
# sudo apt-get install postgresql-16
|
|
|
|
# For macOS:
|
|
# brew install postgresql@16
|
|
|
|
# Create database
|
|
createdb re_workflow_db
|
|
|
|
# Or using psql:
|
|
psql -U postgres
|
|
CREATE DATABASE re_workflow_db;
|
|
\q
|
|
|
|
# Run schema
|
|
psql -U postgres -d re_workflow_db -f database/schema/schema.sql
|
|
```
|
|
|
|
#### Step 3: Configure Environment
|
|
|
|
```bash
|
|
# Copy environment file
|
|
cp .env.example .env
|
|
|
|
# Edit .env with your configurations
|
|
nano .env # or use your preferred editor
|
|
|
|
# Required variables:
|
|
# - Database credentials
|
|
# - JWT secrets
|
|
# - SSO configuration
|
|
# - GCP storage keys
|
|
```
|
|
|
|
#### Step 4: Install Dependencies & Run
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Run TypeScript type checking
|
|
npm run type-check
|
|
|
|
# Run migrations (if using Sequelize CLI)
|
|
npx sequelize-cli db:migrate
|
|
|
|
# Seed data (optional)
|
|
npx sequelize-cli db:seed:all
|
|
|
|
# Start development server (with hot reload)
|
|
npm run dev
|
|
|
|
# Backend will run on: http://localhost:5000
|
|
# API Documentation: http://localhost:5000/api-docs
|
|
```
|
|
|
|
### 10.3 Docker Setup
|
|
|
|
```bash
|
|
# From backend repository root
|
|
cd re-workflow-backend
|
|
|
|
# Copy environment file
|
|
cp .env.example .env
|
|
|
|
# Edit .env if needed
|
|
nano .env
|
|
|
|
# Build and start services (backend + PostgreSQL)
|
|
docker-compose up --build -d
|
|
|
|
# Check logs
|
|
docker-compose logs -f
|
|
|
|
# Access backend: http://localhost:5000
|
|
|
|
# Stop services
|
|
docker-compose down
|
|
|
|
# Stop and remove volumes (clean state)
|
|
docker-compose down -v
|
|
```
|
|
|
|
### 10.4 Running Tests
|
|
|
|
```bash
|
|
# From backend repository
|
|
cd re-workflow-backend
|
|
|
|
npm test # Run all tests (Jest + ts-jest)
|
|
npm run test:unit # Unit tests only
|
|
npm run test:integration # Integration tests only
|
|
npm run test:watch # Watch mode for development
|
|
npm run test:coverage # With coverage report
|
|
|
|
# Coverage report will be in: coverage/
|
|
# View HTML report: open coverage/lcov-report/index.html
|
|
```
|
|
|
|
### 10.5 Code Quality Checks
|
|
|
|
```bash
|
|
# From backend repository
|
|
cd re-workflow-backend
|
|
|
|
npm run lint # ESLint check (TypeScript rules)
|
|
npm run lint:fix # Auto-fix issues
|
|
npm run format # Prettier formatting
|
|
npm run type-check # TypeScript type checking only (no compilation)
|
|
|
|
# Run all quality checks together
|
|
npm run lint && npm run type-check && npm test
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Package.json
|
|
|
|
```json
|
|
{
|
|
"name": "re-workflow-backend",
|
|
"version": "1.0.0",
|
|
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
|
|
"main": "dist/server.js",
|
|
"scripts": {
|
|
"start": "node dist/server.js",
|
|
"dev": "nodemon --exec ts-node src/server.ts",
|
|
"build": "tsc",
|
|
"build:watch": "tsc --watch",
|
|
"start:prod": "NODE_ENV=production node dist/server.js",
|
|
"test": "jest --coverage",
|
|
"test:unit": "jest --testPathPattern=tests/unit",
|
|
"test:integration": "jest --testPathPattern=tests/integration",
|
|
"test:watch": "jest --watch",
|
|
"lint": "eslint src/**/*.ts",
|
|
"lint:fix": "eslint src/**/*.ts --fix",
|
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
"type-check": "tsc --noEmit",
|
|
"db:migrate": "sequelize-cli db:migrate",
|
|
"db:migrate:undo": "sequelize-cli db:migrate:undo",
|
|
"db:seed": "sequelize-cli db:seed:all",
|
|
"clean": "rm -rf dist"
|
|
},
|
|
"dependencies": {
|
|
"express": "^4.21.2",
|
|
"pg": "^8.13.1",
|
|
"pg-hstore": "^2.3.4",
|
|
"sequelize": "^6.37.5",
|
|
"bcryptjs": "^2.4.3",
|
|
"jsonwebtoken": "^9.0.2",
|
|
"passport": "^0.7.0",
|
|
"passport-jwt": "^4.0.1",
|
|
"zod": "^3.24.1",
|
|
"multer": "^1.4.5-lts.1",
|
|
"winston": "^3.17.0",
|
|
"cors": "^2.8.5",
|
|
"helmet": "^8.0.0",
|
|
"dotenv": "^16.4.7",
|
|
"express-rate-limit": "^7.5.0",
|
|
"morgan": "^1.10.0",
|
|
"@google-cloud/storage": "^7.14.0",
|
|
"axios": "^1.7.9",
|
|
"node-cron": "^3.0.3"
|
|
},
|
|
"devDependencies": {
|
|
"@types/express": "^5.0.0",
|
|
"@types/node": "^22.10.5",
|
|
"@types/bcryptjs": "^2.4.6",
|
|
"@types/jsonwebtoken": "^9.0.7",
|
|
"@types/passport": "^1.0.16",
|
|
"@types/passport-jwt": "^4.0.1",
|
|
"@types/multer": "^1.4.12",
|
|
"@types/cors": "^2.8.17",
|
|
"@types/morgan": "^1.9.9",
|
|
"@types/jest": "^29.5.14",
|
|
"@types/supertest": "^6.0.2",
|
|
"typescript": "^5.7.2",
|
|
"ts-node": "^10.9.2",
|
|
"ts-node-dev": "^2.0.0",
|
|
"nodemon": "^3.1.9",
|
|
"jest": "^29.7.0",
|
|
"ts-jest": "^29.2.5",
|
|
"supertest": "^7.0.0",
|
|
"eslint": "^9.17.0",
|
|
"@typescript-eslint/eslint-plugin": "^8.19.1",
|
|
"@typescript-eslint/parser": "^8.19.1",
|
|
"prettier": "^3.4.2",
|
|
"sequelize-cli": "^6.6.2",
|
|
"sequelize-typescript": "^2.1.7"
|
|
},
|
|
"engines": {
|
|
"node": ">=22.0.0",
|
|
"npm": ">=10.0.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
This backend documentation provides:
|
|
|
|
✅ **Complete Backend Architecture** - Server structure, layers, and components
|
|
✅ **Technology Stack** - Node.js 22 LTS + TypeScript 5.7 + PostgreSQL 16
|
|
✅ **Folder Structure** - Detailed organization with TypeScript conventions
|
|
✅ **Type Definitions** - Comprehensive type safety with interfaces and enums
|
|
✅ **Database Schema** - Full PostgreSQL schema with tables, indexes, triggers
|
|
✅ **API Specification** - RESTful endpoints for all features
|
|
✅ **Frontend SSO Integration** - Frontend-handled authentication with backend user management
|
|
✅ **User Management** - Create/update users based on SSO data from frontend
|
|
✅ **JWT Authentication** - Token-based authentication with refresh token support
|
|
✅ **Security Implementation** - JWT validation, RBAC, audit trails
|
|
✅ **Configuration Management** - Environment variables and settings
|
|
✅ **Deployment Architecture** - Docker, docker-compose, CI/CD pipelines
|
|
✅ **Development Setup** - Step-by-step installation and configuration
|
|
✅ **Testing Strategy** - Unit and integration testing with ts-jest
|
|
✅ **Code Quality** - ESLint, Prettier, TypeScript best practices
|
|
|
|
**Technology Stack:** Node.js 22 LTS + TypeScript 5.7 + Express.js 4.21 + PostgreSQL 16
|
|
**Authentication Flow:** Frontend SSO → Backend User Creation/Update → JWT Token Generation
|
|
**Repository:** `re-workflow-backend` (Independent Repository)
|
|
**Status:** ✅ Ready for Implementation
|
|
|