Skip to content

Commit 05ff879

Browse files
committed
feat(logistics): implement PDF invoices, DataTable, and dashboard cards
- Generate PDF invoices with barcode, logo, stamp, and upload to Cloudinary - send email to the reciever if the admin check on auto mailing - Build sortable, draggable, paginated shipment DataTable with actions dropdown - Create responsive ShipmentCard component with sender, receiver, status, dates, and weight/items info - Implement dashboard stats cards with total revenue, total packages, delivery growth, and packages this month - Add loading, error, and no-data states for UI components - Fix TypeScript type issues between TableDelivery, ShipmentData, and ShipmentActions
1 parent ac6e713 commit 05ff879

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+8386
-1426
lines changed

.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Cloudinary
2+
CLOUDINARY_CLOUD_NAME=dk1cria0z
3+
CLOUDINARY_API_KEY=747368826295982
4+
CLOUDINARY_API_SECRET=HbljlQwEoDR6ndvo98KNQy6Lbyk
5+

api/controllers/auth.controller.js

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,22 @@ import jwt from "jsonwebtoken";
22
import User from "../models/user.model.js";
33
import redisClient from "../config/redis.js";
44

5-
function signToken(user) {
5+
function signToken(user, expiresIn = process.env.JWT_EXPIRES || "7d") {
66
return jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, {
7-
expiresIn: process.env.JWT_EXPIRES || "7d",
7+
expiresIn,
88
});
99
}
1010

11+
function signRefreshToken(user) {
12+
return jwt.sign(
13+
{ id: user._id },
14+
process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET,
15+
{
16+
expiresIn: "30d", // Refresh token lasts longer
17+
}
18+
);
19+
}
20+
1121
// ===== LOGIN =====
1222
export const login = async (req, res) => {
1323
const { email, password } = req.body || {};
@@ -20,20 +30,87 @@ export const login = async (req, res) => {
2030
}
2131

2232
const token = signToken(user);
33+
const refreshToken = signRefreshToken(user);
2334

24-
// Store token in Redis with TTL (matches JWT expiry)
35+
// Store both tokens in Redis
2536
const decoded = jwt.decode(token);
2637
const expiresInSec = decoded.exp - Math.floor(Date.now() / 1000);
2738

28-
// ioredis syntax
2939
await redisClient.set(`auth:token:${user._id}`, token, "EX", expiresInSec);
40+
await redisClient.set(
41+
`auth:refresh:${user._id}`,
42+
refreshToken,
43+
"EX",
44+
30 * 24 * 60 * 60
45+
); // 30 days
3046

3147
res.json({
3248
token,
49+
refreshToken,
3350
user: { id: user._id, email: user.email, role: user.role },
3451
});
3552
};
3653

54+
// ===== REFRESH TOKEN =====
55+
export const refreshToken = async (req, res) => {
56+
try {
57+
const { refreshToken } = req.body;
58+
59+
if (!refreshToken) {
60+
return res.status(401).json({ message: "Refresh token required" });
61+
}
62+
63+
// Verify refresh token
64+
const decoded = jwt.verify(
65+
refreshToken,
66+
process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET
67+
);
68+
69+
// Check if refresh token exists in Redis
70+
const storedRefreshToken = await redisClient.get(
71+
`auth:refresh:${decoded.id}`
72+
);
73+
if (!storedRefreshToken || storedRefreshToken !== refreshToken) {
74+
return res.status(401).json({ message: "Invalid refresh token" });
75+
}
76+
77+
// Get user and generate new tokens
78+
const user = await User.findById(decoded.id);
79+
if (!user) {
80+
return res.status(401).json({ message: "User not found" });
81+
}
82+
83+
const newToken = signToken(user);
84+
const newRefreshToken = signRefreshToken(user);
85+
86+
// Update Redis with new tokens
87+
const newDecoded = jwt.decode(newToken);
88+
const expiresInSec = newDecoded.exp - Math.floor(Date.now() / 1000);
89+
90+
await redisClient.set(
91+
`auth:token:${user._id}`,
92+
newToken,
93+
"EX",
94+
expiresInSec
95+
);
96+
await redisClient.set(
97+
`auth:refresh:${user._id}`,
98+
newRefreshToken,
99+
"EX",
100+
30 * 24 * 60 * 60
101+
);
102+
103+
res.json({
104+
token: newToken,
105+
refreshToken: newRefreshToken,
106+
user: { id: user._id, email: user.email, role: user.role },
107+
});
108+
} catch (error) {
109+
console.error("Refresh token error:", error);
110+
res.status(401).json({ message: "Invalid refresh token" });
111+
}
112+
};
113+
37114
// ===== LOGOUT =====
38115
export const logout = async (req, res) => {
39116
try {
@@ -45,7 +122,9 @@ export const logout = async (req, res) => {
45122
const decoded = jwt.decode(token);
46123

47124
if (decoded?.id) {
125+
// Remove both access and refresh tokens
48126
await redisClient.del(`auth:token:${decoded.id}`);
127+
await redisClient.del(`auth:refresh:${decoded.id}`);
49128
}
50129

51130
res.json({ message: "Logged out successfully" });

0 commit comments

Comments
 (0)