@imksh/create 1.0.30 → 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 (247) 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/src/utils/askTemplate.js +2 -1
  6. package/templates/layers/electron/base/electron/main.js +262 -0
  7. package/templates/layers/electron/base/electron/preload.js +37 -0
  8. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  9. package/templates/layers/electron/javascript/_gitignore +38 -0
  10. package/templates/layers/electron/javascript/package.json +49 -0
  11. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  12. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  13. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  14. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  15. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  16. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  17. package/templates/layers/electron/javascript/vite.config.js +29 -0
  18. package/templates/layers/electron/typescript/_gitignore +38 -0
  19. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  20. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  21. package/templates/layers/electron/typescript/package.json +52 -0
  22. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  23. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  24. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  25. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  26. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  27. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  28. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  29. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  30. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  31. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  32. package/templates/layers/next/javascript/package.json +8 -2
  33. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  34. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  35. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  36. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  37. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  38. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  39. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  40. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  41. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  42. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  43. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  44. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  45. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  46. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  47. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  48. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  49. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  50. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  51. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  52. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  53. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  54. package/templates/layers/next/typescript/package.json +14 -3
  55. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  56. package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
  57. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  58. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  59. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  60. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  61. package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
  62. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  63. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  64. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  65. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  66. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
  67. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  68. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  69. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  70. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  71. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  72. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  73. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  74. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  75. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  76. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  77. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  78. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  79. package/templates/layers/node/javascript/.env.example +3 -1
  80. package/templates/layers/node/javascript/package.json +12 -3
  81. package/templates/layers/node/javascript/src/app.js +2 -1
  82. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  83. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  84. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  85. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  86. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  87. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  88. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  89. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  90. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  91. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  92. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  93. package/templates/layers/node/typescript/.env.example +3 -1
  94. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  95. package/templates/layers/node/typescript/package-lock.json +2 -2
  96. package/templates/layers/node/typescript/package.json +17 -3
  97. package/templates/layers/node/typescript/src/app.ts +3 -2
  98. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  99. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  100. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  101. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  102. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  103. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  104. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  105. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  106. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  107. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  108. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  109. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  110. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  111. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  112. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  113. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  114. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  115. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  116. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  117. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  118. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  119. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  120. package/templates/layers/node-prisma/javascript/package.json +11 -2
  121. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  122. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  123. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  124. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  125. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  126. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  127. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  128. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  129. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  130. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  131. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  132. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  133. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  134. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  135. package/templates/layers/node-prisma/typescript/package.json +19 -5
  136. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  137. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  138. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  139. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  140. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  141. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  142. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  143. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  144. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  145. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  146. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  147. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  148. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  149. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  150. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  151. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  152. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  153. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  154. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  155. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  156. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  157. package/templates/layers/prisma/javascript/package.json +8 -0
  158. package/templates/layers/prisma/typescript/package.json +14 -1
  159. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  160. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  161. package/templates/layers/react/javascript/package.json +6 -0
  162. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  163. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  164. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  165. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  166. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  167. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  168. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  169. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  170. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  171. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  172. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  173. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  174. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  175. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  176. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  177. package/templates/layers/react/typescript/package.json +11 -2
  178. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  179. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  180. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  181. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  182. package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
  183. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  184. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  185. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  186. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  187. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  188. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  189. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  190. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  191. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  192. package/templates/layers/react/typescript/src/main.tsx +11 -11
  193. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  194. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  195. package/templates/layers/react-native/base/app.json +9 -22
  196. package/templates/layers/react-native/base/eslint.config.js +5 -1
  197. package/templates/layers/react-native/base/package.json +11 -1
  198. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  199. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  200. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  201. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  202. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  203. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  204. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  205. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  206. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  207. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  208. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  209. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  210. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  211. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  212. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  213. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  214. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  215. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  216. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  217. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  218. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  219. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  220. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  221. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  222. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  223. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  224. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  225. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  226. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  227. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  228. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  229. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  230. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  231. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  232. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  233. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  234. package/templates/presets/electron/preset.json +9 -0
  235. package/templates/presets/electron-ts/preset.json +9 -0
  236. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  237. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  238. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  239. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  240. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  241. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  242. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  243. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  244. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
  245. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  246. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  247. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -1,7 +1,7 @@
1
- import jwt from "jsonwebtoken";
2
1
  import User from "../models/user.model.js";
3
2
  import { verifyToken } from "../utils/jwt.js";
4
3
  import logger from "../utils/logger.js";
4
+ import { ApiError } from "../utils/ApiError.js";
5
5
 
6
6
  export const protect = async (req, res, next) => {
7
7
  let token;
@@ -15,29 +15,23 @@ export const protect = async (req, res, next) => {
15
15
  }
16
16
 
17
17
  if (!token) {
18
- return res
19
- .status(401)
20
- .json({ message: "Not authorized, no token provided" });
18
+ return next(new ApiError(401, "Not authorized, no token provided"));
21
19
  }
22
20
 
23
21
  try {
24
22
  const decoded = verifyToken(token);
25
23
  if (!decoded) {
26
- return res.status(401).json({ message: "Not authorized, invalid token" });
24
+ return next(new ApiError(401, "Not authorized, invalid token"));
27
25
  }
28
26
 
29
27
  req.user = await User.findById(decoded.id).select("-password");
30
28
  if (!req.user) {
31
- return res
32
- .status(401)
33
- .json({ message: "Not authorized, user not found" });
29
+ return next(new ApiError(401, "Not authorized, user not found"));
34
30
  }
35
31
 
36
32
  return next();
37
33
  } catch (error) {
38
34
  logger.error("Auth middleware error", error);
39
- return res
40
- .status(401)
41
- .json({ message: "Not authorized, token verification failed" });
35
+ return next(new ApiError(401, "Not authorized, token verification failed"));
42
36
  }
43
37
  };
@@ -1,11 +1,11 @@
1
+ import { ApiError } from "../utils/ApiError.js";
2
+
1
3
  /**
2
4
  * Not Found Middleware
3
5
  * Catches all unmatched requests and forwards a 404 error
4
6
  */
5
7
  export const notFound = (req, res, next) => {
6
- const error = new Error(`Not Found - ${req.originalUrl}`);
7
- res.status(404);
8
- next(error);
8
+ next(new ApiError(404, `Not Found - ${req.originalUrl}`));
9
9
  };
10
10
 
11
11
  /**
@@ -13,10 +13,12 @@ export const notFound = (req, res, next) => {
13
13
  * Formats error responses nicely in JSON
14
14
  */
15
15
  export const errorHandler = (err, req, res, next) => {
16
- const statusCode = err.status || err.statusCode || (res.statusCode === 200 ? 500 : res.statusCode);
16
+ const statusCode = err.statusCode || err.status || (res.statusCode === 200 ? 500 : res.statusCode);
17
17
  res.status(statusCode).json({
18
18
  success: false,
19
+ statusCode,
19
20
  message: err.message || "An unexpected error occurred",
21
+ errors: err.errors || [],
20
22
  stack: process.env.NODE_ENV === "production" ? undefined : err.stack,
21
23
  });
22
24
  };
@@ -7,6 +7,7 @@ import {
7
7
  updateMe,
8
8
  genSignupOtp,
9
9
  savePushToken,
10
+ googleLogin,
10
11
  } from "../controllers/auth.controller.js";
11
12
  import { protect } from "../middleware/auth.middleware.js";
12
13
  import { apiLimiter } from "../middleware/rateLimiter.js";
@@ -18,6 +19,7 @@ router.use(apiLimiter);
18
19
  router.post("/send-signup-otp", genSignupOtp);
19
20
  router.post("/signup", signup);
20
21
  router.post("/login", login);
22
+ router.post("/google", googleLogin);
21
23
  router.post("/logout", logout);
22
24
  router.get("/me", protect, getMe);
23
25
  router.put("/me", protect, updateMe);
@@ -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
+ };
@@ -1,11 +1,11 @@
1
1
  {
2
- "name": "/Users/karan/workspace/Projects/boilerplate/test-app-node-ts",
2
+ "name": "node-ts-template",
3
3
  "version": "1.0.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
- "name": "/Users/karan/workspace/Projects/boilerplate/test-app-node-ts",
8
+ "name": "node-ts-template",
9
9
  "version": "1.0.0",
10
10
  "dependencies": {
11
11
  "bcrypt": "^5.1.1",
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "/Users/karan/workspace/Projects/boilerplate/test-app-node-ts",
2
+ "name": "node-ts-template",
3
3
  "version": "1.0.0",
4
4
  "main": "src/index.ts",
5
5
  "type": "module",
@@ -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,