@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.
Files changed (152) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/commands/create.js +2 -0
  4. package/src/index.js +1 -1
  5. package/templates/layers/next/javascript/package.json +8 -2
  6. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  7. package/templates/layers/next/typescript/package.json +14 -3
  8. package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -1
  9. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  10. package/templates/layers/next/typescript/src/components/common/Table.tsx +4 -4
  11. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +2 -1
  12. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  13. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  14. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  15. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  16. package/templates/layers/node/javascript/.env.example +3 -1
  17. package/templates/layers/node/javascript/package.json +12 -3
  18. package/templates/layers/node/javascript/src/app.js +2 -1
  19. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  20. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  21. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  22. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  23. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  24. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  25. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  26. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  27. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  28. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  29. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  30. package/templates/layers/node/typescript/.env.example +3 -1
  31. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  32. package/templates/layers/node/typescript/package.json +16 -2
  33. package/templates/layers/node/typescript/src/app.ts +3 -2
  34. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  35. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  36. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  37. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  38. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  39. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  40. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  41. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  42. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  43. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  44. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  45. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  46. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  47. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  48. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  49. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  50. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  51. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  52. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  53. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  54. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  55. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  56. package/templates/layers/node-prisma/javascript/package.json +11 -2
  57. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  58. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  59. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  60. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  61. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  62. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  63. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  64. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  65. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  66. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  67. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  68. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  69. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  70. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  71. package/templates/layers/node-prisma/typescript/package.json +19 -5
  72. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  73. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  74. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  75. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  76. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  77. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  78. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  79. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  80. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  81. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  82. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  83. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  84. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  85. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  86. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  87. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  88. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  89. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  90. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  91. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  92. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  93. package/templates/layers/prisma/javascript/package.json +8 -0
  94. package/templates/layers/prisma/typescript/package.json +14 -1
  95. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  96. package/templates/layers/react/javascript/package.json +6 -0
  97. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  98. package/templates/layers/react/typescript/package.json +11 -2
  99. package/templates/layers/react/typescript/src/components/common/Table.tsx +4 -4
  100. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +1 -1
  101. package/templates/layers/react/typescript/src/main.tsx +11 -11
  102. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  103. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  104. package/templates/layers/react-native/base/app.json +9 -22
  105. package/templates/layers/react-native/base/eslint.config.js +5 -1
  106. package/templates/layers/react-native/base/package.json +11 -1
  107. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  108. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  109. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  110. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  111. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  112. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  113. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  114. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  115. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  116. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  117. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  118. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  119. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  120. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  121. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  122. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  123. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  124. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  125. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  126. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  127. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  128. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  129. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  130. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  131. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  132. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  133. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  134. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  135. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  136. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  137. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  138. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  139. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  140. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  141. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  142. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  143. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  144. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  145. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  146. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  147. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  148. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  149. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  150. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  151. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  152. /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
- const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
37
-
38
- const newUser = await User.create({
39
- name,
40
- email: email.toLowerCase(),
41
- phone,
42
- role: "user",
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
- return user;
22
+ const existingOtp = await Otp.findOne({ email });
23
+ if (!existingOtp) {
24
+ throw new ApiError(400, "OTP match failed");
72
25
  }
73
- /**
74
- * Generate and Send OTP
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
- const existingOtp = await Otp.findOne({ email });
102
- if (existingOtp) {
103
- await existingOtp.deleteOne();
104
- }
105
-
106
- const otp = Math.floor(100000 + Math.random() * 900000).toString();
107
- const hashedOtp = await bcrypt.hash(otp, 10);
108
-
109
- await Otp.create({
110
- email,
111
- otp: hashedOtp,
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
- await sendOtpEmail(email, otp);
115
- return true;
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
- * Verify OTP
119
- */
120
- export const verifyOtp = async ({ email, otp }) => {
121
- const user = await User.findOne({ email });
122
- if (!user) {
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
- return user;
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
- * Reset Password
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
- * Update Profile
155
- */
156
- export const updateProfile = async (userId, { name, phone, file }) => {
157
- const user = await User.findById(userId);
158
- if (!user) {
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
- if (name) user.name = name;
165
- if (phone) user.phone = phone;
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
- if (file) {
168
- const uploadResult = await uploadSingleToCloudinary(file, {
169
- folder: "codelab/avatars",
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
- if (user.avatar && user.avatar.publicId) {
173
- await deleteFromCloudinary(user.avatar.publicId).catch((err) =>
174
- logger.error("Old avatar delete error", err)
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
- user.avatar = {
179
- url: uploadResult.url,
180
- publicId: uploadResult.publicId,
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
- const err = new Error("User not found");
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,4 @@
1
+ import { SuccessResponse } from "./SuccessResponse.js";
2
+
3
+ export { SuccessResponse };
4
+ export const ApiResponse = SuccessResponse;
@@ -0,0 +1,5 @@
1
+ export const asyncHandler = (requestHandler) => {
2
+ return (req, res, next) => {
3
+ Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err));
4
+ };
5
+ };
@@ -59,4 +59,6 @@ RATE_LIMIT_MAX=100
59
59
  # ==========================
60
60
  VAPID_PRIVATE_KEY=
61
61
  VAPID_PUBLIC_KEY=
62
- VAPID_SUBJECT=mailto:noreply@example.com
62
+ VAPID_SUBJECT=mailto:noreply@example.com
63
+ # Google Auth
64
+ GOOGLE_CLIENT_ID=your-google-client-id
@@ -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, NextFunction } from "express";
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.status(200).json({ success: true, message: "API is running" });
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: any) {
10
- logger.error(`MongoDB connection error: ${error.message}`, error);
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: Server;
5
+ let io: SocketIOServer;
5
6
 
6
7
  const userSocketMap: { [key: string]: Set<string> } = {};
7
8
 
8
- const initializeSocket = (server: any) => {
9
- io = new Server(server, {
9
+ const initializeSocket = (server: HttpServer) => {
10
+ io = new SocketIOServer(server, {
10
11
  cors: {
11
12
  origin: ["http://localhost:5173"],
12
13
  credentials: true,