@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,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { OAuth2Client } from "google-auth-library";
|
|
2
|
+
import User, { type IUser } from "../models/user.model.js";
|
|
2
3
|
import Otp from "../models/otp.model.js";
|
|
3
4
|
import bcrypt from "bcrypt";
|
|
4
5
|
import { generateToken } from "../utils/jwt.js";
|
|
@@ -6,28 +7,55 @@ import { sendOtpEmail } from "../utils/emailOtp.js";
|
|
|
6
7
|
import {
|
|
7
8
|
uploadSingleToCloudinary,
|
|
8
9
|
deleteFromCloudinary,
|
|
10
|
+
type CloudinaryUploadFile,
|
|
9
11
|
} from "../utils/cloudinaryUpload.js";
|
|
10
12
|
import logger from "../utils/logger.js";
|
|
13
|
+
import { ApiError } from "../utils/ApiError.js";
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID || "");
|
|
16
|
+
|
|
17
|
+
export interface SignupDTO {
|
|
18
|
+
name: string;
|
|
19
|
+
email: string;
|
|
20
|
+
phone?: string;
|
|
21
|
+
password: string;
|
|
22
|
+
otp: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LoginDTO {
|
|
26
|
+
email: string;
|
|
27
|
+
password: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface VerifyOtpDTO {
|
|
31
|
+
email: string;
|
|
32
|
+
otp: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UpdateProfileDTO {
|
|
36
|
+
name?: string;
|
|
37
|
+
phone?: string;
|
|
38
|
+
file?: CloudinaryUploadFile;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface UserWithSave {
|
|
42
|
+
password?: string;
|
|
43
|
+
save: () => Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const signup = async ({ name, email, phone, password, otp }: SignupDTO) => {
|
|
13
47
|
const existingUser = await User.findOne({ email });
|
|
14
48
|
if (existingUser) {
|
|
15
|
-
|
|
16
|
-
err.status = 409;
|
|
17
|
-
throw err;
|
|
49
|
+
throw new ApiError(409, "User already exists");
|
|
18
50
|
}
|
|
19
51
|
|
|
20
52
|
const existingOtp = await Otp.findOne({ email });
|
|
21
53
|
if (!existingOtp) {
|
|
22
|
-
|
|
23
|
-
err.status = 400;
|
|
24
|
-
throw err;
|
|
54
|
+
throw new ApiError(400, "OTP match failed");
|
|
25
55
|
}
|
|
26
56
|
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
27
57
|
if (!isMatched) {
|
|
28
|
-
|
|
29
|
-
err.status = 400;
|
|
30
|
-
throw err;
|
|
58
|
+
throw new ApiError(400, "OTP match failed");
|
|
31
59
|
}
|
|
32
60
|
const salt = await bcrypt.genSalt(10);
|
|
33
61
|
const hashedPassword = await bcrypt.hash(password, salt);
|
|
@@ -44,27 +72,23 @@ export const signup = async ({ name, email, phone, password, otp }: any) => {
|
|
|
44
72
|
await existingOtp.deleteOne(); // delete OTP after successful signup
|
|
45
73
|
return newUser;
|
|
46
74
|
};
|
|
47
|
-
|
|
75
|
+
|
|
76
|
+
export const login = async ({ email, password }: LoginDTO) => {
|
|
48
77
|
const user = await User.findOne({ email });
|
|
49
78
|
if (!user || !user.password) {
|
|
50
|
-
|
|
51
|
-
err.status = 401;
|
|
52
|
-
throw err;
|
|
79
|
+
throw new ApiError(401, "Invalid Credentials");
|
|
53
80
|
}
|
|
54
81
|
const isValid = await bcrypt.compare(password, user.password);
|
|
55
82
|
if (!isValid) {
|
|
56
|
-
|
|
57
|
-
err.status = 401;
|
|
58
|
-
throw err;
|
|
83
|
+
throw new ApiError(401, "Invalid Credentials");
|
|
59
84
|
}
|
|
60
85
|
return user;
|
|
61
86
|
};
|
|
87
|
+
|
|
62
88
|
export const generateSignupOtp = async (email: string) => {
|
|
63
89
|
const user = await User.findOne({ email });
|
|
64
90
|
if (user) {
|
|
65
|
-
|
|
66
|
-
err.status = 409;
|
|
67
|
-
throw err;
|
|
91
|
+
throw new ApiError(409, "User already exists");
|
|
68
92
|
}
|
|
69
93
|
const existingOtp = await Otp.findOne({ email });
|
|
70
94
|
if (existingOtp) {
|
|
@@ -76,12 +100,11 @@ export const generateSignupOtp = async (email: string) => {
|
|
|
76
100
|
await sendOtpEmail(email, otp);
|
|
77
101
|
return true;
|
|
78
102
|
};
|
|
103
|
+
|
|
79
104
|
export const generateOtp = async (email: string) => {
|
|
80
105
|
const user = await User.findOne({ email });
|
|
81
106
|
if (!user) {
|
|
82
|
-
|
|
83
|
-
err.status = 404;
|
|
84
|
-
throw err;
|
|
107
|
+
throw new ApiError(404, "User not found");
|
|
85
108
|
}
|
|
86
109
|
const existingOtp = await Otp.findOne({ email });
|
|
87
110
|
if (existingOtp) {
|
|
@@ -93,42 +116,37 @@ export const generateOtp = async (email: string) => {
|
|
|
93
116
|
await sendOtpEmail(email, otp);
|
|
94
117
|
return true;
|
|
95
118
|
};
|
|
96
|
-
|
|
119
|
+
|
|
120
|
+
export const verifyOtp = async ({ email, otp }: VerifyOtpDTO) => {
|
|
97
121
|
const user = await User.findOne({ email });
|
|
98
122
|
if (!user) {
|
|
99
|
-
|
|
100
|
-
err.status = 404;
|
|
101
|
-
throw err;
|
|
123
|
+
throw new ApiError(404, "User not found");
|
|
102
124
|
}
|
|
103
125
|
const existingOtp = await Otp.findOne({ email });
|
|
104
126
|
if (!existingOtp) {
|
|
105
|
-
|
|
106
|
-
err.status = 400;
|
|
107
|
-
throw err;
|
|
127
|
+
throw new ApiError(400, "OTP match failed");
|
|
108
128
|
}
|
|
109
129
|
const isMatched = await bcrypt.compare(otp, existingOtp.otp);
|
|
110
130
|
if (!isMatched) {
|
|
111
|
-
|
|
112
|
-
err.status = 400;
|
|
113
|
-
throw err;
|
|
131
|
+
throw new ApiError(400, "OTP match failed");
|
|
114
132
|
}
|
|
115
133
|
return user;
|
|
116
134
|
};
|
|
117
|
-
|
|
135
|
+
|
|
136
|
+
export const resetPassword = async (user: UserWithSave, newPassword: string) => {
|
|
118
137
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
|
119
138
|
user.password = hashedPassword;
|
|
120
139
|
await user.save();
|
|
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 User.findById(userId);
|
|
128
148
|
if (!user) {
|
|
129
|
-
|
|
130
|
-
err.status = 404;
|
|
131
|
-
throw err;
|
|
149
|
+
throw new ApiError(404, "User not found");
|
|
132
150
|
}
|
|
133
151
|
if (name) user.name = name;
|
|
134
152
|
if (phone) user.phone = phone;
|
|
@@ -153,9 +171,7 @@ export const updateProfile = async (
|
|
|
153
171
|
export const savePushToken = async (userId: string, token: string) => {
|
|
154
172
|
const user = await User.findById(userId);
|
|
155
173
|
if (!user) {
|
|
156
|
-
|
|
157
|
-
err.status = 404;
|
|
158
|
-
throw err;
|
|
174
|
+
throw new ApiError(404, "User not found");
|
|
159
175
|
}
|
|
160
176
|
if (!user.expoPushTokens.includes(token)) {
|
|
161
177
|
user.expoPushTokens.push(token);
|
|
@@ -163,3 +179,30 @@ export const savePushToken = async (userId: string, token: string) => {
|
|
|
163
179
|
}
|
|
164
180
|
return user;
|
|
165
181
|
};
|
|
182
|
+
|
|
183
|
+
export const googleLogin = async (idToken: string) => {
|
|
184
|
+
const ticket = await googleClient.verifyIdToken({
|
|
185
|
+
idToken,
|
|
186
|
+
audience: process.env.GOOGLE_CLIENT_ID,
|
|
187
|
+
});
|
|
188
|
+
const payload = ticket.getPayload();
|
|
189
|
+
if (!payload) {
|
|
190
|
+
throw new ApiError(401, "Invalid Google Token");
|
|
191
|
+
}
|
|
192
|
+
const { email, name, picture } = payload;
|
|
193
|
+
if (!email) {
|
|
194
|
+
throw new ApiError(400, "Email not found in Google Token");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let user = await User.findOne({ email });
|
|
198
|
+
if (!user) {
|
|
199
|
+
user = await User.create({
|
|
200
|
+
name: name || "User",
|
|
201
|
+
email: email.toLowerCase(),
|
|
202
|
+
role: "user",
|
|
203
|
+
password: "",
|
|
204
|
+
avatar: { url: picture || `https://placehold.co/600x400?text=${name?.charAt(0).toUpperCase() || 'U'}`, publicId: "" },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return user;
|
|
208
|
+
};
|
|
@@ -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,7 +1,7 @@
|
|
|
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
|
-
import PushSubscription from "../models/pushSubscription.model.js";
|
|
4
|
+
import PushSubscription, { type IPushSubscription } from "../models/pushSubscription.model.js";
|
|
5
5
|
|
|
6
6
|
const expo = new Expo();
|
|
7
7
|
|
|
@@ -19,7 +19,7 @@ export const sendPushToUser = async (
|
|
|
19
19
|
url: string = "/",
|
|
20
20
|
) => {
|
|
21
21
|
try {
|
|
22
|
-
const subscriptions = await PushSubscription.find({ user: userId });
|
|
22
|
+
const subscriptions: IPushSubscription[] = await PushSubscription.find({ user: userId });
|
|
23
23
|
|
|
24
24
|
if (subscriptions.length === 0) return;
|
|
25
25
|
|
|
@@ -30,13 +30,17 @@ export const sendPushToUser = async (
|
|
|
30
30
|
if (webSubs.length > 0) {
|
|
31
31
|
const payload = JSON.stringify({ title, body, url });
|
|
32
32
|
await Promise.allSettled(
|
|
33
|
-
webSubs.map(async (sub) => {
|
|
33
|
+
webSubs.map(async (sub: IPushSubscription) => {
|
|
34
34
|
if (!sub.endpoint || !sub.keys) return;
|
|
35
|
-
const pushSub = {
|
|
35
|
+
const pushSub = {
|
|
36
|
+
endpoint: sub.endpoint,
|
|
37
|
+
keys: sub.keys as { p256dh: string; auth: string },
|
|
38
|
+
};
|
|
36
39
|
try {
|
|
37
40
|
await webpush.sendNotification(pushSub, payload);
|
|
38
|
-
} catch (err:
|
|
39
|
-
|
|
41
|
+
} catch (err: unknown) {
|
|
42
|
+
const error = err as { statusCode?: number };
|
|
43
|
+
if (error.statusCode === 404 || error.statusCode === 410) {
|
|
40
44
|
await sub.deleteOne();
|
|
41
45
|
} else {
|
|
42
46
|
logger.error("Web Push Error", err);
|
|
@@ -64,12 +68,12 @@ export const sendPushToUser = async (
|
|
|
64
68
|
for (const chunk of chunks) {
|
|
65
69
|
try {
|
|
66
70
|
await expo.sendPushNotificationsAsync(chunk);
|
|
67
|
-
} catch (error) {
|
|
71
|
+
} catch (error: unknown) {
|
|
68
72
|
logger.error("Error sending push notification chunk", error);
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
|
-
} catch (error) {
|
|
76
|
+
} catch (error: unknown) {
|
|
73
77
|
logger.error("Error sending push notification to user", error);
|
|
74
78
|
}
|
|
75
79
|
};
|
|
@@ -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,10 +34,14 @@
|
|
|
29
34
|
"web-push": "^3.6.6",
|
|
30
35
|
"pg": "^8.11.3",
|
|
31
36
|
"@prisma/adapter-pg": "^7.0.0",
|
|
32
|
-
"expo-server-sdk": "^3.10.0"
|
|
37
|
+
"expo-server-sdk": "^3.10.0",
|
|
38
|
+
"google-auth-library": "^9.14.2"
|
|
33
39
|
},
|
|
34
40
|
"devDependencies": {
|
|
41
|
+
"dpdm": "^3.14.0",
|
|
42
|
+
"eslint": "^8.53.0",
|
|
35
43
|
"nodemon": "^3.0.1",
|
|
44
|
+
"prettier": "^3.4.2",
|
|
36
45
|
"prisma": "^7.0.0"
|
|
37
46
|
}
|
|
38
|
-
}
|
|
47
|
+
}
|
|
@@ -7,6 +7,7 @@ import morgan from "morgan";
|
|
|
7
7
|
import apiRoutes from "./routes/index.js";
|
|
8
8
|
import { notFound, errorHandler } from "./middleware/errorHandler.js";
|
|
9
9
|
import { apiLimiter } from "./middleware/rateLimiter.js";
|
|
10
|
+
import { SuccessResponse } from "./utils/SuccessResponse.js";
|
|
10
11
|
|
|
11
12
|
const app = express();
|
|
12
13
|
|
|
@@ -32,7 +33,7 @@ app.use("/api", apiRoutes);
|
|
|
32
33
|
|
|
33
34
|
// Example health route
|
|
34
35
|
app.get("/api/health", (req, res) => {
|
|
35
|
-
res
|
|
36
|
+
SuccessResponse(res, 200, "API is running");
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
// Error handling middlewares
|