create-steve-rogers 1.0.0 → 1.0.2

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.
Files changed (94) hide show
  1. package/apps/SFMS/.env +9 -0
  2. package/apps/SFMS/README.md +0 -0
  3. package/apps/SFMS/backend/.env +9 -0
  4. package/apps/SFMS/backend/.env.example +9 -0
  5. package/apps/SFMS/backend/package-lock.json +1580 -0
  6. package/apps/SFMS/backend/package.json +23 -0
  7. package/apps/SFMS/backend/src/config/database.js +7 -0
  8. package/apps/SFMS/backend/src/config/env.js +35 -0
  9. package/apps/SFMS/backend/src/middleware/authMiddleware.js +32 -0
  10. package/apps/SFMS/backend/src/models/Payment.js +12 -0
  11. package/apps/SFMS/backend/src/models/Student.js +12 -0
  12. package/apps/SFMS/backend/src/models/User.js +13 -0
  13. package/apps/SFMS/backend/src/routes/authRoutes.js +93 -0
  14. package/apps/SFMS/backend/src/routes/paymentRoutes.js +117 -0
  15. package/apps/SFMS/backend/src/routes/reportRoutes.js +59 -0
  16. package/apps/SFMS/backend/src/routes/studentRoutes.js +79 -0
  17. package/apps/SFMS/backend/src/server.js +34 -0
  18. package/apps/SFMS/frontend/.env.example +8 -0
  19. package/apps/SFMS/frontend/dist/assets/index-B08X8imN.css +1 -0
  20. package/apps/SFMS/frontend/dist/assets/index-DVO0_wcb.js +67 -0
  21. package/apps/SFMS/frontend/dist/favicon.svg +4 -0
  22. package/apps/SFMS/frontend/dist/index.html +20 -0
  23. package/apps/SFMS/frontend/index.html +19 -0
  24. package/apps/SFMS/frontend/package-lock.json +2667 -0
  25. package/apps/SFMS/frontend/package.json +23 -0
  26. package/apps/SFMS/frontend/postcss.config.js +6 -0
  27. package/apps/SFMS/frontend/public/favicon.svg +4 -0
  28. package/apps/SFMS/frontend/src/App.jsx +41 -0
  29. package/apps/SFMS/frontend/src/api/apiClient.js +41 -0
  30. package/apps/SFMS/frontend/src/components/AppLayout.jsx +60 -0
  31. package/apps/SFMS/frontend/src/context/AuthContext.jsx +79 -0
  32. package/apps/SFMS/frontend/src/index.css +229 -0
  33. package/apps/SFMS/frontend/src/main.jsx +16 -0
  34. package/apps/SFMS/frontend/src/pages/DashboardPage.jsx +82 -0
  35. package/apps/SFMS/frontend/src/pages/LoginPage.jsx +142 -0
  36. package/apps/SFMS/frontend/src/pages/PaymentsPage.jsx +269 -0
  37. package/apps/SFMS/frontend/src/pages/ReportsPage.jsx +114 -0
  38. package/apps/SFMS/frontend/src/pages/StudentsPage.jsx +257 -0
  39. package/apps/SFMS/frontend/tailwind.config.js +21 -0
  40. package/apps/SFMS/frontend/vite.config.js +35 -0
  41. package/apps/SIMS/.env +4 -0
  42. package/apps/SIMS/README.md +138 -0
  43. package/apps/SIMS/backend/.env +4 -0
  44. package/apps/SIMS/backend/.env.example +4 -0
  45. package/apps/SIMS/backend/package-lock.json +1600 -0
  46. package/apps/SIMS/backend/package.json +22 -0
  47. package/apps/SIMS/backend/src/config/db.js +9 -0
  48. package/apps/SIMS/backend/src/controllers/authController.js +93 -0
  49. package/apps/SIMS/backend/src/controllers/simsReportController.js +94 -0
  50. package/apps/SIMS/backend/src/controllers/sparePartController.js +41 -0
  51. package/apps/SIMS/backend/src/controllers/stockInController.js +45 -0
  52. package/apps/SIMS/backend/src/controllers/stockOutController.js +123 -0
  53. package/apps/SIMS/backend/src/middleware/auth.js +8 -0
  54. package/apps/SIMS/backend/src/models/SparePart.js +17 -0
  55. package/apps/SIMS/backend/src/models/StockIn.js +16 -0
  56. package/apps/SIMS/backend/src/models/StockOut.js +18 -0
  57. package/apps/SIMS/backend/src/models/User.js +11 -0
  58. package/apps/SIMS/backend/src/routes/authRoutes.js +12 -0
  59. package/apps/SIMS/backend/src/routes/simsReportRoutes.js +8 -0
  60. package/apps/SIMS/backend/src/routes/sparePartRoutes.js +8 -0
  61. package/apps/SIMS/backend/src/routes/stockInRoutes.js +8 -0
  62. package/apps/SIMS/backend/src/routes/stockOutRoutes.js +10 -0
  63. package/apps/SIMS/backend/src/server.js +62 -0
  64. package/apps/SIMS/backend/src/utils/passwordPolicy.js +10 -0
  65. package/apps/SIMS/backend/src/utils/sparePartHelpers.js +5 -0
  66. package/apps/SIMS/frontend/dist/assets/index-3hv-vGL2.css +2 -0
  67. package/apps/SIMS/frontend/dist/assets/index-T8XT7M6y.js +19 -0
  68. package/apps/SIMS/frontend/dist/index.html +14 -0
  69. package/apps/SIMS/frontend/index.html +13 -0
  70. package/apps/SIMS/frontend/package-lock.json +3053 -0
  71. package/apps/SIMS/frontend/package.json +31 -0
  72. package/apps/SIMS/frontend/src/App.jsx +112 -0
  73. package/apps/SIMS/frontend/src/api/authApi.js +7 -0
  74. package/apps/SIMS/frontend/src/api/client.js +8 -0
  75. package/apps/SIMS/frontend/src/api/simsReportApi.js +5 -0
  76. package/apps/SIMS/frontend/src/api/sparePartsApi.js +4 -0
  77. package/apps/SIMS/frontend/src/api/stockInApi.js +4 -0
  78. package/apps/SIMS/frontend/src/api/stockOutApi.js +6 -0
  79. package/apps/SIMS/frontend/src/api/usersApi.js +3 -0
  80. package/apps/SIMS/frontend/src/components/AppLayout.jsx +60 -0
  81. package/apps/SIMS/frontend/src/index.css +737 -0
  82. package/apps/SIMS/frontend/src/main.jsx +13 -0
  83. package/apps/SIMS/frontend/src/pages/DashboardPage.jsx +179 -0
  84. package/apps/SIMS/frontend/src/pages/LoginPage.jsx +75 -0
  85. package/apps/SIMS/frontend/src/pages/RegisterPage.jsx +78 -0
  86. package/apps/SIMS/frontend/src/pages/ReportsPage.jsx +108 -0
  87. package/apps/SIMS/frontend/src/pages/ResetPasswordPage.jsx +75 -0
  88. package/apps/SIMS/frontend/src/pages/SparePartPage.jsx +128 -0
  89. package/apps/SIMS/frontend/src/pages/StockInPage.jsx +100 -0
  90. package/apps/SIMS/frontend/src/pages/StockOutPage.jsx +206 -0
  91. package/apps/SIMS/frontend/src/utils/passwordPolicy.js +8 -0
  92. package/apps/SIMS/frontend/vite.config.js +8 -0
  93. package/apps/config.js +13 -0
  94. package/package.json +1 -1
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "sims-backend",
3
+ "version": "1.0.0",
4
+ "main": "src/server.js",
5
+ "scripts": {
6
+ "dev": "nodemon src/server.js",
7
+ "start": "node src/server.js"
8
+ },
9
+ "license": "ISC",
10
+ "dependencies": {
11
+ "bcryptjs": "^3.0.3",
12
+ "connect-mongo": "^6.0.0",
13
+ "cors": "^2.8.6",
14
+ "dotenv": "^17.4.2",
15
+ "express": "^5.2.1",
16
+ "express-session": "^1.19.0",
17
+ "mongoose": "^9.5.0"
18
+ },
19
+ "devDependencies": {
20
+ "nodemon": "^3.1.14"
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const connectDatabase = async () => {
4
+ const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/sims";
5
+ await mongoose.connect(mongoUri);
6
+ console.log("MongoDB connected (SIMS database)");
7
+ };
8
+
9
+ module.exports = connectDatabase;
@@ -0,0 +1,93 @@
1
+ const bcrypt = require("bcryptjs");
2
+ const User = require("../models/User");
3
+ const { strongPasswordError } = require("../utils/passwordPolicy");
4
+
5
+ const normalize = (value) => String(value || "").trim();
6
+
7
+ const register = async (req, res) => {
8
+ try {
9
+ const username = normalize(req.body.username);
10
+ const password = String(req.body.password || "");
11
+ if (!username || !password) {
12
+ return res.status(400).json({ message: "Username and password are required" });
13
+ }
14
+ if (username.length < 3) {
15
+ return res.status(400).json({ message: "Username must be at least 3 characters" });
16
+ }
17
+ const pwdErr = strongPasswordError(password);
18
+ if (pwdErr) {
19
+ return res.status(400).json({ message: pwdErr });
20
+ }
21
+ const existingUser = await User.findOne({ username });
22
+ if (existingUser) {
23
+ return res.status(409).json({ message: "Username already exists" });
24
+ }
25
+ const passwordHash = await bcrypt.hash(password, 10);
26
+ await User.create({ username, passwordHash });
27
+ return res.status(201).json({ message: "User created successfully" });
28
+ } catch (error) {
29
+ return res.status(500).json({ message: error.message });
30
+ }
31
+ };
32
+
33
+ const login = async (req, res) => {
34
+ try {
35
+ const username = normalize(req.body.username);
36
+ const password = String(req.body.password || "");
37
+ if (!username || !password) {
38
+ return res.status(400).json({ message: "Username and password are required" });
39
+ }
40
+ const user = await User.findOne({ username });
41
+ if (!user) {
42
+ return res.status(401).json({ message: "Invalid credentials" });
43
+ }
44
+ const isValid = await bcrypt.compare(password, user.passwordHash);
45
+ if (!isValid) {
46
+ return res.status(401).json({ message: "Invalid credentials" });
47
+ }
48
+ req.session.userId = user._id;
49
+ req.session.username = user.username;
50
+ return res.json({ message: "Login successful", username: user.username });
51
+ } catch (error) {
52
+ return res.status(500).json({ message: error.message });
53
+ }
54
+ };
55
+
56
+ const currentUser = (req, res) => {
57
+ if (!req.session.userId) {
58
+ return res.status(401).json({ message: "Unauthorized" });
59
+ }
60
+ return res.json({ username: req.session.username });
61
+ };
62
+
63
+ const logout = (req, res) => {
64
+ req.session.destroy(() => {
65
+ res.clearCookie("connect.sid");
66
+ res.json({ message: "Logged out" });
67
+ });
68
+ };
69
+
70
+ const recoverPassword = async (req, res) => {
71
+ try {
72
+ const username = normalize(req.body.username);
73
+ const newPassword = String(req.body.newPassword || "");
74
+ if (!username || !newPassword) {
75
+ return res.status(400).json({ message: "Username and new password are required" });
76
+ }
77
+ const pwdErr = strongPasswordError(newPassword);
78
+ if (pwdErr) {
79
+ return res.status(400).json({ message: pwdErr });
80
+ }
81
+ const user = await User.findOne({ username });
82
+ if (!user) {
83
+ return res.status(404).json({ message: "User not found" });
84
+ }
85
+ user.passwordHash = await bcrypt.hash(newPassword, 10);
86
+ await user.save();
87
+ return res.json({ message: "Password updated successfully" });
88
+ } catch (error) {
89
+ return res.status(500).json({ message: error.message });
90
+ }
91
+ };
92
+
93
+ module.exports = { register, login, currentUser, logout, recoverPassword };
@@ -0,0 +1,94 @@
1
+ const SparePart = require("../models/SparePart");
2
+ const StockIn = require("../models/StockIn");
3
+ const StockOut = require("../models/StockOut");
4
+
5
+ const dayRange = (dateStr) => {
6
+ const d = new Date(String(dateStr));
7
+ if (Number.isNaN(d.getTime())) return null;
8
+ const start = new Date(d);
9
+ start.setHours(0, 0, 0, 0);
10
+ const end = new Date(start);
11
+ end.setDate(end.getDate() + 1);
12
+ return { start, end };
13
+ };
14
+
15
+ const dailyStockStatus = async (req, res) => {
16
+ try {
17
+ const { date } = req.query;
18
+ if (!date) {
19
+ return res.status(400).json({ message: "date query (YYYY-MM-DD) is required" });
20
+ }
21
+ const range = dayRange(date);
22
+ if (!range) {
23
+ return res.status(400).json({ message: "Invalid date" });
24
+ }
25
+ const parts = await SparePart.find();
26
+ const rows = [];
27
+ for (const p of parts) {
28
+ const inSum = await StockIn.aggregate([
29
+ {
30
+ $match: {
31
+ sparePart: p._id,
32
+ stockInDate: { $gte: range.start, $lt: range.end },
33
+ },
34
+ },
35
+ { $group: { _id: null, total: { $sum: "$stockInQuantity" } } },
36
+ ]);
37
+ const outSum = await StockOut.aggregate([
38
+ {
39
+ $match: {
40
+ sparePart: p._id,
41
+ stockOutDate: { $gte: range.start, $lt: range.end },
42
+ },
43
+ },
44
+ { $group: { _id: null, total: { $sum: "$stockOutQuantity" } } },
45
+ ]);
46
+ const stockInDay = inSum[0]?.total || 0;
47
+ const stockOutDay = outSum[0]?.total || 0;
48
+ rows.push({
49
+ spareName: p.name,
50
+ category: p.category,
51
+ storedQuantity: p.quantity,
52
+ stockInQuantity: stockInDay,
53
+ stockOutQuantity: stockOutDay,
54
+ remainingQuantity: p.quantity,
55
+ });
56
+ }
57
+ return res.json(rows);
58
+ } catch (error) {
59
+ return res.status(500).json({ message: error.message });
60
+ }
61
+ };
62
+
63
+ const dailyStockOut = async (req, res) => {
64
+ try {
65
+ const { date } = req.query;
66
+ if (!date) {
67
+ return res.status(400).json({ message: "date query (YYYY-MM-DD) is required" });
68
+ }
69
+ const range = dayRange(date);
70
+ if (!range) {
71
+ return res.status(400).json({ message: "Invalid date" });
72
+ }
73
+ const list = await StockOut.find({
74
+ stockOutDate: { $gte: range.start, $lt: range.end },
75
+ })
76
+ .populate("sparePart", "name category")
77
+ .sort({ stockOutDate: 1 });
78
+ return res.json(
79
+ list.map((r) => ({
80
+ id: r._id,
81
+ spareName: r.sparePart?.name || "",
82
+ category: r.sparePart?.category || "",
83
+ stockOutQuantity: r.stockOutQuantity,
84
+ stockOutUnitPrice: r.stockOutUnitPrice,
85
+ stockOutTotalPrice: r.stockOutTotalPrice,
86
+ stockOutDate: r.stockOutDate,
87
+ }))
88
+ );
89
+ } catch (error) {
90
+ return res.status(500).json({ message: error.message });
91
+ }
92
+ };
93
+
94
+ module.exports = { dailyStockStatus, dailyStockOut };
@@ -0,0 +1,41 @@
1
+ const SparePart = require("../models/SparePart");
2
+ const { recalcPartTotals } = require("../utils/sparePartHelpers");
3
+
4
+ const create = async (req, res) => {
5
+ try {
6
+ const { name, category, quantity, unitPrice } = req.body;
7
+ if (!name || !category || quantity === undefined || unitPrice === undefined) {
8
+ return res.status(400).json({ message: "Name, category, quantity, and unit price are required" });
9
+ }
10
+ const q = Number(quantity);
11
+ const u = Number(unitPrice);
12
+ if (Number.isNaN(q) || q < 0 || Number.isNaN(u) || u < 0) {
13
+ return res.status(400).json({ message: "Invalid quantity or unit price" });
14
+ }
15
+ const t = q * u;
16
+ const part = await SparePart.create({
17
+ name: String(name).trim(),
18
+ category: String(category).trim(),
19
+ quantity: q,
20
+ unitPrice: u,
21
+ totalPrice: t,
22
+ });
23
+ return res.status(201).json(part);
24
+ } catch (error) {
25
+ if (error.code === 11000) {
26
+ return res.status(409).json({ message: "A spare part with this name and category already exists" });
27
+ }
28
+ return res.status(500).json({ message: error.message });
29
+ }
30
+ };
31
+
32
+ const list = async (_req, res) => {
33
+ try {
34
+ const items = await SparePart.find().sort({ name: 1 });
35
+ return res.json(items);
36
+ } catch (error) {
37
+ return res.status(500).json({ message: error.message });
38
+ }
39
+ };
40
+
41
+ module.exports = { create, list };
@@ -0,0 +1,45 @@
1
+ const SparePart = require("../models/SparePart");
2
+ const StockIn = require("../models/StockIn");
3
+ const { recalcPartTotals } = require("../utils/sparePartHelpers");
4
+
5
+ const create = async (req, res) => {
6
+ try {
7
+ const { sparePartId, stockInQuantity, stockInDate } = req.body;
8
+ if (!sparePartId || stockInQuantity === undefined || !stockInDate) {
9
+ return res.status(400).json({ message: "Spare part, stock-in quantity, and date are required" });
10
+ }
11
+ const qty = Number(stockInQuantity);
12
+ if (Number.isNaN(qty) || qty < 1) {
13
+ return res.status(400).json({ message: "Stock-in quantity must be at least 1" });
14
+ }
15
+ const part = await SparePart.findById(sparePartId);
16
+ if (!part) {
17
+ return res.status(404).json({ message: "Spare part not found" });
18
+ }
19
+ part.quantity += qty;
20
+ recalcPartTotals(part);
21
+ await part.save();
22
+
23
+ const record = await StockIn.create({
24
+ sparePart: sparePartId,
25
+ stockInQuantity: qty,
26
+ stockInDate: new Date(stockInDate),
27
+ });
28
+ return res.status(201).json(record);
29
+ } catch (error) {
30
+ return res.status(500).json({ message: error.message });
31
+ }
32
+ };
33
+
34
+ const list = async (_req, res) => {
35
+ try {
36
+ const list = await StockIn.find()
37
+ .populate("sparePart", "name category")
38
+ .sort({ stockInDate: -1 });
39
+ return res.json(list);
40
+ } catch (error) {
41
+ return res.status(500).json({ message: error.message });
42
+ }
43
+ };
44
+
45
+ module.exports = { create, list };
@@ -0,0 +1,123 @@
1
+ const SparePart = require("../models/SparePart");
2
+ const StockOut = require("../models/StockOut");
3
+ const { recalcPartTotals } = require("../utils/sparePartHelpers");
4
+
5
+ const create = async (req, res) => {
6
+ try {
7
+ const { sparePartId, stockOutQuantity, stockOutUnitPrice, stockOutDate } = req.body;
8
+ if (!sparePartId || stockOutQuantity === undefined || stockOutUnitPrice === undefined || !stockOutDate) {
9
+ return res
10
+ .status(400)
11
+ .json({ message: "Spare part, quantity, unit price, and date are required" });
12
+ }
13
+ const qty = Number(stockOutQuantity);
14
+ const unit = Number(stockOutUnitPrice);
15
+ if (Number.isNaN(qty) || qty < 1) {
16
+ return res.status(400).json({ message: "Invalid stock-out quantity" });
17
+ }
18
+ if (Number.isNaN(unit) || unit < 0) {
19
+ return res.status(400).json({ message: "Invalid unit price" });
20
+ }
21
+ const part = await SparePart.findById(sparePartId);
22
+ if (!part) {
23
+ return res.status(404).json({ message: "Spare part not found" });
24
+ }
25
+ if (part.quantity < qty) {
26
+ return res.status(400).json({ message: "Not enough quantity in stock" });
27
+ }
28
+ part.quantity -= qty;
29
+ recalcPartTotals(part);
30
+ await part.save();
31
+ const total = qty * unit;
32
+ const record = await StockOut.create({
33
+ sparePart: sparePartId,
34
+ stockOutQuantity: qty,
35
+ stockOutUnitPrice: unit,
36
+ stockOutTotalPrice: total,
37
+ stockOutDate: new Date(stockOutDate),
38
+ });
39
+ return res.status(201).json(record);
40
+ } catch (error) {
41
+ return res.status(500).json({ message: error.message });
42
+ }
43
+ };
44
+
45
+ const list = async (_req, res) => {
46
+ try {
47
+ const list = await StockOut.find()
48
+ .populate("sparePart", "name category")
49
+ .sort({ stockOutDate: -1 });
50
+ return res.json(list);
51
+ } catch (error) {
52
+ return res.status(500).json({ message: error.message });
53
+ }
54
+ };
55
+
56
+ const update = async (req, res) => {
57
+ try {
58
+ const current = await StockOut.findById(req.params.id);
59
+ if (!current) {
60
+ return res.status(404).json({ message: "Stock out not found" });
61
+ }
62
+ const part = await SparePart.findById(current.sparePart);
63
+ if (!part) {
64
+ return res.status(404).json({ message: "Spare part not found" });
65
+ }
66
+ part.quantity += current.stockOutQuantity;
67
+ recalcPartTotals(part);
68
+
69
+ const qty = Number(req.body.stockOutQuantity);
70
+ const unit = Number(req.body.stockOutUnitPrice);
71
+ const failRestore = async () => {
72
+ part.quantity -= current.stockOutQuantity;
73
+ recalcPartTotals(part);
74
+ await part.save();
75
+ };
76
+ if (Number.isNaN(qty) || qty < 1) {
77
+ await failRestore();
78
+ return res.status(400).json({ message: "Invalid stock-out quantity" });
79
+ }
80
+ if (Number.isNaN(unit) || unit < 0) {
81
+ await failRestore();
82
+ return res.status(400).json({ message: "Invalid unit price" });
83
+ }
84
+ if (part.quantity < qty) {
85
+ await failRestore();
86
+ return res.status(400).json({ message: "Not enough quantity in stock" });
87
+ }
88
+ part.quantity -= qty;
89
+ recalcPartTotals(part);
90
+ await part.save();
91
+ if (req.body.stockOutDate) {
92
+ current.stockOutDate = new Date(req.body.stockOutDate);
93
+ }
94
+ current.stockOutQuantity = qty;
95
+ current.stockOutUnitPrice = unit;
96
+ current.stockOutTotalPrice = qty * unit;
97
+ await current.save();
98
+ return res.json(current);
99
+ } catch (error) {
100
+ return res.status(500).json({ message: error.message });
101
+ }
102
+ };
103
+
104
+ const remove = async (req, res) => {
105
+ try {
106
+ const current = await StockOut.findById(req.params.id);
107
+ if (!current) {
108
+ return res.status(404).json({ message: "Stock out not found" });
109
+ }
110
+ const part = await SparePart.findById(current.sparePart);
111
+ if (part) {
112
+ part.quantity += current.stockOutQuantity;
113
+ recalcPartTotals(part);
114
+ await part.save();
115
+ }
116
+ await StockOut.findByIdAndDelete(req.params.id);
117
+ return res.json({ message: "Stock out removed" });
118
+ } catch (error) {
119
+ return res.status(500).json({ message: error.message });
120
+ }
121
+ };
122
+
123
+ module.exports = { create, list, update, remove };
@@ -0,0 +1,8 @@
1
+ const requireAuth = (req, res, next) => {
2
+ if (!req.session.userId) {
3
+ return res.status(401).json({ message: "Unauthorized" });
4
+ }
5
+ return next();
6
+ };
7
+
8
+ module.exports = requireAuth;
@@ -0,0 +1,17 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const sparePartSchema = new mongoose.Schema(
4
+ {
5
+ name: { type: String, required: true, trim: true },
6
+ category: { type: String, required: true, trim: true },
7
+ quantity: { type: Number, required: true, min: 0, default: 0 },
8
+ unitPrice: { type: Number, required: true, min: 0 },
9
+ totalPrice: { type: Number, required: true, min: 0 },
10
+ },
11
+ { timestamps: true }
12
+ );
13
+
14
+ // Helps satisfy “duplicate data entry is eliminated” (assessment checklist 2.12)
15
+ sparePartSchema.index({ name: 1, category: 1 }, { unique: true });
16
+
17
+ module.exports = mongoose.model("SparePart", sparePartSchema);
@@ -0,0 +1,16 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const stockInSchema = new mongoose.Schema(
4
+ {
5
+ sparePart: {
6
+ type: mongoose.Schema.Types.ObjectId,
7
+ ref: "SparePart",
8
+ required: true,
9
+ },
10
+ stockInQuantity: { type: Number, required: true, min: 1 },
11
+ stockInDate: { type: Date, required: true },
12
+ },
13
+ { timestamps: true }
14
+ );
15
+
16
+ module.exports = mongoose.model("StockIn", stockInSchema);
@@ -0,0 +1,18 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const stockOutSchema = new mongoose.Schema(
4
+ {
5
+ sparePart: {
6
+ type: mongoose.Schema.Types.ObjectId,
7
+ ref: "SparePart",
8
+ required: true,
9
+ },
10
+ stockOutQuantity: { type: Number, required: true, min: 1 },
11
+ stockOutUnitPrice: { type: Number, required: true, min: 0 },
12
+ stockOutTotalPrice: { type: Number, required: true, min: 0 },
13
+ stockOutDate: { type: Date, required: true },
14
+ },
15
+ { timestamps: true }
16
+ );
17
+
18
+ module.exports = mongoose.model("StockOut", stockOutSchema);
@@ -0,0 +1,11 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const userSchema = new mongoose.Schema(
4
+ {
5
+ username: { type: String, required: true, unique: true, trim: true },
6
+ passwordHash: { type: String, required: true },
7
+ },
8
+ { timestamps: true }
9
+ );
10
+
11
+ module.exports = mongoose.model("User", userSchema);
@@ -0,0 +1,12 @@
1
+ const express = require("express");
2
+ const authController = require("../controllers/authController");
3
+
4
+ const router = express.Router();
5
+
6
+ router.post("/register", authController.register);
7
+ router.post("/login", authController.login);
8
+ router.get("/me", authController.currentUser);
9
+ router.post("/logout", authController.logout);
10
+ router.post("/recover", authController.recoverPassword);
11
+
12
+ module.exports = router;
@@ -0,0 +1,8 @@
1
+ const express = require("express");
2
+ const simsReportController = require("../controllers/simsReportController");
3
+ const requireAuth = require("../middleware/auth");
4
+
5
+ const router = express.Router();
6
+ router.get("/daily-stock-status", requireAuth, simsReportController.dailyStockStatus);
7
+ router.get("/daily-stockout", requireAuth, simsReportController.dailyStockOut);
8
+ module.exports = router;
@@ -0,0 +1,8 @@
1
+ const express = require("express");
2
+ const sparePartController = require("../controllers/sparePartController");
3
+ const requireAuth = require("../middleware/auth");
4
+
5
+ const router = express.Router();
6
+ router.post("/", requireAuth, sparePartController.create);
7
+ router.get("/", requireAuth, sparePartController.list);
8
+ module.exports = router;
@@ -0,0 +1,8 @@
1
+ const express = require("express");
2
+ const stockInController = require("../controllers/stockInController");
3
+ const requireAuth = require("../middleware/auth");
4
+
5
+ const router = express.Router();
6
+ router.post("/", requireAuth, stockInController.create);
7
+ router.get("/", requireAuth, stockInController.list);
8
+ module.exports = router;
@@ -0,0 +1,10 @@
1
+ const express = require("express");
2
+ const stockOutController = require("../controllers/stockOutController");
3
+ const requireAuth = require("../middleware/auth");
4
+
5
+ const router = express.Router();
6
+ router.post("/", requireAuth, stockOutController.create);
7
+ router.get("/", requireAuth, stockOutController.list);
8
+ router.put("/:id", requireAuth, stockOutController.update);
9
+ router.delete("/:id", requireAuth, stockOutController.remove);
10
+ module.exports = router;
@@ -0,0 +1,62 @@
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const session = require("express-session");
4
+ const connectMongo = require("connect-mongo");
5
+ const dotenv = require("dotenv");
6
+ const connectDatabase = require("./config/db");
7
+ const authRoutes = require("./routes/authRoutes");
8
+ const sparePartRoutes = require("./routes/sparePartRoutes");
9
+ const stockInRoutes = require("./routes/stockInRoutes");
10
+ const stockOutRoutes = require("./routes/stockOutRoutes");
11
+ const simsReportRoutes = require("./routes/simsReportRoutes");
12
+
13
+ dotenv.config();
14
+
15
+ const app = express();
16
+ const PORT = process.env.PORT || 5001;
17
+ const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:5174";
18
+ const MongoStore = connectMongo.default || connectMongo.MongoStore || connectMongo;
19
+ const MONGO_URI = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/sims";
20
+
21
+ app.use(
22
+ cors({
23
+ origin: FRONTEND_URL,
24
+ credentials: true,
25
+ })
26
+ );
27
+ app.use(express.json());
28
+ app.set("trust proxy", 1);
29
+ app.use(
30
+ session({
31
+ name: "sims.sid",
32
+ secret: process.env.SESSION_SECRET || "sims-exam-secret",
33
+ resave: false,
34
+ saveUninitialized: false,
35
+ store: MongoStore.create({ mongoUrl: MONGO_URI }),
36
+ cookie: {
37
+ httpOnly: true,
38
+ sameSite: "lax",
39
+ maxAge: 1000 * 60 * 60 * 8,
40
+ },
41
+ })
42
+ );
43
+
44
+ app.get("/api/health", (_req, res) => {
45
+ res.json({ message: "SIMS API is running" });
46
+ });
47
+ app.use("/api/auth", authRoutes);
48
+ app.use("/api/spare-parts", sparePartRoutes);
49
+ app.use("/api/stock-in", stockInRoutes);
50
+ app.use("/api/stock-out", stockOutRoutes);
51
+ app.use("/api/reports", simsReportRoutes);
52
+
53
+ connectDatabase()
54
+ .then(() => {
55
+ app.listen(PORT, () => {
56
+ console.log(`SIMS server running on port ${PORT}`);
57
+ });
58
+ })
59
+ .catch((error) => {
60
+ console.error("Failed to start server:", error.message);
61
+ process.exit(1);
62
+ });
@@ -0,0 +1,10 @@
1
+ const strongPasswordError = (password) => {
2
+ const p = String(password || "");
3
+ if (p.length < 8) return "Password must be at least 8 characters (strong password required)";
4
+ if (!/[A-Z]/.test(p)) return "Password must include an uppercase letter";
5
+ if (!/[a-z]/.test(p)) return "Password must include a lowercase letter";
6
+ if (!/[0-9]/.test(p)) return "Password must include a number";
7
+ return null;
8
+ };
9
+
10
+ module.exports = { strongPasswordError };
@@ -0,0 +1,5 @@
1
+ const recalcPartTotals = (part) => {
2
+ part.totalPrice = part.quantity * part.unitPrice;
3
+ };
4
+
5
+ module.exports = { recalcPartTotals };