@kipicore/dbcore 1.1.665 → 1.1.667

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.
@@ -59,4 +59,6 @@ export declare const ENV_VARIABLE: {
59
59
  API_KEY: any;
60
60
  ENDPOINT: any;
61
61
  };
62
+ REDIS_HOST: any;
63
+ DUMMY_EMAIL_DOMAIN: any;
62
64
  };
@@ -102,6 +102,8 @@ const envValidation = joi_1.default.object()
102
102
  PHONEPE_PASSWORD: joi_1.default.string().optional(),
103
103
  GOOGLEPAY_API_KEY: joi_1.default.string().optional(),
104
104
  GOOGLEPAY_ENDPOINT: joi_1.default.string().optional(),
105
+ REDIS_HOST: joi_1.default.string().optional(),
106
+ DUMMY_EMAIL_DOMAIN: joi_1.default.string().optional(),
105
107
  })
106
108
  .unknown();
107
109
  const { value: envVar, error } = envValidation.prefs({ errors: { label: 'key' } }).validate(process.env);
@@ -170,4 +172,6 @@ exports.ENV_VARIABLE = {
170
172
  API_KEY: envVar.GOOGLEPAY_API_KEY,
171
173
  ENDPOINT: envVar.GOOGLEPAY_ENDPOINT,
172
174
  },
175
+ REDIS_HOST: envVar.REDIS_HOST,
176
+ DUMMY_EMAIL_DOMAIN: envVar.DUMMY_EMAIL_DOMAIN,
173
177
  };
@@ -1,2 +1,4 @@
1
1
  export * from './aws';
2
2
  export * from './env';
3
+ export * from './logger';
4
+ export * from './redisConfig';
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./aws"), exports);
18
18
  __exportStar(require("./env"), exports);
19
+ __exportStar(require("./logger"), exports);
20
+ __exportStar(require("./redisConfig"), exports);
@@ -0,0 +1,2 @@
1
+ import winston from 'winston';
2
+ export declare const logger: winston.Logger;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const winston_1 = __importDefault(require("winston"));
8
+ const winston_daily_rotate_file_1 = __importDefault(require("winston-daily-rotate-file"));
9
+ const env_1 = require("../configs/env");
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const enumerateErrorFormat = winston_1.default.format((info) => {
12
+ if (info.message instanceof Error) {
13
+ info.message = {
14
+ message: info.message.message,
15
+ stack: info.message.stack,
16
+ ...info.message,
17
+ };
18
+ }
19
+ if (info instanceof Error) {
20
+ return {
21
+ // message: info.message,
22
+ stack: info.stack,
23
+ ...info,
24
+ };
25
+ }
26
+ return info;
27
+ });
28
+ const transport = new winston_daily_rotate_file_1.default({
29
+ filename: env_1.ENV_VARIABLE.LOG_FOLDER + env_1.ENV_VARIABLE.LOG_FILE,
30
+ datePattern: 'YYYY-MM-DD',
31
+ zippedArchive: true,
32
+ maxSize: '20m',
33
+ maxFiles: '3',
34
+ // prepend: true,
35
+ });
36
+ exports.logger = winston_1.default.createLogger({
37
+ format: winston_1.default.format.combine(enumerateErrorFormat(), winston_1.default.format.json()),
38
+ transports: [
39
+ transport,
40
+ new winston_1.default.transports.Console({
41
+ level: 'info',
42
+ }),
43
+ ],
44
+ });
@@ -0,0 +1,4 @@
1
+ import Redis from 'ioredis';
2
+ declare const redisClient: Redis;
3
+ export declare const connectRedis: () => Promise<void>;
4
+ export default redisClient;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.connectRedis = void 0;
7
+ const ioredis_1 = __importDefault(require("ioredis"));
8
+ const logger_1 = require("./logger");
9
+ const env_1 = require("./env");
10
+ // const redisClient = new Redis(ENV_VARIABLE.REDIS_URL, {
11
+ // maxRetriesPerRequest: null,
12
+ // });
13
+ const redisClient = new ioredis_1.default({
14
+ host: env_1.ENV_VARIABLE.REDIS_HOST || 'redis',
15
+ port: 6379,
16
+ maxRetriesPerRequest: null,
17
+ });
18
+ redisClient.on('error', err => {
19
+ console.error('Redis Client Error', err);
20
+ logger_1.logger.error(err);
21
+ });
22
+ const connectRedis = async () => {
23
+ // ioredis connects automatically, but we can check the status
24
+ if (redisClient.status === 'ready')
25
+ return;
26
+ return new Promise((resolve, reject) => {
27
+ redisClient.once('ready', () => resolve());
28
+ redisClient.once('error', err => reject(err));
29
+ });
30
+ };
31
+ exports.connectRedis = connectRedis;
32
+ exports.default = redisClient;
@@ -0,0 +1,2 @@
1
+ export function up(queryInterface: any, Sequelize: any): Promise<void>;
2
+ export function down(queryInterface: any, Sequelize: any): Promise<void>;
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+ module.exports = {
3
+ up: async (queryInterface, Sequelize) => {
4
+ const table = await queryInterface.describeTable('file_storage');
5
+ if (table.uploaded_from) {
6
+ // In PostgreSQL, changing from ENUM to VARCHAR requires a USING clause
7
+ try {
8
+ await queryInterface.sequelize.query('ALTER TABLE file_storage ALTER COLUMN uploaded_from TYPE VARCHAR(255) USING uploaded_from::varchar;');
9
+ await queryInterface.changeColumn('file_storage', 'uploaded_from', {
10
+ type: Sequelize.STRING,
11
+ allowNull: true,
12
+ });
13
+ }
14
+ catch (error) {
15
+ // Fallback if the raw query fails
16
+ await queryInterface.changeColumn('file_storage', 'uploaded_from', {
17
+ type: Sequelize.STRING,
18
+ allowNull: true,
19
+ });
20
+ }
21
+ }
22
+ },
23
+ down: async (queryInterface, Sequelize) => {
24
+ const table = await queryInterface.describeTable('file_storage');
25
+ if (table.uploaded_from) {
26
+ try {
27
+ await queryInterface.sequelize.query('ALTER TABLE file_storage ALTER COLUMN uploaded_from TYPE "enum_file_storage_uploaded_from" USING uploaded_from::text::"enum_file_storage_uploaded_from";');
28
+ await queryInterface.changeColumn('file_storage', 'uploaded_from', {
29
+ type: Sequelize.ENUM('INSTITUTE_APP', 'SCHOOL_APP', 'GLOBAL_APP', 'STUDENT_APP', 'VENDOR_APP'),
30
+ allowNull: false,
31
+ });
32
+ }
33
+ catch (error) {
34
+ // Fallback
35
+ await queryInterface.changeColumn('file_storage', 'uploaded_from', {
36
+ type: Sequelize.ENUM('INSTITUTE_APP', 'SCHOOL_APP', 'GLOBAL_APP', 'STUDENT_APP', 'VENDOR_APP'),
37
+ allowNull: false,
38
+ });
39
+ }
40
+ }
41
+ }
42
+ };
@@ -1,2 +1,5 @@
1
1
  export * from './s3Uploader';
2
2
  export * from './utils';
3
+ export * from './sendEmail';
4
+ export * from './sendNotification';
5
+ export * from './sendSMS';
@@ -16,3 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./s3Uploader"), exports);
18
18
  __exportStar(require("./utils"), exports);
19
+ __exportStar(require("./sendEmail"), exports);
20
+ __exportStar(require("./sendNotification"), exports);
21
+ __exportStar(require("./sendSMS"), exports);
@@ -0,0 +1,3 @@
1
+ import { IEmailOptions } from '../index';
2
+ export declare const sendEmailDirect: (emailOptions: IEmailOptions) => Promise<void>;
3
+ export declare const sendEmail: (emailOptions: IEmailOptions) => Promise<void>;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ // import nodemailer, { Transporter } from 'nodemailer';
3
+ // import { IEmailOptions } from '@kipicore/dbcore';
4
+ // import { ENV_VARIABLE } from '../configs/env';
5
+ // import { emailQueue } from '../queues/emailQueue';
6
+ // import { logger } from '../configs/logger';
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.sendEmail = exports.sendEmailDirect = void 0;
9
+ // const hasDummyEmail = (to: string | string[]): boolean => {
10
+ // if (Array.isArray(to)) {
11
+ // return to.some(email => email.toLowerCase().endsWith(ENV_VARIABLE.DUMMY_EMAIL_DOMAIN));
12
+ // }
13
+ // return to.toLowerCase().endsWith(ENV_VARIABLE.DUMMY_EMAIL_DOMAIN);
14
+ // };
15
+ // export const sendEmailDirect = async (emailOptions: IEmailOptions): Promise<void> => {
16
+ // if (hasDummyEmail(emailOptions.to)) {
17
+ // logger.info(`Skipping email for dummy address: ${emailOptions.to}`);
18
+ // return;
19
+ // }
20
+ // // Configure the transporter with type annotations
21
+ // const transporter: Transporter = nodemailer.createTransport({
22
+ // host: ENV_VARIABLE.SMTP_SERVER, // e.g., 'smtp.gmail.com' for Gmail
23
+ // port: ENV_VARIABLE.SMTP_PORT,
24
+ // secure: true, // true for 465, false for other ports
25
+ // auth: {
26
+ // user: ENV_VARIABLE.SMTP_USER, // Your email address
27
+ // pass: ENV_VARIABLE.SMTP_PASS, // Your email password
28
+ // },
29
+ // });
30
+ // if (!emailOptions.from) emailOptions.from = ENV_VARIABLE.SMTP_EMAIL;
31
+ // try {
32
+ // // Send the email
33
+ // const info = await transporter.sendMail(emailOptions);
34
+ // return info;
35
+ // } catch (err) {
36
+ // // Handle errors
37
+ // console.error('Error sending email:', err);
38
+ // throw err;
39
+ // }
40
+ // };
41
+ // export const sendEmail = async (emailOptions: IEmailOptions): Promise<void> => {
42
+ // try {
43
+ // if (hasDummyEmail(emailOptions.to)) {
44
+ // logger.info(`Skipping email for dummy address: ${emailOptions.to}`);
45
+ // return;
46
+ // }
47
+ // await emailQueue.add('send-email', emailOptions);
48
+ // logger.info(`Email added to queue for: ${emailOptions.to}`);
49
+ // } catch (error) {
50
+ // logger.error('Error adding email to queue:', error);
51
+ // // Fallback to direct sending if queue fails (optional, but safer)
52
+ // await sendEmailDirect(emailOptions);
53
+ // }
54
+ // };
55
+ const client_ses_1 = require("@aws-sdk/client-ses");
56
+ const index_1 = require("../index");
57
+ const env_1 = require("../configs/env");
58
+ const emailQueue_1 = require("../queues/emailQueue");
59
+ const logger_1 = require("../configs/logger");
60
+ const hasDummyEmail = (to) => {
61
+ if (Array.isArray(to)) {
62
+ return to.some(email => email.toLowerCase().endsWith(env_1.ENV_VARIABLE.DUMMY_EMAIL_DOMAIN));
63
+ }
64
+ return to.toLowerCase().endsWith(env_1.ENV_VARIABLE.DUMMY_EMAIL_DOMAIN);
65
+ };
66
+ const sendEmailDirect = async (emailOptions) => {
67
+ if (hasDummyEmail(emailOptions.to)) {
68
+ logger_1.logger.info(`Skipping email for dummy address: ${emailOptions.to}`);
69
+ return;
70
+ }
71
+ const ses = await index_1.awsClient.ses();
72
+ if (!emailOptions.from) {
73
+ emailOptions.from = env_1.ENV_VARIABLE.SMTP_EMAIL;
74
+ }
75
+ try {
76
+ const command = new client_ses_1.SendEmailCommand({
77
+ Source: emailOptions.from || 'kipiverseproject@gmail.com',
78
+ Destination: {
79
+ ToAddresses: Array.isArray(emailOptions.to) ? emailOptions.to : [emailOptions.to],
80
+ CcAddresses: (emailOptions.cc || []),
81
+ BccAddresses: (emailOptions.bcc || []),
82
+ },
83
+ Message: {
84
+ Subject: {
85
+ Data: emailOptions.subject,
86
+ },
87
+ Body: {
88
+ Html: emailOptions.html ? { Data: emailOptions.html } : undefined,
89
+ Text: emailOptions.text ? { Data: emailOptions.text } : undefined,
90
+ },
91
+ },
92
+ });
93
+ await ses.send(command);
94
+ logger_1.logger.info(`Email sent via SES to: ${emailOptions.to}`);
95
+ }
96
+ catch (err) {
97
+ logger_1.logger.error('Error sending email via SES:', err);
98
+ throw err;
99
+ }
100
+ };
101
+ exports.sendEmailDirect = sendEmailDirect;
102
+ const sendEmail = async (emailOptions) => {
103
+ try {
104
+ if (hasDummyEmail(emailOptions.to)) {
105
+ logger_1.logger.info(`Skipping email for dummy address: ${emailOptions.to}`);
106
+ return;
107
+ }
108
+ await emailQueue_1.emailQueue.add('send-email', emailOptions);
109
+ logger_1.logger.info(`Email added to queue for: ${emailOptions.to}`);
110
+ }
111
+ catch (error) {
112
+ logger_1.logger.error('Queue failed, fallback to SES:', error);
113
+ // fallback
114
+ await (0, exports.sendEmailDirect)(emailOptions);
115
+ }
116
+ };
117
+ exports.sendEmail = sendEmail;
@@ -0,0 +1,4 @@
1
+ import { INotificationModelAttributes } from '../index';
2
+ export declare const sendPushNotification: (deviceToken: string, notification: INotificationModelAttributes & {
3
+ counts?: Record<string, number>;
4
+ }) => Promise<void | string>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendPushNotification = void 0;
4
+ const admin = require('firebase-admin');
5
+ const env_1 = require("../configs/env");
6
+ // Initialize Firebase if not already initialized
7
+ if (!admin.apps.length) {
8
+ admin.initializeApp({
9
+ credential: admin.credential.cert({
10
+ projectId: env_1.ENV_VARIABLE.FIREBASE_PROJECT_ID,
11
+ clientEmail: env_1.ENV_VARIABLE.FIREBASE_CLIENT_EMAIL,
12
+ privateKey: env_1.ENV_VARIABLE.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
13
+ }),
14
+ });
15
+ }
16
+ const sendPushNotification = async (deviceToken, notification) => {
17
+ try {
18
+ const payload = {
19
+ notification: {
20
+ title: notification.title,
21
+ body: notification.message,
22
+ },
23
+ token: deviceToken,
24
+ };
25
+ const response = await admin.messaging().send(payload);
26
+ return response;
27
+ }
28
+ catch (error) {
29
+ console.error('Error sending push notification:', error);
30
+ return;
31
+ // throw error;
32
+ }
33
+ };
34
+ exports.sendPushNotification = sendPushNotification;
@@ -0,0 +1,5 @@
1
+ export interface ISMSOptions {
2
+ to: string;
3
+ message: string;
4
+ }
5
+ export declare const sendSMSDirect: (smsOptions: ISMSOptions) => Promise<void>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendSMSDirect = void 0;
4
+ const client_sns_1 = require("@aws-sdk/client-sns");
5
+ const index_1 = require("../index");
6
+ const sendSMSDirect = async (smsOptions) => {
7
+ try {
8
+ const snsClient = await index_1.awsClient.sns();
9
+ // const command = new PublishCommand({
10
+ // Message: smsOptions.message,
11
+ // PhoneNumber: '+918469665568',
12
+ // MessageAttributes: {
13
+ // 'AWS.SNS.SMS.SenderID': {
14
+ // DataType: 'String',
15
+ // StringValue: 'KIPI',
16
+ // },
17
+ // 'AWS.SNS.SMS.SMSType': {
18
+ // DataType: 'String',
19
+ // StringValue: 'Transactional',
20
+ // },
21
+ // },
22
+ // });
23
+ const command = new client_sns_1.PublishCommand({
24
+ Message: smsOptions.message,
25
+ PhoneNumber: `+91${smsOptions.to}`,
26
+ });
27
+ await snsClient.send(command);
28
+ }
29
+ catch (error) {
30
+ console.error('SMS sending failed:', error);
31
+ // throw error;
32
+ }
33
+ };
34
+ exports.sendSMSDirect = sendSMSDirect;
package/dist/index.d.ts CHANGED
@@ -17,3 +17,5 @@ export * from './models/psql/index';
17
17
  export * from './types/index';
18
18
  /************ validator ********** */
19
19
  export * from './commonValidator';
20
+ /************ queues *************/
21
+ export * from './queues/index';
package/dist/index.js CHANGED
@@ -33,3 +33,5 @@ __exportStar(require("./models/psql/index"), exports);
33
33
  __exportStar(require("./types/index"), exports);
34
34
  /************ validator ********** */
35
35
  __exportStar(require("./commonValidator"), exports);
36
+ /************ queues *************/
37
+ __exportStar(require("./queues/index"), exports);
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const sequelize_1 = require("sequelize");
4
4
  const index_1 = require("./index");
5
- const app_1 = require("../../constants/app");
6
5
  class FileStorageModel extends sequelize_1.Model {
7
6
  static associate(models) {
8
7
  const { UserModel, UserDirectoryModel } = models;
@@ -72,9 +71,9 @@ FileStorageModel.init({
72
71
  allowNull: true,
73
72
  },
74
73
  uploadedFrom: {
75
- type: sequelize_1.DataTypes.ENUM,
76
- values: Object.values(app_1.APP_TYPE),
77
- allowNull: false,
74
+ type: sequelize_1.DataTypes.STRING,
75
+ field: 'uploaded_from',
76
+ allowNull: true,
78
77
  },
79
78
  instituteId: {
80
79
  type: sequelize_1.DataTypes.UUID,
@@ -0,0 +1,3 @@
1
+ import { Queue } from 'bullmq';
2
+ export declare const EMAIL_QUEUE_NAME = "email-queue";
3
+ export declare const emailQueue: Queue<any, any, string, any, any, string>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.emailQueue = exports.EMAIL_QUEUE_NAME = void 0;
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+ const bullmq_1 = require("bullmq");
9
+ const redisConfig_1 = __importDefault(require("../configs/redisConfig"));
10
+ exports.EMAIL_QUEUE_NAME = 'email-queue';
11
+ exports.emailQueue = new bullmq_1.Queue(exports.EMAIL_QUEUE_NAME, {
12
+ connection: redisConfig_1.default,
13
+ defaultJobOptions: {
14
+ attempts: 3,
15
+ backoff: {
16
+ type: 'exponential',
17
+ delay: 1000,
18
+ },
19
+ removeOnComplete: true,
20
+ removeOnFail: false,
21
+ },
22
+ });
@@ -0,0 +1,3 @@
1
+ import { Worker } from 'bullmq';
2
+ import { IEmailOptions } from '../index';
3
+ export declare const emailWorker: Worker<IEmailOptions, any, string>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.emailWorker = void 0;
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+ const bullmq_1 = require("bullmq");
9
+ const redisConfig_1 = __importDefault(require("../configs/redisConfig"));
10
+ const sendEmail_1 = require("../helpers/sendEmail");
11
+ const logger_1 = require("../configs/logger");
12
+ const emailQueue_1 = require("./emailQueue");
13
+ exports.emailWorker = new bullmq_1.Worker(emailQueue_1.EMAIL_QUEUE_NAME, async (job) => {
14
+ const emailOptions = job.data;
15
+ logger_1.logger.info(`Processing email job ${job.id} to ${emailOptions.to}`);
16
+ try {
17
+ await (0, sendEmail_1.sendEmailDirect)(emailOptions);
18
+ logger_1.logger.info(`Email job ${job.id} sent successfully`);
19
+ }
20
+ catch (error) {
21
+ logger_1.logger.error(`Failed to send email job ${job.id}:`, error);
22
+ throw error;
23
+ }
24
+ }, {
25
+ connection: redisConfig_1.default,
26
+ concurrency: 5,
27
+ });
28
+ exports.emailWorker.on('completed', job => {
29
+ logger_1.logger.info(`Job ${job.id} completed successfully`);
30
+ });
31
+ exports.emailWorker.on('failed', (job, err) => {
32
+ logger_1.logger.error(`Job ${job?.id} failed with error: ${err.message}`);
33
+ });
@@ -0,0 +1,2 @@
1
+ export * from './emailQueue';
2
+ export * from './emailWorker';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./emailQueue"), exports);
18
+ __exportStar(require("./emailWorker"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kipicore/dbcore",
3
- "version": "1.1.665",
3
+ "version": "1.1.667",
4
4
  "description": "Reusable DB core package with Postgres, MongoDB, models, services, interfaces, and types",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
@@ -44,20 +44,25 @@
44
44
  "@aws-sdk/client-sts": "^3.1017.0",
45
45
  "@aws-sdk/s3-request-presigner": "^3.895.0",
46
46
  "axios": "^1.12.2",
47
+ "bullmq": "^5.80.9",
47
48
  "date-fns": "^4.1.0",
48
49
  "dotenv": "^17.2.2",
50
+ "ejs": "^3.1.10",
51
+ "firebase-admin": "^14.2.0",
52
+ "ioredis": "^5.11.1",
49
53
  "joi": "^18.0.1",
50
54
  "mongoose": "^8.5.2",
51
55
  "pg": "^8.12.0",
52
56
  "sequelize": "^6.37.3",
53
- "ejs": "^3.1.10",
54
- "uuid": "^8.3.2"
57
+ "uuid": "^8.3.2",
58
+ "winston": "^3.19.0",
59
+ "winston-daily-rotate-file": "^5.0.0"
55
60
  },
56
61
  "devDependencies": {
57
62
  "@eslint/js": "^9.17.0",
63
+ "@types/ejs": "^3.1.5",
58
64
  "@types/express": "^5.0.6",
59
65
  "@types/mongoose": "^5.11.97",
60
- "@types/ejs": "^3.1.5",
61
66
  "@types/node": "^24.5.2",
62
67
  "@types/sequelize": "^4.28.14",
63
68
  "country-state-city": "^3.2.1",
@@ -75,4 +80,4 @@
75
80
  "LICENSE",
76
81
  ".sequelizerc"
77
82
  ]
78
- }
83
+ }