@imksh/create 1.0.31 → 1.0.32
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/README.md +2 -0
- package/package.json +1 -1
- package/src/commands/create.js +2 -0
- package/src/index.js +1 -1
- package/templates/layers/next/javascript/package.json +8 -2
- package/templates/layers/next/typescript/.eslintrc.json +12 -0
- package/templates/layers/next/typescript/package.json +14 -3
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -1
- package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
- package/templates/layers/next/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +2 -1
- package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
- package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
- package/templates/layers/next/typescript/src/services/authService.ts +1 -1
- package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/node/javascript/.env.example +3 -1
- package/templates/layers/node/javascript/package.json +12 -3
- package/templates/layers/node/javascript/src/app.js +2 -1
- package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
- package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
- package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
- package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
- package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node/typescript/.env.example +3 -1
- package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node/typescript/package.json +16 -2
- package/templates/layers/node/typescript/src/app.ts +3 -2
- package/templates/layers/node/typescript/src/config/db.ts +3 -2
- package/templates/layers/node/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
- package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
- package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
- package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
- package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
- package/templates/layers/node-prisma/javascript/.env.example +3 -1
- package/templates/layers/node-prisma/javascript/package.json +11 -2
- package/templates/layers/node-prisma/javascript/src/app.js +2 -1
- package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
- package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
- package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
- package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node-prisma/typescript/.env.example +3 -1
- package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node-prisma/typescript/package.json +19 -5
- package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
- package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
- package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
- package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
- package/templates/layers/prisma/javascript/package.json +8 -0
- package/templates/layers/prisma/typescript/package.json +14 -1
- package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
- package/templates/layers/react/javascript/package.json +6 -0
- package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
- package/templates/layers/react/typescript/package.json +11 -2
- package/templates/layers/react/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +1 -1
- package/templates/layers/react/typescript/src/main.tsx +11 -11
- package/templates/layers/react/typescript/src/services/authService.ts +1 -1
- package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/react-native/base/app.json +9 -22
- package/templates/layers/react-native/base/eslint.config.js +5 -1
- package/templates/layers/react-native/base/package.json +11 -1
- package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
- package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
- package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
- package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
- package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
- package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
- package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
- package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
- package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
- package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
- package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
- package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
- package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
- package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
- package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
- package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
- package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
- package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
- package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
- package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
- package/templates/layers/react-native/base/src/config/api.ts +5 -5
- package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
- package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
- package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
- package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
- package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
- package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
- package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
- package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
- package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
- package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
- package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
- package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
- package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
- /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OAuth2Client } from "google-auth-library";
|
|
1
2
|
import User from "../models/user.model.js";
|
|
2
3
|
import Otp from "../models/otp.model.js";
|
|
3
4
|
import bcrypt from "bcrypt";
|
|
@@ -5,185 +6,151 @@ import { generateToken } from "../utils/jwt.js";
|
|
|
5
6
|
import { sendOtpEmail } from "../utils/emailOtp.js";
|
|
6
7
|
import { uploadSingleToCloudinary, deleteFromCloudinary } from "../utils/cloudinaryUpload.js";
|
|
7
8
|
import logger from "../utils/logger.js";
|
|
9
|
+
import { ApiError } from "../utils/ApiError.js";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
* User Signup
|
|
11
|
-
*/
|
|
12
|
-
export const signup = async ({ name, email, phone, password, otp }) => {
|
|
13
|
-
const existingUser = await User.findOne({ email });
|
|
14
|
-
if (existingUser) {
|
|
15
|
-
const err = new Error("User already exists");
|
|
16
|
-
err.status = 409;
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const existingOtp = await Otp.findOne({ email });
|
|
21
|
-
if (!existingOtp) {
|
|
22
|
-
const err = new Error("OTP match failed");
|
|
23
|
-
err.status = 400;
|
|
24
|
-
throw err;
|
|
25
|
-
}
|
|
26
|
-
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
27
|
-
if (!isMatched) {
|
|
28
|
-
const err = new Error("OTP match failed");
|
|
29
|
-
err.status = 400;
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const salt = await bcrypt.genSalt(10);
|
|
34
|
-
const hashedPassword = await bcrypt.hash(password, salt);
|
|
11
|
+
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID || "");
|
|
35
12
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
password: hashedPassword,
|
|
44
|
-
avatar: {
|
|
45
|
-
url: photoURL,
|
|
46
|
-
publicId: "",
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await existingOtp.deleteOne(); // delete OTP after successful signup
|
|
51
|
-
return newUser;
|
|
13
|
+
/**
|
|
14
|
+
* User Signup
|
|
15
|
+
*/
|
|
16
|
+
export const signup = async ({ name, email, phone, password, otp }) => {
|
|
17
|
+
const existingUser = await User.findOne({ email });
|
|
18
|
+
if (existingUser) {
|
|
19
|
+
throw new ApiError(409, "User already exists");
|
|
52
20
|
}
|
|
53
|
-
/**
|
|
54
|
-
* User Login
|
|
55
|
-
*/
|
|
56
|
-
export const login = async ({ email, password }) => {
|
|
57
|
-
const user = await User.findOne({ email });
|
|
58
|
-
if (!user) {
|
|
59
|
-
const err = new Error("User not found");
|
|
60
|
-
err.status = 404;
|
|
61
|
-
throw err;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const isValid = await bcrypt.compare(password, user.password);
|
|
65
|
-
if (!isValid) {
|
|
66
|
-
const err = new Error("Invalid Credentials");
|
|
67
|
-
err.status = 401;
|
|
68
|
-
throw err;
|
|
69
|
-
}
|
|
70
21
|
|
|
71
|
-
|
|
22
|
+
const existingOtp = await Otp.findOne({ email });
|
|
23
|
+
if (!existingOtp) {
|
|
24
|
+
throw new ApiError(400, "OTP match failed");
|
|
72
25
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
export const generateSignupOtp = async (email) => {
|
|
77
|
-
const user = await User.findOne({ email });
|
|
78
|
-
if (user) {
|
|
79
|
-
const err = new Error("User already exists");
|
|
80
|
-
err.status = 409;
|
|
81
|
-
throw err;
|
|
82
|
-
}
|
|
83
|
-
const existingOtp = await Otp.findOne({ email });
|
|
84
|
-
if (existingOtp) {
|
|
85
|
-
await existingOtp.deleteOne();
|
|
86
|
-
}
|
|
87
|
-
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
88
|
-
const hashedOtp = await bcrypt.hash(otp, 10);
|
|
89
|
-
await Otp.create({ email, otp: hashedOtp });
|
|
90
|
-
await sendOtpEmail(email, otp);
|
|
91
|
-
return true;
|
|
26
|
+
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
27
|
+
if (!isMatched) {
|
|
28
|
+
throw new ApiError(400, "OTP match failed");
|
|
92
29
|
}
|
|
93
|
-
export const generateOtp = async (email) => {
|
|
94
|
-
const user = await User.findOne({ email });
|
|
95
|
-
if (!user) {
|
|
96
|
-
const err = new Error("User not found");
|
|
97
|
-
err.status = 404;
|
|
98
|
-
throw err;
|
|
99
|
-
}
|
|
100
30
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
31
|
+
const salt = await bcrypt.genSalt(10);
|
|
32
|
+
const hashedPassword = await bcrypt.hash(password, salt);
|
|
33
|
+
const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
|
|
34
|
+
|
|
35
|
+
const newUser = await User.create({
|
|
36
|
+
name,
|
|
37
|
+
email: email.toLowerCase(),
|
|
38
|
+
phone,
|
|
39
|
+
role: "user",
|
|
40
|
+
password: hashedPassword,
|
|
41
|
+
avatar: { url: photoURL, publicId: "" },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await existingOtp.deleteOne();
|
|
45
|
+
return newUser;
|
|
46
|
+
};
|
|
113
47
|
|
|
114
|
-
|
|
115
|
-
|
|
48
|
+
/**
|
|
49
|
+
* User Login
|
|
50
|
+
*/
|
|
51
|
+
export const login = async ({ email, password }) => {
|
|
52
|
+
const user = await User.findOne({ email });
|
|
53
|
+
if (!user || !user.password) {
|
|
54
|
+
throw new ApiError(401, "Invalid Credentials");
|
|
116
55
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const err = new Error("User not found");
|
|
124
|
-
err.status = 404;
|
|
125
|
-
throw err;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const existingOtp = await Otp.findOne({ email });
|
|
129
|
-
if (!existingOtp) {
|
|
130
|
-
const err = new Error("OTP match failed");
|
|
131
|
-
err.status = 400;
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
136
|
-
if (!isMatched) {
|
|
137
|
-
const err = new Error("OTP match failed");
|
|
138
|
-
err.status = 400;
|
|
139
|
-
throw err;
|
|
140
|
-
}
|
|
56
|
+
const isValid = await bcrypt.compare(password, user.password);
|
|
57
|
+
if (!isValid) {
|
|
58
|
+
throw new ApiError(401, "Invalid Credentials");
|
|
59
|
+
}
|
|
60
|
+
return user;
|
|
61
|
+
};
|
|
141
62
|
|
|
142
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Generate Signup OTP
|
|
65
|
+
*/
|
|
66
|
+
export const generateSignupOtp = async (email) => {
|
|
67
|
+
const user = await User.findOne({ email });
|
|
68
|
+
if (user) {
|
|
69
|
+
throw new ApiError(409, "User already exists");
|
|
143
70
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
export const resetPassword = async (user, newPassword) => {
|
|
148
|
-
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
149
|
-
user.password = hashedPassword;
|
|
150
|
-
await user.save();
|
|
151
|
-
return true;
|
|
71
|
+
const existingOtp = await Otp.findOne({ email });
|
|
72
|
+
if (existingOtp) {
|
|
73
|
+
await existingOtp.deleteOne();
|
|
152
74
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const err = new Error("User not found");
|
|
160
|
-
err.status = 404;
|
|
161
|
-
throw err;
|
|
162
|
-
}
|
|
75
|
+
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
76
|
+
const hashedOtp = await bcrypt.hash(otp, 10);
|
|
77
|
+
await Otp.create({ email, otp: hashedOtp });
|
|
78
|
+
await sendOtpEmail(email, otp);
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
163
81
|
|
|
164
|
-
|
|
165
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Generate OTP
|
|
84
|
+
*/
|
|
85
|
+
export const generateOtp = async (email) => {
|
|
86
|
+
const user = await User.findOne({ email });
|
|
87
|
+
if (!user) {
|
|
88
|
+
throw new ApiError(404, "User not found");
|
|
89
|
+
}
|
|
90
|
+
const existingOtp = await Otp.findOne({ email });
|
|
91
|
+
if (existingOtp) {
|
|
92
|
+
await existingOtp.deleteOne();
|
|
93
|
+
}
|
|
94
|
+
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
95
|
+
const hashedOtp = await bcrypt.hash(otp, 10);
|
|
96
|
+
await Otp.create({ email, otp: hashedOtp });
|
|
97
|
+
await sendOtpEmail(email, otp);
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
166
100
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Verify OTP
|
|
103
|
+
*/
|
|
104
|
+
export const verifyOtp = async ({ email, otp }) => {
|
|
105
|
+
const user = await User.findOne({ email });
|
|
106
|
+
if (!user) {
|
|
107
|
+
throw new ApiError(404, "User not found");
|
|
108
|
+
}
|
|
109
|
+
const existingOtp = await Otp.findOne({ email });
|
|
110
|
+
if (!existingOtp) {
|
|
111
|
+
throw new ApiError(400, "OTP match failed");
|
|
112
|
+
}
|
|
113
|
+
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
114
|
+
if (!isMatched) {
|
|
115
|
+
throw new ApiError(400, "OTP match failed");
|
|
116
|
+
}
|
|
117
|
+
return user;
|
|
118
|
+
};
|
|
171
119
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Reset Password
|
|
122
|
+
*/
|
|
123
|
+
export const resetPassword = async (user, newPassword) => {
|
|
124
|
+
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
125
|
+
user.password = hashedPassword;
|
|
126
|
+
await user.save();
|
|
127
|
+
return true;
|
|
128
|
+
};
|
|
177
129
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Update Profile
|
|
132
|
+
*/
|
|
133
|
+
export const updateProfile = async (userId, { name, phone, file }) => {
|
|
134
|
+
const user = await User.findById(userId);
|
|
135
|
+
if (!user) {
|
|
136
|
+
throw new ApiError(404, "User not found");
|
|
137
|
+
}
|
|
138
|
+
if (name) user.name = name;
|
|
139
|
+
if (phone) user.phone = phone;
|
|
140
|
+
if (file) {
|
|
141
|
+
const uploadResult = await uploadSingleToCloudinary(file, {
|
|
142
|
+
folder: "codelab/avatars",
|
|
143
|
+
});
|
|
144
|
+
if (user.avatar && user.avatar.publicId) {
|
|
145
|
+
await deleteFromCloudinary(user.avatar.publicId).catch((err) =>
|
|
146
|
+
logger.error("Old avatar delete error", err)
|
|
147
|
+
);
|
|
182
148
|
}
|
|
183
|
-
|
|
184
|
-
await user.save();
|
|
185
|
-
return user;
|
|
149
|
+
user.avatar = { url: uploadResult.url, publicId: uploadResult.publicId };
|
|
186
150
|
}
|
|
151
|
+
await user.save();
|
|
152
|
+
return user;
|
|
153
|
+
};
|
|
187
154
|
|
|
188
155
|
/**
|
|
189
156
|
* Save Expo Push Token
|
|
@@ -191,9 +158,7 @@ export const updateProfile = async (userId, { name, phone, file }) => {
|
|
|
191
158
|
export const savePushToken = async (userId, token) => {
|
|
192
159
|
const user = await User.findById(userId);
|
|
193
160
|
if (!user) {
|
|
194
|
-
|
|
195
|
-
err.status = 404;
|
|
196
|
-
throw err;
|
|
161
|
+
throw new ApiError(404, "User not found");
|
|
197
162
|
}
|
|
198
163
|
if (!user.expoPushTokens.includes(token)) {
|
|
199
164
|
user.expoPushTokens.push(token);
|
|
@@ -201,3 +166,33 @@ export const savePushToken = async (userId, token) => {
|
|
|
201
166
|
}
|
|
202
167
|
return user;
|
|
203
168
|
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Google Login
|
|
172
|
+
*/
|
|
173
|
+
export const googleLogin = async (idToken) => {
|
|
174
|
+
const ticket = await googleClient.verifyIdToken({
|
|
175
|
+
idToken,
|
|
176
|
+
audience: process.env.GOOGLE_CLIENT_ID,
|
|
177
|
+
});
|
|
178
|
+
const payload = ticket.getPayload();
|
|
179
|
+
if (!payload) {
|
|
180
|
+
throw new ApiError(401, "Invalid Google Token");
|
|
181
|
+
}
|
|
182
|
+
const { email, name, picture } = payload;
|
|
183
|
+
if (!email) {
|
|
184
|
+
throw new ApiError(400, "Email not found in Google Token");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let user = await User.findOne({ email });
|
|
188
|
+
if (!user) {
|
|
189
|
+
user = await User.create({
|
|
190
|
+
name: name || "User",
|
|
191
|
+
email: email.toLowerCase(),
|
|
192
|
+
role: "user",
|
|
193
|
+
password: "",
|
|
194
|
+
avatar: { url: picture || `https://placehold.co/600x400?text=${name?.charAt(0).toUpperCase() || 'U'}`, publicId: "" },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return user;
|
|
198
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
statusCode,
|
|
4
|
+
message = "Something went wrong",
|
|
5
|
+
errors = [],
|
|
6
|
+
stack = "",
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.data = null;
|
|
11
|
+
this.success = false;
|
|
12
|
+
this.errors = errors;
|
|
13
|
+
|
|
14
|
+
if (stack) {
|
|
15
|
+
this.stack = stack;
|
|
16
|
+
} else {
|
|
17
|
+
Error.captureStackTrace(this, this.constructor);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const SuccessResponse = (
|
|
2
|
+
res,
|
|
3
|
+
statusCode,
|
|
4
|
+
message = "Success",
|
|
5
|
+
data = null,
|
|
6
|
+
pagination = undefined,
|
|
7
|
+
) => {
|
|
8
|
+
return res.status(statusCode).json({
|
|
9
|
+
success: statusCode < 400,
|
|
10
|
+
statusCode,
|
|
11
|
+
message,
|
|
12
|
+
data,
|
|
13
|
+
...(pagination && { pagination }),
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
node: true,
|
|
4
|
+
es2022: true,
|
|
5
|
+
},
|
|
6
|
+
parser: "@typescript-eslint/parser",
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaVersion: "latest",
|
|
9
|
+
sourceType: "module",
|
|
10
|
+
},
|
|
11
|
+
plugins: ["@typescript-eslint"],
|
|
12
|
+
extends: [
|
|
13
|
+
"eslint:recommended",
|
|
14
|
+
"plugin:@typescript-eslint/recommended",
|
|
15
|
+
],
|
|
16
|
+
rules: {
|
|
17
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
18
|
+
"no-console": "error",
|
|
19
|
+
},
|
|
20
|
+
overrides: [
|
|
21
|
+
{
|
|
22
|
+
files: ["src/utils/logger.ts"],
|
|
23
|
+
rules: {
|
|
24
|
+
"no-console": "off",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
"start": "tsx src/index.ts",
|
|
8
8
|
"dev": "tsx watch src/index.ts",
|
|
9
9
|
"build": "tsc",
|
|
10
|
+
"lint": "eslint . --ext .ts --max-warnings 0",
|
|
11
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{ts,json}\"",
|
|
13
|
+
"format:check": "prettier --check \"src/**/*.{ts,json}\"",
|
|
14
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.ts\"",
|
|
15
|
+
"ts-prune": "ts-prune",
|
|
16
|
+
"ts-purns": "ts-prune",
|
|
10
17
|
"generate:vapid": "node ./src/utils/generateVapidKeys.js"
|
|
11
18
|
},
|
|
12
19
|
"dependencies": {
|
|
@@ -26,7 +33,8 @@
|
|
|
26
33
|
"nodemailer": "^6.9.7",
|
|
27
34
|
"socket.io": "^4.7.2",
|
|
28
35
|
"web-push": "^3.6.6",
|
|
29
|
-
"expo-server-sdk": "^3.10.0"
|
|
36
|
+
"expo-server-sdk": "^3.10.0",
|
|
37
|
+
"google-auth-library": "^9.14.2"
|
|
30
38
|
},
|
|
31
39
|
"devDependencies": {
|
|
32
40
|
"@types/bcrypt": "^5.0.2",
|
|
@@ -40,9 +48,15 @@
|
|
|
40
48
|
"@types/node": "^20.9.0",
|
|
41
49
|
"@types/nodemailer": "^6.4.14",
|
|
42
50
|
"@types/web-push": "^3.6.3",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
52
|
+
"@typescript-eslint/parser": "^6.10.0",
|
|
53
|
+
"dpdm": "^3.14.0",
|
|
54
|
+
"eslint": "^8.53.0",
|
|
43
55
|
"nodemon": "^3.0.1",
|
|
56
|
+
"prettier": "^3.4.2",
|
|
44
57
|
"ts-node": "^10.9.1",
|
|
58
|
+
"ts-prune": "^0.10.3",
|
|
45
59
|
"tsx": "^4.23.1",
|
|
46
60
|
"typescript": "^5.2.2"
|
|
47
61
|
}
|
|
48
|
-
}
|
|
62
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import express, { Request, Response
|
|
1
|
+
import express, { Request, Response } from "express";
|
|
2
2
|
import helmet from "helmet";
|
|
3
3
|
import cors from "cors";
|
|
4
4
|
import compression from "compression";
|
|
@@ -7,6 +7,7 @@ import morgan from "morgan";
|
|
|
7
7
|
import apiRoutes from "./routes/index.js";
|
|
8
8
|
import { notFound, errorHandler } from "./middleware/errorHandler.js";
|
|
9
9
|
import { apiLimiter } from "./middleware/rateLimiter.js";
|
|
10
|
+
import { SuccessResponse } from "./utils/SuccessResponse.js";
|
|
10
11
|
|
|
11
12
|
const app = express();
|
|
12
13
|
|
|
@@ -27,7 +28,7 @@ app.use(apiLimiter);
|
|
|
27
28
|
app.use("/api", apiRoutes);
|
|
28
29
|
|
|
29
30
|
app.get("/api/health", (req: Request, res: Response) => {
|
|
30
|
-
res
|
|
31
|
+
SuccessResponse(res, 200, "API is running");
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
app.use(notFound);
|
|
@@ -6,8 +6,9 @@ const connectDB = async () => {
|
|
|
6
6
|
const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017/test";
|
|
7
7
|
const conn = await mongoose.connect(MONGO_URI);
|
|
8
8
|
logger.success(`MongoDB connected: ${conn.connection.host}`);
|
|
9
|
-
} catch (error:
|
|
10
|
-
|
|
9
|
+
} catch (error: unknown) {
|
|
10
|
+
const err = error as Error;
|
|
11
|
+
logger.error(`MongoDB connection error: ${err.message}`, error);
|
|
11
12
|
process.exit(1);
|
|
12
13
|
}
|
|
13
14
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Server } from "socket.io";
|
|
1
|
+
import { Server as SocketIOServer } from "socket.io";
|
|
2
|
+
import type { Server as HttpServer } from "http";
|
|
2
3
|
import logger from "../utils/logger.js";
|
|
3
4
|
|
|
4
|
-
let io:
|
|
5
|
+
let io: SocketIOServer;
|
|
5
6
|
|
|
6
7
|
const userSocketMap: { [key: string]: Set<string> } = {};
|
|
7
8
|
|
|
8
|
-
const initializeSocket = (server:
|
|
9
|
-
io = new
|
|
9
|
+
const initializeSocket = (server: HttpServer) => {
|
|
10
|
+
io = new SocketIOServer(server, {
|
|
10
11
|
cors: {
|
|
11
12
|
origin: ["http://localhost:5173"],
|
|
12
13
|
credentials: true,
|