@imksh/create 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/templates/layers/node/javascript/package.json +2 -1
  3. package/templates/layers/node/javascript/src/controllers/push.controller.js +35 -14
  4. package/templates/layers/node/javascript/src/models/pushSubscription.model.js +5 -0
  5. package/templates/layers/node/javascript/src/utils/pushHelper.js +59 -12
  6. package/templates/layers/node/typescript/package-lock.json +3795 -0
  7. package/templates/layers/node/typescript/package.json +2 -1
  8. package/templates/layers/node/typescript/src/controllers/push.controller.ts +35 -14
  9. package/templates/layers/node/typescript/src/models/pushSubscription.model.ts +6 -0
  10. package/templates/layers/node/typescript/src/services/auth.service.ts +3 -3
  11. package/templates/layers/node/typescript/src/utils/pushHelper.ts +66 -12
  12. package/templates/layers/node-prisma/javascript/package.json +6 -3
  13. package/templates/layers/node-prisma/javascript/prisma/schema.prisma +20 -9
  14. package/templates/layers/node-prisma/javascript/prisma.config.js +12 -0
  15. package/templates/layers/node-prisma/javascript/src/config/{db.js → prisma.js} +9 -1
  16. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +58 -19
  17. package/templates/layers/node-prisma/javascript/src/utils/pushHelper.js +62 -14
  18. package/templates/layers/node-prisma/typescript/package-lock.json +4808 -0
  19. package/templates/layers/node-prisma/typescript/package.json +8 -4
  20. package/templates/layers/node-prisma/typescript/prisma/schema.prisma +20 -9
  21. package/templates/layers/node-prisma/typescript/prisma.config.ts +12 -0
  22. package/templates/layers/node-prisma/typescript/src/config/{db.ts → prisma.ts} +9 -1
  23. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -22
  24. package/templates/layers/node-prisma/typescript/src/index.ts +1 -1
  25. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +15 -5
  26. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +134 -128
  27. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +69 -14
  28. package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +2 -1
  29. package/templates/layers/react-native/base/src/config/appConfig.ts +44 -0
@@ -12,7 +12,7 @@
12
12
  "generate:vapid": "node ./src/utils/generateVapidKeys.js"
13
13
  },
14
14
  "dependencies": {
15
- "@prisma/client": "^5.6.0",
15
+ "@prisma/client": "^7.0.0",
16
16
  "bcrypt": "^5.1.1",
17
17
  "compression": "^1.7.4",
18
18
  "cookie-parser": "^1.4.6",
@@ -27,7 +27,10 @@
27
27
  "cloudinary": "^2.0.0",
28
28
  "nodemailer": "^6.9.7",
29
29
  "socket.io": "^4.7.2",
30
- "web-push": "^3.6.6"
30
+ "web-push": "^3.6.6",
31
+ "pg": "^8.11.3",
32
+ "@prisma/adapter-pg": "^7.0.0",
33
+ "expo-server-sdk": "^3.10.0"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/bcrypt": "^5.0.2",
@@ -41,8 +44,9 @@
41
44
  "@types/nodemailer": "^6.4.14",
42
45
  "@types/web-push": "^3.6.3",
43
46
  "@types/node": "^20.9.0",
44
- "prisma": "^5.6.0",
47
+ "prisma": "^7.0.0",
45
48
  "tsx": "^4.23.1",
46
- "typescript": "^5.2.2"
49
+ "typescript": "^5.2.2",
50
+ "@types/pg": "^8.11.0"
47
51
  }
48
52
  }
@@ -4,7 +4,6 @@ generator client {
4
4
 
5
5
  datasource db {
6
6
  provider = "postgresql"
7
- url = env("DATABASE_URL")
8
7
  }
9
8
 
10
9
  model User {
@@ -29,13 +28,25 @@ model Otp {
29
28
  }
30
29
 
31
30
  model PushSubscription {
32
- id String @id @default(uuid())
33
- endpoint String @unique
34
- p256dh String
35
- auth String
36
- role String @default("user")
31
+ id String @id @default(uuid())
32
+ type PushSubscriptionType @default(WEB)
33
+
34
+ // For Web Push
35
+ endpoint String? @unique
36
+ keys Json?
37
+
38
+ // For Expo
39
+ token String? @unique
40
+
37
41
  userId String?
38
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
39
- createdAt DateTime @default(now())
40
- updatedAt DateTime @updatedAt
42
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
43
+
44
+ createdAt DateTime @default(now())
45
+ updatedAt DateTime @updatedAt
46
+ deletedAt DateTime?
47
+ }
48
+
49
+ enum PushSubscriptionType {
50
+ WEB
51
+ EXPO
41
52
  }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from '@prisma/config'
2
+ import 'dotenv/config'
3
+
4
+ export default defineConfig({
5
+ schema: 'prisma/schema.prisma',
6
+ migrations: {
7
+ path: 'prisma/migrations',
8
+ },
9
+ datasource: {
10
+ url: process.env.DATABASE_URL,
11
+ },
12
+ })
@@ -1,7 +1,15 @@
1
1
  import { PrismaClient } from "@prisma/client";
2
+ import { Pool } from "pg";
3
+ import { PrismaPg } from "@prisma/adapter-pg";
4
+ import dotenv from "dotenv";
2
5
  import logger from "../utils/logger.js";
3
6
 
4
- export const prisma = new PrismaClient();
7
+ dotenv.config();
8
+
9
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
10
+ const adapter = new PrismaPg(pool);
11
+
12
+ export const prisma = new PrismaClient({ adapter });
5
13
 
6
14
  const connectDB = async () => {
7
15
  try {
@@ -1,35 +1,74 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
- import { prisma } from "../config/db.js";
2
+ import { prisma } from "../config/prisma.js";
3
3
  import logger from "../utils/logger.js";
4
4
 
5
- export const subscribe = async (req: Request, res: Response, next: NextFunction) => {
5
+ export const subscribe = async (
6
+ req: Request,
7
+ res: Response,
8
+ next: NextFunction,
9
+ ) => {
6
10
  try {
7
11
  const subscription = req.body;
8
- const userId = req.user?._id || req.user?.id; // supports both mongoose and prisma IDs
9
-
10
- if (!subscription || !subscription.endpoint) {
12
+ const userId = req.user?._id || req.user?.id;
13
+
14
+ if (!subscription) {
11
15
  return next({ status: 400, message: "Invalid subscription payload" });
12
16
  }
13
17
 
14
- const existingSub = await prisma.pushSubscription.findUnique({ where: { endpoint: subscription.endpoint } });
18
+ // Determine type: EXPO if token is present, else WEB
19
+ const isExpo = !!subscription.token;
20
+ const isWeb = !!(subscription.endpoint && subscription.keys);
15
21
 
16
- if (existingSub) {
17
- if (userId) {
18
- await prisma.pushSubscription.update({ where: { endpoint: subscription.endpoint }, data: { userId } })
19
- }
20
- return res.status(200).json({ success: true, message: "Already subscribed." });
22
+ if (!isExpo && !isWeb) {
23
+ return next({ status: 400, message: "Missing token (EXPO) or endpoint/keys (WEB)" });
21
24
  }
22
25
 
23
-
24
- await prisma.pushSubscription.create({
25
- data: {
26
- endpoint: subscription.endpoint,
27
- p256dh: subscription.keys.p256dh,
28
- auth: subscription.keys.auth,
29
- userId: userId || null
26
+ if (isExpo) {
27
+ const existingSub = await prisma.pushSubscription.findUnique({
28
+ where: { token: subscription.token },
29
+ });
30
+
31
+ if (existingSub) {
32
+ if (userId && existingSub.userId !== userId) {
33
+ await prisma.pushSubscription.update({
34
+ where: { token: subscription.token },
35
+ data: { userId },
36
+ });
37
+ }
38
+ return res.status(200).json({ success: true, message: "Already subscribed." });
39
+ }
40
+
41
+ await prisma.pushSubscription.create({
42
+ data: {
43
+ type: "EXPO",
44
+ token: subscription.token,
45
+ userId: userId || null,
46
+ },
47
+ });
48
+ } else {
49
+ const existingSub = await prisma.pushSubscription.findUnique({
50
+ where: { endpoint: subscription.endpoint },
51
+ });
52
+
53
+ if (existingSub) {
54
+ if (userId && existingSub.userId !== userId) {
55
+ await prisma.pushSubscription.update({
56
+ where: { endpoint: subscription.endpoint },
57
+ data: { userId },
58
+ });
59
+ }
60
+ return res.status(200).json({ success: true, message: "Already subscribed." });
30
61
  }
31
- });
32
-
62
+
63
+ await prisma.pushSubscription.create({
64
+ data: {
65
+ type: "WEB",
66
+ endpoint: subscription.endpoint,
67
+ keys: subscription.keys,
68
+ userId: userId || null,
69
+ },
70
+ });
71
+ }
33
72
 
34
73
  res.status(201).json({ success: true, message: "Subscription created." });
35
74
  } catch (error) {
@@ -38,14 +77,20 @@ export const subscribe = async (req: Request, res: Response, next: NextFunction)
38
77
  }
39
78
  };
40
79
 
41
- export const unsubscribe = async (req: Request, res: Response, next: NextFunction) => {
80
+ export const unsubscribe = async (
81
+ req: Request,
82
+ res: Response,
83
+ next: NextFunction,
84
+ ) => {
42
85
  try {
43
86
  const { endpoint } = req.body;
44
87
  if (!endpoint) {
45
88
  return next({ status: 400, message: "Endpoint required" });
46
89
  }
47
90
 
48
- await prisma.pushSubscription.delete({ where: { endpoint } }).catch(() => {})
91
+ await prisma.pushSubscription
92
+ .delete({ where: { endpoint } })
93
+ .catch(() => {});
49
94
 
50
95
  res.status(200).json({ success: true, message: "Unsubscribed." });
51
96
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  import dotenv from "dotenv";
2
2
  import http from "http";
3
3
  import app from "./app.js";
4
- import connectDB from "./config/db.js";
4
+ import connectDB from "./config/prisma.js";
5
5
  import logger from "./utils/logger.js";
6
6
 
7
7
  dotenv.config();
@@ -1,9 +1,13 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  import { verifyToken } from "../utils/jwt.js";
3
3
  import logger from "../utils/logger.js";
4
- import { prisma } from "../config/db.js";
4
+ import { prisma } from "../config/prisma.js";
5
5
 
6
- export const protect = async (req: Request, res: Response, next: NextFunction) => {
6
+ export const protect = async (
7
+ req: Request,
8
+ res: Response,
9
+ next: NextFunction,
10
+ ) => {
7
11
  let token: string | undefined;
8
12
 
9
13
  if (req.cookies?.token) {
@@ -16,7 +20,9 @@ export const protect = async (req: Request, res: Response, next: NextFunction) =
16
20
  }
17
21
 
18
22
  if (!token) {
19
- return res.status(401).json({ message: "Not authorized, no token provided" });
23
+ return res
24
+ .status(401)
25
+ .json({ message: "Not authorized, no token provided" });
20
26
  }
21
27
 
22
28
  try {
@@ -30,7 +36,9 @@ export const protect = async (req: Request, res: Response, next: NextFunction) =
30
36
  });
31
37
 
32
38
  if (!user) {
33
- return res.status(401).json({ message: "Not authorized, user not found" });
39
+ return res
40
+ .status(401)
41
+ .json({ message: "Not authorized, user not found" });
34
42
  }
35
43
 
36
44
  const safeUser: any = { ...user };
@@ -40,6 +48,8 @@ export const protect = async (req: Request, res: Response, next: NextFunction) =
40
48
  return next();
41
49
  } catch (error: any) {
42
50
  logger.error("Auth middleware error", error);
43
- return res.status(401).json({ message: "Not authorized, token verification failed" });
51
+ return res
52
+ .status(401)
53
+ .json({ message: "Not authorized, token verification failed" });
44
54
  }
45
55
  };
@@ -1,155 +1,161 @@
1
1
  import bcrypt from "bcrypt";
2
- import { prisma } from "../config/db.js";
2
+ import { prisma } from "../config/prisma.js";
3
3
  import { sendOtpEmail } from "../utils/emailOtp.js";
4
- import { uploadSingleToCloudinary, deleteFromCloudinary } from "../utils/cloudinaryUpload.js";
4
+ import {
5
+ uploadSingleToCloudinary,
6
+ deleteFromCloudinary,
7
+ } from "../utils/cloudinaryUpload.js";
5
8
  import logger from "../utils/logger.js";
6
9
  export const signup = async ({ name, email, phone, password }: any) => {
7
- const existingUser = await prisma.user.findUnique({
8
- where: { email },
9
- });
10
- if (existingUser) {
11
- const err: any = new Error("User already exists");
12
- err.status = 409;
13
- throw err;
14
- }
15
-
16
- const salt = await bcrypt.genSalt(10);
17
- const hashedPassword = await bcrypt.hash(password, salt);
18
- const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
19
-
20
- const newUser = await prisma.user.create({
21
- data: {
22
- name,
23
- email: email.toLowerCase(),
24
- phone,
25
- password: hashedPassword,
26
- avatarUrl: photoURL,
27
- avatarPublicId: "",
28
- },
29
- });
30
-
31
- return newUser;
10
+ const existingUser = await prisma.user.findUnique({
11
+ where: { email },
12
+ });
13
+ if (existingUser) {
14
+ const err: any = new Error("User already exists");
15
+ err.status = 409;
16
+ throw err;
32
17
  }
33
- export const login = async ({ email, password }: any) => {
34
- const user = await prisma.user.findUnique({
35
- where: { email },
36
- });
37
- if (!user) {
38
- const err: any = new Error("User not found");
39
- err.status = 404;
40
- throw err;
41
- }
42
18
 
43
- const isValid = await bcrypt.compare(password, user.password);
44
- if (!isValid) {
45
- const err: any = new Error("Invalid Credentials");
46
- err.status = 401;
47
- throw err;
48
- }
19
+ const salt = await bcrypt.genSalt(10);
20
+ const hashedPassword = await bcrypt.hash(password, salt);
21
+ const photoURL = `https://placehold.co/600x400?text=${name.charAt(0).toUpperCase()}`;
22
+
23
+ const newUser = await prisma.user.create({
24
+ data: {
25
+ name,
26
+ email: email.toLowerCase(),
27
+ phone,
28
+ password: hashedPassword,
29
+ avatarUrl: photoURL,
30
+ avatarPublicId: "",
31
+ },
32
+ });
33
+
34
+ return newUser;
35
+ };
36
+ export const login = async ({ email, password }: any) => {
37
+ const user = await prisma.user.findUnique({
38
+ where: { email },
39
+ });
40
+ if (!user) {
41
+ const err: any = new Error("User not found");
42
+ err.status = 404;
43
+ throw err;
44
+ }
49
45
 
50
- return user;
46
+ const isValid = await bcrypt.compare(password, user.password);
47
+ if (!isValid) {
48
+ const err: any = new Error("Invalid Credentials");
49
+ err.status = 401;
50
+ throw err;
51
51
  }
52
+
53
+ return user;
54
+ };
52
55
  export const generateOtp = async (email: string) => {
53
- const user = await prisma.user.findUnique({
54
- where: { email },
55
- });
56
- if (!user) {
57
- const err: any = new Error("User not found");
58
- err.status = 404;
59
- throw err;
60
- }
56
+ const user = await prisma.user.findUnique({
57
+ where: { email },
58
+ });
59
+ if (!user) {
60
+ const err: any = new Error("User not found");
61
+ err.status = 404;
62
+ throw err;
63
+ }
61
64
 
62
- const existingOtp = await prisma.otp.findUnique({
65
+ const existingOtp = await prisma.otp.findUnique({
66
+ where: { email },
67
+ });
68
+ if (existingOtp) {
69
+ await prisma.otp.delete({
63
70
  where: { email },
64
71
  });
65
- if (existingOtp) {
66
- await prisma.otp.delete({
67
- where: { email },
68
- });
69
- }
72
+ }
70
73
 
71
- const otp = Math.floor(100000 + Math.random() * 900000).toString();
72
- const hashedOtp = await bcrypt.hash(otp, 10);
74
+ const otp = Math.floor(100000 + Math.random() * 900000).toString();
75
+ const hashedOtp = await bcrypt.hash(otp, 10);
73
76
 
74
- await prisma.otp.create({
75
- data: {
76
- email,
77
- otp: hashedOtp,
78
- },
79
- });
77
+ await prisma.otp.create({
78
+ data: {
79
+ email,
80
+ otp: hashedOtp,
81
+ },
82
+ });
80
83
 
81
- await sendOtpEmail(email, otp);
82
- return true;
83
- }
84
+ await sendOtpEmail(email, otp);
85
+ return true;
86
+ };
84
87
  export const verifyOtp = async ({ email, otp }: any) => {
85
- const user = await prisma.user.findUnique({
86
- where: { email },
87
- });
88
- if (!user) {
89
- const err: any = new Error("User not found");
90
- err.status = 404;
91
- throw err;
92
- }
93
-
94
- const existingOtp = await prisma.otp.findUnique({
95
- where: { email },
96
- });
97
- if (!existingOtp) {
98
- const err: any = new Error("OTP match failed");
99
- err.status = 400;
100
- throw err;
101
- }
88
+ const user = await prisma.user.findUnique({
89
+ where: { email },
90
+ });
91
+ if (!user) {
92
+ const err: any = new Error("User not found");
93
+ err.status = 404;
94
+ throw err;
95
+ }
102
96
 
103
- const isMatched = await bcrypt.compare(otp, existingOtp.otp);
104
- if (!isMatched) {
105
- const err: any = new Error("OTP match failed");
106
- err.status = 400;
107
- throw err;
108
- }
97
+ const existingOtp = await prisma.otp.findUnique({
98
+ where: { email },
99
+ });
100
+ if (!existingOtp) {
101
+ const err: any = new Error("OTP match failed");
102
+ err.status = 400;
103
+ throw err;
104
+ }
109
105
 
110
- return user;
106
+ const isMatched = await bcrypt.compare(otp, existingOtp.otp);
107
+ if (!isMatched) {
108
+ const err: any = new Error("OTP match failed");
109
+ err.status = 400;
110
+ throw err;
111
111
  }
112
+
113
+ return user;
114
+ };
112
115
  export const resetPassword = async (user: any, newPassword: any) => {
113
- const hashedPassword = await bcrypt.hash(newPassword, 10);
114
- await prisma.user.update({
115
- where: { id: user.id },
116
- data: { password: hashedPassword },
117
- });
118
- return true;
116
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
117
+ await prisma.user.update({
118
+ where: { id: user.id },
119
+ data: { password: hashedPassword },
120
+ });
121
+ return true;
122
+ };
123
+ export const updateProfile = async (
124
+ userId: any,
125
+ { name, phone, file }: any,
126
+ ) => {
127
+ const user = await prisma.user.findUnique({
128
+ where: { id: userId },
129
+ });
130
+ if (!user) {
131
+ const err: any = new Error("User not found");
132
+ err.status = 404;
133
+ throw err;
119
134
  }
120
- export const updateProfile = async (userId: any, { name, phone, file }: any) => {
121
- const user = await prisma.user.findUnique({
122
- where: { id: userId },
123
- });
124
- if (!user) {
125
- const err: any = new Error("User not found");
126
- err.status = 404;
127
- throw err;
128
- }
129
-
130
- const updateData: any = {};
131
- if (name) updateData.name = name;
132
- if (phone) updateData.phone = phone;
133
135
 
134
- if (file) {
135
- const uploadResult = await uploadSingleToCloudinary(file, {
136
- folder: "codelab/avatars",
137
- });
136
+ const updateData: any = {};
137
+ if (name) updateData.name = name;
138
+ if (phone) updateData.phone = phone;
138
139
 
139
- if (user.avatarPublicId) {
140
- await deleteFromCloudinary(user.avatarPublicId).catch((err) =>
141
- logger.error("Old avatar delete error", err)
142
- );
143
- }
140
+ if (file) {
141
+ const uploadResult = await uploadSingleToCloudinary(file, {
142
+ folder: "codelab/avatars",
143
+ });
144
144
 
145
- updateData.avatarUrl = uploadResult.url;
146
- updateData.avatarPublicId = uploadResult.publicId;
145
+ if (user.avatarPublicId) {
146
+ await deleteFromCloudinary(user.avatarPublicId).catch((err) =>
147
+ logger.error("Old avatar delete error", err),
148
+ );
147
149
  }
148
150
 
149
- const updatedUser = await prisma.user.update({
150
- where: { id: userId },
151
- data: updateData,
152
- });
153
-
154
- return updatedUser;
151
+ updateData.avatarUrl = uploadResult.url;
152
+ updateData.avatarPublicId = uploadResult.publicId;
155
153
  }
154
+
155
+ const updatedUser = await prisma.user.update({
156
+ where: { id: userId },
157
+ data: updateData,
158
+ });
159
+
160
+ return updatedUser;
161
+ };
@@ -1,21 +1,76 @@
1
1
  import webpush from "../config/webPush.js";
2
+ import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
2
3
  import logger from "./logger.js";
3
- import { prisma } from "../config/db.js";
4
+ import { prisma } from "../config/prisma.js";
4
5
 
5
- export const sendPushToUser = async (userId: string, title: string, body: string, url: string = "/") => {
6
+ const expo = new Expo();
7
+
8
+ export interface PushMessagePayload {
9
+ title: string;
10
+ body: string;
11
+ data?: Record<string, unknown>;
12
+ url?: string;
13
+ }
14
+
15
+ export const sendPushToUser = async (
16
+ userId: string,
17
+ title: string,
18
+ body: string,
19
+ url: string = "/",
20
+ ) => {
6
21
  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
- }
22
+ const subscriptions = await prisma.pushSubscription.findMany({
23
+ where: { userId },
24
+ });
25
+
26
+ if (subscriptions.length === 0) return;
27
+
28
+ const webSubs = subscriptions.filter(s => s.type === "WEB");
29
+ const expoSubs = subscriptions.filter(s => s.type === "EXPO");
30
+
31
+ // Web Push
32
+ if (webSubs.length > 0) {
33
+ const payload = JSON.stringify({ title, body, url });
34
+ await Promise.allSettled(
35
+ webSubs.map(async (sub) => {
36
+ if (!sub.endpoint || !sub.keys) return;
37
+ const pushSub = { endpoint: sub.endpoint, keys: sub.keys as any };
38
+ try {
39
+ await webpush.sendNotification(pushSub, payload);
40
+ } catch (err: any) {
41
+ if (err.statusCode === 404 || err.statusCode === 410) {
42
+ await prisma.pushSubscription.delete({ where: { id: sub.id } }).catch(() => {});
43
+ } else {
44
+ logger.error("Web Push Error", err);
45
+ }
46
+ }
47
+ })
48
+ );
49
+ }
50
+
51
+ // Expo Push
52
+ if (expoSubs.length > 0) {
53
+ const messages: ExpoPushMessage[] = [];
54
+ for (const sub of expoSubs) {
55
+ if (!sub.token || !Expo.isExpoPushToken(sub.token)) continue;
56
+ messages.push({
57
+ to: sub.token,
58
+ sound: "default",
59
+ title,
60
+ body,
61
+ data: { url },
62
+ });
63
+ }
64
+
65
+ const chunks = expo.chunkPushNotifications(messages);
66
+ for (const chunk of chunks) {
67
+ try {
68
+ await expo.sendPushNotificationsAsync(chunk);
69
+ } catch (error) {
70
+ logger.error("Error sending push notification chunk", error);
71
+ }
72
+ }
73
+ }
19
74
  } catch (error) {
20
75
  logger.error("Error sending push notification to user", error);
21
76
  }
@@ -7,6 +7,7 @@ import { Colors } from "@/constants/Colors";
7
7
  import { useColorScheme } from "nativewind";
8
8
  import { Txt } from "./common/Typography";
9
9
  import Button from "./ui/buttons/Button";
10
+ import appConfig from "@/config/appConfig";
10
11
 
11
12
  export default function BiometricLock({ children }: { children: React.ReactNode }) {
12
13
  const { isBiometricEnabled, isAppLocked, setIsAppLocked, lockDuration } = useAuthStore();
@@ -80,7 +81,7 @@ export default function BiometricLock({ children }: { children: React.ReactNode
80
81
  </View>
81
82
  <Txt variant="3xl" className="font-extrabold tracking-tight mb-2">App Locked</Txt>
82
83
  <Txt variant="base" className="opacity-70 text-center px-8">
83
- Please authenticate to continue using HireMe
84
+ Please authenticate to continue using {appConfig.app.name}
84
85
  </Txt>
85
86
  </View>
86
87