@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.
- package/package.json +1 -1
- package/src/commands/create.js +2 -1
- package/src/index.js +1 -1
- package/src/utils/askTemplate.js +1 -0
- package/templates/mern-react-native/mobile/_gitignore +43 -0
- package/templates/mern-react-native/mobile/package.json +2 -1
- package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +11 -15
- package/templates/mern-react-native/mobile/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/mern-react-native/mobile/src/app/(tabs)/search.tsx +2 -2
- package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +0 -60
- package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +6 -36
- package/templates/mern-react-native/mobile/src/services/notification.service.ts +22 -0
- package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +158 -33
- package/templates/next/next.config.mjs +1 -0
- package/templates/next/package.json +10 -9
- package/templates/next/postcss.config.js +5 -0
- package/templates/next/src/app/layout.js +1 -0
- package/templates/next/src/app/page.js +1 -1
- package/templates/next/src/components/layout/ProtectedRoute.jsx +3 -3
- package/templates/next/src/index.css +1 -0
- package/templates/next/src/store/useUiStore.js +13 -7
- package/templates/next-ts/.env.example +0 -0
- package/templates/next-ts/AGENTS.md +37 -0
- package/templates/next-ts/CLAUDE.md +1 -0
- package/templates/next-ts/_gitignore +0 -24
- package/templates/next-ts/next-env.d.ts +3 -1
- package/templates/next-ts/next.config.mjs +1 -0
- package/templates/next-ts/package.json +13 -12
- package/templates/next-ts/postcss.config.js +5 -0
- package/templates/next-ts/public/logo.png +0 -0
- package/templates/next-ts/src/app/error.tsx +41 -0
- package/templates/next-ts/src/app/layout.tsx +2 -1
- package/templates/next-ts/src/app/not-found.tsx +5 -0
- package/templates/next-ts/src/app/page.tsx +1 -1
- package/templates/next-ts/src/components/layout/ProtectedRoute.tsx +3 -3
- package/templates/next-ts/src/components/ui/Button.tsx +63 -0
- package/templates/next-ts/src/components/ui/Input.tsx +79 -0
- package/templates/next-ts/src/index.css +1 -0
- package/templates/next-ts/src/store/useUiStore.ts +13 -7
- package/templates/next-ts/tsconfig.json +19 -5
- package/templates/production-fullstack/client/index.html +14 -0
- package/templates/production-fullstack/client/package.json +47 -0
- package/templates/production-fullstack/client/public/_redirects +1 -0
- package/templates/production-fullstack/client/public/favicon.ico +0 -0
- package/templates/production-fullstack/client/public/favicon.svg +1 -0
- package/templates/production-fullstack/client/public/logo.png +0 -0
- package/templates/production-fullstack/client/public/manifest.json +24 -0
- package/templates/production-fullstack/client/public/sw.js +56 -0
- package/templates/production-fullstack/client/src/App.tsx +68 -0
- package/templates/production-fullstack/client/src/assets/animations/loading.json +1 -0
- package/templates/production-fullstack/client/src/assets/data/navigation.ts +29 -0
- package/templates/production-fullstack/client/src/assets/images/logo.png +0 -0
- package/templates/production-fullstack/client/src/components/layout/ProtectedRoute.tsx +23 -0
- package/templates/production-fullstack/client/src/components/layout/PublicLayout.tsx +13 -0
- package/templates/production-fullstack/client/src/components/layout/SidebarLayout.tsx +37 -0
- package/templates/production-fullstack/client/src/components/shared/Breadcrumbs.tsx +27 -0
- package/templates/production-fullstack/client/src/components/shared/LottieIcon.tsx +36 -0
- package/templates/production-fullstack/client/src/components/shared/NotFound.tsx +78 -0
- package/templates/production-fullstack/client/src/components/shared/PwaInstallPrompt.tsx +72 -0
- package/templates/production-fullstack/client/src/components/shared/SEO.tsx +67 -0
- package/templates/production-fullstack/client/src/components/shared/Scroll.tsx +25 -0
- package/templates/production-fullstack/client/src/components/shared/Unauthorized.tsx +76 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/CardSkeleton.tsx +23 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/DashboardSkeleton.tsx +19 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/ListSkeleton.tsx +24 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/PageSkeleton.tsx +26 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/ProfileSkeleton.tsx +23 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/Skeleton.tsx +18 -0
- package/templates/production-fullstack/client/src/components/shared/skeletons/common/TableSkeleton.tsx +33 -0
- package/templates/production-fullstack/client/src/components/ui/ConfirmModal.tsx +152 -0
- package/templates/production-fullstack/client/src/components/ui/Header.tsx +230 -0
- package/templates/production-fullstack/client/src/components/ui/Loading.tsx +19 -0
- package/templates/production-fullstack/client/src/components/ui/Modal.tsx +106 -0
- package/templates/production-fullstack/client/src/components/ui/SidebarHeader.tsx +34 -0
- package/templates/production-fullstack/client/src/components/ui/Table.tsx +118 -0
- package/templates/production-fullstack/client/src/components/ui/ToggleSwitch.tsx +38 -0
- package/templates/production-fullstack/client/src/components/ui/sidebar.tsx +152 -0
- package/templates/production-fullstack/client/src/config/index.ts +44 -0
- package/templates/production-fullstack/client/src/constants/navigation.ts +14 -0
- package/templates/production-fullstack/client/src/features/auth/api/authService.ts +19 -0
- package/templates/production-fullstack/client/src/features/auth/pages/Login.tsx +233 -0
- package/templates/production-fullstack/client/src/features/auth/store/useAuthStore.ts +143 -0
- package/templates/production-fullstack/client/src/features/core/pages/Home.tsx +150 -0
- package/templates/production-fullstack/client/src/features/core/pages/Profile.tsx +9 -0
- package/templates/production-fullstack/client/src/hooks/useLenis.ts +28 -0
- package/templates/production-fullstack/client/src/hooks/usePwaInstall.ts +45 -0
- package/templates/production-fullstack/client/src/hooks/useWindowSize.ts +25 -0
- package/templates/production-fullstack/client/src/index.css +158 -0
- package/templates/production-fullstack/client/src/lib/axios.ts +22 -0
- package/templates/production-fullstack/client/src/lib/dayjs.ts +12 -0
- package/templates/production-fullstack/client/src/lib/query-client.ts +11 -0
- package/templates/production-fullstack/client/src/lib/socket.ts +6 -0
- package/templates/production-fullstack/client/src/main.tsx +28 -0
- package/templates/production-fullstack/client/src/store/useUiStore.ts +74 -0
- package/templates/production-fullstack/client/src/utils/formatDate.ts +19 -0
- package/templates/production-fullstack/client/src/utils/getInitials.ts +10 -0
- package/templates/production-fullstack/client/src/vite-env.d.ts +1 -0
- package/templates/production-fullstack/client/tsconfig.json +25 -0
- package/templates/production-fullstack/client/tsconfig.node.json +10 -0
- package/templates/production-fullstack/client/vite.config.ts +8 -0
- package/templates/production-fullstack/mobile/LICENSE +21 -0
- package/templates/production-fullstack/mobile/app.json +84 -0
- package/templates/production-fullstack/mobile/assets/animations/celebrate.json +1 -0
- package/templates/production-fullstack/mobile/assets/animations/infinity.json +1 -0
- package/templates/production-fullstack/mobile/assets/animations/loading.json +1 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Bold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_ExtraBold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Medium.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_Regular.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/fonts/Inter_SemiBold.ttf +0 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logo.png +0 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logo.svg +1 -0
- package/templates/production-fullstack/mobile/assets/images/logo/logoWithBg.png +0 -0
- package/templates/production-fullstack/mobile/babel.config.js +13 -0
- package/templates/production-fullstack/mobile/drizzle.config.ts +8 -0
- package/templates/production-fullstack/mobile/eas.json +24 -0
- package/templates/production-fullstack/mobile/eslint.config.js +10 -0
- package/templates/production-fullstack/mobile/metro.config.js +6 -0
- package/templates/production-fullstack/mobile/nativewind-env.d.ts +3 -0
- package/templates/production-fullstack/mobile/package.json +89 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/_layout.tsx +12 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/change-password.tsx +112 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/production-fullstack/mobile/src/app/(auth)/login.tsx +253 -0
- package/templates/production-fullstack/mobile/src/app/_layout.tsx +102 -0
- package/templates/production-fullstack/mobile/src/app/global.css +82 -0
- package/templates/production-fullstack/mobile/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/production-fullstack/mobile/src/components/common/ConfirmationToast.tsx +67 -0
- package/templates/production-fullstack/mobile/src/components/common/Loading.tsx +26 -0
- package/templates/production-fullstack/mobile/src/components/common/LocationLoader.tsx +591 -0
- package/templates/production-fullstack/mobile/src/components/common/ScreenHeader.tsx +68 -0
- package/templates/production-fullstack/mobile/src/components/common/TabHeader.tsx +58 -0
- package/templates/production-fullstack/mobile/src/components/common/Typography.tsx +113 -0
- package/templates/production-fullstack/mobile/src/components/common/Welcome.tsx +55 -0
- package/templates/production-fullstack/mobile/src/components/ui/BottomModal.tsx +159 -0
- package/templates/production-fullstack/mobile/src/components/ui/ClassicTabBar.tsx +129 -0
- package/templates/production-fullstack/mobile/src/components/ui/CustomTabBar.tsx +238 -0
- package/templates/production-fullstack/mobile/src/components/ui/Sidebar.tsx +258 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/Button.tsx +180 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/ButtonGroup.tsx +32 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/FloatingActionButton.tsx +72 -0
- package/templates/production-fullstack/mobile/src/components/ui/buttons/IconButton.tsx +84 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Accordion.tsx +82 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Avatar.tsx +47 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Badge.tsx +104 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Card.tsx +36 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/Image.tsx +43 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/ImageViewer.tsx +33 -0
- package/templates/production-fullstack/mobile/src/components/ui/data-display/SwipeableRow.tsx +94 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/CircularProgress.tsx +38 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/GestureWrapper.tsx +114 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/ProgressBar.tsx +51 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/Skeleton.tsx +84 -0
- package/templates/production-fullstack/mobile/src/components/ui/feedback/Spinner.tsx +50 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Checkbox.tsx +54 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Input.tsx +158 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/OTPInput.tsx +83 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/PasswordInput.tsx +16 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Radio.tsx +54 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/SearchBar.tsx +53 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Slider.tsx +49 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/Switch.tsx +75 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/TextArea.tsx +15 -0
- package/templates/production-fullstack/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Container.tsx +23 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Divider.tsx +20 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Grid.tsx +62 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/KeyboardAvoidingViewWrapper.tsx +16 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/ScrollContainer.tsx +21 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Section.tsx +51 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Spacer.tsx +6 -0
- package/templates/production-fullstack/mobile/src/components/ui/layout/Stack.tsx +34 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/Pagination.tsx +61 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/ProgressStepper.tsx +66 -0
- package/templates/production-fullstack/mobile/src/components/ui/navigation/Tabs.tsx +173 -0
- package/templates/production-fullstack/mobile/src/config/sidebar.config.ts +72 -0
- package/templates/production-fullstack/mobile/src/config/tabNavigation.ts +64 -0
- package/templates/production-fullstack/mobile/src/constants/Colors.ts +71 -0
- package/templates/production-fullstack/mobile/src/db/index.ts +9 -0
- package/templates/production-fullstack/mobile/src/db/schema.ts +14 -0
- package/templates/production-fullstack/mobile/src/features/auth/api/auth.service.ts +35 -0
- package/templates/production-fullstack/mobile/src/features/auth/store/useAuthStore.ts +273 -0
- package/templates/production-fullstack/mobile/src/features/auth/types/auth.type.ts +14 -0
- package/templates/production-fullstack/mobile/src/hooks/useDebounce.ts +17 -0
- package/templates/production-fullstack/mobile/src/lib/axios.ts +27 -0
- package/templates/production-fullstack/mobile/src/lib/query-client.ts +11 -0
- package/templates/production-fullstack/mobile/src/lib/socket.ts +36 -0
- package/templates/production-fullstack/mobile/src/lib/storage.ts +55 -0
- package/templates/production-fullstack/mobile/src/services/permission.service.ts +214 -0
- package/templates/production-fullstack/mobile/src/store/localStore.ts +7 -0
- package/templates/production-fullstack/mobile/src/store/useNotificationStore.ts +53 -0
- package/templates/production-fullstack/mobile/src/types/common.type.ts +6 -0
- package/templates/production-fullstack/mobile/src/types/images.d.ts +24 -0
- package/templates/production-fullstack/mobile/src/utils/toast.ts +26 -0
- package/templates/production-fullstack/mobile/tailwind.config.js +71 -0
- package/templates/production-fullstack/mobile/tsconfig.json +23 -0
- package/templates/production-fullstack/server/.env.example +63 -0
- package/templates/production-fullstack/server/README.md +75 -0
- package/templates/production-fullstack/server/eslint.config.js +13 -0
- package/templates/production-fullstack/server/package.json +58 -0
- package/templates/production-fullstack/server/prisma/schema/core/attachment.prisma +14 -0
- package/templates/production-fullstack/server/prisma/schema/core/notification.prisma +18 -0
- package/templates/production-fullstack/server/prisma/schema/core/permission.prisma +26 -0
- package/templates/production-fullstack/server/prisma/schema/core/role.prisma +25 -0
- package/templates/production-fullstack/server/prisma/schema/core/user.prisma +56 -0
- package/templates/production-fullstack/server/prisma/schema/enums/common.prisma +13 -0
- package/templates/production-fullstack/server/prisma/schema/enums/notification.prisma +6 -0
- package/templates/production-fullstack/server/prisma/schema/enums/user.prisma +11 -0
- package/templates/production-fullstack/server/prisma/schema/schema.prisma +7 -0
- package/templates/production-fullstack/server/prisma.config.ts +14 -0
- package/templates/production-fullstack/server/src/app.ts +36 -0
- package/templates/production-fullstack/server/src/common/middlewares/auth.middleware.ts +45 -0
- package/templates/production-fullstack/server/src/common/middlewares/errorHandler.ts +16 -0
- package/templates/production-fullstack/server/src/common/middlewares/multer.ts +20 -0
- package/templates/production-fullstack/server/src/common/middlewares/rateLimiter.ts +53 -0
- package/templates/production-fullstack/server/src/common/services/email.service.ts +28 -0
- package/templates/production-fullstack/server/src/common/services/push.service.ts +65 -0
- package/templates/production-fullstack/server/src/common/services/storage.service.ts +9 -0
- package/templates/production-fullstack/server/src/common/types/express.d.ts +10 -0
- package/templates/production-fullstack/server/src/common/utils/cloudinaryUpload.ts +44 -0
- package/templates/production-fullstack/server/src/common/utils/emailOtp.ts +30 -0
- package/templates/production-fullstack/server/src/common/utils/genOtpToken.ts +22 -0
- package/templates/production-fullstack/server/src/common/utils/generateVapidKeys.ts +12 -0
- package/templates/production-fullstack/server/src/common/utils/jwt.ts +27 -0
- package/templates/production-fullstack/server/src/common/utils/pushHelper.ts +22 -0
- package/templates/production-fullstack/server/src/config/env.ts +2 -0
- package/templates/production-fullstack/server/src/config/logger.ts +58 -0
- package/templates/production-fullstack/server/src/config/prisma.ts +26 -0
- package/templates/production-fullstack/server/src/features/attachments/upload.controller.ts +22 -0
- package/templates/production-fullstack/server/src/features/attachments/upload.routes.ts +11 -0
- package/templates/production-fullstack/server/src/features/auth/auth.controller.ts +124 -0
- package/templates/production-fullstack/server/src/features/auth/auth.routes.ts +22 -0
- package/templates/production-fullstack/server/src/features/auth/auth.service.ts +155 -0
- package/templates/production-fullstack/server/src/features/notifications/push.controller.ts +55 -0
- package/templates/production-fullstack/server/src/features/notifications/push.routes.ts +11 -0
- package/templates/production-fullstack/server/src/index.ts +17 -0
- package/templates/production-fullstack/server/src/routes/index.ts +12 -0
- package/templates/production-fullstack/server/src/sockets/auth.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/connection.ts +31 -0
- package/templates/production-fullstack/server/src/sockets/emitters/assignment.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/attendance.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/exam.emitter.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/emitters/notification.emitter.ts +17 -0
- package/templates/production-fullstack/server/src/sockets/events.ts +27 -0
- package/templates/production-fullstack/server/src/sockets/handlers/attendance.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/chat.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/index.ts +15 -0
- package/templates/production-fullstack/server/src/sockets/handlers/lms.handler.ts +0 -0
- package/templates/production-fullstack/server/src/sockets/handlers/notification.handler.ts +39 -0
- package/templates/production-fullstack/server/src/sockets/index.ts +39 -0
- package/templates/production-fullstack/server/src/sockets/middleware.ts +54 -0
- package/templates/production-fullstack/server/src/sockets/rooms.ts +11 -0
- package/templates/production-fullstack/server/src/sockets/types.ts +32 -0
- package/templates/production-fullstack/server/tsconfig.json +14 -0
- package/templates/react-native/package.json +2 -1
- package/templates/react-native/src/app/(auth)/edit-profile.tsx +11 -15
- package/templates/react-native/src/app/(tabs)/dashboard.tsx +2 -2
- package/templates/react-native/src/app/(tabs)/search.tsx +2 -2
- package/templates/react-native/src/config/sidebar.config.ts +0 -60
- package/templates/react-native/src/config/tabNavigation.ts +6 -36
- package/templates/react-native/src/services/notification.service.ts +22 -0
- package/templates/react-native/src/store/useNotificationStore.ts +158 -33
- /package/templates/{react-native → production-fullstack/mobile}/expo-env.d.ts +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { verifyToken } from "../utils/jwt.js";
|
|
3
|
+
import logger from "../../config/logger.js";
|
|
4
|
+
import { prisma } from "../../config/prisma.js";
|
|
5
|
+
|
|
6
|
+
export const protect = async (req: Request, res: Response, next: NextFunction) => {
|
|
7
|
+
let token: string | undefined;
|
|
8
|
+
|
|
9
|
+
if (req.cookies?.token) {
|
|
10
|
+
token = req.cookies.token;
|
|
11
|
+
} else if (
|
|
12
|
+
req.headers.authorization &&
|
|
13
|
+
req.headers.authorization.startsWith("Bearer")
|
|
14
|
+
) {
|
|
15
|
+
token = req.headers.authorization.split(" ")[1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!token) {
|
|
19
|
+
return res.status(401).json({ message: "Not authorized, no token provided" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const decoded: any = verifyToken(token);
|
|
24
|
+
if (!decoded) {
|
|
25
|
+
return res.status(401).json({ message: "Not authorized, invalid token" });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const user = await prisma.user.findUnique({
|
|
29
|
+
where: { id: decoded.id },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!user) {
|
|
33
|
+
return res.status(401).json({ message: "Not authorized, user not found" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const safeUser: any = { ...user };
|
|
37
|
+
delete safeUser.password;
|
|
38
|
+
|
|
39
|
+
req.user = safeUser;
|
|
40
|
+
return next();
|
|
41
|
+
} catch (error: any) {
|
|
42
|
+
logger.error("Auth middleware error", error);
|
|
43
|
+
return res.status(401).json({ message: "Not authorized, token verification failed" });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
|
|
3
|
+
export const notFound = (req: Request, res: Response, next: NextFunction) => {
|
|
4
|
+
const error = new Error(`Not Found - ${req.originalUrl}`);
|
|
5
|
+
res.status(404);
|
|
6
|
+
next(error);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
|
|
10
|
+
const statusCode = err.status || err.statusCode || (res.statusCode === 200 ? 500 : res.statusCode);
|
|
11
|
+
res.status(statusCode).json({
|
|
12
|
+
success: false,
|
|
13
|
+
message: err.message || "An unexpected error occurred",
|
|
14
|
+
stack: process.env.NODE_ENV === "production" ? undefined : err.stack,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import multer, { FileFilterCallback } from "multer";
|
|
2
|
+
import { Request } from "express";
|
|
3
|
+
|
|
4
|
+
const storage = multer.memoryStorage();
|
|
5
|
+
|
|
6
|
+
const fileFilter = (req: Request, file: Express.Multer.File, cb: FileFilterCallback) => {
|
|
7
|
+
if (file.mimetype.startsWith("image/")) {
|
|
8
|
+
cb(null, true);
|
|
9
|
+
} else {
|
|
10
|
+
cb(new Error("Only image files are allowed!"));
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const upload = multer({
|
|
15
|
+
storage,
|
|
16
|
+
fileFilter,
|
|
17
|
+
limits: {
|
|
18
|
+
fileSize: 5 * 1024 * 1024, // 5MB limit
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import rateLimit from "express-rate-limit";
|
|
2
|
+
import { Request, Response } from "express";
|
|
3
|
+
|
|
4
|
+
interface LimiterConfig {
|
|
5
|
+
windowMs: number;
|
|
6
|
+
max: number;
|
|
7
|
+
message: string;
|
|
8
|
+
skipSuccessfulRequests?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const createLimiter = ({
|
|
12
|
+
windowMs,
|
|
13
|
+
max,
|
|
14
|
+
message,
|
|
15
|
+
skipSuccessfulRequests = false,
|
|
16
|
+
}: LimiterConfig) =>
|
|
17
|
+
rateLimit({
|
|
18
|
+
windowMs,
|
|
19
|
+
max,
|
|
20
|
+
standardHeaders: true,
|
|
21
|
+
legacyHeaders: false,
|
|
22
|
+
skipSuccessfulRequests,
|
|
23
|
+
|
|
24
|
+
keyGenerator: (req: any) => {
|
|
25
|
+
return req.user?.id || req.ip || req.headers["x-forwarded-for"] || req.socket.remoteAddress;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
handler: (req: Request, res: Response) => {
|
|
29
|
+
res.status(429).json({
|
|
30
|
+
success: false,
|
|
31
|
+
message,
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const apiLimiter = createLimiter({
|
|
37
|
+
windowMs: 15 * 60 * 1000,
|
|
38
|
+
max: 100,
|
|
39
|
+
message: "Too many requests. Please try again later.",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const authLimiter = createLimiter({
|
|
43
|
+
windowMs: 15 * 60 * 1000,
|
|
44
|
+
max: 10,
|
|
45
|
+
skipSuccessfulRequests: true,
|
|
46
|
+
message: "Too many login attempts. Please try again later.",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const uploadLimiter = createLimiter({
|
|
50
|
+
windowMs: 60 * 60 * 1000,
|
|
51
|
+
max: 20,
|
|
52
|
+
message: "Upload limit reached. Please try again in an hour.",
|
|
53
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
import logger from "../../config/logger.js";
|
|
3
|
+
|
|
4
|
+
const sendEmail = async (to: string, subject: string, message: string) => {
|
|
5
|
+
try {
|
|
6
|
+
const transporter = nodemailer.createTransport({
|
|
7
|
+
service: "gmail",
|
|
8
|
+
auth: {
|
|
9
|
+
user: process.env.SMTP_USER,
|
|
10
|
+
pass: process.env.SMTP_PASS,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const mailOptions = {
|
|
15
|
+
from: process.env.SMTP_USER,
|
|
16
|
+
to,
|
|
17
|
+
subject,
|
|
18
|
+
html: message,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const res = await transporter.sendMail(mailOptions);
|
|
22
|
+
logger.success(`Email sent to ${to}: ${res.messageId}`);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
logger.error("Failed to send email", error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default sendEmail;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import webpush from "web-push";
|
|
2
|
+
import { Expo, ExpoPushMessage } from "expo-server-sdk";
|
|
3
|
+
import prisma from "../../config/prisma.js";
|
|
4
|
+
import logger from "../../config/logger.js";
|
|
5
|
+
|
|
6
|
+
// Initialize Web Push
|
|
7
|
+
webpush.setVapidDetails(
|
|
8
|
+
(process.env.VAPID_SUBJECT as string) || "",
|
|
9
|
+
(process.env.VAPID_PUBLIC_KEY as string) || "",
|
|
10
|
+
(process.env.VAPID_PRIVATE_KEY as string) || ""
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default webpush;
|
|
14
|
+
|
|
15
|
+
// Initialize Expo Push
|
|
16
|
+
const expo = new Expo();
|
|
17
|
+
|
|
18
|
+
export const sendPushNotification = async (
|
|
19
|
+
userId: string,
|
|
20
|
+
title: string,
|
|
21
|
+
body: string,
|
|
22
|
+
data?: Record<string, unknown>
|
|
23
|
+
) => {
|
|
24
|
+
try {
|
|
25
|
+
// 1. Fetch all Expo push tokens for the user
|
|
26
|
+
const subscriptions = await prisma.pushSubscription.findMany({
|
|
27
|
+
where: {
|
|
28
|
+
userId,
|
|
29
|
+
type: "EXPO",
|
|
30
|
+
token: { not: null },
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (subscriptions.length === 0) return;
|
|
35
|
+
|
|
36
|
+
// 2. Create the messages
|
|
37
|
+
const messages: ExpoPushMessage[] = [];
|
|
38
|
+
for (const sub of subscriptions) {
|
|
39
|
+
if (!sub.token || !Expo.isExpoPushToken(sub.token)) {
|
|
40
|
+
logger.error(`Push token ${sub.token} is not a valid Expo push token`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
messages.push({
|
|
45
|
+
to: sub.token,
|
|
46
|
+
sound: "default",
|
|
47
|
+
title,
|
|
48
|
+
body,
|
|
49
|
+
data,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Send the messages
|
|
54
|
+
const chunks = expo.chunkPushNotifications(messages);
|
|
55
|
+
for (const chunk of chunks) {
|
|
56
|
+
try {
|
|
57
|
+
await expo.sendPushNotificationsAsync(chunk);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error("Error sending chunk of push notifications:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error("Failed to send push notification:", error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import cloudinary from "../services/storage.service.js";
|
|
2
|
+
import logger from "../../config/logger.js";
|
|
3
|
+
|
|
4
|
+
const uploadToCloudinary = async (file: any, options: any = {}) => {
|
|
5
|
+
if (!file?.buffer) {
|
|
6
|
+
throw new Error("Invalid file.");
|
|
7
|
+
}
|
|
8
|
+
const dataURI = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
|
|
9
|
+
const result = await cloudinary.uploader.upload(dataURI, {
|
|
10
|
+
folder: options.folder || "uploads",
|
|
11
|
+
...options,
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
url: result.secure_url,
|
|
15
|
+
publicId: result.public_id,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const uploadSingleToCloudinary = async (file: any, options: any = {}) => {
|
|
20
|
+
try {
|
|
21
|
+
return await uploadToCloudinary(file, options);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.error("Cloudinary Upload Error", error);
|
|
24
|
+
throw new Error("Failed to upload image.");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const uploadMultipleToCloudinary = async (files: any[] = [], options: any = {}) => {
|
|
29
|
+
try {
|
|
30
|
+
return await Promise.all(files.map((file) => uploadToCloudinary(file, options)));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger.error("Cloudinary Upload Error", error);
|
|
33
|
+
throw new Error("Failed to upload images.");
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const deleteFromCloudinary = async (publicId: string) => {
|
|
38
|
+
try {
|
|
39
|
+
return await cloudinary.uploader.destroy(publicId);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
logger.error("Cloudinary Delete Error", error);
|
|
42
|
+
throw new Error("Failed to delete image.");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import sendEmail from "../services/email.service.js";
|
|
2
|
+
|
|
3
|
+
const APP_NAME = process.env.APP_NAME || "My App";
|
|
4
|
+
const APP_URL = process.env.APP_URL || "";
|
|
5
|
+
const APP_PRIMARY_COLOR = process.env.APP_PRIMARY_COLOR || "#2563eb";
|
|
6
|
+
|
|
7
|
+
export const emailOtp = (otp: string, expiry = 10) => ({
|
|
8
|
+
subject: `${APP_NAME} - Verification Code`,
|
|
9
|
+
html: `
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8" />
|
|
14
|
+
<title>${APP_NAME}</title>
|
|
15
|
+
</head>
|
|
16
|
+
<body style="margin:0;padding:40px;background:#f3f4f6;font-family:Arial,sans-serif;">
|
|
17
|
+
<div style="background:#fff;border-radius:12px;padding:30px;max-width:500px;margin:auto;">
|
|
18
|
+
<h2>Verification Code</h2>
|
|
19
|
+
<p>Your code is valid for ${expiry} minutes:</p>
|
|
20
|
+
<div style="font-size:32px;font-weight:bold;color:${APP_PRIMARY_COLOR};text-align:center;margin:20px 0;">${otp}</div>
|
|
21
|
+
</div>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
`,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const sendOtpEmail = async (to: string, otp: string, expiry = 10) => {
|
|
28
|
+
const template = emailOtp(otp, expiry);
|
|
29
|
+
await sendEmail(to, template.subject, template.html);
|
|
30
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { Response } from "express";
|
|
3
|
+
|
|
4
|
+
const genOtpToken = (user: any, res?: Response) => {
|
|
5
|
+
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
6
|
+
const token = jwt.sign({ id: user._id || user.id }, JWT_SECRET, {
|
|
7
|
+
expiresIn: "10m",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
if (res) {
|
|
11
|
+
res.cookie("token", token, {
|
|
12
|
+
httpOnly: true,
|
|
13
|
+
secure: process.env.NODE_ENV === "production",
|
|
14
|
+
sameSite: "strict",
|
|
15
|
+
maxAge: 10 * 60 * 1000, // 10 minutes
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return token;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default genOtpToken;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import webpush from "web-push";
|
|
2
|
+
import logger from "../../config/logger.js";
|
|
3
|
+
|
|
4
|
+
const generateVapidKeys = () => {
|
|
5
|
+
const vapidKeys = webpush.generateVAPIDKeys();
|
|
6
|
+
logger.info("Generated VAPID Keys:");
|
|
7
|
+
logger.info(`Public Key: ${vapidKeys.publicKey}`);
|
|
8
|
+
logger.info(`Private Key: ${vapidKeys.privateKey}`);
|
|
9
|
+
return vapidKeys;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default generateVapidKeys;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { Response } from "express";
|
|
3
|
+
|
|
4
|
+
export const generateToken = (user: any, res?: Response) => {
|
|
5
|
+
const payload = { id: user._id || user.id };
|
|
6
|
+
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
7
|
+
const token = jwt.sign(payload, JWT_SECRET, { expiresIn: "30d" });
|
|
8
|
+
|
|
9
|
+
if (res) {
|
|
10
|
+
res.cookie("token", token, {
|
|
11
|
+
httpOnly: true,
|
|
12
|
+
secure: process.env.NODE_ENV === "production",
|
|
13
|
+
sameSite: "strict",
|
|
14
|
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return token;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const verifyToken = (token: string) => {
|
|
21
|
+
try {
|
|
22
|
+
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
23
|
+
return jwt.verify(token, JWT_SECRET);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import webpush from "../services/push.service.js";
|
|
2
|
+
import logger from "../../config/logger.js";
|
|
3
|
+
import { prisma } from "../../config/prisma.js";
|
|
4
|
+
|
|
5
|
+
export const sendPushToUser = async (userId: string, title: string, body: string, url: string = "/") => {
|
|
6
|
+
try {
|
|
7
|
+
const payload = JSON.stringify({ title, body, url });
|
|
8
|
+
const subs = await prisma.pushSubscription.findMany({ where: { userId } });
|
|
9
|
+
for (const sub of subs) {
|
|
10
|
+
const pushSub = { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } };
|
|
11
|
+
await webpush.sendNotification(pushSub, payload).catch(err => {
|
|
12
|
+
if (err.statusCode === 404 || err.statusCode === 410) {
|
|
13
|
+
prisma.pushSubscription.delete({ where: { id: sub.id } }).catch(()=>{});
|
|
14
|
+
} else {
|
|
15
|
+
logger.error("Push Error", err);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error("Error sending push notification to user", error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const colors = {
|
|
2
|
+
reset: "\x1b[0m",
|
|
3
|
+
info: "\x1b[36m",
|
|
4
|
+
success: "\x1b[32m",
|
|
5
|
+
warn: "\x1b[33m",
|
|
6
|
+
error: "\x1b[31m",
|
|
7
|
+
timestamp: "\x1b[90m",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type LogLevel = "info" | "success" | "warn" | "error";
|
|
11
|
+
|
|
12
|
+
const formatMessage = (level: LogLevel, message: string) => {
|
|
13
|
+
const timestamp = new Date().toISOString();
|
|
14
|
+
|
|
15
|
+
return `${colors.timestamp}[${timestamp}]${colors.reset} ${
|
|
16
|
+
colors[level]
|
|
17
|
+
}[${level.toUpperCase()}]${colors.reset} ${message}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const print = (
|
|
21
|
+
level: LogLevel,
|
|
22
|
+
message: string,
|
|
23
|
+
...meta: unknown[]
|
|
24
|
+
) => {
|
|
25
|
+
const formatted = formatMessage(level, message);
|
|
26
|
+
|
|
27
|
+
switch (level) {
|
|
28
|
+
case "warn":
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.warn(formatted, ...meta);
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case "error":
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.error(formatted, ...meta);
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log(formatted, ...meta);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const logger = {
|
|
45
|
+
info: (message: string, ...meta: unknown[]) =>
|
|
46
|
+
print("info", message, ...meta),
|
|
47
|
+
|
|
48
|
+
success: (message: string, ...meta: unknown[]) =>
|
|
49
|
+
print("success", message, ...meta),
|
|
50
|
+
|
|
51
|
+
warn: (message: string, ...meta: unknown[]) =>
|
|
52
|
+
print("warn", message, ...meta),
|
|
53
|
+
|
|
54
|
+
error: (message: string, ...meta: unknown[]) =>
|
|
55
|
+
print("error", message, ...meta),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default logger;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PrismaClient } from "@prisma/client";
|
|
2
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
3
|
+
import pg from "pg";
|
|
4
|
+
import logger from "./logger.js";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const connectionString = process.env.DATABASE_URL;
|
|
10
|
+
const pool = new pg.Pool({ connectionString });
|
|
11
|
+
const adapter = new PrismaPg(pool);
|
|
12
|
+
|
|
13
|
+
export const prisma = new PrismaClient({ adapter });
|
|
14
|
+
|
|
15
|
+
const connectDB = async () => {
|
|
16
|
+
try {
|
|
17
|
+
await prisma.$connect();
|
|
18
|
+
logger.success("Prisma Database connected successfully");
|
|
19
|
+
} catch (error: unknown) {
|
|
20
|
+
const err = error as Error;
|
|
21
|
+
logger.error(`Prisma Database connection failed: ${err.message}`, err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default connectDB;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { uploadSingleToCloudinary } from "../../common/utils/cloudinaryUpload.js";
|
|
3
|
+
import logger from "../../config/logger.js";
|
|
4
|
+
|
|
5
|
+
export const uploadFile = async (req: Request, res: Response, next: NextFunction) => {
|
|
6
|
+
try {
|
|
7
|
+
if (!req.file) {
|
|
8
|
+
return res.status(400).json({ success: false, message: "No file uploaded" });
|
|
9
|
+
}
|
|
10
|
+
const result = await uploadSingleToCloudinary(req.file, {
|
|
11
|
+
folder: req.body.folder || "general_uploads",
|
|
12
|
+
});
|
|
13
|
+
res.status(200).json({
|
|
14
|
+
success: true,
|
|
15
|
+
message: "File uploaded successfully",
|
|
16
|
+
data: result,
|
|
17
|
+
});
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error("Upload controller error", error);
|
|
20
|
+
next(error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { upload } from "../../common/middlewares/multer.js";
|
|
3
|
+
import { uploadFile } from "./upload.controller.js";
|
|
4
|
+
import { protect } from "../../common/middlewares/auth.middleware.js";
|
|
5
|
+
import { uploadLimiter } from "../../common/middlewares/rateLimiter.js";
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
router.post("/", protect, uploadLimiter, upload.single("file"), uploadFile);
|
|
10
|
+
|
|
11
|
+
export default router;
|
|
@@ -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
|
+
};
|