startx 0.0.1 → 0.1.0
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/.prettierignore +0 -13
- package/.prettierrc.js +52 -52
- package/.vscode/launch.json +32 -0
- package/.vscode/settings.json +9 -3
- package/apps/core-server/.env.example +18 -24
- package/apps/core-server/Dockerfile +35 -61
- package/apps/core-server/eslint.config.ts +7 -0
- package/apps/core-server/package.json +41 -52
- package/apps/core-server/src/config/custom-type.ts +2 -40
- package/apps/core-server/src/events/index.ts +37 -37
- package/apps/core-server/src/index.ts +4 -13
- package/apps/core-server/src/middlewares/auth-middleware.ts +24 -7
- package/apps/core-server/src/middlewares/cors-middleware.ts +7 -6
- package/apps/core-server/src/middlewares/error-middleware.ts +7 -4
- package/apps/core-server/src/middlewares/logger-middleware.ts +81 -21
- package/apps/core-server/src/middlewares/notfound-middleware.ts +6 -14
- package/apps/core-server/src/middlewares/serve-static.ts +30 -24
- package/apps/core-server/src/routes/files/router.ts +9 -7
- package/apps/core-server/src/routes/server.ts +30 -36
- package/apps/core-server/tsdown.config.ts +4 -3
- package/biome.json +58 -60
- package/configs/eslint-config/package.json +16 -19
- package/configs/eslint-config/src/configs/base.ts +185 -225
- package/configs/eslint-config/src/configs/extend.ts +3 -0
- package/configs/eslint-config/src/configs/frontend.ts +81 -56
- package/configs/eslint-config/src/configs/node.ts +6 -6
- package/configs/eslint-config/src/plugin.ts +1 -0
- package/configs/eslint-config/src/rules/index.ts +8 -12
- package/configs/eslint-config/src/rules/no-json-parse-json-stringify.test.ts +30 -17
- package/configs/eslint-config/src/rules/no-json-parse-json-stringify.ts +52 -49
- package/configs/eslint-config/src/rules/no-uncaught-json-parse.ts +43 -45
- package/configs/tsdown-config/package.json +10 -3
- package/configs/typescript-config/package.json +10 -1
- package/configs/typescript-config/tsconfig.common.json +3 -3
- package/configs/vitest-config/dist/base.mjs +1 -0
- package/configs/vitest-config/dist/frontend.mjs +1 -0
- package/configs/vitest-config/dist/node.mjs +1 -0
- package/configs/vitest-config/package.json +12 -0
- package/configs/vitest-config/src/base.ts +17 -29
- package/configs/vitest-config/src/index.ts +1 -0
- package/package.json +21 -13
- package/packages/@repo/constants/eslint.config.ts +4 -0
- package/packages/@repo/constants/package.json +16 -0
- package/packages/@repo/constants/src/index.ts +8 -8
- package/packages/@repo/db/eslint.config.ts +4 -0
- package/packages/@repo/db/package.json +16 -8
- package/packages/@repo/db/src/index.ts +26 -20
- package/packages/@repo/db/src/schema/common.ts +45 -49
- package/packages/@repo/env/eslint.config.ts +4 -0
- package/packages/@repo/env/package.json +39 -0
- package/packages/@repo/env/src/default-env.ts +12 -0
- package/packages/@repo/env/src/define-env.ts +70 -0
- package/packages/@repo/env/src/index.ts +2 -0
- package/packages/@repo/env/src/utils.ts +52 -0
- package/packages/@repo/env/tsconfig.json +7 -0
- package/packages/@repo/lib/eslint.config.ts +4 -0
- package/packages/@repo/lib/package.json +34 -34
- package/packages/@repo/lib/src/bucket-module/file-storage.ts +50 -49
- package/packages/@repo/lib/src/bucket-module/index.ts +3 -0
- package/packages/@repo/lib/src/bucket-module/s3-storage.ts +120 -114
- package/packages/@repo/lib/src/bucket-module/utils.ts +10 -11
- package/packages/@repo/lib/src/{cookie-module.ts → cookie-module/cookie-module.ts} +48 -42
- package/packages/@repo/lib/src/cookie-module/index.ts +1 -0
- package/packages/@repo/lib/src/extra/index.ts +1 -0
- package/packages/@repo/lib/src/extra/pagination-module.ts +35 -0
- package/packages/@repo/lib/src/{token-module.ts → extra/token-module.ts} +12 -5
- package/packages/@repo/lib/src/file-system-module/index.ts +170 -0
- package/packages/@repo/lib/src/{hashing-module.ts → hashing-module/index.ts} +9 -9
- package/packages/@repo/lib/src/index.ts +0 -26
- package/packages/@repo/lib/src/mail-module/index.ts +2 -0
- package/packages/@repo/lib/src/mail-module/mock.ts +8 -8
- package/packages/@repo/lib/src/mail-module/nodemailer.ts +17 -7
- package/packages/@repo/lib/src/notification-module/index.ts +1 -172
- package/packages/@repo/lib/src/notification-module/push-notification.ts +97 -90
- package/packages/@repo/lib/src/{oauth2-client.ts → oauth2-module/index.ts} +107 -109
- package/packages/@repo/lib/src/otp-module/index.ts +91 -0
- package/packages/@repo/lib/src/session-module/index.ts +113 -0
- package/packages/@repo/lib/src/utils.ts +43 -42
- package/packages/@repo/lib/src/validation-module/index.ts +242 -0
- package/packages/@repo/logger/eslint.config.ts +4 -0
- package/packages/@repo/logger/package.json +40 -0
- package/packages/@repo/logger/src/index.ts +2 -0
- package/packages/@repo/logger/src/logger.ts +72 -0
- package/packages/@repo/{lib/src/logger-module → logger/src}/memory-profiler.ts +64 -65
- package/packages/@repo/logger/tsconfig.json +7 -0
- package/packages/@repo/mail/eslint.config.ts +4 -0
- package/packages/@repo/mail/package.json +10 -3
- package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +169 -168
- package/packages/@repo/mail/src/index.ts +1 -2
- package/packages/@repo/mail/tsconfig.json +3 -3
- package/packages/@repo/redis/dist/index.d.mts +3 -0
- package/packages/@repo/redis/dist/index.mjs +5 -0
- package/packages/@repo/redis/dist/lib/redis-client.d.mts +7 -0
- package/packages/@repo/redis/dist/lib/redis-client.mjs +25 -0
- package/packages/@repo/redis/dist/lib/redis-client.mjs.map +1 -0
- package/packages/@repo/redis/dist/lib/redis-module.d.mts +5 -0
- package/packages/@repo/redis/dist/lib/redis-module.mjs +6 -0
- package/packages/@repo/redis/dist/lib/redis-module.mjs.map +1 -0
- package/packages/@repo/redis/eslint.config.ts +4 -0
- package/packages/@repo/redis/package.json +13 -10
- package/packages/@repo/redis/src/index.ts +2 -2
- package/packages/@repo/redis/src/lib/redis-client.ts +36 -23
- package/packages/@repo/redis/src/lib/redis-module.ts +69 -3
- package/packages/cli/dist/index.mjs +203 -0
- package/packages/cli/eslint.config.ts +4 -0
- package/packages/cli/package.json +44 -0
- package/packages/cli/tsconfig.json +12 -0
- package/packages/cli/tsdown.config.ts +17 -0
- package/packages/ui/components.json +0 -1
- package/packages/ui/eslint.config.ts +4 -0
- package/packages/ui/package.json +16 -3
- package/packages/ui/postcss.config.mjs +9 -9
- package/packages/ui/src/components/lib/utils.ts +53 -53
- package/packages/ui/src/components/ui/alert-dialog.tsx +118 -116
- package/packages/ui/src/components/ui/avatar.tsx +52 -53
- package/packages/ui/src/components/ui/badge.tsx +45 -46
- package/packages/ui/src/components/ui/breadcrumb.tsx +108 -109
- package/packages/ui/src/components/ui/card.tsx +91 -92
- package/packages/ui/src/components/ui/carousel.tsx +243 -243
- package/packages/ui/src/components/ui/checkbox.tsx +32 -32
- package/packages/ui/src/components/ui/command.tsx +144 -155
- package/packages/ui/src/components/ui/dialog.tsx +124 -127
- package/packages/ui/src/components/ui/form.tsx +166 -165
- package/packages/ui/src/components/ui/input-otp.tsx +74 -76
- package/packages/ui/src/components/ui/input.tsx +19 -21
- package/packages/ui/src/components/ui/multiple-select.tsx +4 -4
- package/packages/ui/src/{components/lucide.tsx → lucide.ts} +3 -3
- package/packages/ui/tailwind.config.ts +94 -94
- package/packages/ui/tsconfig.json +7 -1
- package/pnpm-workspace.yaml +41 -1
- package/turbo.json +20 -27
- package/apps/core-server/eslint.config.mjs +0 -47
- package/configs/eslint-config/src/rules/no-dynamic-import-template.ts +0 -32
- package/configs/eslint-config/src/rules/no-plain-errors.ts +0 -50
- package/configs/eslint-config/tsdown.config.ts +0 -11
- package/packages/@repo/constants/eslint.config.mjs +0 -21
- package/packages/@repo/db/eslint.config.mjs +0 -21
- package/packages/@repo/lib/eslint.config.mjs +0 -49
- package/packages/@repo/lib/src/command-module.ts +0 -77
- package/packages/@repo/lib/src/constants.ts +0 -3
- package/packages/@repo/lib/src/custom-type.ts +0 -54
- package/packages/@repo/lib/src/env.ts +0 -13
- package/packages/@repo/lib/src/file-system/index.ts +0 -90
- package/packages/@repo/lib/src/logger-module/log-config.ts +0 -16
- package/packages/@repo/lib/src/logger-module/logger.ts +0 -78
- package/packages/@repo/lib/src/mail-module/api.ts +0 -0
- package/packages/@repo/lib/src/otp-module.ts +0 -98
- package/packages/@repo/lib/src/pagination-module.ts +0 -49
- package/packages/@repo/lib/src/user-session.ts +0 -117
- package/packages/@repo/lib/src/validation-module.ts +0 -187
- package/packages/@repo/mail/tsconfig.build.json +0 -14
- package/packages/@repo/mail/tsdown.config.ts +0 -9
- package/packages/@repo/redis/eslint.config.mjs +0 -8
- package/packages/ui/eslint.config.mjs +0 -18
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { logger } from "
|
|
2
|
-
|
|
3
|
-
class MockMailService {
|
|
4
|
-
static sendMail({ text, to }: { to: string; text: string }) {
|
|
5
|
-
logger.info(`Email sent: ${to} ${text}`);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
export { MockMailService };
|
|
1
|
+
import { logger } from "@repo/logger";
|
|
2
|
+
|
|
3
|
+
class MockMailService {
|
|
4
|
+
static sendMail({ text, to }: { to: string; text: string }) {
|
|
5
|
+
logger.info(`Email sent: ${to} ${text}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export { MockMailService };
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { defineEnv } from "@repo/env";
|
|
1
2
|
import type { Transporter } from "nodemailer";
|
|
2
3
|
import * as nodemailer from "nodemailer";
|
|
4
|
+
import z from "zod";
|
|
3
5
|
|
|
4
|
-
import { logger } from "
|
|
6
|
+
import { logger } from "@repo/logger";
|
|
5
7
|
|
|
8
|
+
const credentials = defineEnv({
|
|
9
|
+
SMTP_HOST: z.string(),
|
|
10
|
+
SMTP_PORT: z.string(),
|
|
11
|
+
SMTP_USER: z.string(),
|
|
12
|
+
SMTP_PASSWORD: z.string(),
|
|
13
|
+
SMTP_MAIL_ENCRYPTION: z.string().default("tls"),
|
|
14
|
+
SENDER_MAIL: z.string(),
|
|
15
|
+
});
|
|
6
16
|
class SMTPMailService {
|
|
7
17
|
private static transporter: Transporter;
|
|
8
18
|
|
|
@@ -10,12 +20,12 @@ class SMTPMailService {
|
|
|
10
20
|
private static initializeTransporter() {
|
|
11
21
|
if (!this.transporter) {
|
|
12
22
|
this.transporter = nodemailer.createTransport({
|
|
13
|
-
host:
|
|
14
|
-
port: Number.parseInt(
|
|
15
|
-
secure:
|
|
23
|
+
host: credentials.SMTP_HOST,
|
|
24
|
+
port: Number.parseInt(credentials.SMTP_PORT) ?? 587,
|
|
25
|
+
secure: credentials.SMTP_MAIL_ENCRYPTION === "ssl",
|
|
16
26
|
auth: {
|
|
17
|
-
user:
|
|
18
|
-
pass:
|
|
27
|
+
user: credentials.SMTP_USER,
|
|
28
|
+
pass: credentials.SMTP_PASSWORD,
|
|
19
29
|
},
|
|
20
30
|
});
|
|
21
31
|
}
|
|
@@ -25,7 +35,7 @@ class SMTPMailService {
|
|
|
25
35
|
this.initializeTransporter();
|
|
26
36
|
|
|
27
37
|
const mailOptions = {
|
|
28
|
-
from: `Bemyguest <${
|
|
38
|
+
from: `Bemyguest <${credentials.SENDER_MAIL}>`,
|
|
29
39
|
to,
|
|
30
40
|
subject,
|
|
31
41
|
text,
|
|
@@ -1,172 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// import {
|
|
3
|
-
// notificationsTable,
|
|
4
|
-
// notificationTypeEnum,
|
|
5
|
-
// userDetails,
|
|
6
|
-
// } from "@repo/db/schema";
|
|
7
|
-
// import { desc, eq, inArray, sql } from "drizzle-orm";
|
|
8
|
-
// import { FcmPayload, PushNotificationManager } from "./push-notification.js";
|
|
9
|
-
// import logger from "../logger-module/logger.js";
|
|
10
|
-
|
|
11
|
-
// export type CreateNotificationPayload = {
|
|
12
|
-
// title: string;
|
|
13
|
-
// inAppTitle?: string;
|
|
14
|
-
// userId: string;
|
|
15
|
-
// description?: string;
|
|
16
|
-
// type: (typeof notificationTypeEnum.enumValues)[number][];
|
|
17
|
-
// url?: string;
|
|
18
|
-
// image?: string;
|
|
19
|
-
// data: any;
|
|
20
|
-
// };
|
|
21
|
-
|
|
22
|
-
// export class NotificationModule {
|
|
23
|
-
// /**
|
|
24
|
-
// * Updates the user's FCM token
|
|
25
|
-
// * @param userID the id of the user
|
|
26
|
-
// * @param fcmToken the FCM token to be updated
|
|
27
|
-
// * @throws if the update fails
|
|
28
|
-
// */
|
|
29
|
-
// static async uploadFcmToken(userID: string, fcmToken: string) {
|
|
30
|
-
// try {
|
|
31
|
-
// await db
|
|
32
|
-
// .update(userDetails)
|
|
33
|
-
// .set({ fcmToken })
|
|
34
|
-
// .where(eq(userDetails.userId, userID));
|
|
35
|
-
// } catch (error) {
|
|
36
|
-
// throw error;
|
|
37
|
-
// }
|
|
38
|
-
// }
|
|
39
|
-
// /**
|
|
40
|
-
// * Mark the given notification IDs as read
|
|
41
|
-
// * @param notificationIds the IDs of the notifications to mark as read
|
|
42
|
-
// * @throws if the update fails
|
|
43
|
-
// */
|
|
44
|
-
// static async markAsRead(notificationIds: string[]) {
|
|
45
|
-
// try {
|
|
46
|
-
// await db
|
|
47
|
-
// .update(notificationsTable)
|
|
48
|
-
// .set({ read: true })
|
|
49
|
-
// .where(inArray(notificationsTable.id, notificationIds));
|
|
50
|
-
// } catch (error) {
|
|
51
|
-
// throw error;
|
|
52
|
-
// }
|
|
53
|
-
// }
|
|
54
|
-
// /**
|
|
55
|
-
// * Send a notification to a user
|
|
56
|
-
// * @param payload the notification payload
|
|
57
|
-
// * @throws if either the in-app notification or the push notification fails
|
|
58
|
-
// */
|
|
59
|
-
// static async sendNotification(
|
|
60
|
-
// payload: CreateNotificationPayload,
|
|
61
|
-
// options: { push?: boolean; inApp?: boolean } = { push: true, inApp: true }
|
|
62
|
-
// ) {
|
|
63
|
-
// try {
|
|
64
|
-
// if (options.inApp)
|
|
65
|
-
// await NotificationModule.sendInAppNotification(payload);
|
|
66
|
-
// if (options.push) await NotificationModule.sendPushNotification(payload);
|
|
67
|
-
// } catch (error) {
|
|
68
|
-
// throw error;
|
|
69
|
-
// }
|
|
70
|
-
// }
|
|
71
|
-
// /**
|
|
72
|
-
// * Sends an in-app notification by inserting a new record into the notifications table.
|
|
73
|
-
// *
|
|
74
|
-
// * @param payload - The notification payload containing details such as title, userId, description, type, url, image, and data.
|
|
75
|
-
// * @throws if the database insert operation fails
|
|
76
|
-
// */
|
|
77
|
-
|
|
78
|
-
// static async sendInAppNotification(payload: CreateNotificationPayload) {
|
|
79
|
-
// await db.insert(notificationsTable).values({
|
|
80
|
-
// title: payload.inAppTitle || payload.title,
|
|
81
|
-
// userId: payload.userId,
|
|
82
|
-
// // description: payload.description,
|
|
83
|
-
// type: payload.type,
|
|
84
|
-
// url: payload.url,
|
|
85
|
-
// image: payload.image,
|
|
86
|
-
// data: { ...payload.data, url: payload.url, type: payload.type },
|
|
87
|
-
// });
|
|
88
|
-
// }
|
|
89
|
-
|
|
90
|
-
// /**
|
|
91
|
-
// * Sends a push notification using the FCM token associated with the given user ID.
|
|
92
|
-
// *
|
|
93
|
-
// * @param payload - The notification payload containing details such as title, userId, description, type, url, image, and data.
|
|
94
|
-
// * @throws if the database query for the FCM token fails or if the FCM token is not found
|
|
95
|
-
// */
|
|
96
|
-
// static async sendPushNotification(payload: CreateNotificationPayload) {
|
|
97
|
-
// const [user] = await db
|
|
98
|
-
// .select({
|
|
99
|
-
// fcmToken: userDetails.fcmToken,
|
|
100
|
-
// allowNotifications: userDetails.notifications,
|
|
101
|
-
// })
|
|
102
|
-
// .from(userDetails)
|
|
103
|
-
// .where(eq(userDetails.userId, payload.userId));
|
|
104
|
-
// if (!user?.fcmToken) {
|
|
105
|
-
// logger.error("No FCM token found");
|
|
106
|
-
// return;
|
|
107
|
-
// }
|
|
108
|
-
// if (!user.allowNotifications) {
|
|
109
|
-
// logger.info("User does not allow notifications");
|
|
110
|
-
// return;
|
|
111
|
-
// }
|
|
112
|
-
// PushNotificationManager.sendNotification([
|
|
113
|
-
// {
|
|
114
|
-
// notification: {
|
|
115
|
-
// body: payload.description,
|
|
116
|
-
// title: removeCurlyBraces(payload.title),
|
|
117
|
-
// },
|
|
118
|
-
// token: user.fcmToken,
|
|
119
|
-
// data: {
|
|
120
|
-
// ...payload.data,
|
|
121
|
-
// url: payload.url,
|
|
122
|
-
// type: JSON.stringify(payload.type),
|
|
123
|
-
// },
|
|
124
|
-
// },
|
|
125
|
-
// ]);
|
|
126
|
-
// }
|
|
127
|
-
|
|
128
|
-
// static async sendBulkNotification(
|
|
129
|
-
// userIds: string[],
|
|
130
|
-
// payload: Omit<CreateNotificationPayload, "userId">
|
|
131
|
-
// ) {
|
|
132
|
-
// await db.insert(notificationsTable).values(
|
|
133
|
-
// userIds.map((userId) => ({
|
|
134
|
-
// title: payload.title,
|
|
135
|
-
// userId: userId,
|
|
136
|
-
// description: payload.description,
|
|
137
|
-
// type: payload.type,
|
|
138
|
-
// url: payload.url,
|
|
139
|
-
// image: payload.image,
|
|
140
|
-
// data: { ...payload.data, url: payload.url, type: payload.type },
|
|
141
|
-
// }))
|
|
142
|
-
// );
|
|
143
|
-
// const users = await db
|
|
144
|
-
// .select({
|
|
145
|
-
// fcmToken: userDetails.fcmToken,
|
|
146
|
-
// allowNotifications: userDetails.notifications,
|
|
147
|
-
// })
|
|
148
|
-
// .from(userDetails)
|
|
149
|
-
// .where(inArray(userDetails.userId, userIds));
|
|
150
|
-
// await PushNotificationManager.sendNotification(
|
|
151
|
-
// users
|
|
152
|
-
// .filter((u) => u.fcmToken && u.allowNotifications)
|
|
153
|
-
// .map((u) => ({
|
|
154
|
-
// notification: {
|
|
155
|
-
// body: payload.description,
|
|
156
|
-
// title: removeCurlyBraces(payload.title),
|
|
157
|
-
// },
|
|
158
|
-
// token: u.fcmToken!,
|
|
159
|
-
// data: {
|
|
160
|
-
// ...payload.data,
|
|
161
|
-
// url: payload.url,
|
|
162
|
-
// type: JSON.stringify(payload.type),
|
|
163
|
-
// },
|
|
164
|
-
// }))
|
|
165
|
-
// );
|
|
166
|
-
// }
|
|
167
|
-
// }
|
|
168
|
-
|
|
169
|
-
// function removeCurlyBraces(text: string) {
|
|
170
|
-
// // The regular expression captures the text between '{{' and '}}'
|
|
171
|
-
// return text.replace(/\{\{(.*?)\}\}/g, "$1");
|
|
172
|
-
// }
|
|
1
|
+
export * from "./push-notification.js";
|
|
@@ -1,90 +1,97 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
import { defineEnv } from "@repo/env";
|
|
2
|
+
import { logger } from "@repo/logger";
|
|
3
|
+
import firebaseAdmin from "firebase-admin";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import z from "zod";
|
|
6
|
+
|
|
7
|
+
import { __dirname } from "../utils.js";
|
|
8
|
+
|
|
9
|
+
export type FcmPayload = {
|
|
10
|
+
data?: Record<string, any> & {
|
|
11
|
+
title: string;
|
|
12
|
+
body?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
};
|
|
15
|
+
token: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const credentials = defineEnv({
|
|
19
|
+
FIREBASE_PROJECT_ID: z.string(),
|
|
20
|
+
FIREBASE_CLIENT_EMAIL: z.string(),
|
|
21
|
+
FIREBASE_PRIVATE_KEY: z.string(),
|
|
22
|
+
FIREBASE_KEY_PATH: z.string().optional().default("/keys/firebase-messaging-secret.json"),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
type InitOptions =
|
|
26
|
+
| {
|
|
27
|
+
type: "file";
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: "env";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class PushNotificationManager {
|
|
34
|
+
private static initialized = false;
|
|
35
|
+
|
|
36
|
+
static initialize(options: InitOptions) {
|
|
37
|
+
if (this.initialized) return;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
let credential: firebaseAdmin.credential.Credential;
|
|
41
|
+
|
|
42
|
+
if (options.type === "file") {
|
|
43
|
+
credential = firebaseAdmin.credential.cert(
|
|
44
|
+
path.join(__dirname(), credentials.FIREBASE_KEY_PATH)
|
|
45
|
+
);
|
|
46
|
+
} else if (options.type === "env") {
|
|
47
|
+
credential = firebaseAdmin.credential.cert({
|
|
48
|
+
projectId: credentials.FIREBASE_PROJECT_ID,
|
|
49
|
+
clientEmail: credentials.FIREBASE_CLIENT_EMAIL,
|
|
50
|
+
privateKey: credentials.FIREBASE_PRIVATE_KEY.replace(/\\n/g, "\n"),
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error("Invalid initialization options");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
firebaseAdmin.initializeApp({ credential });
|
|
57
|
+
this.initialized = true;
|
|
58
|
+
logger.info("Firebase Admin initialized");
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error("Firebase initialization error:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async sendNotification(payload: FcmPayload[]) {
|
|
65
|
+
if (!this.initialized) {
|
|
66
|
+
logger.warn("PushNotificationManager not initialized. Skipping notification.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const formattedPayload = payload.map(item => {
|
|
71
|
+
const data: Record<string, string> = {};
|
|
72
|
+
|
|
73
|
+
for (const key in item.data) {
|
|
74
|
+
const value = item.data[key];
|
|
75
|
+
if (value !== null) {
|
|
76
|
+
data[key] = typeof value === "string" ? value : String(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
data: {
|
|
82
|
+
...data,
|
|
83
|
+
title: item.data?.title ?? "",
|
|
84
|
+
body: item.data?.body ?? "",
|
|
85
|
+
},
|
|
86
|
+
token: item.token,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const response = await firebaseAdmin.messaging().sendEach(formattedPayload);
|
|
92
|
+
logger.info("Successfully sent message:", response.responses);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.error("Error sending message:", error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,109 +1,107 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url.searchParams.append("
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
body.append("
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
}
|
|
1
|
+
import { logger } from "@repo/logger";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
type AuthorizationURLParams = {
|
|
5
|
+
authorizationURL: string;
|
|
6
|
+
clientID: string;
|
|
7
|
+
redirectURI: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
state: string;
|
|
10
|
+
aud?: string;
|
|
11
|
+
code_challenge?: string;
|
|
12
|
+
code_challenge_method?: string;
|
|
13
|
+
offline_access?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type AccessTokenParams = {
|
|
17
|
+
tokenURL: string;
|
|
18
|
+
clientID: string;
|
|
19
|
+
clientSecret: string;
|
|
20
|
+
redirectURI: string;
|
|
21
|
+
code: string;
|
|
22
|
+
code_verifier?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ProfileRouteParams = {
|
|
26
|
+
profileRoute: string;
|
|
27
|
+
accessToken: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class OauthClient {
|
|
31
|
+
static getAuthorizationUrl(params: AuthorizationURLParams) {
|
|
32
|
+
// constructing authorization url
|
|
33
|
+
const url = new URL(params.authorizationURL);
|
|
34
|
+
// adding our client id
|
|
35
|
+
url.searchParams.append("client_id", params.clientID);
|
|
36
|
+
// adding the endpoint to redirect after user gives consent
|
|
37
|
+
url.searchParams.append("redirect_uri", params.redirectURI);
|
|
38
|
+
// choosing the response type (code here which we'll use to redeem an accessToken)
|
|
39
|
+
url.searchParams.append("response_type", "code");
|
|
40
|
+
// required for twitter, just a random string (will be needed when redeeming accessToken from code)
|
|
41
|
+
if (params.code_challenge) {
|
|
42
|
+
url.searchParams.append("code_challenge", params.code_challenge);
|
|
43
|
+
}
|
|
44
|
+
if (params.code_challenge_method) {
|
|
45
|
+
url.searchParams.append("code_challenge_method", params.code_challenge_method);
|
|
46
|
+
}
|
|
47
|
+
if (params.offline_access) {
|
|
48
|
+
url.searchParams.append("access_type", "offline");
|
|
49
|
+
url.searchParams.append("prompt", "consent");
|
|
50
|
+
}
|
|
51
|
+
// adding scopes which specify what are we asking from user
|
|
52
|
+
if (params.scopes) {
|
|
53
|
+
url.searchParams.append("scope", params.scopes.join(" "));
|
|
54
|
+
}
|
|
55
|
+
// just a random value on state
|
|
56
|
+
url.searchParams.append("state", params.state);
|
|
57
|
+
|
|
58
|
+
// not important, sometimes necessary, takes baseUrl
|
|
59
|
+
if (params.aud) {
|
|
60
|
+
url.searchParams.append("aud", params.aud);
|
|
61
|
+
}
|
|
62
|
+
return url.toString();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static async getAccessToken(params: AccessTokenParams, options?: { authInBody?: boolean }) {
|
|
66
|
+
try {
|
|
67
|
+
const body = new URLSearchParams();
|
|
68
|
+
body.append("grant_type", "authorization_code");
|
|
69
|
+
body.append("code", params.code);
|
|
70
|
+
body.append("redirect_uri", params.redirectURI);
|
|
71
|
+
if (params.code_verifier) {
|
|
72
|
+
body.append("code_verifier", params.code_verifier);
|
|
73
|
+
}
|
|
74
|
+
if (options?.authInBody) {
|
|
75
|
+
body.append("client_id", params.clientID);
|
|
76
|
+
body.append("client_secret", params.clientSecret);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { data } = await axios.post<
|
|
80
|
+
Record<"access_token" | "refresh_token" | "scope" | "id_token", string>
|
|
81
|
+
>(params.tokenURL, body, {
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
84
|
+
},
|
|
85
|
+
auth: {
|
|
86
|
+
username: params.clientID,
|
|
87
|
+
password: params.clientSecret,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
return data;
|
|
91
|
+
} catch (error: any) {
|
|
92
|
+
if (error?.response?.data) {
|
|
93
|
+
logger.error(Error(JSON.stringify(error?.response?.data, null, 2)));
|
|
94
|
+
} else logger.error(error);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//
|
|
99
|
+
static async getUserInfo(params: ProfileRouteParams) {
|
|
100
|
+
const { data } = await axios.get(params.profileRoute, {
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${params.accessToken}`,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
}
|