@imksh/create 1.0.31 → 1.0.33
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/README.md +2 -0
- package/package.json +1 -1
- package/src/commands/create.js +2 -0
- package/src/index.js +1 -1
- package/templates/layers/next/javascript/package.json +8 -2
- package/templates/layers/next/typescript/.eslintrc.json +12 -0
- package/templates/layers/next/typescript/package.json +14 -3
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -1
- package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
- package/templates/layers/next/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +2 -1
- package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
- package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
- package/templates/layers/next/typescript/src/services/authService.ts +1 -1
- package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/node/javascript/.env.example +3 -1
- package/templates/layers/node/javascript/package.json +12 -3
- package/templates/layers/node/javascript/src/app.js +2 -1
- package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
- package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
- package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
- package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
- package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node/typescript/.env.example +3 -1
- package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node/typescript/package.json +16 -2
- package/templates/layers/node/typescript/src/app.ts +3 -2
- package/templates/layers/node/typescript/src/config/db.ts +3 -2
- package/templates/layers/node/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
- package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
- package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
- package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
- package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
- package/templates/layers/node-prisma/javascript/.env.example +3 -1
- package/templates/layers/node-prisma/javascript/package.json +11 -2
- package/templates/layers/node-prisma/javascript/src/app.js +2 -1
- package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
- package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
- package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
- package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
- package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
- package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
- package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
- package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
- package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
- package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
- package/templates/layers/node-prisma/typescript/.env.example +3 -1
- package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
- package/templates/layers/node-prisma/typescript/package.json +19 -5
- package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
- package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
- package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
- package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
- package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
- package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
- package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
- package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
- package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
- package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
- package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
- package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
- package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
- package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
- package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
- package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
- package/templates/layers/prisma/javascript/package.json +8 -0
- package/templates/layers/prisma/typescript/package.json +14 -1
- package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
- package/templates/layers/react/javascript/package.json +6 -0
- package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
- package/templates/layers/react/typescript/package.json +11 -2
- package/templates/layers/react/typescript/src/components/common/Table.tsx +4 -4
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +1 -1
- package/templates/layers/react/typescript/src/main.tsx +11 -11
- package/templates/layers/react/typescript/src/services/authService.ts +1 -1
- package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
- package/templates/layers/react-native/base/app.json +9 -22
- package/templates/layers/react-native/base/eslint.config.js +5 -1
- package/templates/layers/react-native/base/package.json +40 -33
- package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
- package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
- package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
- package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
- package/templates/layers/react-native/base/src/app/_layout.tsx +4 -8
- package/templates/layers/react-native/base/src/app/screens/doc-viewer.tsx +378 -0
- package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
- package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +123 -0
- package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
- package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
- package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
- package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +90 -91
- package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
- package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
- package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
- package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
- package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
- package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
- package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
- package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
- package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
- package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
- package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
- package/templates/layers/react-native/base/src/config/api.ts +5 -5
- package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
- package/templates/layers/react-native/base/src/services/permission.service.ts +168 -44
- package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
- package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
- package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
- package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
- package/templates/layers/react-native/base/src/utils/env.ts +47 -0
- package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
- package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
- package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
- package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
- package/templates/presets/production-fullstack/overrides/mobile/package.json +39 -29
- package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
- package/templates/presets/production-fullstack/overrides/mobile/src/services/permission.service.ts +168 -44
- package/templates/presets/production-fullstack/overrides/mobile/src/store/useNotificationStore.ts +16 -0
- package/templates/presets/production-fullstack/overrides/mobile/src/utils/env.ts +47 -0
- package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
- package/templates/layers/react-native/base/src/app/doc-viewer.tsx +0 -193
- package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
- package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
- /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
|
@@ -1,19 +1,46 @@
|
|
|
1
|
+
import { OAuth2Client } from "google-auth-library";
|
|
1
2
|
import bcrypt from "bcrypt";
|
|
2
3
|
import { prisma } from "../config/prisma.js";
|
|
3
4
|
import { sendOtpEmail } from "../utils/emailOtp.js";
|
|
4
5
|
import {
|
|
5
6
|
uploadSingleToCloudinary,
|
|
6
7
|
deleteFromCloudinary,
|
|
8
|
+
type CloudinaryUploadFile,
|
|
7
9
|
} from "../utils/cloudinaryUpload.js";
|
|
8
10
|
import logger from "../utils/logger.js";
|
|
9
|
-
|
|
11
|
+
import { ApiError } from "../utils/ApiError.js";
|
|
12
|
+
|
|
13
|
+
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID || "");
|
|
14
|
+
|
|
15
|
+
export interface SignupDTO {
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
phone?: string;
|
|
19
|
+
password: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LoginDTO {
|
|
23
|
+
email: string;
|
|
24
|
+
password: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface VerifyOtpDTO {
|
|
28
|
+
email: string;
|
|
29
|
+
otp: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UpdateProfileDTO {
|
|
33
|
+
name?: string;
|
|
34
|
+
phone?: string;
|
|
35
|
+
file?: CloudinaryUploadFile;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const signup = async ({ name, email, phone, password }: SignupDTO) => {
|
|
10
39
|
const existingUser = await prisma.user.findUnique({
|
|
11
40
|
where: { email },
|
|
12
41
|
});
|
|
13
42
|
if (existingUser) {
|
|
14
|
-
|
|
15
|
-
err.status = 409;
|
|
16
|
-
throw err;
|
|
43
|
+
throw new ApiError(409, "User already exists");
|
|
17
44
|
}
|
|
18
45
|
|
|
19
46
|
const salt = await bcrypt.genSalt(10);
|
|
@@ -33,33 +60,29 @@ export const signup = async ({ name, email, phone, password }: any) => {
|
|
|
33
60
|
|
|
34
61
|
return newUser;
|
|
35
62
|
};
|
|
36
|
-
|
|
63
|
+
|
|
64
|
+
export const login = async ({ email, password }: LoginDTO) => {
|
|
37
65
|
const user = await prisma.user.findUnique({
|
|
38
66
|
where: { email },
|
|
39
67
|
});
|
|
40
68
|
if (!user) {
|
|
41
|
-
|
|
42
|
-
err.status = 404;
|
|
43
|
-
throw err;
|
|
69
|
+
throw new ApiError(404, "User not found");
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
const isValid = await bcrypt.compare(password, user.password);
|
|
47
73
|
if (!isValid) {
|
|
48
|
-
|
|
49
|
-
err.status = 401;
|
|
50
|
-
throw err;
|
|
74
|
+
throw new ApiError(401, "Invalid Credentials");
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
return user;
|
|
54
78
|
};
|
|
79
|
+
|
|
55
80
|
export const generateOtp = async (email: string) => {
|
|
56
81
|
const user = await prisma.user.findUnique({
|
|
57
82
|
where: { email },
|
|
58
83
|
});
|
|
59
84
|
if (!user) {
|
|
60
|
-
|
|
61
|
-
err.status = 404;
|
|
62
|
-
throw err;
|
|
85
|
+
throw new ApiError(404, "User not found");
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
const existingOtp = await prisma.otp.findUnique({
|
|
@@ -84,35 +107,31 @@ export const generateOtp = async (email: string) => {
|
|
|
84
107
|
await sendOtpEmail(email, otp);
|
|
85
108
|
return true;
|
|
86
109
|
};
|
|
87
|
-
|
|
110
|
+
|
|
111
|
+
export const verifyOtp = async ({ email, otp }: VerifyOtpDTO) => {
|
|
88
112
|
const user = await prisma.user.findUnique({
|
|
89
113
|
where: { email },
|
|
90
114
|
});
|
|
91
115
|
if (!user) {
|
|
92
|
-
|
|
93
|
-
err.status = 404;
|
|
94
|
-
throw err;
|
|
116
|
+
throw new ApiError(404, "User not found");
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
const existingOtp = await prisma.otp.findUnique({
|
|
98
120
|
where: { email },
|
|
99
121
|
});
|
|
100
122
|
if (!existingOtp) {
|
|
101
|
-
|
|
102
|
-
err.status = 400;
|
|
103
|
-
throw err;
|
|
123
|
+
throw new ApiError(400, "OTP match failed");
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
107
127
|
if (!isMatched) {
|
|
108
|
-
|
|
109
|
-
err.status = 400;
|
|
110
|
-
throw err;
|
|
128
|
+
throw new ApiError(400, "OTP match failed");
|
|
111
129
|
}
|
|
112
130
|
|
|
113
131
|
return user;
|
|
114
132
|
};
|
|
115
|
-
|
|
133
|
+
|
|
134
|
+
export const resetPassword = async (user: { id: string }, newPassword: string) => {
|
|
116
135
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
117
136
|
await prisma.user.update({
|
|
118
137
|
where: { id: user.id },
|
|
@@ -120,20 +139,24 @@ export const resetPassword = async (user: any, newPassword: any) => {
|
|
|
120
139
|
});
|
|
121
140
|
return true;
|
|
122
141
|
};
|
|
142
|
+
|
|
123
143
|
export const updateProfile = async (
|
|
124
|
-
userId:
|
|
125
|
-
{ name, phone, file }:
|
|
144
|
+
userId: string,
|
|
145
|
+
{ name, phone, file }: UpdateProfileDTO,
|
|
126
146
|
) => {
|
|
127
147
|
const user = await prisma.user.findUnique({
|
|
128
148
|
where: { id: userId },
|
|
129
149
|
});
|
|
130
150
|
if (!user) {
|
|
131
|
-
|
|
132
|
-
err.status = 404;
|
|
133
|
-
throw err;
|
|
151
|
+
throw new ApiError(404, "User not found");
|
|
134
152
|
}
|
|
135
153
|
|
|
136
|
-
const updateData:
|
|
154
|
+
const updateData: {
|
|
155
|
+
name?: string;
|
|
156
|
+
phone?: string;
|
|
157
|
+
avatarUrl?: string;
|
|
158
|
+
avatarPublicId?: string;
|
|
159
|
+
} = {};
|
|
137
160
|
if (name) updateData.name = name;
|
|
138
161
|
if (phone) updateData.phone = phone;
|
|
139
162
|
|
|
@@ -159,3 +182,32 @@ export const updateProfile = async (
|
|
|
159
182
|
|
|
160
183
|
return updatedUser;
|
|
161
184
|
};
|
|
185
|
+
|
|
186
|
+
export const googleLogin = async (idToken: string) => {
|
|
187
|
+
const ticket = await googleClient.verifyIdToken({
|
|
188
|
+
idToken,
|
|
189
|
+
audience: process.env.GOOGLE_CLIENT_ID,
|
|
190
|
+
});
|
|
191
|
+
const payload = ticket.getPayload();
|
|
192
|
+
if (!payload) {
|
|
193
|
+
throw new ApiError(401, "Invalid Google Token");
|
|
194
|
+
}
|
|
195
|
+
const { email, name, picture } = payload;
|
|
196
|
+
if (!email) {
|
|
197
|
+
throw new ApiError(400, "Email not found in Google Token");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let user = await prisma.user.findUnique({ where: { email } });
|
|
201
|
+
if (!user) {
|
|
202
|
+
user = await prisma.user.create({
|
|
203
|
+
data: {
|
|
204
|
+
name: name || "User",
|
|
205
|
+
email: email.toLowerCase(),
|
|
206
|
+
password: "",
|
|
207
|
+
avatarUrl: picture || `https://placehold.co/600x400?text=${name?.charAt(0).toUpperCase() || 'U'}`,
|
|
208
|
+
avatarPublicId: "",
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return user;
|
|
213
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CloudinaryUploadFile } from "../utils/cloudinaryUpload.js";
|
|
2
|
+
|
|
3
|
+
export {};
|
|
2
4
|
|
|
3
5
|
declare global {
|
|
4
6
|
namespace Express {
|
|
5
7
|
interface Request {
|
|
6
|
-
user?:
|
|
7
|
-
file?:
|
|
8
|
+
user?: Record<string, unknown> & { _id?: unknown; id?: unknown; toObject?: () => Record<string, unknown> };
|
|
9
|
+
file?: CloudinaryUploadFile;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
10
12
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
public statusCode: number;
|
|
3
|
+
public data: unknown;
|
|
4
|
+
public success: boolean;
|
|
5
|
+
public errors: unknown[];
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
statusCode: number,
|
|
9
|
+
message: string = "Something went wrong",
|
|
10
|
+
errors: unknown[] = [],
|
|
11
|
+
stack: string = "",
|
|
12
|
+
) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.data = null;
|
|
16
|
+
this.success = false;
|
|
17
|
+
this.errors = errors;
|
|
18
|
+
|
|
19
|
+
if (stack) {
|
|
20
|
+
this.stack = stack;
|
|
21
|
+
} else if (typeof (Error as unknown as { captureStackTrace?: (target: object, constructor: unknown) => void }).captureStackTrace === "function") {
|
|
22
|
+
(Error as unknown as { captureStackTrace: (target: object, constructor: unknown) => void }).captureStackTrace(this, this.constructor);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
|
|
3
|
+
export const SuccessResponse = (
|
|
4
|
+
res: Response,
|
|
5
|
+
statusCode: number,
|
|
6
|
+
message: string = "Success",
|
|
7
|
+
data: unknown = null,
|
|
8
|
+
pagination?: unknown,
|
|
9
|
+
) => {
|
|
10
|
+
return res.status(statusCode).json({
|
|
11
|
+
success: statusCode < 400,
|
|
12
|
+
statusCode,
|
|
13
|
+
message,
|
|
14
|
+
data,
|
|
15
|
+
...(pagination ? { pagination } : {}),
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
|
|
3
|
+
export const asyncHandler = (
|
|
4
|
+
requestHandler: (
|
|
5
|
+
req: Request,
|
|
6
|
+
res: Response,
|
|
7
|
+
next: NextFunction,
|
|
8
|
+
) => Promise<unknown>,
|
|
9
|
+
) => {
|
|
10
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
11
|
+
Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err));
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import cloudinary from "../config/cloudinary.js";
|
|
2
2
|
import logger from "./logger.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
export interface CloudinaryUploadFile {
|
|
5
|
+
mimetype?: string;
|
|
6
|
+
buffer?: Buffer;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CloudinaryOptions {
|
|
11
|
+
folder?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const uploadToCloudinary = async (file: CloudinaryUploadFile, options: CloudinaryOptions = {}) => {
|
|
5
16
|
if (!file?.buffer) {
|
|
6
17
|
throw new Error("Invalid file.");
|
|
7
18
|
}
|
|
8
|
-
const dataURI = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
|
|
19
|
+
const dataURI = `data:${file.mimetype || "application/octet-stream"};base64,${file.buffer.toString("base64")}`;
|
|
9
20
|
const result = await cloudinary.uploader.upload(dataURI, {
|
|
10
21
|
folder: options.folder || "uploads",
|
|
11
22
|
...options,
|
|
@@ -16,19 +27,19 @@ const uploadToCloudinary = async (file: any, options: any = {}) => {
|
|
|
16
27
|
};
|
|
17
28
|
};
|
|
18
29
|
|
|
19
|
-
export const uploadSingleToCloudinary = async (file:
|
|
30
|
+
export const uploadSingleToCloudinary = async (file: CloudinaryUploadFile, options: CloudinaryOptions = {}) => {
|
|
20
31
|
try {
|
|
21
32
|
return await uploadToCloudinary(file, options);
|
|
22
|
-
} catch (error) {
|
|
33
|
+
} catch (error: unknown) {
|
|
23
34
|
logger.error("Cloudinary Upload Error", error);
|
|
24
35
|
throw new Error("Failed to upload image.");
|
|
25
36
|
}
|
|
26
37
|
};
|
|
27
38
|
|
|
28
|
-
export const uploadMultipleToCloudinary = async (files:
|
|
39
|
+
export const uploadMultipleToCloudinary = async (files: CloudinaryUploadFile[] = [], options: CloudinaryOptions = {}) => {
|
|
29
40
|
try {
|
|
30
41
|
return await Promise.all(files.map((file) => uploadToCloudinary(file, options)));
|
|
31
|
-
} catch (error) {
|
|
42
|
+
} catch (error: unknown) {
|
|
32
43
|
logger.error("Cloudinary Upload Error", error);
|
|
33
44
|
throw new Error("Failed to upload images.");
|
|
34
45
|
}
|
|
@@ -37,7 +48,7 @@ export const uploadMultipleToCloudinary = async (files: any[] = [], options: any
|
|
|
37
48
|
export const deleteFromCloudinary = async (publicId: string) => {
|
|
38
49
|
try {
|
|
39
50
|
return await cloudinary.uploader.destroy(publicId);
|
|
40
|
-
} catch (error) {
|
|
51
|
+
} catch (error: unknown) {
|
|
41
52
|
logger.error("Cloudinary Delete Error", error);
|
|
42
53
|
throw new Error("Failed to delete image.");
|
|
43
54
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
import { Response } from "express";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
export interface UserOtpTokenPayload {
|
|
5
|
+
_id?: unknown;
|
|
6
|
+
id?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const genOtpToken = (user: UserOtpTokenPayload, res?: Response) => {
|
|
5
11
|
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
6
12
|
const token = jwt.sign({ id: user._id || user.id }, JWT_SECRET, {
|
|
7
13
|
expiresIn: "10m",
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
import { Response } from "express";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface UserTokenPayload {
|
|
5
|
+
_id?: unknown;
|
|
6
|
+
id?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const generateToken = (user: UserTokenPayload, res?: Response) => {
|
|
5
11
|
const payload = { id: user._id || user.id };
|
|
6
12
|
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
7
13
|
const token = jwt.sign(payload, JWT_SECRET, { expiresIn: "30d" });
|
|
@@ -21,7 +27,7 @@ export const verifyToken = (token: string) => {
|
|
|
21
27
|
try {
|
|
22
28
|
const JWT_SECRET = process.env.JWT_SECRET || "fallbacksecret";
|
|
23
29
|
return jwt.verify(token, JWT_SECRET);
|
|
24
|
-
} catch
|
|
30
|
+
} catch {
|
|
25
31
|
return null;
|
|
26
32
|
}
|
|
27
33
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
const colors = {
|
|
2
3
|
reset: "\x1b[0m",
|
|
3
4
|
info: "\x1b[36m", // Cyan
|
|
@@ -18,7 +19,7 @@ export const logger = {
|
|
|
18
19
|
info: (msg: string) => console.log(formatMessage("info", msg)),
|
|
19
20
|
success: (msg: string) => console.log(formatMessage("success", msg)),
|
|
20
21
|
warn: (msg: string) => console.warn(formatMessage("warn", msg)),
|
|
21
|
-
error: (msg: string, err?:
|
|
22
|
+
error: (msg: string, err?: unknown) => {
|
|
22
23
|
console.error(formatMessage("error", msg));
|
|
23
24
|
if (err) console.error(err);
|
|
24
25
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import webpush from "../config/webPush.js";
|
|
2
|
-
import { Expo, ExpoPushMessage
|
|
2
|
+
import { Expo, ExpoPushMessage } from "expo-server-sdk";
|
|
3
3
|
import logger from "./logger.js";
|
|
4
4
|
import { prisma } from "../config/prisma.js";
|
|
5
5
|
|
|
@@ -34,11 +34,15 @@ export const sendPushToUser = async (
|
|
|
34
34
|
await Promise.allSettled(
|
|
35
35
|
webSubs.map(async (sub) => {
|
|
36
36
|
if (!sub.endpoint || !sub.keys) return;
|
|
37
|
-
const pushSub = {
|
|
37
|
+
const pushSub = {
|
|
38
|
+
endpoint: sub.endpoint,
|
|
39
|
+
keys: sub.keys as unknown as { p256dh: string; auth: string },
|
|
40
|
+
};
|
|
38
41
|
try {
|
|
39
42
|
await webpush.sendNotification(pushSub, payload);
|
|
40
|
-
} catch (err:
|
|
41
|
-
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
const error = err as { statusCode?: number };
|
|
45
|
+
if (error.statusCode === 404 || error.statusCode === 410) {
|
|
42
46
|
await prisma.pushSubscription.delete({ where: { id: sub.id } }).catch(() => {});
|
|
43
47
|
} else {
|
|
44
48
|
logger.error("Web Push Error", err);
|
|
@@ -66,12 +70,12 @@ export const sendPushToUser = async (
|
|
|
66
70
|
for (const chunk of chunks) {
|
|
67
71
|
try {
|
|
68
72
|
await expo.sendPushNotificationsAsync(chunk);
|
|
69
|
-
} catch (error) {
|
|
73
|
+
} catch (error: unknown) {
|
|
70
74
|
logger.error("Error sending push notification chunk", error);
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
|
-
} catch (error) {
|
|
78
|
+
} catch (error: unknown) {
|
|
75
79
|
logger.error("Error sending push notification to user", error);
|
|
76
80
|
}
|
|
77
81
|
};
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "node src/index.js",
|
|
8
8
|
"dev": "nodemon src/index.js",
|
|
9
|
+
"lint": "eslint src/**/*.js",
|
|
10
|
+
"lint:fix": "eslint src/**/*.js --fix",
|
|
11
|
+
"format": "prettier --write \"src/**/*.{js,json}\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.{js,json}\"",
|
|
13
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.js\"",
|
|
9
14
|
"db:push": "prisma db push",
|
|
10
15
|
"db:generate": "prisma generate",
|
|
11
16
|
"generate:vapid": "node ./src/utils/generateVapidKeys.js"
|
|
@@ -29,7 +34,10 @@
|
|
|
29
34
|
"web-push": "^3.6.6"
|
|
30
35
|
},
|
|
31
36
|
"devDependencies": {
|
|
37
|
+
"dpdm": "^3.14.0",
|
|
38
|
+
"eslint": "^8.53.0",
|
|
32
39
|
"nodemon": "^3.0.1",
|
|
40
|
+
"prettier": "^3.4.2",
|
|
33
41
|
"prisma": "^5.6.0"
|
|
34
42
|
}
|
|
35
43
|
}
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
"start": "tsx src/index.ts",
|
|
8
8
|
"dev": "tsx watch src/index.ts",
|
|
9
9
|
"build": "tsc",
|
|
10
|
+
"lint": "eslint . --ext .ts --max-warnings 0",
|
|
11
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{ts,json}\"",
|
|
13
|
+
"format:check": "prettier --check \"src/**/*.{ts,json}\"",
|
|
14
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.ts\"",
|
|
15
|
+
"ts-prune": "ts-prune",
|
|
16
|
+
"ts-purns": "ts-prune",
|
|
10
17
|
"db:push": "prisma db push",
|
|
11
18
|
"db:generate": "prisma generate",
|
|
12
19
|
"generate:vapid": "node ./src/utils/generateVapidKeys.js"
|
|
@@ -38,10 +45,16 @@
|
|
|
38
45
|
"@types/jsonwebtoken": "^9.0.5",
|
|
39
46
|
"@types/morgan": "^1.9.9",
|
|
40
47
|
"@types/multer": "^1.4.11",
|
|
48
|
+
"@types/node": "^20.9.0",
|
|
41
49
|
"@types/nodemailer": "^6.4.14",
|
|
42
50
|
"@types/web-push": "^3.6.3",
|
|
43
|
-
"@
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
52
|
+
"@typescript-eslint/parser": "^6.10.0",
|
|
53
|
+
"dpdm": "^3.14.0",
|
|
54
|
+
"eslint": "^8.53.0",
|
|
55
|
+
"prettier": "^3.4.2",
|
|
44
56
|
"prisma": "^5.6.0",
|
|
57
|
+
"ts-prune": "^0.10.3",
|
|
45
58
|
"tsx": "^4.23.1",
|
|
46
59
|
"typescript": "^5.2.2"
|
|
47
60
|
}
|
|
@@ -7,8 +7,9 @@ const connectDB = async () => {
|
|
|
7
7
|
try {
|
|
8
8
|
await prisma.$connect();
|
|
9
9
|
logger.success("Prisma Database connected successfully");
|
|
10
|
-
} catch (error:
|
|
11
|
-
|
|
10
|
+
} catch (error: unknown) {
|
|
11
|
+
const err = error as Error;
|
|
12
|
+
logger.error(`Prisma Database connection failed: ${err.message}`, error);
|
|
12
13
|
process.exit(1);
|
|
13
14
|
}
|
|
14
15
|
};
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
10
|
+
"lint:fix": "eslint . --ext js,jsx --fix",
|
|
11
|
+
"format": "prettier --write \"src/**/*.{js,jsx,css,json}\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx,css,json}\"",
|
|
13
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.{js,jsx}\"",
|
|
10
14
|
"preview": "vite preview"
|
|
11
15
|
},
|
|
12
16
|
"dependencies": {
|
|
@@ -29,10 +33,12 @@
|
|
|
29
33
|
"@types/react": "^18.2.37",
|
|
30
34
|
"@types/react-dom": "^18.2.15",
|
|
31
35
|
"@vitejs/plugin-react": "^4.2.0",
|
|
36
|
+
"dpdm": "^3.14.0",
|
|
32
37
|
"eslint": "^8.53.0",
|
|
33
38
|
"eslint-plugin-react": "^7.33.2",
|
|
34
39
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
35
40
|
"eslint-plugin-react-refresh": "^0.4.4",
|
|
41
|
+
"prettier": "^3.4.2",
|
|
36
42
|
"vite": "^5.0.0"
|
|
37
43
|
}
|
|
38
44
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
env: { browser: true, es2020: true },
|
|
4
|
+
extends: [
|
|
5
|
+
"eslint:recommended",
|
|
6
|
+
"plugin:@typescript-eslint/recommended",
|
|
7
|
+
"plugin:react-hooks/recommended",
|
|
8
|
+
],
|
|
9
|
+
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
|
10
|
+
parser: "@typescript-eslint/parser",
|
|
11
|
+
plugins: ["react-refresh", "@typescript-eslint"],
|
|
12
|
+
rules: {
|
|
13
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
14
|
+
"no-console": "error",
|
|
15
|
+
"react-refresh/only-export-components": [
|
|
16
|
+
"warn",
|
|
17
|
+
{ allowConstantExport: true },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "tsc && vite build",
|
|
9
9
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
10
|
+
"lint:fix": "eslint . --ext ts,tsx --fix",
|
|
11
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
|
|
13
|
+
"circular-check": "dpdm --circular --no-warning --no-tree --exit-code circular:1 \"src/**/*.{ts,tsx}\"",
|
|
14
|
+
"ts-prune": "ts-prune",
|
|
15
|
+
"ts-purns": "ts-prune",
|
|
10
16
|
"preview": "vite preview"
|
|
11
17
|
},
|
|
12
18
|
"dependencies": {
|
|
@@ -31,11 +37,14 @@
|
|
|
31
37
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
32
38
|
"@typescript-eslint/parser": "^6.10.0",
|
|
33
39
|
"@vitejs/plugin-react": "^4.2.0",
|
|
40
|
+
"dpdm": "^3.14.0",
|
|
34
41
|
"eslint": "^8.53.0",
|
|
42
|
+
"eslint-plugin-react": "^7.33.2",
|
|
35
43
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
36
44
|
"eslint-plugin-react-refresh": "^0.4.4",
|
|
45
|
+
"prettier": "^3.4.2",
|
|
46
|
+
"ts-prune": "^0.10.3",
|
|
37
47
|
"typescript": "^5.2.2",
|
|
38
|
-
"vite": "^5.0.0"
|
|
39
|
-
"eslint-plugin-react": "^7.33.2"
|
|
48
|
+
"vite": "^5.0.0"
|
|
40
49
|
}
|
|
41
50
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from "react";
|
|
2
2
|
import { Pagination } from "./Pagination";
|
|
3
3
|
|
|
4
|
-
export interface TableColumn<T =
|
|
4
|
+
export interface TableColumn<T = Record<string, unknown>> {
|
|
5
5
|
header: React.ReactNode | string;
|
|
6
6
|
accessor?: keyof T | string;
|
|
7
|
-
render?: (value:
|
|
7
|
+
render?: (value: unknown, row: T, index: number) => React.ReactNode;
|
|
8
8
|
headerClass?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
interface TableProps<T =
|
|
11
|
+
interface TableProps<T = Record<string, unknown>> {
|
|
12
12
|
columns: TableColumn<T>[];
|
|
13
13
|
data: T[];
|
|
14
14
|
loading?: boolean;
|
|
@@ -23,7 +23,7 @@ interface TableProps<T = any> {
|
|
|
23
23
|
header?: React.ReactNode; // custom header content to render above the table
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const Table = <T extends Record<string,
|
|
26
|
+
const Table = <T extends Record<string, unknown>>({
|
|
27
27
|
columns = [],
|
|
28
28
|
data = [],
|
|
29
29
|
loading = false,
|
|
@@ -8,17 +8,17 @@ if ("serviceWorker" in navigator) {
|
|
|
8
8
|
window.addEventListener("load", () => {
|
|
9
9
|
navigator.serviceWorker
|
|
10
10
|
.register("/sw.js")
|
|
11
|
-
.
|
|
12
|
-
console.log("Service Worker registered successfully:", reg.scope),
|
|
13
|
-
)
|
|
14
|
-
.catch((err) => console.log("Service Worker registration failed:", err));
|
|
11
|
+
.catch(() => {});
|
|
15
12
|
});
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
const rootElement = document.getElementById("root");
|
|
16
|
+
if (rootElement) {
|
|
17
|
+
ReactDOM.createRoot(rootElement).render(
|
|
18
|
+
<React.StrictMode>
|
|
19
|
+
<BrowserRouter>
|
|
20
|
+
<App />
|
|
21
|
+
</BrowserRouter>
|
|
22
|
+
</React.StrictMode>,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -12,7 +12,7 @@ export const authService = {
|
|
|
12
12
|
|
|
13
13
|
getMe: () => api.get("/auth/me"),
|
|
14
14
|
|
|
15
|
-
updateMe: (data:
|
|
15
|
+
updateMe: (data: FormData | Record<string, unknown>) => api.put("/auth/me", data),
|
|
16
16
|
|
|
17
17
|
logout: () =>
|
|
18
18
|
api.post("/auth/logout"),
|