From 0ee9e5126f60538eba9f87ab1c2b0a172cfe926a Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Fri, 20 Dec 2024 19:24:57 +0200 Subject: [PATCH 01/17] hotfix(messaging): AI integration bug --- src/database/seed/json/users.json | 24 ++++++++++++++++++++++-- src/models/messageModel.ts | 2 +- src/services/googleAIService.ts | 6 ++++-- src/sockets/MessagingServices.ts | 13 ++++++++++++- src/sockets/messages.ts | 14 +++++++------- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/database/seed/json/users.json b/src/database/seed/json/users.json index 0ab2e2c..057b982 100644 --- a/src/database/seed/json/users.json +++ b/src/database/seed/json/users.json @@ -28,7 +28,7 @@ "phoneNumber": "+201055445511", "password": "12345678", "passwordConfirm": "12345678", - "accountStatus": "deactivated" + "accountStatus": "active" }, { "email": "front3@gmail.com", @@ -38,7 +38,7 @@ "phoneNumber": "+201055287611", "password": "12345678", "passwordConfirm": "12345678", - "accountStatus": "banned" + "accountStatus": "active" }, { "email": "front4@gmail.com", @@ -109,5 +109,25 @@ "password": "12345678", "passwordConfirm": "12345678", "accountStatus": "active" + }, + { + "email": "banned@gmail.com", + "username": "Forever_Banned", + "screenFirstName": "Lonely", + "screenLastName": "Soul", + "phoneNumber": "+201055900090", + "password": "12345678", + "passwordConfirm": "12345678", + "accountStatus": "banned" + }, + { + "email": "deactivated@gmail.com", + "username": "Forever_Deactivated", + "screenFirstName": "Stolen", + "screenLastName": "Soul", + "phoneNumber": "+201055900090", + "password": "12345678", + "passwordConfirm": "12345678", + "accountStatus": "deactivated" } ] diff --git a/src/models/messageModel.ts b/src/models/messageModel.ts index 5ad101a..61e12a0 100644 --- a/src/models/messageModel.ts +++ b/src/models/messageModel.ts @@ -16,7 +16,7 @@ const messageSchema = new mongoose.Schema({ }, isAppropriate: { type: Boolean, - default: false, + default: true, }, isForward: { type: Boolean, diff --git a/src/services/googleAIService.ts b/src/services/googleAIService.ts index 4f19eb3..de0e7c0 100644 --- a/src/services/googleAIService.ts +++ b/src/services/googleAIService.ts @@ -4,7 +4,7 @@ const hf = new HfInference(process.env.HF_API_KEY); const modelName = 'unitary/toxic-bert'; -export async function detectInappropriateContent(text: string): Promise { +async function detectInappropriateContent(text: string): Promise { try { const response = await hf.textClassification({ model: modelName, @@ -32,4 +32,6 @@ export async function detectInappropriateContent(text: string): Promise console.error('Error detecting inappropriate content:', error); throw new Error('Failed to detect inappropriate content'); } -} \ No newline at end of file +} + +export default detectInappropriateContent; \ No newline at end of file diff --git a/src/sockets/MessagingServices.ts b/src/sockets/MessagingServices.ts index fd4bc6d..a15fd30 100644 --- a/src/sockets/MessagingServices.ts +++ b/src/sockets/MessagingServices.ts @@ -6,6 +6,7 @@ import User from '@models/userModel'; import GroupChannel from '@models/groupChannelModel'; import Message from '@models/messageModel'; import IMessage from '@base/types/message'; +import detectInappropriateContent from '@base/services/googleAIService'; export interface Member { user: Types.ObjectId; @@ -18,7 +19,7 @@ export const check = async ( senderId: any, additionalData?: any ) => { - const { chatType, checkAdmin, newMessageIsReply } = additionalData; + const { chatType, checkAdmin, newMessageIsReply, content } = additionalData; if (!chat || chat.isDeleted) { return ack({ @@ -64,6 +65,16 @@ export const check = async ( success: false, message: 'only admins can post and reply to this chat', }); + if ( + chat?.type === 'group' && + !chat.isFilterd && + (await detectInappropriateContent(content)) + ) { + return ack({ + success: false, + message: 'inappropriate content', + }); + } if (chat.type === 'channel' && !newMessageIsReply) return ack({ success: false, diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index e35b592..52d782b 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -3,7 +3,6 @@ import { Server, Socket } from 'socket.io'; import IMessage from '@base/types/message'; import Message from '@models/messageModel'; import { enableDestruction } from '@services/chatService'; -import { detectInappropriateContent } from '@services/googleAIService'; import Chat from '@base/models/chatModel'; import { check, informSessions, updateDraft } from './MessagingServices'; @@ -45,6 +44,7 @@ const handleMessaging = async ( const chat = await Chat.findById(chatId); const func = await check(chat, ack, senderId, { newMessageIsReply: isReply, + content, }); if (!func) return; @@ -64,8 +64,6 @@ const handleMessaging = async ( } } - const isAppropriate = await detectInappropriateContent(content); - const message = new Message({ media, content, @@ -74,7 +72,6 @@ const handleMessaging = async ( senderId, chatId, parentMessageId, - isAppropriate, }); await message.save(); @@ -86,9 +83,12 @@ const handleMessaging = async ( await updateDraft(io, senderId, chatId, ''); socket.to(chatId).emit('RECEIVE_MESSAGE', message, async (res: any) => { - if (res.success) { - if (res.isRead) message.readBy.push(res.userId); - else message.deliveredTo.push(res.userId); + if (res.success && res.userId !== senderId) { + if (res.isRead && !message.readBy.includes(res.userId)) { + message.readBy.push(res.userId); + } else if (!message.deliveredTo.includes(res.userId)) { + message.deliveredTo.push(res.userId); + } message.save(); informSessions( io, From 79be90914fdb7b6866b32927c64f99e5a673f3cb Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Fri, 20 Dec 2024 19:38:49 +0200 Subject: [PATCH 02/17] hotfix(seed): solved username bug --- src/database/seed/json/users.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/seed/json/users.json b/src/database/seed/json/users.json index 057b982..a01d07a 100644 --- a/src/database/seed/json/users.json +++ b/src/database/seed/json/users.json @@ -122,7 +122,7 @@ }, { "email": "deactivated@gmail.com", - "username": "Forever_Deactivated", + "username": "I_HOPE_I_RETURN", "screenFirstName": "Stolen", "screenLastName": "Soul", "phoneNumber": "+201055900090", From a428ba67662061a0b4a5ff758091e173887b587d Mon Sep 17 00:00:00 2001 From: Peter Safwat <118059454+PeterSafwatHBK@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:06:16 +0200 Subject: [PATCH 03/17] feat(chats): apply unfiltering --- src/controllers/chatController.ts | 29 +++++++++++++++++++++++------ src/routes/chatRoute.ts | 8 +++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 16da07f..c1c0123 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -333,13 +333,29 @@ export const getVoiceCallsInChat = catchAsync( ); export const filterChatGroups = catchAsync( async (req: Request, res: Response, next: NextFunction) => { - const { chatId } = req.params; + const chatId = req.params.chatId; + console.log(chatId); + const groupChannel = await GroupChannel.findById(chatId); + if (!groupChannel) { + return res.status(404).json({ + status: 'fail', + message: 'Group/Channel not found with the given chatId', + }); + } + + groupChannel.isFilterd = true; + res.status(200).json({ + status: 'success', + message: 'isFiltered set to true successfully', + data: groupChannel, + }); + } +); +export const unfilterChatGroups = catchAsync( + async (req: Request, res: Response, next: NextFunction) => { + const chatId = req.params.chatId; - const groupChannel = await GroupChannel.findOneAndUpdate( - { chatId }, - { isFiltered: true }, - { new: true } - ); + const groupChannel = await GroupChannel.findById(chatId); if (!groupChannel) { return res.status(404).json({ @@ -347,6 +363,7 @@ export const filterChatGroups = catchAsync( message: 'GroupChannel not found with the given chatId', }); } + groupChannel.isFilterd = false; res.status(200).json({ status: 'success', diff --git a/src/routes/chatRoute.ts b/src/routes/chatRoute.ts index af928ba..5a92258 100644 --- a/src/routes/chatRoute.ts +++ b/src/routes/chatRoute.ts @@ -14,9 +14,10 @@ import { invite, join, getVoiceCallsInChat, - filterChatGroups + filterChatGroups, + unfilterChatGroups } from '@base/controllers/chatController'; -import { protect } from '@base/middlewares/authMiddleware'; +import { protect , isAdmin } from '@base/middlewares/authMiddleware'; import upload from '@base/config/fileUploads'; import restrictTo from '@base/middlewares/chatMiddlewares'; @@ -46,6 +47,7 @@ router.get('/messages/:chatId', restrictTo(), getMessages); router.get('/members/:chatId', restrictTo(), getChatMembers); router.get('/:chatId', restrictTo(), getChat); -router.patch('/groups/filter', restrictTo('admin'), filterChatGroups); +router.patch('/groups/filter/:chatId', isAdmin, filterChatGroups); +router.patch('/groups/unfilter/:chatId', isAdmin, unfilterChatGroups); export default router; From cccef7e5f8a7d755130f3287ada702268c2e0c79 Mon Sep 17 00:00:00 2001 From: Peter Safwat <118059454+PeterSafwatHBK@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:20:17 +0200 Subject: [PATCH 04/17] Update chatController.ts --- src/controllers/chatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index c1c0123..369f388 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -344,6 +344,7 @@ export const filterChatGroups = catchAsync( } groupChannel.isFilterd = true; + await groupChannel.save(); res.status(200).json({ status: 'success', message: 'isFiltered set to true successfully', @@ -364,6 +365,7 @@ export const unfilterChatGroups = catchAsync( }); } groupChannel.isFilterd = false; + await groupChannel.save(); res.status(200).json({ status: 'success', From f012bf89f49d027532def9ba0b5deffa6b21c9b9 Mon Sep 17 00:00:00 2001 From: Peter Safwat <118059454+PeterSafwatHBK@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:58:42 +0200 Subject: [PATCH 05/17] Update userController.ts (#165) --- src/controllers/userController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index fca94a6..75b1e61 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -107,7 +107,7 @@ export const getUser = catchAsync(async (req: GetUser, res: Response) => { export const getAllUsers = catchAsync(async (req: Request, res: Response) => { const users = await User.find( {}, - 'username screenFirstName screenLastName email photo status bio' + 'username screenFirstName screenLastName email photo status bio accountStatus' ); return res.status(200).json({ @@ -335,4 +335,4 @@ export const banUser = catchAsync(async (req: Request, res: Response) => { status: 'success', message: 'User banned successfully', }); -}); \ No newline at end of file +}); From 3c04a5c577f8608b71c33f927d0c5c66b875f7d5 Mon Sep 17 00:00:00 2001 From: akramhany Date: Sat, 21 Dec 2024 03:42:35 +0200 Subject: [PATCH 06/17] chore(logs): log something in voice calls --- src/sockets/voiceCalls.ts | 4 +++- src/sockets/voiceCallsServices.ts | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sockets/voiceCalls.ts b/src/sockets/voiceCalls.ts index f4598ad..e1d10e3 100644 --- a/src/sockets/voiceCalls.ts +++ b/src/sockets/voiceCalls.ts @@ -36,6 +36,7 @@ async function handleCreateCall( chatId = '123'; } + console.log('User Started Call: ', userId); const voiceCall = await createVoiceCall(chatId, userId); io.to(chatId).emit('CALL-STARTED', { @@ -52,7 +53,8 @@ async function handleJoinCall( userId: string ) { const { voiceCallId } = data; - + console.log('Client joined a call, CallId:', voiceCallId); + console.log('UserId: ', userId); await addClientToCall(socket, userId, voiceCallId); socket.join(voiceCallId); diff --git a/src/sockets/voiceCallsServices.ts b/src/sockets/voiceCallsServices.ts index c031da8..40f8972 100644 --- a/src/sockets/voiceCallsServices.ts +++ b/src/sockets/voiceCallsServices.ts @@ -39,6 +39,8 @@ export async function createVoiceCall(chatId: string, userId: string) { await voiceCall.save(); + console.log('Voice Call created: ', voiceCall._id); + return voiceCall; } @@ -71,6 +73,7 @@ export async function addClientToCall( // Add the client socket id into the map if (!clientSocketMap[voiceCallId]) clientSocketMap[voiceCallId] = {}; clientSocketMap[voiceCallId][userId] = socket.id; + console.log('clientSocketMap: ', clientSocketMap[voiceCallId]); } export async function removeClientFromCall( From 3732fced1e42c8048b3c878a55572892621b6631 Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Sat, 21 Dec 2024 05:43:16 +0200 Subject: [PATCH 07/17] fix: solved various bugs --- src/controllers/chatController.ts | 94 +---------- src/models/chatModel.ts | 9 ++ src/routes/chatRoute.ts | 12 +- src/services/chatService.ts | 34 ++-- src/sockets/MessagingServices.ts | 5 +- src/sockets/chats.ts | 260 +++++++++++++++++++----------- src/sockets/messages.ts | 1 + 7 files changed, 209 insertions(+), 206 deletions(-) diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 369f388..2fbbbdb 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -1,12 +1,10 @@ import AppError from '@base/errors/AppError'; import Chat from '@base/models/chatModel'; import Message from '@base/models/messageModel'; -import NormalChat from '@base/models/normalChatModel'; import User from '@base/models/userModel'; import { getChats, getLastMessage, - unmute, deleteChatPictureFile, getUnreadMessages, } from '@base/services/chatService'; @@ -65,12 +63,13 @@ export const getMessages = catchAsync( const pageByMsgId = req.query.page === '0' ? undefined : req.query.page; const limit: number = parseInt(req.query.limit as string, 10) || 20; const filter: any = { chatId }; - if (pageByMsgId) { - filter._id = { $lt: pageByMsgId }; - } if (req.query.timestamp) { filter.timestamp = { $gte: req.query.timestamp }; } + else if (pageByMsgId) { + const message = await Message.findById(pageByMsgId); + filter.timestamp = { $lt: message.timestamp }; + } const messages = await Message.find(filter) .sort({ timestamp: -1 }) @@ -105,41 +104,6 @@ export const postMediaFile = catchAsync(async (req: any, res: Response) => { }); }); -export const enableSelfDestructing = catchAsync( - async (req: Request, res: Response, next: NextFunction) => { - const { chatId } = req.params; - const { destructionDuration } = req.body; - const destructionTimestamp = Date.now(); - if (!destructionDuration) - return next(new AppError('missing required fields', 400)); - const chat = await NormalChat.findByIdAndUpdate(chatId, { - destructionDuration, - destructionTimestamp, - }); - - if (!chat) return next(new AppError('No chat with the provided id', 404)); - res.status(200).json({ - status: 'success', - message: 'Destruction time is enabled successfuly', - }); - } -); - -export const disableSelfDestructing = catchAsync( - async (req: Request, res: Response, next: NextFunction) => { - const { chatId } = req.params; - const chat = NormalChat.findByIdAndUpdate(chatId, { - destructionTimestamp: undefined, - destructionDuration: undefined, - }); - if (!chat) return next(new AppError('No chat with the provided id', 404)); - res.status(200).json({ - status: 'success', - message: 'Destruction time is disabled successfuly', - }); - } -); - export const getChat = catchAsync(async (req: Request, res: Response) => { const { chatId } = req.params; const chat = await Chat.findById(chatId).populate( @@ -194,49 +158,6 @@ export const getChatMembers = catchAsync( } ); -export const muteChat = catchAsync( - async (req: Request, res: Response, next: NextFunction) => { - const { chatId } = req.params; - const { muteDuration } = req.body; - const user: IUser = req.user as IUser; - if (!user) return next(new AppError('login first', 403)); - if (!muteDuration) - return next(new AppError('missing required fields', 400)); - user.chats.forEach((c: any) => { - if (c.chat.equals(chatId)) { - c.isMuted = true; - c.muteDuration = muteDuration; - } - }); - await user.save({ validateBeforeSave: false }); - unmute(user, chatId, muteDuration); - res.status(200).json({ - status: 'success', - message: 'Chat muted successfully', - }); - } -); - -export const unmuteChat = catchAsync( - async (req: Request, res: Response, next: NextFunction) => { - const { chatId } = req.params; - const user: IUser = req.user as IUser; - if (!user) return next(new AppError('login first', 403)); - - user.chats.forEach((c: any) => { - if (c.chat.equals(chatId)) { - c.isMuted = false; - c.muteDuration = undefined; - } - }); - await user.save({ validateBeforeSave: false }); - res.status(200).json({ - status: 'success', - message: 'Chat unmuted successfully', - }); - } -); - export const updateChatPicture = catchAsync(async (req: any, res: Response) => { const { chatId } = req.params; @@ -333,8 +254,7 @@ export const getVoiceCallsInChat = catchAsync( ); export const filterChatGroups = catchAsync( async (req: Request, res: Response, next: NextFunction) => { - const chatId = req.params.chatId; - console.log(chatId); + const { chatId } = req.params; const groupChannel = await GroupChannel.findById(chatId); if (!groupChannel) { return res.status(404).json({ @@ -342,7 +262,7 @@ export const filterChatGroups = catchAsync( message: 'Group/Channel not found with the given chatId', }); } - + groupChannel.isFilterd = true; await groupChannel.save(); res.status(200).json({ @@ -354,7 +274,7 @@ export const filterChatGroups = catchAsync( ); export const unfilterChatGroups = catchAsync( async (req: Request, res: Response, next: NextFunction) => { - const chatId = req.params.chatId; + const { chatId } = req.params; const groupChannel = await GroupChannel.findById(chatId); diff --git a/src/models/chatModel.ts b/src/models/chatModel.ts index 5adc6b0..33684e1 100644 --- a/src/models/chatModel.ts +++ b/src/models/chatModel.ts @@ -62,5 +62,14 @@ chatSchema.virtual('numberOfMembers').get(function () { return Array.isArray(this.members) ? this.members.length : 0; }); +chatSchema.pre('save', function (next) { + if (!this.isModified('members')) return next(); + const uniqueUsers = new Set(this.members.map((m) => m.user.toString())); + if (uniqueUsers.size !== this.members.length) { + return next(new Error('Members must have unique users.')); + } + next(); +}); + const Chat = mongoose.model('Chat', chatSchema); export default Chat; diff --git a/src/routes/chatRoute.ts b/src/routes/chatRoute.ts index 5a92258..33ef9df 100644 --- a/src/routes/chatRoute.ts +++ b/src/routes/chatRoute.ts @@ -3,21 +3,17 @@ import { getAllChats, getMessages, postMediaFile, - enableSelfDestructing, - disableSelfDestructing, getChat, setPrivacy, getChatMembers, - muteChat, - unmuteChat, updateChatPicture, invite, join, getVoiceCallsInChat, filterChatGroups, - unfilterChatGroups + unfilterChatGroups, } from '@base/controllers/chatController'; -import { protect , isAdmin } from '@base/middlewares/authMiddleware'; +import { protect, isAdmin } from '@base/middlewares/authMiddleware'; import upload from '@base/config/fileUploads'; import restrictTo from '@base/middlewares/chatMiddlewares'; @@ -34,10 +30,6 @@ router.patch( ); router.patch('/privacy/:chatId', restrictTo('admin'), setPrivacy); -router.patch('/destruct/:chatId', restrictTo(), enableSelfDestructing); -router.patch('/un-destruct/:chatId', restrictTo(), disableSelfDestructing); -router.patch('/mute/:chatId', restrictTo(), muteChat); -router.patch('/unmute/:chatId', restrictTo(), unmuteChat); router.get('/invite/:chatId', restrictTo('admin'), invite); router.post('/join/:token', join); diff --git a/src/services/chatService.ts b/src/services/chatService.ts index 2e782c2..cc92021 100644 --- a/src/services/chatService.ts +++ b/src/services/chatService.ts @@ -1,12 +1,12 @@ import mongoose from 'mongoose'; import NormalChat from '@base/models/normalChatModel'; import Message from '@base/models/messageModel'; -import { Socket } from 'socket.io'; +import { Server, Socket } from 'socket.io'; import User from '@base/models/userModel'; -import IUser from '@base/types/user'; import AppError from '@base/errors/AppError'; import GroupChannel from '@base/models/groupChannelModel'; import deleteFile from '@base/utils/deleteFile'; +import { informSessions } from '@base/sockets/MessagingServices'; export const getLastMessage = async (chats: any) => { const lastMessages = await Promise.all( @@ -76,20 +76,26 @@ export const enableDestruction = async ( } }; -export const unmute = async ( - user: IUser, +export const muteUnmuteChat = async ( + io: Server, + userId: string, chatId: string, - muteDuration: number + event: string, + muteDuration?: number ) => { - setTimeout(async () => { - user.chats.forEach((c: any) => { - if (c.chat.equals(chatId)) { - c.isMuted = false; - c.muteDuration = undefined; - } - }); - await user.save({ validateBeforeSave: false }); - }, muteDuration * 1000); + User.findByIdAndUpdate( + userId, + { + $set: { + 'chats.$[elem].isMuted': muteDuration, + 'chats.$[elem].muteDuration': muteDuration, + }, + }, + { + arrayFilters: [{ 'elem.chat': chatId }], + } + ); + informSessions(io, userId, { chatId }, event); }; export const deleteChatPictureFile = async ( diff --git a/src/sockets/MessagingServices.ts b/src/sockets/MessagingServices.ts index a15fd30..ca076c8 100644 --- a/src/sockets/MessagingServices.ts +++ b/src/sockets/MessagingServices.ts @@ -19,7 +19,8 @@ export const check = async ( senderId: any, additionalData?: any ) => { - const { chatType, checkAdmin, newMessageIsReply, content } = additionalData; + const { chatType, checkAdmin, newMessageIsReply, content, sendMessage } = + additionalData; if (!chat || chat.isDeleted) { return ack({ @@ -57,7 +58,7 @@ export const check = async ( message: 'you do not have permission as you are not an admin', }); - if (sender.Role !== 'admin' && chat.type !== 'private') { + if (sendMessage && sender.Role !== 'admin' && chat.type !== 'private') { const groupChannelChat = await GroupChannel.findById(chat._id); if (!groupChannelChat.messagingPermission) diff --git a/src/sockets/chats.ts b/src/sockets/chats.ts index 987ef18..d8f661f 100644 --- a/src/sockets/chats.ts +++ b/src/sockets/chats.ts @@ -4,11 +4,12 @@ import User from '@models/userModel'; import Chat from '@models/chatModel'; import GroupChannel from '@models/groupChannelModel'; import NormalChat from '@base/models/normalChatModel'; +import { muteUnmuteChat } from '@base/services/chatService'; +import IUser from '@base/types/user'; import { check, informSessions, joinRoom, - Member, updateDraft, } from './MessagingServices'; @@ -45,7 +46,7 @@ const handleAddAdmins = async ( return; } - Chat.findByIdAndUpdate( + await Chat.findByIdAndUpdate( chatId, { $set: { 'members.$[elem].Role': 'admin' } }, { @@ -311,27 +312,20 @@ const handleLeaveGroupChannel = async ( senderId: any ) => { const { chatId } = data; - const chat = await Chat.findById(chatId); - if (!chat || chat.isDeleted) - return ack({ - success: false, - message: 'could not leave the group', - error: 'this chat does no longer exist', - }); - const isMember = chat.members.some( - (member: any) => member.user.toString() === senderId.toString() - ); - if (!isMember) - return ack({ - success: false, - message: 'could not leave the group', - error: 'you are not a member of this chat', - }); + const chat = await GroupChannel.findById(chatId); + const func = await check(chat, ack, senderId, { + chatType: ['group', 'channel'], + }); + if (!func) return func; - await Chat.updateOne( - { _id: chatId }, - { $pull: { members: { user: senderId } } } - ); + await Promise.all([ + GroupChannel.findByIdAndUpdate(chatId, { + $pull: { members: { user: senderId } }, + }), + User.findByIdAndUpdate(senderId, { + $pull: { chats: { chat: chatId } }, + }), + ]); socket .to(chatId) @@ -351,65 +345,51 @@ const handleRemoveMembers = async ( senderId: any ) => { const { chatId, members } = data; - const forbiddenUsers: string[] = []; - - const chat = await Chat.findById(chatId); - if (!chat || chat.isDeleted) - return ack({ - success: false, - message: 'could not remove members from the group', - error: 'this chat does no longer exist', - }); - - const admin: Member = chat.members.find((m) => - m.user.equals(senderId) - ) as unknown as Member; + const invalidUsers: string[] = []; - if (!admin) - return ack({ - success: false, - message: 'could not remove members from the group', - error: 'you are no longer a member of this group', - }); + const chat = await GroupChannel.findById(chatId); - if (admin.Role === 'member') - return ack({ - success: false, - message: 'could not remove members from the group', - error: 'you do not have permission', - }); + const func = await check(chat, ack, senderId, { + chatType: ['group', 'channel'], + checkAdmin: true, + }); + if (!func) return func; await Promise.all( members.map(async (memberId: any) => { const user = await User.findById(memberId); if (!user) { - forbiddenUsers.push(memberId); + invalidUsers.push(memberId); return; } const isMember = chat.members.some((m: any) => m.user.equals(memberId)); if (!isMember) { - forbiddenUsers.push(memberId); + invalidUsers.push(memberId); return; } - await Chat.updateOne( - { _id: chatId }, - { $pull: { members: { user: memberId } } } - ); - - await informSessions(io, memberId, { chatId }, 'REMOVE_MEMBERS_SERVER'); + await Promise.all([ + GroupChannel.findByIdAndUpdate(chatId, { + $pull: { members: { user: memberId } }, + }), + User.findByIdAndUpdate(memberId, { + $pull: { chats: { chat: chatId } }, + }), + ]); }) ); - if (forbiddenUsers.length > 0) - return ack({ - success: false, - message: 'Some users could not be added', - error: `Could not remove users with IDs: ${forbiddenUsers.join(', ')}`, - }); + + socket + .to(chatId) + .emit('REMOVE_MEMBERS_SERVER', { chatId, memberId: senderId }); + ack({ success: true, - message: 'Members removed successfully', + message: + invalidUsers.length > 0 + ? `Some users could not be removed, IDs: ${invalidUsers.join(', ')}` + : 'Members removed successfully', data: {}, }); }; @@ -431,37 +411,11 @@ const handleSetPermission = async ( }); const chat = await GroupChannel.findById(chatId); - if (!chat || chat.isDeleted) - return ack({ - success: false, - message: 'could not update permissions', - error: 'this chat does no longer exist', - }); - - if (chat.type === 'private') - return ack({ - success: false, - message: 'could not update permissions', - error: 'cannot change permissions for private chats', - }); - - const admin: Member = chat.members.find((m: Member) => - m.user.equals(senderId) - ) as unknown as Member; - - if (!admin) - return ack({ - success: false, - message: 'could not update permissions', - error: 'you are no longer a member of this group', - }); - - if (admin.Role === 'member') - return ack({ - success: false, - message: 'could not change group permissions', - error: 'you do not have permission', - }); + const func = await check(chat, ack, senderId, { + chatType: ['group', 'channel'], + checkAdmin: true, + }); + if (!func) return func; if (type === 'post') chat.messagingPermission = who === 'everyone'; else if (type === 'download') chat.downloadingPermission = who === 'everyone'; @@ -474,6 +428,106 @@ const handleSetPermission = async ( }); }; +const handleSetPrivacy = async ( + io: any, + socket: Socket, + data: any, + ack: Function, + senderId: any +) => { + const { chatId, privacy } = data; + + const chat = await GroupChannel.findById(chatId); + const func = await check(chat, ack, senderId, { + chatType: ['group', 'channel'], + checkAdmin: true, + }); + if (!func) return func; + + chat.privacy = privacy; + await chat.save(); + socket.to(chatId).emit('SET_PRIVACY_SERVER', { chatId, privacy }); + ack({ + success: true, + message: 'privacy updated successfully', + data: {}, + }); +}; + +const handleMuteChat = async ( + io: any, + socket: Socket, + data: any, + ack: Function, + senderId: any +) => { + const { muteDuration, chatId } = data; + const userId = ((await User.findById(senderId)) as IUser) + ._id as Types.ObjectId; + + await muteUnmuteChat( + io, + userId.toString(), + chatId, + 'MUTE_CHAT', + muteDuration + ); + if (muteDuration !== -1) { + setTimeout(async () => { + muteUnmuteChat(io, userId.toString(), chatId, 'UNMUTE_CHAT'); + }, muteDuration * 1000); + } + + ack({ success: true, message: 'Chat muted successfully' }); +}; + +const handleUnMuteChat = async ( + io: any, + socket: Socket, + data: any, + ack: Function, + senderId: any +) => { + const { chatId } = data; + const userId = ((await User.findById(senderId)) as IUser) + ._id as Types.ObjectId; + await muteUnmuteChat(io, userId.toString(), chatId, 'UNMUTE_CHAT'); + ack({ success: true, message: 'Chat unmuted successfully' }); +}; + +const handleEnableDestruction = async ( + io: any, + socket: Socket, + data: any, + ack: Function, + senderId: any +) => { + const { destructionDuration, chatId } = data; + const destructionTimestamp = Date.now(); + await NormalChat.findByIdAndUpdate(chatId, { + destructionDuration, + destructionTimestamp, + }); + await informSessions(io, senderId, { chatId }, 'ENABLE_DESTRUCTION_SERVER'); + ack({ success: true, message: 'Chat enabled destruction successfully' }); +}; + +const handleDisableDestruction = async ( + io: any, + socket: Socket, + data: any, + ack: Function, + senderId: any +) => { + const { chatId } = data; + await NormalChat.findByIdAndUpdate(chatId, { + destructionTimestamp: undefined, + destructionDuration: undefined, + }); + await informSessions(io, senderId, { chatId }, 'ENABLE_DESTRUCTION_SERVER'); + ack({ success: true, message: 'Chat disabled destruction successfully' }); +}; + const handleDraftMessage = async ( io: Server, socket: Socket, @@ -507,6 +561,10 @@ const registerChatHandlers = (io: Server, socket: Socket, userId: any) => { handleSetPermission(io, socket, data, ack, userId); }); + socket.on('SET_PRIVACY_CLIENT', (data: any, ack: Function) => { + handleSetPrivacy(io, socket, data, ack, userId); + }); + socket.on('ADD_ADMINS_CLIENT', (data: any, ack: Function) => { handleAddAdmins(io, socket, data, ack, userId); }); @@ -519,6 +577,22 @@ const registerChatHandlers = (io: Server, socket: Socket, userId: any) => { handleRemoveMembers(io, socket, data, ack, userId); }); + socket.on('MUTE_CHAT_CLIENT', (data: any, ack: Function) => { + handleMuteChat(io, socket, data, ack, userId); + }); + + socket.on('UNMUTE_CHAT_CLIENT', (data: any, ack: Function) => { + handleUnMuteChat(io, socket, data, ack, userId); + }); + + socket.on('ENABLE_DESTRUCTION_CLIENT', (data: any, ack: Function) => { + handleEnableDestruction(io, socket, data, ack, userId); + }); + + socket.on('DISABLE_DESTRUCTION_CLIENT', (data: any, ack: Function) => { + handleDisableDestruction(io, socket, data, ack, userId); + }); + socket.on('UPDATE_DRAFT_CLIENT', (data: any, ack: Function) => handleDraftMessage(io, socket, data, ack, userId) ); diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index 52d782b..01c050d 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -45,6 +45,7 @@ const handleMessaging = async ( const func = await check(chat, ack, senderId, { newMessageIsReply: isReply, content, + sendMessage: true, }); if (!func) return; From baceab22498b8fc7e3832d9f49819d2055802634 Mon Sep 17 00:00:00 2001 From: Peter Safwat <118059454+PeterSafwatHBK@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:39:04 +0200 Subject: [PATCH 08/17] Update userRoute.ts (#168) --- src/routes/userRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/userRoute.ts b/src/routes/userRoute.ts index 1e0745f..8efc856 100644 --- a/src/routes/userRoute.ts +++ b/src/routes/userRoute.ts @@ -67,7 +67,7 @@ router.delete('/picture', deletePicture); router.get('/contacts/stories', getAllContactsStories); router.get('/:userId/stories', getStory); router.get('/:userId', getUser); -router.get('/',isAdmin, getAllUsers); +router.get('/', getAllUsers); export default router; From 20c28739a627c9ec81c7f90707d605638797ffe6 Mon Sep 17 00:00:00 2001 From: Akram Hany <109467185+akramhany@users.noreply.github.com> Date: Sat, 21 Dec 2024 14:24:29 +0200 Subject: [PATCH 09/17] chore(logs):put some logs (#170) --- src/sockets/voiceCalls.ts | 11 +++++++++-- src/sockets/voiceCallsServices.ts | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sockets/voiceCalls.ts b/src/sockets/voiceCalls.ts index e1d10e3..22db3c8 100644 --- a/src/sockets/voiceCalls.ts +++ b/src/sockets/voiceCalls.ts @@ -53,8 +53,9 @@ async function handleJoinCall( userId: string ) { const { voiceCallId } = data; - console.log('Client joined a call, CallId:', voiceCallId); - console.log('UserId: ', userId); + console.log( + `Client Joined call, clientId: ${userId} , callId: ${voiceCallId}` + ); await addClientToCall(socket, userId, voiceCallId); socket.join(voiceCallId); @@ -73,6 +74,10 @@ async function handleSignal( ) { const { type, targetId, voiceCallId, data } = signalData; + console.log( + `Signal Sent, type: ${type}, senderId: ${userId}, targetId: ${targetId}, voiceCallId: ${voiceCallId}` + ); + const socketId = getClientSocketId(voiceCallId, targetId); io.to(socketId).emit('SIGNAL-CLIENT', { @@ -91,6 +96,8 @@ async function handleLeaveCall( ) { const { voiceCallId } = data; + console.log(`Client Left, clientId: ${userId}, voiceCallId: ${voiceCallId}`); + socket.leave(voiceCallId); await removeClientFromCall(userId, voiceCallId); diff --git a/src/sockets/voiceCallsServices.ts b/src/sockets/voiceCallsServices.ts index 40f8972..7570d57 100644 --- a/src/sockets/voiceCallsServices.ts +++ b/src/sockets/voiceCallsServices.ts @@ -97,6 +97,8 @@ export async function removeClientFromCall( if (voiceCall.currentParticipants.length === 0) { await endVoiceCall(voiceCallId); } + + console.log('clientSocketMap: ', clientSocketMap[voiceCallId]); } export function getClientSocketMap(): ClientSocketMap { From 0cfbc94f3bba9e39a7f5ef8b1219f0d131146c74 Mon Sep 17 00:00:00 2001 From: somiaelshemy Date: Sat, 21 Dec 2024 15:58:08 +0200 Subject: [PATCH 10/17] fix(message): fix announcements --- src/sockets/messages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index 01c050d..9431798 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -19,7 +19,7 @@ const handleMessaging = async ( senderId: string ) => { let { media, content, contentType, parentMessageId } = data; - const { chatId, chatType, isReply, isForward } = data; + const { chatId, chatType, isReply, isForward, isAnnouncement } = data; if ( (!isForward && @@ -73,6 +73,7 @@ const handleMessaging = async ( senderId, chatId, parentMessageId, + isAnnouncement, }); await message.save(); From 07926a61b5909a6d1f8e6fa8038c8dc58696dfd2 Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Sat, 21 Dec 2024 19:34:01 +0200 Subject: [PATCH 11/17] feat(messages): added mentions --- docs/api/chat.swagger.ts | 20 ++++++++++++----- docs/api/sockets.swagger.ts | 3 +++ src/controllers/chatController.ts | 2 +- src/services/chatService.ts | 14 +++++++----- src/sockets/chats.ts | 37 +++++++++---------------------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/api/chat.swagger.ts b/docs/api/chat.swagger.ts index d397fd8..177256e 100644 --- a/docs/api/chat.swagger.ts +++ b/docs/api/chat.swagger.ts @@ -124,6 +124,20 @@ * items: * type: string * description: IDs of users blocked by this member. + * unreadMessages: + * type: array + * items: + * type: object + * properties: + * chatId: + * type: string + * description: Unique identifier of the chat this message belongs to. + * unreadMessagesCount: + * type: string + * description: Number of unread messages in the chat. + * isMentioned: + * type: boolean + * description: Indicates if the user is mentioned in the chat. * lastMessages: * type: array * items: @@ -171,12 +185,6 @@ * type: string * format: date-time * description: Timestamp when the message was sent. - * __v: - * type: integer - * description: Version key. - * id: - * type: string - * description: Alias for `_id`. * 401: * description: User is not logged in or the request is invalid. * content: diff --git a/docs/api/sockets.swagger.ts b/docs/api/sockets.swagger.ts index 785f66b..b031547 100644 --- a/docs/api/sockets.swagger.ts +++ b/docs/api/sockets.swagger.ts @@ -45,6 +45,9 @@ * isForward: * type: boolean * description: Indicates if the message is forwarded. + * isAnouncement: + * type: boolean + * description: Indicates if the message is an anouncement. * responses: * 200: * description: Message sent successfully. diff --git a/src/controllers/chatController.ts b/src/controllers/chatController.ts index 2fbbbdb..61fd401 100644 --- a/src/controllers/chatController.ts +++ b/src/controllers/chatController.ts @@ -45,7 +45,7 @@ export const getAllChats = catchAsync( 'username screenFirstName screenLastName phoneNumber photo status isAdmin stories blockedUsers' ), getLastMessage(allChats), - getUnreadMessages(allChats, userId), + getUnreadMessages(allChats, user), ]); res.status(200).json({ diff --git a/src/services/chatService.ts b/src/services/chatService.ts index cc92021..f3405f7 100644 --- a/src/services/chatService.ts +++ b/src/services/chatService.ts @@ -25,17 +25,21 @@ export const getLastMessage = async (chats: any) => { return lastMessages; }; -export const getUnreadMessages = async ( - chats: any, - userId: mongoose.Types.ObjectId -) => +export const getUnreadMessages = async (chats: any, user: any) => Promise.all( chats.map(async (chat: any) => ({ chatId: chat.chat._id, unreadMessagesCount: await Message.countDocuments({ chatId: chat.chat._id, - readBy: { $nin: [userId] }, + readBy: { $nin: [user._id] }, }), + isMentioned: + (await Message.exists({ + chatId: chat.chat._id, + readBy: { $nin: [user._id] }, + senderId: { $ne: user._id }, + content: new RegExp(`@${user.username}`, 'i'), + })) !== null, })) ); diff --git a/src/sockets/chats.ts b/src/sockets/chats.ts index d8f661f..1faad14 100644 --- a/src/sockets/chats.ts +++ b/src/sockets/chats.ts @@ -121,7 +121,7 @@ const handleAddMembers = async ( chat?.members.push({ user: userId, Role: 'member' }); const userWasMember = user.chats.some((c: any) => c.chat.equals(chatId)); if (!userWasMember) - User.findByIdAndUpdate( + await User.findByIdAndUpdate( userId, { $push: { chats: { chat: chatId } } }, { new: true } @@ -263,35 +263,20 @@ const handleDeleteGroupChannel = async ( senderId: any ) => { const { chatId } = data; - const chat = await Chat.findById(chatId); - if (!chat || chat.isDeleted) - return ack({ - success: false, - message: 'Could not delete the group', - error: 'no chat found with the provided id', - }); + const chat = await GroupChannel.findById(chatId); + const func = await check(chat, ack, senderId, { + chatType: ['group', 'channel'], + checkAdmin: true, + }); + if (!func) return func; - const chatMembers = chat.members; - const isCreator = chatMembers.some( - (member) => member.user.toString() === senderId && member.Role === 'admin' + await User.updateMany( + { _id: { $in: chat.members.map((m: any) => m.user) } }, + { $pull: { chats: { chat: chatId } } } ); - if (!isCreator) - return ack({ - success: false, - message: 'Could not delete the group', - error: 'you are not authorized to delete the group', - }); - - chatMembers.map(async (member: any) => { - await informSessions( - io, - member.user, - { chatId }, - 'DELETE_GROUP_CHANNEL_SERVER' - ); - }); + socket.to(chatId).emit('DELETE_GROUP_CHANNEL_SERVER', { chatId }); chat.members = []; chat.isDeleted = true; From 8c17b833c5c8585ba23362049b5c1aaa6228c0a2 Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Sat, 21 Dec 2024 20:25:07 +0200 Subject: [PATCH 12/17] fix: solved filtering bug --- src/services/googleAIService.ts | 52 +++++++++++++++++--------------- src/sockets/MessagingServices.ts | 27 +++++++++-------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/services/googleAIService.ts b/src/services/googleAIService.ts index de0e7c0..b4af570 100644 --- a/src/services/googleAIService.ts +++ b/src/services/googleAIService.ts @@ -5,33 +5,37 @@ const hf = new HfInference(process.env.HF_API_KEY); const modelName = 'unitary/toxic-bert'; async function detectInappropriateContent(text: string): Promise { - try { - const response = await hf.textClassification({ - model: modelName, - inputs: text, - }); + try { + const response = await hf.textClassification({ + model: modelName, + inputs: text, + }); - console.log('Model Response:', JSON.stringify(response, null, 2)); + console.log('Model Response:', JSON.stringify(response, null, 2)); - const relevantLabels = ['toxic', 'obscene', 'insult', 'severe_toxic']; - const threshold = 0.7; + const relevantLabels = ['toxic', 'obscene', 'insult', 'severe_toxic']; + const threshold = 0.7; - interface TextClassificationResult { - label: string; - score: number; - } - - const toxicityScore = (response as TextClassificationResult[]) - .filter((result) => relevantLabels.includes(result.label.toLowerCase()) && result.score > threshold) - .reduce((acc, curr) => acc + curr.score, 0); - - console.log(`Total Toxicity Score: ${toxicityScore}`); - - return toxicityScore < threshold; - } catch (error) { - console.error('Error detecting inappropriate content:', error); - throw new Error('Failed to detect inappropriate content'); + interface TextClassificationResult { + label: string; + score: number; } + + const toxicityScore = (response as TextClassificationResult[]) + .filter( + (result) => + relevantLabels.includes(result.label.toLowerCase()) && + result.score > threshold + ) + .reduce((acc, curr) => acc + curr.score, 0); + + console.log(`Total Toxicity Score: ${toxicityScore}`); + + return toxicityScore >= threshold; + } catch (error) { + console.error('Error detecting inappropriate content:', error); + throw new Error('Failed to detect inappropriate content'); + } } -export default detectInappropriateContent; \ No newline at end of file +export default detectInappropriateContent; diff --git a/src/sockets/MessagingServices.ts b/src/sockets/MessagingServices.ts index ca076c8..c3301ae 100644 --- a/src/sockets/MessagingServices.ts +++ b/src/sockets/MessagingServices.ts @@ -58,17 +58,11 @@ export const check = async ( message: 'you do not have permission as you are not an admin', }); - if (sendMessage && sender.Role !== 'admin' && chat.type !== 'private') { + if (sendMessage && chat.type !== 'private') { const groupChannelChat = await GroupChannel.findById(chat._id); - - if (!groupChannelChat.messagingPermission) - return ack({ - success: false, - message: 'only admins can post and reply to this chat', - }); if ( chat?.type === 'group' && - !chat.isFilterd && + chat.isFilterd && (await detectInappropriateContent(content)) ) { return ack({ @@ -76,11 +70,18 @@ export const check = async ( message: 'inappropriate content', }); } - if (chat.type === 'channel' && !newMessageIsReply) - return ack({ - success: false, - message: 'only admins can post to this channel', - }); + if (sender.Role !== 'admin') { + if (!groupChannelChat.messagingPermission) + return ack({ + success: false, + message: 'only admins can post and reply to this chat', + }); + if (chat.type === 'channel' && !newMessageIsReply) + return ack({ + success: false, + message: 'only admins can post to this channel', + }); + } } return true; }; From 05f881acea777b7fe249f5ae9686f52510579d0c Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Sat, 21 Dec 2024 21:48:26 +0200 Subject: [PATCH 13/17] fix: solved appropriate content bug --- src/controllers/userController.ts | 114 +++++++++++++++++++----------- src/models/messageModel.ts | 2 + src/sockets/MessagingServices.ts | 10 +-- src/sockets/messages.ts | 12 ++-- src/types/message.ts | 4 +- 5 files changed, 90 insertions(+), 52 deletions(-) diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 75b1e61..d99a0dc 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -14,22 +14,24 @@ interface GetUser extends Request { //TODO: add a user here that would contain the user data. } -export const getCurrentUser = catchAsync(async (req: GetUser, res: Response) => { - const userId = req.user.id; - - const user = await User.findById(userId); - - if (!user) { - throw new AppError('No User exists with this ID', 404); +export const getCurrentUser = catchAsync( + async (req: GetUser, res: Response) => { + const userId = req.user.id; + + const user = await User.findById(userId); + + if (!user) { + throw new AppError('No User exists with this ID', 404); + } + return res.status(200).json({ + status: 'success', + message: 'User retrieved successfuly', + data: { + user, + }, + }); } - return res.status(200).json({ - status: 'success', - message: 'User retrieved successfuly', - data: { - user, - }, - }); -}); +); export const updateCurrentUser = catchAsync(async (req: any, res: Response) => { const userData = req.body; @@ -84,7 +86,14 @@ export const getUser = catchAsync(async (req: GetUser, res: Response) => { throw new AppError('No User exists with this ID', 404); } - const fieldsToGet = ['username', 'screenFirstName', 'screenLastName', 'email', 'status', 'bio']; + const fieldsToGet = [ + 'username', + 'screenFirstName', + 'screenLastName', + 'email', + 'status', + 'bio', + ]; if ( user.picturePrivacy === 'everyone' || @@ -123,7 +132,11 @@ export const updateBio = catchAsync(async (req: any, res: Response) => { const { bio } = req.body; const userId = req.user.id; - const user = await User.findByIdAndUpdate(userId, { bio }, { new: true, runValidators: true }); + const user = await User.findByIdAndUpdate( + userId, + { bio }, + { new: true, runValidators: true } + ); if (!user) { throw new AppError('No User exists with this ID', 404); @@ -161,7 +174,11 @@ export const updateEmail = catchAsync(async (req: any, res: Response) => { const { email } = req.body; const userId = req.user.id; - const user = await User.findByIdAndUpdate(userId, { email }, { new: true, runValidators: true }); + const user = await User.findByIdAndUpdate( + userId, + { email }, + { new: true, runValidators: true } + ); if (!user) { throw new AppError('No User exists with this ID', 404); @@ -263,9 +280,9 @@ export const deletePicture = catchAsync(async (req: any, res: Response) => { data: {}, }); }); + export const getAllGroups = catchAsync(async (req: Request, res: Response) => { - const groupsAndChannels = await GroupChannel.find(); // Use `find()` in Mongoose to retrieve all documents - console.log(groupsAndChannels) + const groupsAndChannels = await GroupChannel.find(); return res.status(200).json({ status: 'success', message: 'Groups and Channels retrieved successfully', @@ -274,8 +291,23 @@ export const getAllGroups = catchAsync(async (req: Request, res: Response) => { }, }); }); + +export const toggleAutomaticDownload = catchAsync( + async (req: any, res: Response) => { + const userId = req.user.id; + const { enabled } = req.body; + + User.findByIdAndUpdate(userId, { automaticDownloadEnable: enabled }); + return res.status(200).json({ + status: 'success', + message: 'Automatic download settings updated successfully', + data: {}, + }); + } +); + export const activateUser = catchAsync(async (req: Request, res: Response) => { - const userId = req.params.userId; + const { userId } = req.params; const user = await User.findById(userId); if (!user) { @@ -284,7 +316,7 @@ export const activateUser = catchAsync(async (req: Request, res: Response) => { message: 'User not found', }); } -if (user.accountStatus === 'banned') { + if (user.accountStatus === 'banned') { return res.status(400).json({ status: 'fail', message: 'User is Banned', @@ -298,27 +330,29 @@ if (user.accountStatus === 'banned') { message: 'User activated successfully', }); }); -export const deactivateUser = catchAsync(async (req: Request, res: Response) => { - const userId = req.params.userId; - console.log(req.params) - const user = await User.findById(userId); - if (!user) { - return res.status(404).json({ - status: 'fail', - message: 'User not found', +export const deactivateUser = catchAsync( + async (req: Request, res: Response) => { + const { userId } = req.params; + console.log(req.params); + const user = await User.findById(userId); + if (!user) { + return res.status(404).json({ + status: 'fail', + message: 'User not found', + }); + } + + user.accountStatus = 'deactivated'; + await user.save(); + + return res.status(200).json({ + status: 'success', + message: 'User deactivated successfully', }); } - - user.accountStatus = 'deactivated'; - await user.save(); - - return res.status(200).json({ - status: 'success', - message: 'User deactivated successfully', - }); -}); +); export const banUser = catchAsync(async (req: Request, res: Response) => { - const userId = req.params.userId; + const { userId } = req.params; const user = await User.findById(userId); if (!user) { diff --git a/src/models/messageModel.ts b/src/models/messageModel.ts index 61e12a0..930c834 100644 --- a/src/models/messageModel.ts +++ b/src/models/messageModel.ts @@ -5,6 +5,8 @@ import Communication from './communicationModel'; const messageSchema = new mongoose.Schema({ content: String, media: String, + mediaName: String, + mediaSize: Number, contentType: { type: String, enum: ['text', 'image', 'GIF', 'sticker', 'audio', 'video', 'file', 'link'], diff --git a/src/sockets/MessagingServices.ts b/src/sockets/MessagingServices.ts index c3301ae..e35c42d 100644 --- a/src/sockets/MessagingServices.ts +++ b/src/sockets/MessagingServices.ts @@ -64,12 +64,8 @@ export const check = async ( chat?.type === 'group' && chat.isFilterd && (await detectInappropriateContent(content)) - ) { - return ack({ - success: false, - message: 'inappropriate content', - }); - } + ) + return 'inappropriate'; if (sender.Role !== 'admin') { if (!groupChannelChat.messagingPermission) return ack({ @@ -83,7 +79,7 @@ export const check = async ( }); } } - return true; + return 'ok'; }; export const informSessions = async ( diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index 9431798..9ace9fb 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -18,7 +18,8 @@ const handleMessaging = async ( ack: Function, senderId: string ) => { - let { media, content, contentType, parentMessageId } = data; + let { media, mediaName, mediaSize, content, contentType, parentMessageId } = + data; const { chatId, chatType, isReply, isForward, isAnnouncement } = data; if ( @@ -42,12 +43,12 @@ const handleMessaging = async ( }); const chat = await Chat.findById(chatId); - const func = await check(chat, ack, senderId, { + const valid = await check(chat, ack, senderId, { newMessageIsReply: isReply, content, sendMessage: true, }); - if (!func) return; + if (!valid) return; let parentMessage; if (isForward || isReply) { @@ -60,13 +61,15 @@ const handleMessaging = async ( }); if (isForward) { - ({ content, contentType, media } = parentMessage); + ({ content, contentType, media, mediaName, mediaSize } = parentMessage); parentMessageId = undefined; } } const message = new Message({ media, + mediaName, + mediaSize, content, contentType, isForward, @@ -74,6 +77,7 @@ const handleMessaging = async ( chatId, parentMessageId, isAnnouncement, + isAppropriate: valid === 'ok', }); await message.save(); diff --git a/src/types/message.ts b/src/types/message.ts index 0e4b363..470dd96 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -2,8 +2,10 @@ import { Types } from 'mongoose'; import ICommunication from './communication'; interface IMessage extends ICommunication { - media: string; content: string; + media: string; + mediaName: string; + mediaSize: number; contentType: string; isPinned: boolean; isForward: boolean; From 0fb3891756a6c50388997d4b30f5961bdba0f841 Mon Sep 17 00:00:00 2001 From: akramhany Date: Sat, 21 Dec 2024 01:10:49 +0200 Subject: [PATCH 14/17] feat(firebase): install firebase --- .env.example | 16 +- package-lock.json | 1773 +++++++++++++++++++++++++++++----------- package.json | 1 + src/config/firebase.ts | 11 + src/server.ts | 1 + 5 files changed, 1302 insertions(+), 500 deletions(-) create mode 100644 src/config/firebase.ts diff --git a/.env.example b/.env.example index 76a7ba4..b6527f5 100644 --- a/.env.example +++ b/.env.example @@ -44,4 +44,18 @@ GITHUB_CLIENT_SECRET=githu-client-secret CROSS_PLATFORM_OAUTH_REDIRECT_URL=telware://telware.online/social-auth-loading FRONTEND_URL=localhost:5174 -GROUP_SIZE= 5 \ No newline at end of file +GROUP_SIZE= 5 + +FIREBASE_SERVICE_ACCOUNT='{ + "type": "service_account", + "project_id": "your-project-id", + "private_key_id": "your-private-key-id", + "private_key": "-----BEGIN PRIVATE KEY-----\\nYOUR_PRIVATE_KEY\\n-----END PRIVATE KEY-----\\n", + "client_email": "your-client-email@your-project.iam.gserviceaccount.com", + "client_id": "your-client-id", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-client-email%40your-project.iam.gserviceaccount.com" + "universe_domain": "googleapis.com" +}' diff --git a/package-lock.json b/package-lock.json index 2dc05fc..94ee706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^7.4.1", "express-session": "^1.18.1", + "firebase-admin": "^13.0.2", "helmet": "^8.0.0", "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", @@ -1882,262 +1883,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", @@ -2154,118 +1899,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -2341,70 +1974,260 @@ "type-fest": "^0.20.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.3.0.tgz", + "integrity": "sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.0.tgz", + "integrity": "sha512-yHmUtGwEbW6HsKpPqT140/L6GpHtquHogRLgtanJFep3UAfDkE0fQfC49U+F9irCAoJVlv3M7VSp4rrtO4LnfA==" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==" + }, + "node_modules/@firebase/component": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", + "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", + "dependencies": { + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", + "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.11", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", + "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", + "dependencies": { + "@firebase/component": "0.6.11", + "@firebase/database": "1.0.10", + "@firebase/database-types": "1.0.7", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", + "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.2" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", + "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "node_modules/@google-cloud/firestore": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", + "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", + "optional": true, "dependencies": { - "argparse": "^2.0.1" + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "optional": true, "dependencies": { - "brace-expansion": "^1.1.7" + "arrify": "^2.0.0", + "extend": "^3.0.2" }, "engines": { - "node": "*" + "node": ">=14.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "optional": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "optional": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=14" } }, - "node_modules/@faker-js/faker": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.3.0.tgz", - "integrity": "sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], + "node_modules/@google-cloud/storage": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, "engines": { - "node": ">=18.0.0", - "npm": ">=9.0.0" + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/@google/generative-ai": { @@ -2415,6 +2238,37 @@ "node": ">=18.0.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz", + "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@huggingface/inference": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-2.8.1.tgz", @@ -2868,6 +2722,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -2993,6 +2857,15 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -3005,6 +2878,70 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "optional": true + }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -3155,6 +3092,15 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -3243,17 +3189,21 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "optional": true + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -3334,8 +3284,7 @@ "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -3386,11 +3335,16 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", - "dev": true, "dependencies": { "@types/node": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -3403,8 +3357,7 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/morgan": { "version": "1.9.9", @@ -3495,20 +3448,44 @@ "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", - "dev": true + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -3518,7 +3495,6 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -3559,6 +3535,12 @@ "@types/serve-static": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "optional": true + }, "node_modules/@types/ua-parser-js": { "version": "0.7.39", "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", @@ -3818,6 +3800,18 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3916,6 +3910,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4161,6 +4156,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -4194,6 +4198,15 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4393,6 +4406,25 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -4446,6 +4478,14 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4498,6 +4538,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4711,6 +4752,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4799,6 +4841,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -4834,7 +4877,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, + "devOptional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -4872,6 +4915,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4882,7 +4926,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true }, "node_modules/color-support": { "version": "1.1.3", @@ -5445,6 +5490,32 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", @@ -5529,6 +5600,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", @@ -5813,7 +5893,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -6639,6 +6719,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6824,6 +6913,14 @@ "node >=0.6.0" ] }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6879,6 +6976,28 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-xml-parser": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz", + "integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6888,6 +7007,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7007,6 +7137,42 @@ "node": ">=8" } }, + "node_modules/firebase-admin": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.2.tgz", + "integrity": "sha512-YWVpoN+tZVSRXF0qC0gojoF5bSqvBRbnBk8+xUtFiguM2L4vB7f0moAwV1VVWDDHvTnvQ68OyTMpdp6wKo/clw==", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.11.0", + "@google-cloud/storage": "^7.14.0" + } + }, + "node_modules/firebase-admin/node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -7171,20 +7337,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7212,6 +7364,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -7241,6 +7399,167 @@ "node": ">=10" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gaxios/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/gaxios/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gcp-metadata/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true, + "peer": true + }, + "node_modules/gcp-metadata/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true, + "peer": true + }, + "node_modules/gcp-metadata/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -7262,7 +7581,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, + "devOptional": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7448,6 +7767,131 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/google-gax/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/google-gax/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "node_modules/google-gax/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7462,7 +7906,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -7470,6 +7915,37 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -7507,6 +7983,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -7644,6 +8121,22 @@ "node": ">=0.10.0" } }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "optional": true + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7675,6 +8168,25 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -7784,6 +8296,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -8162,7 +8675,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -8274,7 +8786,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isstream": { "version": "0.1.2", @@ -8998,6 +9511,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9034,6 +9555,14 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -9043,7 +9572,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -9161,6 +9691,44 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -9236,6 +9804,11 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9268,6 +9841,17 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9336,6 +9920,12 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "optional": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9357,6 +9947,31 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -9663,6 +10278,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9955,6 +10571,14 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12663,6 +13287,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -12859,7 +13492,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -13233,6 +13866,42 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -13635,7 +14304,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -13699,6 +14368,29 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14281,6 +14973,21 @@ "node": ">= 0.8" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "optional": true + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -14494,6 +15201,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", @@ -14540,6 +15259,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14716,49 +15436,81 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/telware-backend": { - "version": "1.0.0", - "resolved": "file:", - "license": "ISC", + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "optional": true, "dependencies": { - "@faker-js/faker": "^9.0.3", - "axios": "^1.7.7", - "bcrypt": "^5.1.1", - "body-parser": "^1.20.3", - "connect-redis": "^7.1.1", - "cors": "^2.8.5", - "crypto": "^1.0.1", - "dotenv": "^16.4.5", - "express": "^4.21.1", - "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.4.1", - "express-session": "^1.18.1", - "helmet": "^8.0.0", - "hpp": "^0.2.3", - "jsonwebtoken": "^9.0.2", - "module-alias": "^2.2.3", - "mongodb": "^6.11.0", - "mongoose": "^8.7.2", - "morgan": "^1.10.0", - "multer": "^1.4.5-lts.1", - "node-fetch": "^3.3.2", - "nodemailer": "^6.9.15", - "npm": "^10.9.1", - "passport": "^0.7.0", - "passport-github2": "^0.1.12", - "passport-google-oauth20": "^2.0.0", - "redis": "^4.7.0", - "request": "^2.88.2", - "socket.io": "^4.8.1", - "supertest": "^7.0.0", - "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.1", - "telware-backend": "file:", - "ua-parser-js": "^1.0.39", - "validator": "^13.12.0", - "yamljs": "^0.3.0" + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/teeny-request/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/teeny-request/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "node_modules/teeny-request/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, + "node_modules/telware-backend": { + "resolved": "", + "link": true + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -14798,7 +15550,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/tmpl": { "version": "1.0.5", @@ -15124,8 +15877,7 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tsx": { "version": "4.19.2", @@ -15723,6 +16475,27 @@ "node": ">=12" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-url": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", @@ -15739,6 +16512,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -15860,7 +16634,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15882,6 +16656,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -15922,7 +16697,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -15958,7 +16733,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, + "devOptional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -15976,7 +16751,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=12" } @@ -15994,7 +16769,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 5053f44..3e4faf8 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^7.4.1", "express-session": "^1.18.1", + "firebase-admin": "^13.0.2", "helmet": "^8.0.0", "hpp": "^0.2.3", "jsonwebtoken": "^9.0.2", diff --git a/src/config/firebase.ts b/src/config/firebase.ts new file mode 100644 index 0000000..a47780d --- /dev/null +++ b/src/config/firebase.ts @@ -0,0 +1,11 @@ +import admin from 'firebase-admin'; + +const serviceAccount = JSON.parse( + process.env.FIREBASE_SERVICE_ACCOUNT as string +); + +admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), +}); + +export default admin; diff --git a/src/server.ts b/src/server.ts index 3c93c46..2828727 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,7 @@ import http from 'http'; import '@config/env'; import '@config/passport'; +import '@config/firebase'; import mongoDBConnection from '@config/mongoDB'; import app from '@base/app'; import socketSetup from './sockets/socket'; From cb2b33154b8613aa935fb9a5f4e2feb824331d99 Mon Sep 17 00:00:00 2001 From: akramhany Date: Sat, 21 Dec 2024 18:31:58 +0200 Subject: [PATCH 15/17] feat(fcmToken): implement an endpoint to update the FCM token --- docs/api/user.swagger.ts | 68 +++++++++++++++++++++++++++++++ src/controllers/userController.ts | 27 +++++++++++- src/models/userModel.ts | 16 +++++--- src/routes/userRoute.ts | 15 +++---- src/types/user.ts | 1 + 5 files changed, 112 insertions(+), 15 deletions(-) diff --git a/docs/api/user.swagger.ts b/docs/api/user.swagger.ts index 461c91f..3cb2630 100644 --- a/docs/api/user.swagger.ts +++ b/docs/api/user.swagger.ts @@ -1416,3 +1416,71 @@ * - Session not found, you are not allowed here! * - You are not authorized to view these stories */ + +/** + * @swagger + * /users/fcm-token: + * patch: + * summary: Update the FCM token of the authenticated user + * description: This endpoint allows an authenticated user to update their Firebase Cloud Messaging (FCM) token. + * tags: + * - User + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * fcmToken: + * type: string + * description: The FCM token to be updated. + * example: "aslkdfadslfdaskf" + * required: + * - fcmToken + * responses: + * 201: + * description: FCM token updated successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: "success" + * message: + * type: string + * example: "User fcm token updated successfully" + * data: + * type: object + * example: {} + * 404: + * description: No user exists with the given ID. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: "error" + * message: + * type: string + * example: "No User exists with this ID" + * 500: + * description: Internal server error. + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: "error" + * message: + * type: string + * example: "Internal Server Error" + */ diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index d99a0dc..1ca3045 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -282,7 +282,8 @@ export const deletePicture = catchAsync(async (req: any, res: Response) => { }); export const getAllGroups = catchAsync(async (req: Request, res: Response) => { - const groupsAndChannels = await GroupChannel.find(); + const groupsAndChannels = await GroupChannel.find(); // Use `find()` in Mongoose to retrieve all documents + return res.status(200).json({ status: 'success', message: 'Groups and Channels retrieved successfully', @@ -330,10 +331,10 @@ export const activateUser = catchAsync(async (req: Request, res: Response) => { message: 'User activated successfully', }); }); + export const deactivateUser = catchAsync( async (req: Request, res: Response) => { const { userId } = req.params; - console.log(req.params); const user = await User.findById(userId); if (!user) { return res.status(404).json({ @@ -351,6 +352,7 @@ export const deactivateUser = catchAsync( }); } ); + export const banUser = catchAsync(async (req: Request, res: Response) => { const { userId } = req.params; @@ -370,3 +372,24 @@ export const banUser = catchAsync(async (req: Request, res: Response) => { message: 'User banned successfully', }); }); + +export const updateFCMToken = catchAsync(async (req: any, res: Response) => { + const userId = req.user.id; + const { fcmToken } = req.body; + + const user = await User.findOneAndUpdate( + { _id: userId }, + { fcmToken }, + { new: true, runValidators: true } + ); + + if (!user) { + throw new AppError('No User exists with this ID', 404); + } + + return res.status(201).json({ + status: 'success', + message: 'User fcm token updated successfuly', + data: {}, + }); +}); diff --git a/src/models/userModel.ts b/src/models/userModel.ts index a797111..4fa9a4c 100644 --- a/src/models/userModel.ts +++ b/src/models/userModel.ts @@ -30,6 +30,10 @@ const userSchema = new mongoose.Schema( message: 'Username can contain only letters, numbers and underscore', }, }, + fcmToken: { + type: String, + default: '', + }, screenFirstName: { type: String, default: '', @@ -235,21 +239,21 @@ const userSchema = new mongoose.Schema( userSchema.index({ email: 1 }, { background: true }); -userSchema.pre('save', async function (next) { +userSchema.pre('save', async function(next) { if (!this.isModified('password') || !this.password) return next(); this.password = await bcrypt.hash(this.password, 12); this.passwordConfirm = undefined; next(); }); -userSchema.pre('save', function (next) { +userSchema.pre('save', function(next) { if (this.provider === 'local') { this.providerId = this._id as string; } next(); }); -userSchema.methods.isCorrectPassword = async function ( +userSchema.methods.isCorrectPassword = async function( candidatePass: string ): Promise { const result = await bcrypt.compare(candidatePass, this.password); @@ -257,7 +261,7 @@ userSchema.methods.isCorrectPassword = async function ( return result; }; -userSchema.methods.passwordChanged = function (tokenIssuedAt: number): boolean { +userSchema.methods.passwordChanged = function(tokenIssuedAt: number): boolean { if ( this.changedPasswordAt && this.changedPasswordAt.getTime() / 1000 > tokenIssuedAt @@ -266,7 +270,7 @@ userSchema.methods.passwordChanged = function (tokenIssuedAt: number): boolean { return false; }; -userSchema.methods.generateSaveConfirmationCode = function (): string { +userSchema.methods.generateSaveConfirmationCode = function(): string { const confirmationCode: string = generateConfirmationCode(); this.emailVerificationCode = crypto .createHash('sha256') @@ -277,7 +281,7 @@ userSchema.methods.generateSaveConfirmationCode = function (): string { return confirmationCode; }; -userSchema.methods.createResetPasswordToken = function (): string { +userSchema.methods.createResetPasswordToken = function(): string { const resetPasswordToken = crypto.randomBytes(32).toString('hex'); this.resetPasswordToken = crypto diff --git a/src/routes/userRoute.ts b/src/routes/userRoute.ts index 8efc856..34753de 100644 --- a/src/routes/userRoute.ts +++ b/src/routes/userRoute.ts @@ -22,7 +22,8 @@ import { getAllGroups, activateUser, deactivateUser, - banUser + banUser, + updateFCMToken, } from '@controllers/userController'; import { deleteStory, @@ -31,7 +32,7 @@ import { getStory, postStory, } from '@controllers/storyController'; -import { protect,isAdmin,isActive } from '@middlewares/authMiddleware'; +import { protect, isAdmin, isActive } from '@middlewares/authMiddleware'; const router = Router(); @@ -48,10 +49,10 @@ router.post('/block/:id', block); router.delete('/block/:id', unblock); // Admin routes -router.patch('/activate/:userId',isAdmin,activateUser); -router.patch('/deactivate/:userId',isAdmin,deactivateUser); -router.patch('/ban/:userId',isAdmin,banUser); -router.get('/all-groups',isAdmin,getAllGroups); +router.patch('/activate/:userId', isAdmin, activateUser); +router.patch('/deactivate/:userId', isAdmin, deactivateUser); +router.patch('/ban/:userId', isAdmin, banUser); +router.get('/all-groups', isAdmin, getAllGroups); // User routes router.get('/me', getCurrentUser); @@ -63,11 +64,11 @@ router.patch('/email', updateEmail); router.patch('/username', updateUsername); router.patch('/screen-name', updateScreenName); router.patch('/picture', upload.single('file'), updatePicture); +router.patch('/fcm-token', updateFCMToken); router.delete('/picture', deletePicture); router.get('/contacts/stories', getAllContactsStories); router.get('/:userId/stories', getStory); router.get('/:userId', getUser); router.get('/', getAllUsers); - export default router; diff --git a/src/types/user.ts b/src/types/user.ts index 970d709..fd89b08 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -7,6 +7,7 @@ interface IUser extends Document { screenFirstName: string; screenLastName: string; email: string; + fcmToken: string; phoneNumber: string | undefined; password: string | undefined; passwordConfirm: string | undefined; From c61a87df79616bfd50ea47e384dc93fbdc70ea70 Mon Sep 17 00:00:00 2001 From: akramhany Date: Sat, 21 Dec 2024 22:03:31 +0200 Subject: [PATCH 16/17] feat(messages-notifications): implement normal messages notifications --- src/config/firebase.ts | 2 ++ src/sockets/messages.ts | 3 ++ src/sockets/notifications.ts | 56 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/sockets/notifications.ts diff --git a/src/config/firebase.ts b/src/config/firebase.ts index a47780d..6350540 100644 --- a/src/config/firebase.ts +++ b/src/config/firebase.ts @@ -8,4 +8,6 @@ admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); +export const messaging = admin.messaging(); + export default admin; diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index 9ace9fb..bab859d 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -5,6 +5,7 @@ import Message from '@models/messageModel'; import { enableDestruction } from '@services/chatService'; import Chat from '@base/models/chatModel'; import { check, informSessions, updateDraft } from './MessagingServices'; +import handleNotifications from './notifications'; interface PinUnPinMessageData { chatId: string | Types.ObjectId; @@ -82,6 +83,8 @@ const handleMessaging = async ( await message.save(); + handleNotifications(message.id.toString()); + if (parentMessage && isReply && chatType === 'channel') { parentMessage.threadMessages.push(message._id as Types.ObjectId); await parentMessage.save(); diff --git a/src/sockets/notifications.ts b/src/sockets/notifications.ts new file mode 100644 index 0000000..fdd4bcc --- /dev/null +++ b/src/sockets/notifications.ts @@ -0,0 +1,56 @@ +import { messaging } from '@base/config/firebase'; +import Chat from '@base/models/chatModel'; +import Message from '@base/models/messageModel'; +import User from '@base/models/userModel'; + +const sendNotification = async (fcmToken: string, title: string, body: any) => { + const message = { + notification: { + title, + body, + }, + token: fcmToken, + }; + + try { + const response = await messaging.send(message); + console.log('Notification sent successfully:', response); + } catch (error) { + console.error('Error sending notification:', error); + } +}; + +const sendNotificationToChat = async (senderId: string, chatId: string) => { + const targetChat = await Chat.findById(chatId).populate('members'); + + if (!targetChat) return; + + const memberIds = targetChat.members.filter( + (memberId) => memberId.toString() !== senderId + ); + + const members = await User.find({ _id: { $in: memberIds } }, 'chats'); + + members.forEach((member) => { + const targetChatInfo = member.chats.find( + ({ chat, isMuted }) => chat.toString() === chatId.toString() && !isMuted + ); + + if (targetChatInfo) { + sendNotification( + member.fcmToken, + 'Message Received', + `Message received from ${member.username}` + ); + } + }); +}; + +const handleNotifications = async (messageId: string) => { + const message = await Message.findById(messageId); + + const { senderId, chatId } = message; + sendNotificationToChat(senderId, chatId); +}; + +export default handleNotifications; From a4867b67c306ca1214838e0e1c248d7ad19bffa3 Mon Sep 17 00:00:00 2001 From: AhmedHamed3699 Date: Sat, 21 Dec 2024 22:35:23 +0200 Subject: [PATCH 17/17] fix: changed mention regex --- src/services/chatService.ts | 31 +++++++++++++++++-------------- src/sockets/messages.ts | 9 +++++---- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/services/chatService.ts b/src/services/chatService.ts index f3405f7..a220abe 100644 --- a/src/services/chatService.ts +++ b/src/services/chatService.ts @@ -25,23 +25,26 @@ export const getLastMessage = async (chats: any) => { return lastMessages; }; -export const getUnreadMessages = async (chats: any, user: any) => - Promise.all( - chats.map(async (chat: any) => ({ - chatId: chat.chat._id, - unreadMessagesCount: await Message.countDocuments({ +export const getUnreadMessages = async (chats: any, user: any) => { + const mentionRegex = /@[[^]]+](([^)]+))/g; + return Promise.all( + chats.map(async (chat: any) => { + const unreadMessages = await Message.find({ chatId: chat.chat._id, + senderId: { $ne: user._id }, readBy: { $nin: [user._id] }, - }), - isMentioned: - (await Message.exists({ - chatId: chat.chat._id, - readBy: { $nin: [user._id] }, - senderId: { $ne: user._id }, - content: new RegExp(`@${user.username}`, 'i'), - })) !== null, - })) + }); + return { + chatId: chat.chat._id, + unreadMessagesCount: unreadMessages.length, + isMentioned: + unreadMessages.filter((message: any) => + mentionRegex.test(message.content) + ).length > 0, + }; + }) ); +}; export const getChats = async ( userId: mongoose.Types.ObjectId, diff --git a/src/sockets/messages.ts b/src/sockets/messages.ts index bab859d..dce8126 100644 --- a/src/sockets/messages.ts +++ b/src/sockets/messages.ts @@ -107,11 +107,12 @@ const handleMessaging = async ( ); } }); - const res = { - messageId: message._id, - }; enableDestruction(socket, message, chatId); - ack({ success: true, message: 'Message sent successfully', res }); + ack({ + success: true, + message: 'Message sent successfully', + data: message, + }); }; const handleEditMessage = async (socket: Socket, data: any, ack: Function) => {