alpe-temp 1.0.0
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/.env.example +13 -0
- package/backend-project/README.md +133 -0
- package/backend-project/package-lock.json +2559 -0
- package/backend-project/package.json +25 -0
- package/backend-project/server.js +28 -0
- package/backend-project/src/app.js +84 -0
- package/backend-project/src/config/app.config.js +72 -0
- package/backend-project/src/config/db.js +20 -0
- package/backend-project/src/config/env.js +19 -0
- package/backend-project/src/middleware/auth.middleware.js +33 -0
- package/backend-project/src/middleware/error.middleware.js +19 -0
- package/backend-project/src/modules/_example/example.controller.js +82 -0
- package/backend-project/src/modules/_example/example.model.js +47 -0
- package/backend-project/src/modules/_example/example.routes.js +43 -0
- package/backend-project/src/modules/_example/example.service.js +58 -0
- package/backend-project/src/modules/auth/auth.controller.js +47 -0
- package/backend-project/src/modules/auth/auth.routes.js +16 -0
- package/backend-project/src/modules/auth/auth.service.js +57 -0
- package/backend-project/src/modules/auth/user.model.js +41 -0
- package/backend-project/src/modules/department/department.controller.js +54 -0
- package/backend-project/src/modules/department/department.model.js +10 -0
- package/backend-project/src/modules/department/department.routes.js +15 -0
- package/backend-project/src/modules/department/department.service.js +29 -0
- package/backend-project/src/modules/employee/employee.controller.js +63 -0
- package/backend-project/src/modules/employee/employee.model.js +15 -0
- package/backend-project/src/modules/employee/employee.routes.js +16 -0
- package/backend-project/src/modules/employee/employee.service.js +30 -0
- package/backend-project/src/modules/excel/excel.controller.js +61 -0
- package/backend-project/src/modules/excel/excel.routes.js +13 -0
- package/backend-project/src/modules/excel/excel.service.js +303 -0
- package/backend-project/src/modules/reports/reports.controller.js +41 -0
- package/backend-project/src/modules/reports/reports.routes.js +10 -0
- package/backend-project/src/modules/salary/salary.controller.js +70 -0
- package/backend-project/src/modules/salary/salary.model.js +23 -0
- package/backend-project/src/modules/salary/salary.routes.js +16 -0
- package/backend-project/src/modules/salary/salary.service.js +44 -0
- package/backend-project/src/seed.js +36 -0
- package/backend-project/src/utils/response.js +35 -0
- package/backend-project/src/utils/token.js +27 -0
- package/bin/epms.js +161 -0
- package/frontend-project/README.md +16 -0
- package/frontend-project/dist/assets/index-B08ICGra.js +20 -0
- package/frontend-project/dist/assets/index-D_cqT2Z6.css +1 -0
- package/frontend-project/dist/car.jfif +0 -0
- package/frontend-project/dist/favicon.svg +1 -0
- package/frontend-project/dist/icons.svg +24 -0
- package/frontend-project/dist/index.html +14 -0
- package/frontend-project/dist/logo.png +0 -0
- package/frontend-project/eslint.config.js +21 -0
- package/frontend-project/index.html +13 -0
- package/frontend-project/package-lock.json +3660 -0
- package/frontend-project/package.json +33 -0
- package/frontend-project/postcss.config.js +6 -0
- package/frontend-project/tailwind.config.js +15 -0
- package/frontend-project/vite.config.js +8 -0
- package/package.json +41 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
const User = require('./user.model');
|
|
3
|
+
const env = require('../../config/env');
|
|
4
|
+
const { generateTokens, verifyRefreshToken } = require('../../utils/token');
|
|
5
|
+
|
|
6
|
+
const fail = (message, statusCode) => {
|
|
7
|
+
const err = new Error(message);
|
|
8
|
+
err.statusCode = statusCode;
|
|
9
|
+
throw err;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const AuthService = {
|
|
13
|
+
async signup({ name, email, password }) {
|
|
14
|
+
const existing = await User.findOne({ email: email.toLowerCase() });
|
|
15
|
+
if (existing) fail('Email is already registered', 409);
|
|
16
|
+
|
|
17
|
+
const hashed = await bcrypt.hash(password, env.BCRYPT_ROUNDS);
|
|
18
|
+
const user = await User.create({ name, email, password: hashed });
|
|
19
|
+
const tokens = generateTokens(user.toSafeObject());
|
|
20
|
+
|
|
21
|
+
return { user: user.toSafeObject(), ...tokens };
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async login({ email, password }) {
|
|
25
|
+
const user = await User.findOne({ email: email.toLowerCase() });
|
|
26
|
+
if (!user) fail('Invalid credentials', 401);
|
|
27
|
+
|
|
28
|
+
const valid = await bcrypt.compare(password, user.password);
|
|
29
|
+
if (!valid) fail('Invalid credentials', 401);
|
|
30
|
+
|
|
31
|
+
const tokens = generateTokens(user.toSafeObject());
|
|
32
|
+
return { user: user.toSafeObject(), ...tokens };
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async refresh(refreshToken) {
|
|
36
|
+
let payload;
|
|
37
|
+
try {
|
|
38
|
+
payload = verifyRefreshToken(refreshToken);
|
|
39
|
+
} catch {
|
|
40
|
+
fail('Invalid or expired refresh token', 401);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const user = await User.findById(payload.id);
|
|
44
|
+
if (!user) fail('User not found', 401);
|
|
45
|
+
|
|
46
|
+
const tokens = generateTokens(user.toSafeObject());
|
|
47
|
+
return { user: user.toSafeObject(), ...tokens };
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async me(userId) {
|
|
51
|
+
const user = await User.findById(userId);
|
|
52
|
+
if (!user) fail('User not found', 404);
|
|
53
|
+
return user.toSafeObject();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports = AuthService;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { Schema, model } = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const userSchema = new Schema(
|
|
4
|
+
{
|
|
5
|
+
name: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: true,
|
|
8
|
+
trim: true,
|
|
9
|
+
},
|
|
10
|
+
email: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: true,
|
|
13
|
+
unique: true,
|
|
14
|
+
lowercase: true,
|
|
15
|
+
trim: true,
|
|
16
|
+
},
|
|
17
|
+
password: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
role: {
|
|
22
|
+
type: String,
|
|
23
|
+
enum: ['user', 'admin'],
|
|
24
|
+
default: 'user',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{ timestamps: true }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Never expose the password hash in query results
|
|
31
|
+
userSchema.methods.toSafeObject = function () {
|
|
32
|
+
return {
|
|
33
|
+
id: this._id,
|
|
34
|
+
name: this.name,
|
|
35
|
+
email: this.email,
|
|
36
|
+
role: this.role,
|
|
37
|
+
createdAt: this.createdAt,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports = model('User', userSchema);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const DepartmentService = require('./department.service');
|
|
2
|
+
const res_ = require('../../utils/response');
|
|
3
|
+
|
|
4
|
+
const DepartmentController = {
|
|
5
|
+
async create(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const dept = await DepartmentService.create(req.body);
|
|
8
|
+
return res_.created(res, dept, 'Department created');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
return res_.error(res, err.message, err.statusCode || 500);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async list(req, res) {
|
|
15
|
+
try {
|
|
16
|
+
const departments = await DepartmentService.list();
|
|
17
|
+
return res_.success(res, { departments });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return res_.error(res, err.message, 500);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async getById(req, res) {
|
|
24
|
+
try {
|
|
25
|
+
const dept = await DepartmentService.getById(req.params.id);
|
|
26
|
+
if (!dept) return res_.notFound(res, 'Department not found');
|
|
27
|
+
return res_.success(res, dept);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return res_.error(res, err.message, 500);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async update(req, res) {
|
|
34
|
+
try {
|
|
35
|
+
const dept = await DepartmentService.update(req.params.id, req.body);
|
|
36
|
+
if (!dept) return res_.notFound(res, 'Department not found');
|
|
37
|
+
return res_.success(res, dept, 'Department updated');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return res_.error(res, err.message, 500);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async remove(req, res) {
|
|
44
|
+
try {
|
|
45
|
+
const dept = await DepartmentService.remove(req.params.id);
|
|
46
|
+
if (!dept) return res_.notFound(res, 'Department not found');
|
|
47
|
+
return res_.success(res, null, 'Department deleted');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return res_.error(res, err.message, 500);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
module.exports = DepartmentController;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const departmentSchema = new mongoose.Schema({
|
|
4
|
+
departmentCode: { type: String, required: true, unique: true, trim: true },
|
|
5
|
+
departmentName: { type: String, required: true, trim: true },
|
|
6
|
+
grossSalary: { type: Number, required: true },
|
|
7
|
+
totalDeduction: { type: Number, required: true, default: 0 },
|
|
8
|
+
}, { timestamps: true });
|
|
9
|
+
|
|
10
|
+
module.exports = mongoose.model('Department', departmentSchema);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const DepartmentController = require('./department.controller');
|
|
3
|
+
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.use(authenticate);
|
|
8
|
+
|
|
9
|
+
router.post('/', DepartmentController.create);
|
|
10
|
+
router.get('/', DepartmentController.list);
|
|
11
|
+
router.get('/:id', DepartmentController.getById);
|
|
12
|
+
router.put('/:id', DepartmentController.update);
|
|
13
|
+
router.delete('/:id', DepartmentController.remove);
|
|
14
|
+
|
|
15
|
+
module.exports = router;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const Department = require('./department.model');
|
|
2
|
+
|
|
3
|
+
const DepartmentService = {
|
|
4
|
+
async create(data) {
|
|
5
|
+
return Department.create(data);
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
async list() {
|
|
9
|
+
return Department.find().sort({ departmentCode: 1 });
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async getByCode(code) {
|
|
13
|
+
return Department.findOne({ departmentCode: code });
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async getById(id) {
|
|
17
|
+
return Department.findById(id);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async update(id, data) {
|
|
21
|
+
return Department.findByIdAndUpdate(id, data, { new: true });
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async remove(id) {
|
|
25
|
+
return Department.findByIdAndDelete(id);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = DepartmentService;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const EmployeeService = require('./employee.service');
|
|
2
|
+
const res_ = require('../../utils/response');
|
|
3
|
+
|
|
4
|
+
const EmployeeController = {
|
|
5
|
+
async create(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const emp = await EmployeeService.create(req.body);
|
|
8
|
+
return res_.created(res, emp, 'Employee created');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
return res_.error(res, err.message, err.statusCode || 500);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async list(req, res) {
|
|
15
|
+
try {
|
|
16
|
+
const employees = await EmployeeService.list();
|
|
17
|
+
return res_.success(res, { employees });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return res_.error(res, err.message, 500);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async getById(req, res) {
|
|
24
|
+
try {
|
|
25
|
+
const emp = await EmployeeService.getById(req.params.id);
|
|
26
|
+
if (!emp) return res_.notFound(res, 'Employee not found');
|
|
27
|
+
return res_.success(res, emp);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return res_.error(res, err.message, 500);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async update(req, res) {
|
|
34
|
+
try {
|
|
35
|
+
const emp = await EmployeeService.update(req.params.id, req.body);
|
|
36
|
+
if (!emp) return res_.notFound(res, 'Employee not found');
|
|
37
|
+
return res_.success(res, emp, 'Employee updated');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return res_.error(res, err.message, 500);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async remove(req, res) {
|
|
44
|
+
try {
|
|
45
|
+
const emp = await EmployeeService.remove(req.params.id);
|
|
46
|
+
if (!emp) return res_.notFound(res, 'Employee not found');
|
|
47
|
+
return res_.success(res, null, 'Employee deleted');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return res_.error(res, err.message, 500);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async count(req, res) {
|
|
54
|
+
try {
|
|
55
|
+
const count = await EmployeeService.count();
|
|
56
|
+
return res_.success(res, { count });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return res_.error(res, err.message, 500);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
module.exports = EmployeeController;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const employeeSchema = new mongoose.Schema({
|
|
4
|
+
employeeNumber: { type: String, required: true, unique: true, trim: true },
|
|
5
|
+
firstName: { type: String, required: true, trim: true },
|
|
6
|
+
lastName: { type: String, required: true, trim: true },
|
|
7
|
+
position: { type: String, required: true, trim: true },
|
|
8
|
+
address: { type: String, trim: true },
|
|
9
|
+
telephone: { type: String, trim: true },
|
|
10
|
+
gender: { type: String, enum: ['male', 'female'], required: true },
|
|
11
|
+
hiredDate: { type: Date, required: true },
|
|
12
|
+
department: { type: mongoose.Schema.Types.ObjectId, ref: 'Department' },
|
|
13
|
+
}, { timestamps: true });
|
|
14
|
+
|
|
15
|
+
module.exports = mongoose.model('Employee', employeeSchema);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const EmployeeController = require('./employee.controller');
|
|
3
|
+
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.use(authenticate);
|
|
8
|
+
|
|
9
|
+
router.post('/', EmployeeController.create);
|
|
10
|
+
router.get('/', EmployeeController.list);
|
|
11
|
+
router.get('/count', EmployeeController.count);
|
|
12
|
+
router.get('/:id', EmployeeController.getById);
|
|
13
|
+
router.put('/:id', EmployeeController.update);
|
|
14
|
+
router.delete('/:id', EmployeeController.remove);
|
|
15
|
+
|
|
16
|
+
module.exports = router;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const Employee = require('./employee.model');
|
|
2
|
+
|
|
3
|
+
const EmployeeService = {
|
|
4
|
+
async create(data) {
|
|
5
|
+
const emp = await Employee.create(data);
|
|
6
|
+
return Employee.populate(emp, { path: 'department' });
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async list() {
|
|
10
|
+
return Employee.find().populate('department').sort({ createdAt: -1 });
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async getById(id) {
|
|
14
|
+
return Employee.findById(id).populate('department');
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async update(id, data) {
|
|
18
|
+
return Employee.findByIdAndUpdate(id, data, { new: true }).populate('department');
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async remove(id) {
|
|
22
|
+
return Employee.findByIdAndDelete(id);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async count() {
|
|
26
|
+
return Employee.countDocuments();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
module.exports = EmployeeService;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const ExcelService = require('./excel.service');
|
|
2
|
+
const res_ = require('../../utils/response');
|
|
3
|
+
|
|
4
|
+
const ExcelController = {
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/excel/export/users
|
|
7
|
+
* Demo: exports a users sheet directly to the browser.
|
|
8
|
+
* Replace `sampleData` with a real DB query.
|
|
9
|
+
*/
|
|
10
|
+
async exportUsers(req, res) {
|
|
11
|
+
try {
|
|
12
|
+
const sampleData = [
|
|
13
|
+
{ id: 'u-001', name: 'Alice Martin', email: 'alice@example.com', role: 'admin', joined: new Date('2024-01-15'), revenue: 4800 },
|
|
14
|
+
{ id: 'u-002', name: 'Bob Karenzi', email: 'bob@example.com', role: 'user', joined: new Date('2024-03-22'), revenue: 1200 },
|
|
15
|
+
{ id: 'u-003', name: 'Claire Umutesi', email: 'claire@example.com', role: 'user', joined: new Date('2024-07-09'), revenue: 2500 },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const sheets = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Users',
|
|
21
|
+
columns: [
|
|
22
|
+
{ header: 'ID', key: 'id', width: 10 },
|
|
23
|
+
{ header: 'Name', key: 'name', width: 25 },
|
|
24
|
+
{ header: 'Email', key: 'email', width: 35 },
|
|
25
|
+
{ header: 'Role', key: 'role', width: 12 },
|
|
26
|
+
{ header: 'Joined', key: 'joined', width: 14, type: 'date' },
|
|
27
|
+
{ header: 'Revenue', key: 'revenue', width: 15, type: 'currency' },
|
|
28
|
+
],
|
|
29
|
+
rows: sampleData,
|
|
30
|
+
totals: { revenue: `=SUM(F2:F${sampleData.length + 1})` },
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
await ExcelService.exportToResponse(res, 'users-report', sheets);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return res_.error(res, err.message);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* GET /api/excel/export/custom
|
|
42
|
+
* Generic export: pass `sheetName` + `data` as JSON body or query params.
|
|
43
|
+
* Useful for ad-hoc client-driven exports.
|
|
44
|
+
*/
|
|
45
|
+
async exportCustom(req, res) {
|
|
46
|
+
try {
|
|
47
|
+
const { sheetName = 'Export', data = [] } = req.body;
|
|
48
|
+
|
|
49
|
+
if (!Array.isArray(data) || !data.length) {
|
|
50
|
+
return res_.badRequest(res, '`data` must be a non-empty array');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sheet = ExcelService.buildSheetDef(sheetName, data);
|
|
54
|
+
await ExcelService.exportToResponse(res, sheetName, [sheet]);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return res_.error(res, err.message);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = ExcelController;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const ExcelController = require('./excel.controller');
|
|
3
|
+
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
// All export routes require authentication
|
|
8
|
+
router.use(authenticate);
|
|
9
|
+
|
|
10
|
+
router.get('/export/users', ExcelController.exportUsers);
|
|
11
|
+
router.post('/export/custom', ExcelController.exportCustom);
|
|
12
|
+
|
|
13
|
+
module.exports = router;
|