create-nodemin-app 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +82 -0
- package/package.json +25 -0
- package/templates/HRMS_Mongodb/README.md +331 -0
- package/templates/HRMS_Mongodb/backend/.env.example +6 -0
- package/templates/HRMS_Mongodb/backend/package-lock.json +1646 -0
- package/templates/HRMS_Mongodb/backend/package.json +26 -0
- package/templates/HRMS_Mongodb/backend/src/config/db.js +9 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/authController.js +187 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/departmentController.js +70 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/employeeController.js +178 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/positionController.js +66 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/auth.js +57 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/errorHandler.js +32 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/restrictToAdmin.js +5 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/validate.js +13 -0
- package/templates/HRMS_Mongodb/backend/src/models/Department.js +19 -0
- package/templates/HRMS_Mongodb/backend/src/models/Employee.js +81 -0
- package/templates/HRMS_Mongodb/backend/src/models/Position.js +19 -0
- package/templates/HRMS_Mongodb/backend/src/models/User.js +40 -0
- package/templates/HRMS_Mongodb/backend/src/routes/authRoutes.js +27 -0
- package/templates/HRMS_Mongodb/backend/src/routes/departmentRoutes.js +33 -0
- package/templates/HRMS_Mongodb/backend/src/routes/employeeRoutes.js +39 -0
- package/templates/HRMS_Mongodb/backend/src/routes/positionRoutes.js +32 -0
- package/templates/HRMS_Mongodb/backend/src/server.js +74 -0
- package/templates/HRMS_Mongodb/backend/src/utils/roles.js +5 -0
- package/templates/HRMS_Mongodb/backend/src/utils/seed.js +78 -0
- package/templates/HRMS_Mongodb/backend/src/validators/authValidator.js +61 -0
- package/templates/HRMS_Mongodb/backend/src/validators/departmentValidator.js +21 -0
- package/templates/HRMS_Mongodb/backend/src/validators/employeeValidator.js +27 -0
- package/templates/HRMS_Mongodb/backend/src/validators/positionValidator.js +26 -0
- package/templates/HRMS_Mongodb/frontend/index.html +19 -0
- package/templates/HRMS_Mongodb/frontend/package-lock.json +2812 -0
- package/templates/HRMS_Mongodb/frontend/package.json +25 -0
- package/templates/HRMS_Mongodb/frontend/public/favicon.svg +4 -0
- package/templates/HRMS_Mongodb/frontend/src/App.jsx +50 -0
- package/templates/HRMS_Mongodb/frontend/src/api/axios.js +54 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ProtectedRoute.jsx +26 -0
- package/templates/HRMS_Mongodb/frontend/src/components/layout/DashboardLayout.jsx +16 -0
- package/templates/HRMS_Mongodb/frontend/src/components/layout/Sidebar.jsx +108 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Button.jsx +33 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Input.jsx +20 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Modal.jsx +48 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Select.jsx +27 -0
- package/templates/HRMS_Mongodb/frontend/src/context/AuthContext.jsx +97 -0
- package/templates/HRMS_Mongodb/frontend/src/index.css +34 -0
- package/templates/HRMS_Mongodb/frontend/src/main.jsx +16 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Dashboard.jsx +78 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Departments.jsx +144 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Employees.jsx +297 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/LeaveReport.jsx +113 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Login.jsx +92 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Positions.jsx +157 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Register.jsx +93 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/ResetPassword.jsx +135 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/roles.js +1 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/session.js +5 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/validation.js +66 -0
- package/templates/HRMS_Mongodb/frontend/vite.config.js +16 -0
- package/templates/HRMS_Mysql/backend/db.js +13 -0
- package/templates/HRMS_Mysql/backend/package-lock.json +1614 -0
- package/templates/HRMS_Mysql/backend/package.json +21 -0
- package/templates/HRMS_Mysql/backend/server.js +421 -0
- package/templates/HRMS_Mysql/frontend/dist/assets/index-CtLtQf3_.js +75 -0
- package/templates/HRMS_Mysql/frontend/dist/assets/index-Dq1AXlEY.css +1 -0
- package/templates/HRMS_Mysql/frontend/dist/index.html +14 -0
- package/templates/HRMS_Mysql/frontend/dist/vite.svg +1 -0
- package/templates/HRMS_Mysql/frontend/index.html +13 -0
- package/templates/HRMS_Mysql/frontend/package-lock.json +2978 -0
- package/templates/HRMS_Mysql/frontend/package.json +25 -0
- package/templates/HRMS_Mysql/frontend/postcss.config.js +6 -0
- package/templates/HRMS_Mysql/frontend/public/vite.svg +1 -0
- package/templates/HRMS_Mysql/frontend/src/App.jsx +55 -0
- package/templates/HRMS_Mysql/frontend/src/api.js +11 -0
- package/templates/HRMS_Mysql/frontend/src/components/Layout.jsx +59 -0
- package/templates/HRMS_Mysql/frontend/src/index.css +7 -0
- package/templates/HRMS_Mysql/frontend/src/main.jsx +13 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Dashboard.jsx +45 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Departments.jsx +108 -0
- package/templates/HRMS_Mysql/frontend/src/pages/EmployeeStatusReport.jsx +72 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Employees.jsx +252 -0
- package/templates/HRMS_Mysql/frontend/src/pages/ForgotPassword.jsx +66 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Login.jsx +79 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Positions.jsx +109 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Register.jsx +95 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Users.jsx +133 -0
- package/templates/HRMS_Mysql/frontend/tailwind.config.js +26 -0
- package/templates/HRMS_Mysql/frontend/vite.config.js +15 -0
- package/templates/HRMS_Mysql/hrms_schema.sql +57 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const employeeSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
empFirstName: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: [true, 'First name is required'],
|
|
8
|
+
trim: true,
|
|
9
|
+
maxlength: [50, 'First name cannot exceed 50 characters'],
|
|
10
|
+
},
|
|
11
|
+
empLastName: {
|
|
12
|
+
type: String,
|
|
13
|
+
required: [true, 'Last name is required'],
|
|
14
|
+
trim: true,
|
|
15
|
+
maxlength: [50, 'Last name cannot exceed 50 characters'],
|
|
16
|
+
},
|
|
17
|
+
empEmail: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: [true, 'Email is required'],
|
|
20
|
+
unique: true,
|
|
21
|
+
lowercase: true,
|
|
22
|
+
trim: true,
|
|
23
|
+
match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email'],
|
|
24
|
+
},
|
|
25
|
+
empTelephone: {
|
|
26
|
+
type: String,
|
|
27
|
+
required: [true, 'Telephone is required'],
|
|
28
|
+
trim: true,
|
|
29
|
+
maxlength: [20, 'Telephone cannot exceed 20 characters'],
|
|
30
|
+
},
|
|
31
|
+
empGender: {
|
|
32
|
+
type: String,
|
|
33
|
+
required: [true, 'Gender is required'],
|
|
34
|
+
enum: ['Male', 'Female', 'Other'],
|
|
35
|
+
},
|
|
36
|
+
empAddress: {
|
|
37
|
+
type: String,
|
|
38
|
+
required: [true, 'Address is required'],
|
|
39
|
+
trim: true,
|
|
40
|
+
maxlength: [300, 'Address cannot exceed 300 characters'],
|
|
41
|
+
},
|
|
42
|
+
empDateOfBirth: {
|
|
43
|
+
type: Date,
|
|
44
|
+
required: [true, 'Date of birth is required'],
|
|
45
|
+
},
|
|
46
|
+
empHireDate: {
|
|
47
|
+
type: Date,
|
|
48
|
+
required: [true, 'Hire date is required'],
|
|
49
|
+
},
|
|
50
|
+
empStatus: {
|
|
51
|
+
type: String,
|
|
52
|
+
required: [true, 'Status is required'],
|
|
53
|
+
// FIXED: Enum updated to match new system business rules
|
|
54
|
+
enum: {
|
|
55
|
+
values: ['Active', 'On Leave', 'Left', 'Blacklisted', 'Deceased', 'On Mission'],
|
|
56
|
+
message: '{VALUE} is not a recognized employee status context structure'
|
|
57
|
+
},
|
|
58
|
+
default: 'Active',
|
|
59
|
+
},
|
|
60
|
+
department: {
|
|
61
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
62
|
+
ref: 'Department',
|
|
63
|
+
required: [true, 'Department is required'],
|
|
64
|
+
},
|
|
65
|
+
position: {
|
|
66
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
67
|
+
ref: 'Position',
|
|
68
|
+
required: [true, 'Position is required'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{ timestamps: true }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
employeeSchema.virtual('fullName').get(function () {
|
|
75
|
+
return `${this.empFirstName} ${this.empLastName}`;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
employeeSchema.set('toJSON', { virtuals: true });
|
|
79
|
+
employeeSchema.set('toObject', { virtuals: true });
|
|
80
|
+
|
|
81
|
+
export default mongoose.model('Employee', employeeSchema);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const positionSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
posName: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: [true, 'Position name is required'],
|
|
8
|
+
unique: true,
|
|
9
|
+
trim: true,
|
|
10
|
+
},
|
|
11
|
+
description: {
|
|
12
|
+
type: String,
|
|
13
|
+
trim: true,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{ timestamps: true }
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export default mongoose.model('Position', positionSchema);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
|
|
4
|
+
const userSchema = new mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
userName: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: [true, 'Username is required'],
|
|
9
|
+
unique: true,
|
|
10
|
+
trim: true,
|
|
11
|
+
minlength: [3, 'Username must be at least 3 characters'],
|
|
12
|
+
maxlength: [30, 'Username cannot exceed 30 characters'],
|
|
13
|
+
},
|
|
14
|
+
password: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: [true, 'Password is required'],
|
|
17
|
+
minlength: [6, 'Password must be at least 6 characters'],
|
|
18
|
+
select: false,
|
|
19
|
+
},
|
|
20
|
+
employee: {
|
|
21
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
22
|
+
ref: 'Employee',
|
|
23
|
+
required: [true, 'Employee reference is required'],
|
|
24
|
+
unique: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{ timestamps: true }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
userSchema.pre('save', async function (next) {
|
|
31
|
+
if (!this.isModified('password')) return next();
|
|
32
|
+
this.password = await bcrypt.hash(this.password, 12);
|
|
33
|
+
next();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
userSchema.methods.comparePassword = async function (candidatePassword) {
|
|
37
|
+
return bcrypt.compare(candidatePassword, this.password);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default mongoose.model('User', userSchema);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import {
|
|
3
|
+
login,
|
|
4
|
+
register,
|
|
5
|
+
resetPassword,
|
|
6
|
+
getMe
|
|
7
|
+
} from '../controllers/authController.js';
|
|
8
|
+
import { protect } from '../middleware/auth.js';
|
|
9
|
+
import { validate } from '../middleware/validate.js';
|
|
10
|
+
import {
|
|
11
|
+
loginRules,
|
|
12
|
+
registerRules,
|
|
13
|
+
resetPasswordRules
|
|
14
|
+
} from '../validators/authValidator.js';
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
|
|
18
|
+
// Public Authenticated Entry Endpoints
|
|
19
|
+
// FIXED: Removed "/auth" prefix so paths map cleanly directly to server.js base strings
|
|
20
|
+
router.post('/login', loginRules, validate, login);
|
|
21
|
+
router.post('/register', registerRules, validate, register);
|
|
22
|
+
router.post('/reset-password', resetPasswordRules, validate, resetPassword);
|
|
23
|
+
|
|
24
|
+
// Private Account Infrastructure Operations
|
|
25
|
+
router.get('/me', protect, getMe);
|
|
26
|
+
|
|
27
|
+
export default router;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import {
|
|
3
|
+
getDepartments,
|
|
4
|
+
getDepartment,
|
|
5
|
+
createDepartment,
|
|
6
|
+
updateDepartment,
|
|
7
|
+
deleteDepartment,
|
|
8
|
+
} from '../controllers/departmentController.js';
|
|
9
|
+
import { protect } from '../middleware/auth.js';
|
|
10
|
+
import { validate } from '../middleware/validate.js';
|
|
11
|
+
import {
|
|
12
|
+
createDepartmentRules,
|
|
13
|
+
updateDepartmentRules,
|
|
14
|
+
departmentIdRule,
|
|
15
|
+
} from '../validators/departmentValidator.js';
|
|
16
|
+
|
|
17
|
+
const router = express.Router();
|
|
18
|
+
|
|
19
|
+
// Require login for all department operations
|
|
20
|
+
router.use(protect);
|
|
21
|
+
|
|
22
|
+
router
|
|
23
|
+
.route('/')
|
|
24
|
+
.get(getDepartments)
|
|
25
|
+
.post(createDepartmentRules, validate, createDepartment);
|
|
26
|
+
|
|
27
|
+
router
|
|
28
|
+
.route('/:id')
|
|
29
|
+
.get(departmentIdRule, validate, getDepartment)
|
|
30
|
+
.put(updateDepartmentRules, validate, updateDepartment)
|
|
31
|
+
.delete(departmentIdRule, validate, deleteDepartment);
|
|
32
|
+
|
|
33
|
+
export default router;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import {
|
|
3
|
+
getEmployees,
|
|
4
|
+
getEmployee,
|
|
5
|
+
createEmployee,
|
|
6
|
+
updateEmployee,
|
|
7
|
+
deleteEmployee,
|
|
8
|
+
getEmployeeStats,
|
|
9
|
+
getLeaveReport
|
|
10
|
+
} from '../controllers/employeeController.js';
|
|
11
|
+
import { protect } from '../middleware/auth.js';
|
|
12
|
+
import { validate } from '../middleware/validate.js';
|
|
13
|
+
import {
|
|
14
|
+
createEmployeeRules,
|
|
15
|
+
updateEmployeeRules,
|
|
16
|
+
employeeIdRule,
|
|
17
|
+
} from '../validators/employeeValidator.js';
|
|
18
|
+
|
|
19
|
+
const router = express.Router();
|
|
20
|
+
|
|
21
|
+
// 1. Authenticate all requests down this pipeline
|
|
22
|
+
router.use(protect);
|
|
23
|
+
|
|
24
|
+
// 2. Metrics Endpoint
|
|
25
|
+
router.get('/stats', getEmployeeStats);
|
|
26
|
+
|
|
27
|
+
// 3. Explicit Base Route Directives (Separated to eliminate compilation ambiguity)
|
|
28
|
+
router.get('/', getEmployees);
|
|
29
|
+
router.post('/', createEmployeeRules, validate, createEmployee);
|
|
30
|
+
|
|
31
|
+
// 4. Document Specific Parameter Route Directives
|
|
32
|
+
router.get('/:id', employeeIdRule, validate, getEmployee);
|
|
33
|
+
router.put('/:id', updateEmployeeRules, validate, updateEmployee);
|
|
34
|
+
router.delete('/:id', employeeIdRule, validate, deleteEmployee);
|
|
35
|
+
|
|
36
|
+
//report
|
|
37
|
+
router.get('/leave-status-report', getLeaveReport);
|
|
38
|
+
|
|
39
|
+
export default router;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import {
|
|
3
|
+
getPositions,
|
|
4
|
+
getPosition,
|
|
5
|
+
createPosition,
|
|
6
|
+
updatePosition,
|
|
7
|
+
deletePosition,
|
|
8
|
+
} from '../controllers/positionController.js';
|
|
9
|
+
import { protect } from '../middleware/auth.js';
|
|
10
|
+
import { validate } from '../middleware/validate.js';
|
|
11
|
+
import {
|
|
12
|
+
createPositionRules,
|
|
13
|
+
updatePositionRules,
|
|
14
|
+
positionIdRule,
|
|
15
|
+
} from '../validators/positionValidator.js';
|
|
16
|
+
|
|
17
|
+
const router = express.Router();
|
|
18
|
+
|
|
19
|
+
router.use(protect);
|
|
20
|
+
|
|
21
|
+
router
|
|
22
|
+
.route('/')
|
|
23
|
+
.get(getPositions)
|
|
24
|
+
.post(createPositionRules, validate, createPosition);
|
|
25
|
+
|
|
26
|
+
router
|
|
27
|
+
.route('/:id')
|
|
28
|
+
.get(positionIdRule, validate, getPosition)
|
|
29
|
+
.put(updatePositionRules, validate, updatePosition)
|
|
30
|
+
.delete(positionIdRule, validate, deletePosition);
|
|
31
|
+
|
|
32
|
+
export default router;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import cors from 'cors';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import rateLimit from 'express-rate-limit';
|
|
6
|
+
import mongoSanitize from 'express-mongo-sanitize';
|
|
7
|
+
import connectDB from './config/db.js';
|
|
8
|
+
import authRoutes from './routes/authRoutes.js';
|
|
9
|
+
import departmentRoutes from './routes/departmentRoutes.js';
|
|
10
|
+
import positionRoutes from './routes/positionRoutes.js';
|
|
11
|
+
import employeeRoutes from './routes/employeeRoutes.js'; // Added missing router import
|
|
12
|
+
import { notFound, errorHandler } from './middleware/errorHandler.js';
|
|
13
|
+
|
|
14
|
+
await connectDB();
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
|
|
18
|
+
app.use(helmet());
|
|
19
|
+
app.use(
|
|
20
|
+
cors({
|
|
21
|
+
origin: process.env.CLIENT_URL || 'http://localhost:5173',
|
|
22
|
+
credentials: true,
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const limiter = rateLimit({
|
|
27
|
+
windowMs: 15 * 60 * 1000,
|
|
28
|
+
max: 200,
|
|
29
|
+
message: { success: false, message: 'Too many requests, please try again later.' },
|
|
30
|
+
});
|
|
31
|
+
app.use('/api', limiter);
|
|
32
|
+
|
|
33
|
+
app.use(express.json({ limit: '10kb' }));
|
|
34
|
+
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
|
|
35
|
+
app.use(mongoSanitize());
|
|
36
|
+
|
|
37
|
+
// Input validation and HTML element sanitization engine
|
|
38
|
+
app.use((req, res, next) => {
|
|
39
|
+
const sanitizeString = (value) => {
|
|
40
|
+
if (typeof value === 'string') {
|
|
41
|
+
return value.replace(/<[^>]*>/g, '').trim();
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) return value.map(sanitizeString);
|
|
44
|
+
if (value && typeof value === 'object') {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const key of Object.keys(value)) {
|
|
47
|
+
out[key] = sanitizeString(value[key]);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (req.body) req.body = sanitizeString(req.body);
|
|
55
|
+
if (req.query) req.query = sanitizeString(req.query);
|
|
56
|
+
if (req.params) req.params = sanitizeString(req.params);
|
|
57
|
+
next();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.get('/api/health', (req, res) => {
|
|
61
|
+
res.json({ success: true, message: 'Infrastructure API Hub is running smoothly' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// App Pipelines
|
|
65
|
+
app.use('/api/auth', authRoutes);
|
|
66
|
+
app.use('/api/departments', departmentRoutes);
|
|
67
|
+
app.use('/api/positions', positionRoutes);
|
|
68
|
+
app.use('/api/employees', employeeRoutes); // Fixed: Mounted the employee management routes
|
|
69
|
+
|
|
70
|
+
app.use(notFound);
|
|
71
|
+
app.use(errorHandler);
|
|
72
|
+
|
|
73
|
+
const PORT = process.env.PORT || 5000;
|
|
74
|
+
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
|
+
import connectDB from '../config/db.js';
|
|
4
|
+
import Department from '../models/Department.js';
|
|
5
|
+
import Position from '../models/Position.js';
|
|
6
|
+
import Employee from '../models/Employee.js';
|
|
7
|
+
import User from '../models/User.js';
|
|
8
|
+
|
|
9
|
+
const seed = async () => {
|
|
10
|
+
await connectDB();
|
|
11
|
+
|
|
12
|
+
await Promise.all([
|
|
13
|
+
User.deleteMany(),
|
|
14
|
+
Employee.deleteMany(),
|
|
15
|
+
Department.deleteMany(),
|
|
16
|
+
Position.deleteMany(),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const departments = await Department.insertMany([
|
|
20
|
+
{ departName: 'Sales' },
|
|
21
|
+
{ departName: 'Warehouse' },
|
|
22
|
+
{ departName: 'Human Resources' },
|
|
23
|
+
{ departName: 'Finance' },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const positions = await Position.insertMany([
|
|
27
|
+
{ posName: 'Sales Manager', requiredQualification: 'Bachelor in Business or equivalent' },
|
|
28
|
+
{ posName: 'Store Keeper', requiredQualification: 'Diploma in Logistics' },
|
|
29
|
+
{ posName: 'HR Officer', requiredQualification: 'Bachelor in Human Resource Management' },
|
|
30
|
+
{ posName: 'Accountant', requiredQualification: 'Bachelor in Accounting, CPA preferred' },
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const employees = await Employee.insertMany([
|
|
34
|
+
{
|
|
35
|
+
empFirstName: 'Jean',
|
|
36
|
+
empLastName: 'Uwimana',
|
|
37
|
+
empEmail: 'jean.uwimana@dabenterprise.rw',
|
|
38
|
+
empTelephone: '+250788123456',
|
|
39
|
+
empGender: 'Male',
|
|
40
|
+
empAddress: 'Kigali, Gasabo',
|
|
41
|
+
empDateOfBirth: new Date('1990-05-15'),
|
|
42
|
+
empHireDate: new Date('2020-01-10'),
|
|
43
|
+
empStatus: 'Active',
|
|
44
|
+
department: departments[2]._id,
|
|
45
|
+
position: positions[2]._id,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
empFirstName: 'Marie',
|
|
49
|
+
empLastName: 'Mukamana',
|
|
50
|
+
empEmail: 'marie.mukamana@dabenterprise.rw',
|
|
51
|
+
empTelephone: '+250788654321',
|
|
52
|
+
empGender: 'Female',
|
|
53
|
+
empAddress: 'Kigali, Kicukiro',
|
|
54
|
+
empDateOfBirth: new Date('1992-08-22'),
|
|
55
|
+
empHireDate: new Date('2021-03-01'),
|
|
56
|
+
empStatus: 'Active',
|
|
57
|
+
department: departments[0]._id,
|
|
58
|
+
position: positions[0]._id,
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
await User.create({
|
|
63
|
+
userName: 'admin',
|
|
64
|
+
password: 'Admin@123',
|
|
65
|
+
employee: employees[0]._id,
|
|
66
|
+
role: 'admin',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
console.log('Database seeded successfully!');
|
|
70
|
+
console.log('Login: admin / Admin@123');
|
|
71
|
+
console.log('Reset password test email: jean.uwimana@dabenterprise.rw');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
seed().catch((err) => {
|
|
76
|
+
console.error(err);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { body } from 'express-validator';
|
|
2
|
+
|
|
3
|
+
export const loginRules = [
|
|
4
|
+
body('userName')
|
|
5
|
+
.trim()
|
|
6
|
+
.notEmpty()
|
|
7
|
+
.withMessage('Username is required'),
|
|
8
|
+
body('password')
|
|
9
|
+
.notEmpty()
|
|
10
|
+
.withMessage('Password is required')
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const registerRules = [
|
|
14
|
+
body('userName')
|
|
15
|
+
.trim()
|
|
16
|
+
.notEmpty()
|
|
17
|
+
.withMessage('Username is required')
|
|
18
|
+
.isLength({ min: 3, max: 30 })
|
|
19
|
+
.withMessage('Username must be between 3 and 30 characters'),
|
|
20
|
+
|
|
21
|
+
body('email')
|
|
22
|
+
.trim()
|
|
23
|
+
.notEmpty()
|
|
24
|
+
.withMessage('Email is required')
|
|
25
|
+
.isEmail()
|
|
26
|
+
.withMessage('Please provide a valid email address')
|
|
27
|
+
.normalizeEmail(),
|
|
28
|
+
|
|
29
|
+
body('password')
|
|
30
|
+
.notEmpty()
|
|
31
|
+
.withMessage('Password is required')
|
|
32
|
+
.isLength({ min: 6 })
|
|
33
|
+
.withMessage('Password must be at least 6 characters long')
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// FIXED: Added backend validation requirements for the new fields
|
|
37
|
+
export const resetPasswordRules = [
|
|
38
|
+
body('email')
|
|
39
|
+
.trim()
|
|
40
|
+
.notEmpty()
|
|
41
|
+
.withMessage('Email is required')
|
|
42
|
+
.isEmail()
|
|
43
|
+
.withMessage('Please provide a valid email address')
|
|
44
|
+
.normalizeEmail(),
|
|
45
|
+
|
|
46
|
+
body('password')
|
|
47
|
+
.notEmpty()
|
|
48
|
+
.withMessage('New password is required')
|
|
49
|
+
.isLength({ min: 6 })
|
|
50
|
+
.withMessage('Password must be at least 6 characters long'),
|
|
51
|
+
|
|
52
|
+
body('confirmPassword')
|
|
53
|
+
.notEmpty()
|
|
54
|
+
.withMessage('Please confirm your new password')
|
|
55
|
+
.custom((value, { req }) => {
|
|
56
|
+
if (value !== req.body.password) {
|
|
57
|
+
throw new Error('Passwords do not match');
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
})
|
|
61
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { body, param } from 'express-validator';
|
|
2
|
+
|
|
3
|
+
export const createDepartmentRules = [
|
|
4
|
+
body('departName')
|
|
5
|
+
.trim()
|
|
6
|
+
.notEmpty()
|
|
7
|
+
.withMessage('Department name is required')
|
|
8
|
+
.isLength({ max: 100 })
|
|
9
|
+
.withMessage('Department name cannot exceed 100 characters'),
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export const updateDepartmentRules = [
|
|
13
|
+
param('id').isMongoId().withMessage('Invalid department ID'),
|
|
14
|
+
body('departName')
|
|
15
|
+
.trim()
|
|
16
|
+
.notEmpty()
|
|
17
|
+
.withMessage('Department name is required')
|
|
18
|
+
.isLength({ max: 100 }),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const departmentIdRule = [param('id').isMongoId().withMessage('Invalid department ID')];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { body, param } from 'express-validator';
|
|
2
|
+
|
|
3
|
+
const employeeFields = [
|
|
4
|
+
body('empFirstName').trim().notEmpty().withMessage('First name is required').isLength({ max: 50 }),
|
|
5
|
+
body('empLastName').trim().notEmpty().withMessage('Last name is required').isLength({ max: 50 }),
|
|
6
|
+
body('empEmail').trim().notEmpty().withMessage('Email is required').isEmail().withMessage('Invalid email').normalizeEmail(),
|
|
7
|
+
body('empTelephone').trim().notEmpty().withMessage('Telephone is required').isLength({ max: 20 }),
|
|
8
|
+
body('empGender').notEmpty().withMessage('Gender is required').isIn(['Male', 'Female', 'Other']),
|
|
9
|
+
body('empAddress').trim().notEmpty().withMessage('Address is required').isLength({ max: 300 }),
|
|
10
|
+
body('empDateOfBirth').notEmpty().withMessage('Date of birth is required').isISO8601().withMessage('Invalid date of birth'),
|
|
11
|
+
body('empHireDate').notEmpty().withMessage('Hire date is required').isISO8601().withMessage('Invalid hire date'),
|
|
12
|
+
body('empStatus')
|
|
13
|
+
.notEmpty()
|
|
14
|
+
.withMessage('Status is required')
|
|
15
|
+
.isIn(['Active', 'On Leave', 'Left','Blacklisted', 'Deceased','On Mission' ]),
|
|
16
|
+
body('department').notEmpty().withMessage('Department is required').isMongoId(),
|
|
17
|
+
body('position').notEmpty().withMessage('Position is required').isMongoId(),
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export const createEmployeeRules = employeeFields;
|
|
21
|
+
|
|
22
|
+
export const updateEmployeeRules = [
|
|
23
|
+
param('id').isMongoId().withMessage('Invalid employee ID'),
|
|
24
|
+
...employeeFields.map((rule) => rule.optional()),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const employeeIdRule = [param('id').isMongoId().withMessage('Invalid employee ID')];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { body, param } from 'express-validator';
|
|
2
|
+
|
|
3
|
+
export const createPositionRules = [
|
|
4
|
+
body('posName')
|
|
5
|
+
.trim()
|
|
6
|
+
.notEmpty()
|
|
7
|
+
.withMessage('Position name is required')
|
|
8
|
+
.isLength({ max: 100 }),
|
|
9
|
+
body('requiredQualification')
|
|
10
|
+
.trim()
|
|
11
|
+
.notEmpty()
|
|
12
|
+
.withMessage('Required qualification is required')
|
|
13
|
+
.isLength({ max: 500 }),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const updatePositionRules = [
|
|
17
|
+
param('id').isMongoId().withMessage('Invalid position ID'),
|
|
18
|
+
body('posName').trim().notEmpty().withMessage('Position name is required').isLength({ max: 100 }),
|
|
19
|
+
body('requiredQualification')
|
|
20
|
+
.trim()
|
|
21
|
+
.notEmpty()
|
|
22
|
+
.withMessage('Required qualification is required')
|
|
23
|
+
.isLength({ max: 500 }),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const positionIdRule = [param('id').isMongoId().withMessage('Invalid position ID')];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>DAB Enterprise HRMS</title>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
+
<link
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&display=swap"
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
/>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|