@imksh/create 1.0.25 → 1.0.27

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 (271) hide show
  1. package/package.json +1 -1
  2. package/src/commands/create.js +2 -1
  3. package/src/index.js +1 -1
  4. package/src/utils/askTemplate.js +1 -0
  5. package/templates/mern-react-native/mobile/_gitignore +43 -0
  6. package/templates/mern-react-native/mobile/package.json +2 -1
  7. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +11 -15
  8. package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
  9. package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
  10. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +0 -60
  11. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +6 -36
  12. package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
  13. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +158 -33
  14. package/templates/next/next.config.mjs +1 -0
  15. package/templates/next/package.json +10 -9
  16. package/templates/next/postcss.config.js +5 -0
  17. package/templates/next/src/app/layout.js +1 -0
  18. package/templates/next/src/app/page.js +1 -1
  19. package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
  20. package/templates/next/src/index.css +1 -0
  21. package/templates/next/src/store/useUiStore.js +13 -7
  22. package/templates/next-ts/.env.example +0 -0
  23. package/templates/next-ts/AGENTS.md +37 -0
  24. package/templates/next-ts/CLAUDE.md +1 -0
  25. package/templates/next-ts/next-env.d.ts +3 -1
  26. package/templates/next-ts/next.config.mjs +1 -0
  27. package/templates/next-ts/package.json +13 -12
  28. package/templates/next-ts/postcss.config.js +5 -0
  29. package/templates/next-ts/public/logo.png +0 -0
  30. package/templates/next-ts/src/app/error.tsx +41 -0
  31. package/templates/next-ts/src/app/layout.tsx +2 -1
  32. package/templates/next-ts/src/app/not-found.tsx +5 -0
  33. package/templates/next-ts/src/app/page.tsx +1 -1
  34. package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
  35. package/templates/next-ts/src/components/ui/Button.tsx +63 -0
  36. package/templates/next-ts/src/components/ui/Input.tsx +79 -0
  37. package/templates/next-ts/src/index.css +1 -0
  38. package/templates/next-ts/src/store/useUiStore.ts +13 -7
  39. package/templates/next-ts/tsconfig.json +19 -5
  40. package/templates/production-fullstack/client/_gitignore +24 -0
  41. package/templates/production-fullstack/client/index.html +14 -0
  42. package/templates/production-fullstack/client/package.json +47 -0
  43. package/templates/production-fullstack/client/public/_redirects +1 -0
  44. package/templates/production-fullstack/client/public/favicon.ico +0 -0
  45. package/templates/production-fullstack/client/public/favicon.svg +1 -0
  46. package/templates/production-fullstack/client/public/logo.png +0 -0
  47. package/templates/production-fullstack/client/public/manifest.json +24 -0
  48. package/templates/production-fullstack/client/public/sw.js +56 -0
  49. package/templates/production-fullstack/client/src/App.tsx +68 -0
  50. package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
  51. package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
  52. package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
  53. package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
  54. package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
  55. package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
  56. package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
  57. package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
  58. package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
  59. package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
  60. package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
  61. package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
  62. package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
  63. package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
  64. package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
  65. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
  66. package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
  67. package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
  68. package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
  69. package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
  70. package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
  71. package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
  72. package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
  73. package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
  74. package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
  75. package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
  76. package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
  77. package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
  78. package/templates/production-fullstack/client/src/config/index.ts +44 -0
  79. package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
  80. package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
  81. package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
  82. package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
  83. package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
  84. package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
  85. package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
  86. package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
  87. package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
  88. package/templates/production-fullstack/client/src/index.css +158 -0
  89. package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
  90. package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
  91. package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
  92. package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
  93. package/templates/production-fullstack/client/src/main.tsx +28 -0
  94. package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
  95. package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
  96. package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
  97. package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
  98. package/templates/production-fullstack/client/tsconfig.json +25 -0
  99. package/templates/production-fullstack/client/tsconfig.node.json +10 -0
  100. package/templates/production-fullstack/client/vite.config.ts +8 -0
  101. package/templates/production-fullstack/mobile/LICENSE +21 -0
  102. package/templates/production-fullstack/mobile/_gitignore +43 -0
  103. package/templates/production-fullstack/mobile/app.json +84 -0
  104. package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
  105. package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
  106. package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
  107. package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
  108. package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
  109. package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
  110. package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
  111. package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
  112. package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
  113. package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
  114. package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
  115. package/templates/production-fullstack/mobile/babel.config.js +13 -0
  116. package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
  117. package/templates/production-fullstack/mobile/eas.json +24 -0
  118. package/templates/production-fullstack/mobile/eslint.config.js +10 -0
  119. package/templates/production-fullstack/mobile/expo-env.d.ts +3 -0
  120. package/templates/production-fullstack/mobile/metro.config.js +6 -0
  121. package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
  122. package/templates/production-fullstack/mobile/package.json +89 -0
  123. package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
  124. package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
  125. package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  126. package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  127. package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
  128. package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
  129. package/templates/production-fullstack/mobile/src/app/global.css +82 -0
  130. package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  131. package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
  132. package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
  133. package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
  134. package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
  135. package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
  136. package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
  137. package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
  138. package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
  139. package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
  140. package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
  141. package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
  142. package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
  143. package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
  144. package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
  145. package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
  146. package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
  147. package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
  148. package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
  149. package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
  150. package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
  151. package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
  152. package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
  153. package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
  154. package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
  155. package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
  156. package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
  157. package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
  158. package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
  159. package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  160. package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  161. package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
  162. package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  163. package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
  164. package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
  165. package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
  166. package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
  167. package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
  168. package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
  169. package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
  170. package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  171. package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
  172. package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
  173. package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
  174. package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
  175. package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
  176. package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
  177. package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
  178. package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
  179. package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
  180. package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
  181. package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
  182. package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
  183. package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
  184. package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
  185. package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
  186. package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
  187. package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
  188. package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
  189. package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
  190. package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
  191. package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
  192. package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
  193. package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
  194. package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
  195. package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
  196. package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
  197. package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
  198. package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
  199. package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
  200. package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
  201. package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
  202. package/templates/production-fullstack/mobile/tsconfig.json +23 -0
  203. package/templates/production-fullstack/server/.env.example +63 -0
  204. package/templates/production-fullstack/server/README.md +75 -0
  205. package/templates/production-fullstack/server/_gitignore +24 -0
  206. package/templates/production-fullstack/server/eslint.config.js +13 -0
  207. package/templates/production-fullstack/server/package.json +58 -0
  208. package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
  209. package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
  210. package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
  211. package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
  212. package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
  213. package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
  214. package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
  215. package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
  216. package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
  217. package/templates/production-fullstack/server/prisma.config.ts +14 -0
  218. package/templates/production-fullstack/server/src/app.ts +36 -0
  219. package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
  220. package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
  221. package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
  222. package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
  223. package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
  224. package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
  225. package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
  226. package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
  227. package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
  228. package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
  229. package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
  230. package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
  231. package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
  232. package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
  233. package/templates/production-fullstack/server/src/config/env.ts +2 -0
  234. package/templates/production-fullstack/server/src/config/logger.ts +58 -0
  235. package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
  236. package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
  237. package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
  238. package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
  239. package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
  240. package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
  241. package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
  242. package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
  243. package/templates/production-fullstack/server/src/index.ts +17 -0
  244. package/templates/production-fullstack/server/src/routes/index.ts +12 -0
  245. package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
  246. package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
  247. package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
  248. package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
  249. package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
  250. package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
  251. package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
  252. package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
  253. package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
  254. package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
  255. package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
  256. package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
  257. package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
  258. package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
  259. package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
  260. package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
  261. package/templates/production-fullstack/server/tsconfig.json +14 -0
  262. package/templates/react/src/assets/animations/loading.json +1 -0
  263. package/templates/react-native/_gitignore +43 -0
  264. package/templates/react-native/package.json +2 -1
  265. package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
  266. package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
  267. package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
  268. package/templates/react-native/src/config/sidebar.config.ts +0 -60
  269. package/templates/react-native/src/config/tabNavigation.ts +6 -36
  270. package/templates/react-native/src/services/notification.service.ts +22 -0
  271. package/templates/react-native/src/store/useNotificationStore.ts +158 -33
@@ -0,0 +1,124 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import * as authService from "./auth.service.js";
3
+ import { generateToken } from "../../common/utils/jwt.js";
4
+ import genOtpToken from "../../common/utils/genOtpToken.js";
5
+ import logger from "../../config/logger.js";
6
+
7
+ export const signup = async (req: Request, res: Response, next: NextFunction) => {
8
+ try {
9
+ const { name, email, phone, password } = req.body;
10
+ if (!email || !password || !name) {
11
+ return next({ status: 400, message: "All fields are required." });
12
+ }
13
+ const newUser = await authService.signup({ name, email, phone, password });
14
+ const token = await generateToken(newUser, res);
15
+ const safeUser: any = { ...newUser };
16
+ delete safeUser.password;
17
+ res.status(201).json({ message: "Registration Successful", data: safeUser, token });
18
+ } catch (error) {
19
+ logger.error("Error in signup controller", error);
20
+ next(error);
21
+ }
22
+ };
23
+
24
+ export const login = async (req: Request, res: Response, next: NextFunction) => {
25
+ try {
26
+ const { email, password } = req.body;
27
+ if (!email || !password) {
28
+ return next({ status: 400, message: "All fields are required." });
29
+ }
30
+ const user = await authService.login({ email, password });
31
+ const token = await generateToken(user, res);
32
+ const safeUser: any = { ...user };
33
+ delete safeUser.password;
34
+ res.status(200).json({ message: "Login successfully", data: safeUser, token });
35
+ } catch (error) {
36
+ logger.error("Error in login controller", error);
37
+ next(error);
38
+ }
39
+ };
40
+
41
+ export const logout = async (req: Request, res: Response, next: NextFunction) => {
42
+ try {
43
+ res.clearCookie("token", {
44
+ httpOnly: true,
45
+ secure: process.env.NODE_ENV === "production",
46
+ sameSite: "strict",
47
+ path: "/",
48
+ });
49
+ res.status(200).json({ message: "Logout Successfully" });
50
+ } catch (error) {
51
+ logger.error("Error in logout controller", error);
52
+ next(error);
53
+ }
54
+ };
55
+
56
+ export const getMe = async (req: Request, res: Response, next: NextFunction) => {
57
+ try {
58
+ res.status(200).json({ message: "User Authenticated", data: req.user });
59
+ } catch (error) {
60
+ logger.error("Error in getMe controller", error);
61
+ next(error);
62
+ }
63
+ };
64
+
65
+ export const genOtp = async (req: Request, res: Response, next: NextFunction) => {
66
+ try {
67
+ const { email } = req.body;
68
+ if (!email) {
69
+ return next({ status: 400, message: "Email is required." });
70
+ }
71
+ await authService.generateOtp(email);
72
+ res.status(200).json({ message: "Otp sent successfully" });
73
+ } catch (error) {
74
+ logger.error("Error in genOtp", error);
75
+ next(error);
76
+ }
77
+ };
78
+
79
+ export const verifyOtp = async (req: Request, res: Response, next: NextFunction) => {
80
+ try {
81
+ const { email, otp } = req.body;
82
+ if (!email || !otp) {
83
+ return next({ status: 400, message: "All fields are required." });
84
+ }
85
+ const user = await authService.verifyOtp({ email, otp });
86
+ await genOtpToken(user, res);
87
+ res.status(200).json({ message: "Otp verified successfully" });
88
+ } catch (error) {
89
+ logger.error("Error in verifyOtp", error);
90
+ next(error);
91
+ }
92
+ };
93
+
94
+ export const resetPassword = async (req: Request, res: Response, next: NextFunction) => {
95
+ try {
96
+ const { newPassword } = req.body;
97
+ const currentUser = req.user;
98
+ if (!newPassword) {
99
+ return next({ status: 400, message: "All fields are required." });
100
+ }
101
+ await authService.resetPassword(currentUser, newPassword);
102
+ res.status(200).json({ message: "Password updated successfully" });
103
+ } catch (error) {
104
+ logger.error("Error in resetPassword", error);
105
+ next(error);
106
+ }
107
+ };
108
+
109
+ export const updateMe = async (req: Request, res: Response, next: NextFunction) => {
110
+ try {
111
+ const { name, phone } = req.body;
112
+ const user = await authService.updateProfile(req.user.id, {
113
+ name,
114
+ phone,
115
+ file: req.file,
116
+ });
117
+ const safeUser: any = { ...user };
118
+ delete safeUser.password;
119
+ res.status(200).json({ success: true, data: safeUser });
120
+ } catch (error) {
121
+ logger.error("Error updating profile", error);
122
+ next(error);
123
+ }
124
+ };
@@ -0,0 +1,22 @@
1
+ import express from "express";
2
+ import {
3
+ signup,
4
+ login,
5
+ getMe,
6
+ logout,
7
+ updateMe,
8
+ } from "./auth.controller.js";
9
+ import { protect } from "../../common/middlewares/auth.middleware.js";
10
+ import { apiLimiter } from "../../common/middlewares/rateLimiter.js";
11
+
12
+ const router = express.Router();
13
+
14
+ router.use(apiLimiter);
15
+
16
+ router.post("/signup", signup);
17
+ router.post("/login", login);
18
+ router.post("/logout", logout);
19
+ router.get("/me", protect, getMe);
20
+ router.put("/me", protect, updateMe);
21
+
22
+ export default router;
@@ -0,0 +1,155 @@
1
+ import bcrypt from "bcrypt";
2
+ import { prisma } from "../../config/prisma.js";
3
+ import { sendOtpEmail } from "../../common/utils/emailOtp.js";
4
+ import { uploadSingleToCloudinary, deleteFromCloudinary } from "../../common/utils/cloudinaryUpload.js";
5
+ import logger from "../../config/logger.js";
6
+ export const signup = async ({ name, email, phone, password }: any) => {
7
+ const existingUser = await prisma.user.findUnique({
8
+ where: { email },
9
+ });
10
+ if (existingUser) {
11
+ const err: any = new Error("User already exists");
12
+ err.status = 409;
13
+ throw err;
14
+ }
15
+
16
+ const salt = await bcrypt.genSalt(10);
17
+ const hashedPassword = await bcrypt.hash(password, salt);
18
+ const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
19
+
20
+ const newUser = await prisma.user.create({
21
+ data: {
22
+ name,
23
+ email: email.toLowerCase(),
24
+ phone,
25
+ password: hashedPassword,
26
+ avatarUrl: photoURL,
27
+ avatarPublicId: "",
28
+ },
29
+ });
30
+
31
+ return newUser;
32
+ }
33
+ export const login = async ({ email, password }: any) => {
34
+ const user = await prisma.user.findUnique({
35
+ where: { email },
36
+ });
37
+ if (!user) {
38
+ const err: any = new Error("User not found");
39
+ err.status = 404;
40
+ throw err;
41
+ }
42
+
43
+ const isValid = await bcrypt.compare(password, user.password);
44
+ if (!isValid) {
45
+ const err: any = new Error("Invalid Credentials");
46
+ err.status = 401;
47
+ throw err;
48
+ }
49
+
50
+ return user;
51
+ }
52
+ export const generateOtp = async (email: string) => {
53
+ const user = await prisma.user.findUnique({
54
+ where: { email },
55
+ });
56
+ if (!user) {
57
+ const err: any = new Error("User not found");
58
+ err.status = 404;
59
+ throw err;
60
+ }
61
+
62
+ const existingOtp = await prisma.otp.findUnique({
63
+ where: { email },
64
+ });
65
+ if (existingOtp) {
66
+ await prisma.otp.delete({
67
+ where: { email },
68
+ });
69
+ }
70
+
71
+ const otp = Math.floor(100000 + Math.random() * 900000).toString();
72
+ const hashedOtp = await bcrypt.hash(otp, 10);
73
+
74
+ await prisma.otp.create({
75
+ data: {
76
+ email,
77
+ otp: hashedOtp,
78
+ },
79
+ });
80
+
81
+ await sendOtpEmail(email, otp);
82
+ return true;
83
+ }
84
+ export const verifyOtp = async ({ email, otp }: any) => {
85
+ const user = await prisma.user.findUnique({
86
+ where: { email },
87
+ });
88
+ if (!user) {
89
+ const err: any = new Error("User not found");
90
+ err.status = 404;
91
+ throw err;
92
+ }
93
+
94
+ const existingOtp = await prisma.otp.findUnique({
95
+ where: { email },
96
+ });
97
+ if (!existingOtp) {
98
+ const err: any = new Error("OTP match failed");
99
+ err.status = 400;
100
+ throw err;
101
+ }
102
+
103
+ const isMatched = await bcrypt.compare(otp, existingOtp.otp);
104
+ if (!isMatched) {
105
+ const err: any = new Error("OTP match failed");
106
+ err.status = 400;
107
+ throw err;
108
+ }
109
+
110
+ return user;
111
+ }
112
+ export const resetPassword = async (user: any, newPassword: any) => {
113
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
114
+ await prisma.user.update({
115
+ where: { id: user.id },
116
+ data: { password: hashedPassword },
117
+ });
118
+ return true;
119
+ }
120
+ export const updateProfile = async (userId: any, { name, phone, file }: any) => {
121
+ const user = await prisma.user.findUnique({
122
+ where: { id: userId },
123
+ });
124
+ if (!user) {
125
+ const err: any = new Error("User not found");
126
+ err.status = 404;
127
+ throw err;
128
+ }
129
+
130
+ const updateData: any = {};
131
+ if (name) updateData.name = name;
132
+ if (phone) updateData.phone = phone;
133
+
134
+ if (file) {
135
+ const uploadResult = await uploadSingleToCloudinary(file, {
136
+ folder: "codelab/avatars",
137
+ });
138
+
139
+ if (user.avatarPublicId) {
140
+ await deleteFromCloudinary(user.avatarPublicId).catch((err) =>
141
+ logger.error("Old avatar delete error", err)
142
+ );
143
+ }
144
+
145
+ updateData.avatarUrl = uploadResult.url;
146
+ updateData.avatarPublicId = uploadResult.publicId;
147
+ }
148
+
149
+ const updatedUser = await prisma.user.update({
150
+ where: { id: userId },
151
+ data: updateData,
152
+ });
153
+
154
+ return updatedUser;
155
+ }
@@ -0,0 +1,55 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { prisma } from "../../config/prisma.js";
3
+ import logger from "../../config/logger.js";
4
+
5
+ export const subscribe = async (req: Request, res: Response, next: NextFunction) => {
6
+ try {
7
+ const subscription = req.body;
8
+ const userId = req.user?._id || req.user?.id; // supports both mongoose and prisma IDs
9
+
10
+ if (!subscription || !subscription.endpoint) {
11
+ return next({ status: 400, message: "Invalid subscription payload" });
12
+ }
13
+
14
+ const existingSub = await prisma.pushSubscription.findUnique({ where: { endpoint: subscription.endpoint } });
15
+
16
+ if (existingSub) {
17
+ if (userId) {
18
+ await prisma.pushSubscription.update({ where: { endpoint: subscription.endpoint }, data: { userId } })
19
+ }
20
+ return res.status(200).json({ success: true, message: "Already subscribed." });
21
+ }
22
+
23
+
24
+ await prisma.pushSubscription.create({
25
+ data: {
26
+ endpoint: subscription.endpoint,
27
+ p256dh: subscription.keys.p256dh,
28
+ auth: subscription.keys.auth,
29
+ userId: userId || null
30
+ }
31
+ });
32
+
33
+
34
+ res.status(201).json({ success: true, message: "Subscription created." });
35
+ } catch (error) {
36
+ logger.error("Error in push subscribe controller", error);
37
+ next(error);
38
+ }
39
+ };
40
+
41
+ export const unsubscribe = async (req: Request, res: Response, next: NextFunction) => {
42
+ try {
43
+ const { endpoint } = req.body;
44
+ if (!endpoint) {
45
+ return next({ status: 400, message: "Endpoint required" });
46
+ }
47
+
48
+ await prisma.pushSubscription.delete({ where: { endpoint } }).catch(() => {})
49
+
50
+ res.status(200).json({ success: true, message: "Unsubscribed." });
51
+ } catch (error) {
52
+ logger.error("Error in push unsubscribe controller", error);
53
+ next(error);
54
+ }
55
+ };
@@ -0,0 +1,11 @@
1
+ import express from "express";
2
+ import { subscribe, unsubscribe } from "./push.controller.js";
3
+ import { protect } from "../../common/middlewares/auth.middleware.js";
4
+
5
+ const router = express.Router();
6
+
7
+ // You can remove protect if you want anonymous users to be able to subscribe
8
+ router.post("/subscribe", protect, subscribe);
9
+ router.post("/unsubscribe", unsubscribe);
10
+
11
+ export default router;
@@ -0,0 +1,17 @@
1
+ import dotenv from "dotenv";
2
+ import http from "http";
3
+ import app from "./app.js";
4
+ import connectDB from "./config/prisma.js";
5
+ import logger from "./config/logger.js";
6
+
7
+ dotenv.config();
8
+
9
+ const PORT = process.env.PORT || 5000;
10
+
11
+ connectDB();
12
+
13
+ const server = http.createServer(app);
14
+
15
+ server.listen(PORT, () => {
16
+ logger.success(`Server is running on port ${PORT}`);
17
+ });
@@ -0,0 +1,12 @@
1
+ import express from "express";
2
+ import authRoutes from "../features/auth/auth.routes.js";
3
+ import uploadRoutes from "../features/attachments/upload.routes.js";
4
+ import pushRoutes from "../features/notifications/push.routes.js";
5
+
6
+ const router = express.Router();
7
+
8
+ router.use("/auth", authRoutes);
9
+ router.use("/upload", uploadRoutes);
10
+ router.use("/push", pushRoutes);
11
+
12
+ export default router;
@@ -0,0 +1,31 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import logger from "../config/logger.js";
3
+ import { registerHandlers } from "./handlers/index.js";
4
+ import { getUserRoom, getInstitutionRoom } from "./rooms.js";
5
+ import { SocketEvents } from "./events.js";
6
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
7
+
8
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
9
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
10
+
11
+ export const onConnection = (io: AppServer, socket: AppSocket) => {
12
+ const user = socket.data.user;
13
+
14
+ logger.info(`Socket connected: ${socket.id} (User: ${user.id})`);
15
+
16
+ // Auto-join user-specific room
17
+ socket.join(getUserRoom(user.id));
18
+
19
+ // Auto-join institution room if applicable
20
+ if (user.institutionId) {
21
+ socket.join(getInstitutionRoom(user.institutionId));
22
+ }
23
+
24
+ // Register all event handlers
25
+ registerHandlers(io, socket);
26
+
27
+ // Handle disconnect
28
+ socket.on(SocketEvents.DISCONNECT, (reason) => {
29
+ logger.info(`Socket disconnected: ${socket.id} (User: ${user.id}, Reason: ${reason})`);
30
+ });
31
+ };
@@ -0,0 +1,17 @@
1
+ import { getIO } from "../index.js";
2
+ import { getUserRoom } from "../rooms.js";
3
+ import { SocketEvents } from "../events.js";
4
+ import logger from "../../config/logger.js";
5
+
6
+ /**
7
+ * Emits a new notification to a specific user.
8
+ * This is meant to be called from business logic services.
9
+ */
10
+ export const emitNewNotification = (userId: string, payload: unknown) => {
11
+ try {
12
+ const io = getIO();
13
+ io.to(getUserRoom(userId)).emit(SocketEvents.NOTIFICATION_NEW, payload);
14
+ } catch (error) {
15
+ logger.error(`Failed to emit notification to user ${userId}`, error);
16
+ }
17
+ };
@@ -0,0 +1,27 @@
1
+ export const SocketEvents = {
2
+ // Connection Events
3
+ DISCONNECT: "disconnect",
4
+ CONNECT_ERROR: "connect_error",
5
+
6
+ // Room Events
7
+ SUBSCRIBE: "room:subscribe",
8
+ UNSUBSCRIBE: "room:unsubscribe",
9
+
10
+ // Notification Events
11
+ NOTIFICATION_NEW: "notification:new",
12
+ NOTIFICATION_MARK_AS_READ: "notification:markAsRead",
13
+
14
+ // Chat Events
15
+ CHAT_NEW_MESSAGE: "chat:newMessage",
16
+ CHAT_TYPING: "chat:typing",
17
+ CHAT_STOP_TYPING: "chat:stopTyping",
18
+
19
+ // LMS Events
20
+ LMS_ASSIGNMENT_CREATED: "lms:assignmentCreated",
21
+ LMS_GRADE_PUBLISHED: "lms:gradePublished",
22
+
23
+ // Attendance Events
24
+ ATTENDANCE_MARKED: "attendance:marked",
25
+ } as const;
26
+
27
+ export type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents];
@@ -0,0 +1,15 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import { registerNotificationHandlers } from "./notification.handler.js";
3
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
4
+
5
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
6
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ export const registerHandlers = (io: AppServer, socket: AppSocket) => {
9
+ // Register domain specific handlers
10
+ registerNotificationHandlers(io, socket);
11
+
12
+ // Future handlers:
13
+ // registerChatHandlers(io, socket);
14
+ // registerLmsHandlers(io, socket);
15
+ };
@@ -0,0 +1,39 @@
1
+ import { Socket, Server } from "socket.io";
2
+ import logger from "../../config/logger.js";
3
+ import { SocketEvents } from "../events.js";
4
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "../types.js";
5
+
6
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
8
+
9
+ export const registerNotificationHandlers = (_io: AppServer, socket: AppSocket) => {
10
+ socket.on(SocketEvents.NOTIFICATION_MARK_AS_READ, (payload, callback) => {
11
+ try {
12
+ const { notificationId } = payload;
13
+ logger.info(`Notification marked as read: ${notificationId} by user ${socket.data.user.id}`);
14
+
15
+ if (typeof callback === "function") {
16
+ callback({ success: true, notificationId });
17
+ }
18
+ } catch (error) {
19
+ logger.error("Error in notification:markAsRead handler", error);
20
+ if (typeof callback === "function") {
21
+ callback({ success: false, error: "Failed to mark as read" });
22
+ }
23
+ }
24
+ });
25
+
26
+ socket.on(SocketEvents.SUBSCRIBE, (payload) => {
27
+ if (payload?.topic) {
28
+ socket.join(`topic:${payload.topic}`);
29
+ logger.info(`User ${socket.data.user.id} subscribed to topic: ${payload.topic}`);
30
+ }
31
+ });
32
+
33
+ socket.on(SocketEvents.UNSUBSCRIBE, (payload) => {
34
+ if (payload?.topic) {
35
+ socket.leave(`topic:${payload.topic}`);
36
+ logger.info(`User ${socket.data.user.id} unsubscribed from topic: ${payload.topic}`);
37
+ }
38
+ });
39
+ };
@@ -0,0 +1,39 @@
1
+ import { Server } from "socket.io";
2
+ import { socketAuthMiddleware } from "./middleware.js";
3
+ import { onConnection } from "./connection.js";
4
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
5
+
6
+ type AppServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ let io: AppServer;
9
+
10
+ const initializeSocket = (server: import("http").Server) => {
11
+ io = new Server(server, {
12
+ cors: {
13
+ origin: ["http://localhost:5173"],
14
+ credentials: true,
15
+ },
16
+ // To support Redis in the future, you would add the adapter here:
17
+ // adapter: createAdapter(pubClient, subClient)
18
+ });
19
+
20
+ // Apply Authentication Middleware
21
+ io.use(socketAuthMiddleware);
22
+
23
+ // Handle Connections
24
+ io.on("connection", (socket) => {
25
+ onConnection(io, socket);
26
+ });
27
+
28
+ return io;
29
+ };
30
+
31
+ export const getIO = (): AppServer => {
32
+ if (!io) {
33
+ throw new Error("Socket.IO has not been initialized yet");
34
+ }
35
+ return io;
36
+ };
37
+
38
+ export { io };
39
+ export default initializeSocket;
@@ -0,0 +1,54 @@
1
+ import { Socket } from "socket.io";
2
+ import { verifyToken } from "../common/utils/jwt.js";
3
+ import { SocketData, ServerToClientEvents, ClientToServerEvents, InterServerEvents } from "./types.js";
4
+ import { prisma } from "../config/prisma.js";
5
+
6
+ type AppSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
7
+
8
+ const parseCookie = (cookieString: string, name: string) => {
9
+ const match = cookieString.match(new RegExp("(^| )" + name + "=([^;]+)"));
10
+ if (match) return match[2];
11
+ return null;
12
+ };
13
+
14
+ export const socketAuthMiddleware = async (socket: AppSocket, next: (err?: Error) => void) => {
15
+ try {
16
+ let token = socket.handshake.auth.token;
17
+
18
+ // Fallback to cookie if token is not explicitly passed in handshake
19
+ if (!token && socket.handshake.headers.cookie) {
20
+ token = parseCookie(socket.handshake.headers.cookie, "token");
21
+ }
22
+
23
+ if (!token) {
24
+ return next(new Error("Authentication error: No token provided"));
25
+ }
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const decoded: any = verifyToken(token);
29
+
30
+ if (!decoded || !decoded.id) {
31
+ return next(new Error("Authentication error: Invalid token"));
32
+ }
33
+
34
+ const user = await prisma.user.findUnique({
35
+ where: { id: decoded.id },
36
+ select: { id: true, roles: true, institutionId: true },
37
+ });
38
+
39
+ if (!user) {
40
+ return next(new Error("Authentication error: User not found"));
41
+ }
42
+
43
+ // Attach user to socket data
44
+ socket.data.user = {
45
+ id: user.id,
46
+ roles: user.roles,
47
+ institutionId: user.institutionId,
48
+ };
49
+
50
+ next();
51
+ } catch {
52
+ next(new Error("Authentication error: Token verification failed"));
53
+ }
54
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Room generator functions.
3
+ * Using a consistent namespace pattern (e.g. user:<id>) ensures
4
+ * that rooms don't overlap and can be easily managed or targeted.
5
+ */
6
+
7
+ export const getUserRoom = (userId: string) => `user:${userId}`;
8
+ export const getInstitutionRoom = (institutionId: string) => `institution:${institutionId}`;
9
+ export const getClassRoom = (classId: string) => `class:${classId}`;
10
+ export const getSectionRoom = (sectionId: string) => `section:${sectionId}`;
11
+ export const getTopicRoom = (topic: string) => `topic:${topic}`;