@imksh/create 1.0.25 → 1.0.26

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 (267) 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/_gitignore +0 -24
  26. package/templates/next-ts/next-env.d.ts +3 -1
  27. package/templates/next-ts/next.config.mjs +1 -0
  28. package/templates/next-ts/package.json +13 -12
  29. package/templates/next-ts/postcss.config.js +5 -0
  30. package/templates/next-ts/public/logo.png +0 -0
  31. package/templates/next-ts/src/app/error.tsx +41 -0
  32. package/templates/next-ts/src/app/layout.tsx +2 -1
  33. package/templates/next-ts/src/app/not-found.tsx +5 -0
  34. package/templates/next-ts/src/app/page.tsx +1 -1
  35. package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
  36. package/templates/next-ts/src/components/ui/Button.tsx +63 -0
  37. package/templates/next-ts/src/components/ui/Input.tsx +79 -0
  38. package/templates/next-ts/src/index.css +1 -0
  39. package/templates/next-ts/src/store/useUiStore.ts +13 -7
  40. package/templates/next-ts/tsconfig.json +19 -5
  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/app.json +84 -0
  103. package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
  104. package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
  105. package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
  106. package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
  107. package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
  108. package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
  109. package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
  110. package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
  111. package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
  112. package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
  113. package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
  114. package/templates/production-fullstack/mobile/babel.config.js +13 -0
  115. package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
  116. package/templates/production-fullstack/mobile/eas.json +24 -0
  117. package/templates/production-fullstack/mobile/eslint.config.js +10 -0
  118. package/templates/production-fullstack/mobile/metro.config.js +6 -0
  119. package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
  120. package/templates/production-fullstack/mobile/package.json +89 -0
  121. package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
  122. package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
  123. package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  124. package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  125. package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
  126. package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
  127. package/templates/production-fullstack/mobile/src/app/global.css +82 -0
  128. package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  129. package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
  130. package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
  131. package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
  132. package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
  133. package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
  134. package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
  135. package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
  136. package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
  137. package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
  138. package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
  139. package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
  140. package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
  141. package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
  142. package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
  143. package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
  144. package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
  145. package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
  146. package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
  147. package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
  148. package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
  149. package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
  150. package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
  151. package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
  152. package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
  153. package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
  154. package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
  155. package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
  156. package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
  157. package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  158. package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  159. package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
  160. package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  161. package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
  162. package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
  163. package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
  164. package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
  165. package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
  166. package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
  167. package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
  168. package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  169. package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
  170. package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
  171. package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
  172. package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
  173. package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
  174. package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
  175. package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
  176. package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
  177. package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
  178. package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
  179. package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
  180. package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
  181. package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
  182. package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
  183. package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
  184. package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
  185. package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
  186. package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
  187. package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
  188. package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
  189. package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
  190. package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
  191. package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
  192. package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
  193. package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
  194. package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
  195. package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
  196. package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
  197. package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
  198. package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
  199. package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
  200. package/templates/production-fullstack/mobile/tsconfig.json +23 -0
  201. package/templates/production-fullstack/server/.env.example +63 -0
  202. package/templates/production-fullstack/server/README.md +75 -0
  203. package/templates/production-fullstack/server/eslint.config.js +13 -0
  204. package/templates/production-fullstack/server/package.json +58 -0
  205. package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
  206. package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
  207. package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
  208. package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
  209. package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
  210. package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
  211. package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
  212. package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
  213. package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
  214. package/templates/production-fullstack/server/prisma.config.ts +14 -0
  215. package/templates/production-fullstack/server/src/app.ts +36 -0
  216. package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
  217. package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
  218. package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
  219. package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
  220. package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
  221. package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
  222. package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
  223. package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
  224. package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
  225. package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
  226. package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
  227. package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
  228. package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
  229. package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
  230. package/templates/production-fullstack/server/src/config/env.ts +2 -0
  231. package/templates/production-fullstack/server/src/config/logger.ts +58 -0
  232. package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
  233. package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
  234. package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
  235. package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
  236. package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
  237. package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
  238. package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
  239. package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
  240. package/templates/production-fullstack/server/src/index.ts +17 -0
  241. package/templates/production-fullstack/server/src/routes/index.ts +12 -0
  242. package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
  243. package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
  244. package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
  245. package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
  246. package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
  247. package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
  248. package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
  249. package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
  250. package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
  251. package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
  252. package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
  253. package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
  254. package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
  255. package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
  256. package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
  257. package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
  258. package/templates/production-fullstack/server/tsconfig.json +14 -0
  259. package/templates/react-native/package.json +2 -1
  260. package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
  261. package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
  262. package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
  263. package/templates/react-native/src/config/sidebar.config.ts +0 -60
  264. package/templates/react-native/src/config/tabNavigation.ts +6 -36
  265. package/templates/react-native/src/services/notification.service.ts +22 -0
  266. package/templates/react-native/src/store/useNotificationStore.ts +158 -33
  267. /package/templates/{react-native → production-fullstack/mobile}/expo-env.d.ts +0 -0
@@ -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}`;
@@ -0,0 +1,32 @@
1
+ import { SocketEvents } from "./events.js";
2
+
3
+ // Basic user info injected into socket.data by the auth middleware
4
+ export interface SocketUser {
5
+ id: string;
6
+ roles?: { role: { name: string } }[] | unknown;
7
+ institutionId?: string | null;
8
+ }
9
+
10
+ export interface SocketData {
11
+ user: SocketUser;
12
+ }
13
+
14
+ export interface ServerToClientEvents {
15
+ [SocketEvents.NOTIFICATION_NEW]: (payload: unknown) => void;
16
+ [SocketEvents.CHAT_NEW_MESSAGE]: (payload: unknown) => void;
17
+ [SocketEvents.LMS_ASSIGNMENT_CREATED]: (payload: unknown) => void;
18
+ [SocketEvents.ATTENDANCE_MARKED]: (payload: unknown) => void;
19
+ }
20
+
21
+ export interface ClientToServerEvents {
22
+ [SocketEvents.SUBSCRIBE]: (payload: { topic: string }) => void;
23
+ [SocketEvents.UNSUBSCRIBE]: (payload: { topic: string }) => void;
24
+ [SocketEvents.NOTIFICATION_MARK_AS_READ]: (
25
+ payload: { notificationId: string },
26
+ callback: (response: { success: boolean; notificationId?: string; error?: string }) => void
27
+ ) => void;
28
+ }
29
+
30
+ export interface InterServerEvents {
31
+ ping: () => void;
32
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "mobile",
2
+ "name": "shivAi",
3
3
  "main": "expo-router/entry",
4
4
  "version": "1.0.0",
5
5
  "dependencies": {
@@ -19,6 +19,7 @@
19
19
  "expo-font": "~57.0.1",
20
20
  "expo-glass-effect": "~57.0.1",
21
21
  "expo-image": "~57.0.1",
22
+ "expo-image-picker": "^57.0.6",
22
23
  "expo-linear-gradient": "^57.0.1",
23
24
  "expo-linking": "~57.0.3",
24
25
  "expo-notifications": "~57.0.6",
@@ -3,8 +3,8 @@ import { View, TouchableOpacity } from "react-native";
3
3
  import { useAuthStore } from "../../store/useAuthStore";
4
4
  import { Txt } from "../../components/common/Typography";
5
5
  import { router, useFocusEffect } from "expo-router";
6
- import * as ImagePicker from "expo-image-picker";
7
6
  import { Ionicons } from "@expo/vector-icons";
7
+ import ImagePickerModal from "../../components/ui/inputs/ImagePickerModal";
8
8
  import Avatar from "../../components/ui/data-display/Avatar";
9
9
 
10
10
  import ScrollContainer from "../../components/ui/layout/ScrollContainer";
@@ -27,19 +27,7 @@ export default function EditProfileScreen() {
27
27
  const [name, setName] = useState(user?.name || "");
28
28
  const [phone, setPhone] = useState(user?.phone || "");
29
29
  const [avatarUri, setAvatarUri] = useState<string | null>(null);
30
-
31
- const pickImage = async () => {
32
- const result = await ImagePicker.launchImageLibraryAsync({
33
- mediaTypes: ImagePicker.MediaTypeOptions.Images,
34
- allowsEditing: true,
35
- aspect: [1, 1],
36
- quality: 0.8,
37
- });
38
-
39
- if (!result.canceled && result.assets[0]) {
40
- setAvatarUri(result.assets[0].uri);
41
- }
42
- };
30
+ const [isImagePickerOpen, setIsImagePickerOpen] = useState(false);
43
31
 
44
32
  const handleSave = async () => {
45
33
  const formData = new FormData();
@@ -112,7 +100,7 @@ export default function EditProfileScreen() {
112
100
  )}
113
101
  </View>
114
102
  <TouchableOpacity
115
- onPress={pickImage}
103
+ onPress={() => setIsImagePickerOpen(true)}
116
104
  className="absolute bottom-0 right-0 bg-primary p-2 rounded-full border-2 border-base-100"
117
105
  >
118
106
  <Ionicons name="camera" size={18} color="#fff" />
@@ -155,6 +143,14 @@ export default function EditProfileScreen() {
155
143
  </View>
156
144
  </View>
157
145
  </ScrollContainer>
146
+ <ImagePickerModal
147
+ isOpen={isImagePickerOpen}
148
+ onClose={() => setIsImagePickerOpen(false)}
149
+ onImageSelected={(image) => setAvatarUri(image.uri)}
150
+ allowsEditing
151
+ title="Profile Picture"
152
+ subtitle="Choose a new profile picture"
153
+ />
158
154
  </>
159
155
  );
160
156
  }
@@ -1,11 +1,11 @@
1
1
  import { View } from "react-native";
2
2
  import React from "react";
3
- import HomeHeader from "@/components/common/HomeHeader";
3
+ import TabHeader from "@/components/common/TabHeader";
4
4
 
5
5
  const dashboard = () => {
6
6
  return (
7
7
  <View>
8
- <HomeHeader title="Dashboard" icon="apps-outline" />
8
+ <TabHeader title="Dashboard" showMenuButton={false} />
9
9
  </View>
10
10
  );
11
11
  };