From 1f50ca6fc3fbd00442233cf81bff1218797220f3 Mon Sep 17 00:00:00 2001 From: Rena Tsagka <57152951+Renc17@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:59:56 +0300 Subject: [PATCH 01/14] refactor(chat)!: admin/router missing endpoints/query params/participants actions (#1135) * refactor(chat): sendInvitation props as object * fix(chat): missing populate query param from router * fix(chat): missing search message regex matching * chore(chat): cleanups * fix(chat): router invitation token count * refactor(chat): admin route createRoom participants logs, set creator * refactor(chat): participants actions * fix(chat): router query params id types * feat(chat): router remove member from room route * fix(chat): admin route getRooms missing params * fix(chat): admin route deleteRooms missing audit-mode config check * feat(chat): admin route removeUsers/addUsers/roomInvitations/roomById * feat(chat): participants join log on invitation accept --- modules/chat/src/Chat.ts | 6 +- modules/chat/src/admin/index.ts | 350 ++++++++++++++++-- .../src/models/ChatParticipantsLog.schema.ts | 4 +- modules/chat/src/routes/InvitationRoutes.ts | 17 +- modules/chat/src/routes/index.ts | 121 ++++-- modules/chat/src/utils/index.ts | 37 +- 6 files changed, 441 insertions(+), 94 deletions(-) diff --git a/modules/chat/src/Chat.ts b/modules/chat/src/Chat.ts index a93c52427..1f71310d9 100644 --- a/modules/chat/src/Chat.ts +++ b/modules/chat/src/Chat.ts @@ -47,8 +47,8 @@ export default class Chat extends ManagedModule { private adminRouter: AdminHandlers; private userRouter: ChatRoutes; private database: DatabaseProvider; - private _emailServing: boolean; - private _pushNotificationsServing: boolean; + private _emailServing: boolean = false; + private _pushNotificationsServing: boolean = false; constructor() { super('chat'); @@ -133,7 +133,7 @@ export default class Chat extends ManagedModule { } try { - await validateUsersInput(this.grpcSdk, participants); + await validateUsersInput(participants); } catch (e) { return callback({ code: (e as GrpcError).code, message: (e as GrpcError).message }); } diff --git a/modules/chat/src/admin/index.ts b/modules/chat/src/admin/index.ts index bcfeb4d73..3b0ebd211 100644 --- a/modules/chat/src/admin/index.ts +++ b/modules/chat/src/admin/index.ts @@ -9,17 +9,27 @@ import { UnparsedRouterResponse, } from '@conduitplatform/grpc-sdk'; import { + ConduitBoolean, ConduitNumber, + ConduitObjectId, ConduitString, + ConfigController, GrpcServer, RoutingManager, } from '@conduitplatform/module-tools'; import { status } from '@grpc/grpc-js'; -import { isNil } from 'lodash-es'; -import { populateArray } from '../utils/index.js'; -import { ChatMessage, ChatRoom, User } from '../models/index.js'; +import { isEmpty, isNil } from 'lodash-es'; +import { + ChatMessage, + ChatParticipantsLog, + ChatRoom, + InvitationToken, + User, +} from '../models/index.js'; +import { Config } from '../config/index.js'; import escapeStringRegexp from 'escape-string-regexp'; +import { sendInvitations } from '../utils/index.js'; export class AdminHandlers { private readonly routingManager: RoutingManager; @@ -33,18 +43,30 @@ export class AdminHandlers { } async getRooms(call: ParsedRouterRequest): Promise { - const { sort, search, populate } = call.request.params; + const { sort, search, users, deleted, populate } = call.request.params as { + sort?: string[]; + search?: string; + users?: string[]; + deleted?: boolean; + populate?: string[]; + }; const { skip } = call.request.params ?? 0; const { limit } = call.request.params ?? 25; - let query: Query = {}; - let identifier, populates; - if (!isNil(populate)) { - populates = populateArray(populate); - } + let query: Query = { + $and: [], + }; if (!isNil(search)) { - identifier = escapeStringRegexp(search); - query = { name: { $regex: `.*${identifier}.*`, $options: 'i' } }; + query.$and?.push({ + name: { $regex: `.*${escapeStringRegexp(search)}.*`, $options: 'i' }, + }); + } + if (!isNil(deleted)) { + query.$and?.push({ deleted }); + } + if (!isEmpty(users)) { + query.$and?.push({ participants: { $in: users } }); } + if (!query.$and?.length) query = {}; const chatRoomDocumentsPromise = ChatRoom.getInstance().findMany( query, @@ -52,7 +74,7 @@ export class AdminHandlers { skip, limit, sort, - populates, + populate, ); const totalCountPromise = ChatRoom.getInstance().countDocuments(query); @@ -67,7 +89,10 @@ export class AdminHandlers { } async createRoom(call: ParsedRouterRequest): Promise { - const { participants } = call.request.params as { participants: string[] }; + const { participants, creator } = call.request.params as { + participants: string[]; + creator: string; + }; if (participants.length === 0) { // array check is required throw new GrpcError( @@ -76,20 +101,37 @@ export class AdminHandlers { ); } await this.validateUsersInput(participants); - const chatRoom = await ChatRoom.getInstance() + const unique = new Set([...participants]); + let chatRoom = await ChatRoom.getInstance() .create({ name: call.request.params.name, - participants: Array.from(new Set([...participants])), + participants: Array.from(unique), + creator: creator, }) .catch((e: Error) => { throw new GrpcError(status.INTERNAL, e.message); }); + const participantsLog = await ChatParticipantsLog.getInstance().createMany([ + { + user: creator, + action: 'create', + chatRoom: chatRoom._id, + }, + ...Array.from(unique).map((userId: string) => ({ + user: userId, + action: 'join', + chatRoom: chatRoom._id, + })), + ]); + chatRoom = (await ChatRoom.getInstance().findByIdAndUpdate(chatRoom._id, { + participantsLog: participantsLog.map(log => log._id), + })) as ChatRoom; ConduitGrpcSdk.Metrics?.increment('chat_rooms_total'); return chatRoom; } async deleteRooms(call: ParsedRouterRequest): Promise { - const { ids } = call.request.params; + const { ids } = call.request.params as { ids: string[] }; if (ids.length === 0) { // array check is required throw new GrpcError( @@ -97,32 +139,33 @@ export class AdminHandlers { 'ids is required and must be a non-empty array', ); } - await ChatRoom.getInstance() - .deleteMany({ _id: { $in: ids } }) - .catch((e: Error) => { - throw new GrpcError(status.INTERNAL, e.message); - }); - await ChatMessage.getInstance() - .deleteMany({ room: { $in: ids } }) - .catch((e: Error) => { - throw new GrpcError(status.INTERNAL, e.message); - }); + const config = ConfigController.getInstance().config as Config; + if (config.auditMode) { + await Promise.all( + ids.map(roomId => { + ChatRoom.getInstance().findByIdAndUpdate(roomId, { + deleted: true, + }); + ChatMessage.getInstance().updateMany({ room: roomId }, { deleted: true }); + }), + ); + } else { + await ChatRoom.getInstance().deleteMany({ _id: { $in: ids } }); + await ChatMessage.getInstance().deleteMany({ room: { $in: ids } }); + await ChatParticipantsLog.getInstance().deleteMany({ chatRoom: { $in: ids } }); + } ConduitGrpcSdk.Metrics?.decrement('chat_rooms_total'); return 'Done'; } async getMessages(call: ParsedRouterRequest): Promise { - const { senderUser, roomId, populate, sort } = call.request.params; + const { senderUser, roomId, populate, sort, search } = call.request.params; const { skip } = call.request.params ?? 0; const { limit } = call.request.params ?? 25; const query: Query = { ...(senderUser ? { senderUser } : {}), ...(roomId ? { room: roomId } : {}), }; - let populates; - if (!isNil(populate)) { - populates = populateArray(populate); - } if (!isNil(senderUser)) { const user = await User.getInstance().findOne({ _id: senderUser }); if (isNil(user)) { @@ -135,6 +178,9 @@ export class AdminHandlers { throw new GrpcError(status.NOT_FOUND, `Room ${roomId} does not exists`); } } + if (!isNil(search)) { + query.message = { $regex: search, $options: 'i' }; + } const messagesPromise = ChatMessage.getInstance().findMany( query, @@ -142,7 +188,7 @@ export class AdminHandlers { skip, limit, sort, - populates, + populate, ); const countPromise = ChatMessage.getInstance().countDocuments(query); const [messages, count] = await Promise.all([messagesPromise, countPromise]).catch( @@ -171,6 +217,167 @@ export class AdminHandlers { return 'Done'; } + async getRoomById(call: ParsedRouterRequest): Promise { + const { roomId } = call.request.urlParams as { roomId: string }; + const { populate } = call.request.queryParams as { populate: string[] }; + const room = await ChatRoom.getInstance().findOne( + { _id: roomId }, + undefined, + populate, + ); + if (isNil(room)) throw new GrpcError(status.NOT_FOUND, 'Room does not exist'); + return room; + } + + async getRoomInvitations(call: ParsedRouterRequest): Promise { + const { roomId } = call.request.urlParams as { roomId: string }; + const { skip, limit, sort, populate } = call.request.queryParams as { + skip?: number; + limit?: number; + sort?: string; + populate?: string[]; + }; + const invitations = await InvitationToken.getInstance() + .findMany( + { room: roomId }, + undefined, + skip ?? 0, + limit ?? 10, + sort ?? '-createdAt', + populate, + ) + .catch((e: Error) => { + throw new GrpcError(status.INTERNAL, e.message); + }); + const count = await InvitationToken.getInstance().countDocuments({ + room: roomId, + }); + return { invitations, count }; + } + + async removeFromRoom(call: ParsedRouterRequest): Promise { + const { roomId } = call.request.urlParams as { roomId: string }; + const { users } = call.request.bodyParams as { users: string[] }; + if (!users.length) { + throw new GrpcError( + status.INVALID_ARGUMENT, + 'users field is required and must be a non-empty array', + ); + } + const room = await ChatRoom.getInstance().findOne({ _id: roomId }); + if (isNil(room) || room.deleted) + throw new GrpcError(status.NOT_FOUND, "Room doesn't exists!"); + if (!room.participants.length) + throw new GrpcError(status.CANCELLED, 'Room is already empty!'); + + const unique = Array.from(new Set(users)); + const toBeRemoved = unique.flatMap(user => { + const index = (room.participants as string[]).indexOf(user); + if (index > -1) return user; + else return []; + }); + if (!toBeRemoved.length) + throw new GrpcError(status.INVALID_ARGUMENT, 'Users are not participants of room!'); + + room.participants = room.participants.flatMap(user => { + const index = toBeRemoved.indexOf(user as string); + if (index > -1) return []; + else return user as string; + }); + const participantsLog = await Promise.all( + toBeRemoved.map(user => + ChatParticipantsLog.getInstance().create({ + user, + action: 'remove', + chatRoom: room._id, + }), + ), + ).catch(e => { + throw new GrpcError(status.INTERNAL, e.message); + }); + room.participantsLog.push(...participantsLog); + await ChatRoom.getInstance() + .findByIdAndUpdate(room._id, room) + .catch((e: Error) => { + throw new GrpcError(status.INTERNAL, e.message); + }); + + const config = ConfigController.getInstance().config as Config; + if (!room.participants.length && config.deleteEmptyRooms) { + if (config.auditMode) { + await ChatRoom.getInstance().findByIdAndUpdate(room._id, { + deleted: true, + }); + await ChatMessage.getInstance().updateMany({ room: room._id }, { deleted: true }); + } else { + await ChatRoom.getInstance().deleteOne({ _id: roomId }); + await ChatMessage.getInstance().deleteMany({ room: room._id }); + } + } + return 'OK'; + } + + async addUserToRoom(call: ParsedRouterRequest): Promise { + const { roomId } = call.request.urlParams as { roomId: string }; + const { users } = call.request.bodyParams as { users: string[] }; + + const room = await ChatRoom.getInstance().findOne({ _id: roomId }, undefined, [ + 'creator', + ]); + if (isNil(room)) throw new GrpcError(status.NOT_FOUND, "Room doesn't exist"); + if (room.deleted) throw new GrpcError(status.NOT_FOUND, "Room doesn't exist"); + + const unique = Array.from(new Set(users)); + const found = await User.getInstance().findMany({ + _id: { $in: unique }, + }); + if (found.length !== unique.length) { + throw new GrpcError(status.INVALID_ARGUMENT, "User doesn't exist"); + } + const toBeAdded = found.filter( + user => !(room.participants as string[]).includes(user._id), + ); + + if (!toBeAdded.length) + throw new GrpcError(status.INVALID_ARGUMENT, 'Users are already room members!'); + + const config = ConfigController.getInstance().config as Config; + if (config.explicit_room_joins.enabled) { + const serverConfig = await this.grpcSdk.config.get('router'); + await sendInvitations({ + users: toBeAdded, + sender: room.creator as User, + room, + url: serverConfig.hostUrl, + sendEmail: config.explicit_room_joins.send_email, + sendNotification: config.explicit_room_joins.send_notification, + grpcSdk: this.grpcSdk, + }); + } else { + const participantsLog = await ChatParticipantsLog.getInstance().createMany( + toBeAdded.map(user => ({ + user: user._id, + action: 'added', + chatRoom: room._id, + })), + ); + await ChatRoom.getInstance().findByIdAndUpdate(room._id, { + participants: Array.from( + new Set([ + ...(room.participants as string[]), + ...toBeAdded.map(user => user._id), + ]), + ), + participantsLog: [ + ...room.participantsLog, + ...participantsLog.map(log => log._id), + ], + }); + } + + return 'OK'; + } + private registerAdminRoutes() { this.routingManager.clear(); this.routingManager.route( @@ -181,8 +388,11 @@ export class AdminHandlers { queryParams: { skip: ConduitNumber.Optional, limit: ConduitNumber.Optional, - sort: ConduitString.Optional, + sort: [ConduitString.Optional], search: ConduitString.Optional, + users: [ConduitObjectId.Optional], + deleted: ConduitBoolean.Optional, + populate: [ConduitString.Optional], }, }, new ConduitRouteReturnDefinition('GetRooms', { @@ -198,7 +408,8 @@ export class AdminHandlers { description: `Creates a new chat room.`, bodyParams: { name: ConduitString.Required, - participants: { type: [TYPE.String], required: true }, // handler array check is still required + participants: [ConduitObjectId.Required], // handler array check is still required + creator: ConduitObjectId.Required, }, }, new ConduitRouteReturnDefinition(ChatRoom.name), @@ -225,9 +436,10 @@ export class AdminHandlers { skip: ConduitNumber.Optional, limit: ConduitNumber.Optional, sort: ConduitString.Optional, - senderUser: ConduitString.Optional, - roomId: ConduitString.Optional, + senderUser: ConduitObjectId.Optional, + roomId: ConduitObjectId.Optional, search: ConduitString.Optional, + populate: [ConduitString.Optional], }, }, new ConduitRouteReturnDefinition('GetMessages', { @@ -248,6 +460,72 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('DeleteMessages', 'String'), this.deleteMessages.bind(this), ); + this.routingManager.route( + { + path: '/rooms/:roomId', + action: ConduitRouteActions.GET, + description: `Returns room by id.`, + urlParams: { + roomId: ConduitObjectId.Required, + }, + queryParams: { + populate: [ConduitString.Optional], + }, + }, + new ConduitRouteReturnDefinition('GetRoomById', ChatRoom.name), + this.getRoomById.bind(this), + ); + this.routingManager.route( + { + path: '/invitations/:roomId', + action: ConduitRouteActions.GET, + description: `Returns room invitations.`, + urlParams: { + roomId: ConduitObjectId.Required, + }, + queryParams: { + skip: ConduitNumber.Optional, + limit: ConduitNumber.Optional, + sort: ConduitString.Optional, + populate: [ConduitString.Optional], + }, + }, + new ConduitRouteReturnDefinition('GetInvitationsResponse', { + invitations: [InvitationToken.name], + count: ConduitNumber.Required, + }), + this.getRoomInvitations.bind(this), + ); + this.routingManager.route( + { + path: '/rooms/:roomId/add', + action: ConduitRouteActions.UPDATE, + description: 'Adds users to room', + urlParams: { + roomId: ConduitObjectId.Required, + }, + bodyParams: { + users: [ConduitObjectId.Required], + }, + }, + new ConduitRouteReturnDefinition('RoomAddResponse', 'String'), + this.addUserToRoom.bind(this), + ); + this.routingManager.route( + { + path: '/room/:roomId/remove', + action: ConduitRouteActions.UPDATE, + description: 'Removes users from room', + urlParams: { + roomId: ConduitObjectId.Required, + }, + bodyParams: { + users: [ConduitObjectId.Required], + }, + }, + new ConduitRouteReturnDefinition('RoomLeaveResponse', 'String'), + this.removeFromRoom.bind(this), + ); this.routingManager.registerRoutes(); } diff --git a/modules/chat/src/models/ChatParticipantsLog.schema.ts b/modules/chat/src/models/ChatParticipantsLog.schema.ts index 31469dd5c..6f1de26e5 100644 --- a/modules/chat/src/models/ChatParticipantsLog.schema.ts +++ b/modules/chat/src/models/ChatParticipantsLog.schema.ts @@ -7,7 +7,7 @@ const schema: ConduitModel = { _id: TYPE.ObjectId, action: { type: TYPE.String, - enum: ['add', 'remove', 'create', 'join', 'leave'], + enum: ['added', 'remove', 'create', 'join', 'leave'], required: true, }, user: { @@ -39,7 +39,7 @@ const collectionName = undefined; export class ChatParticipantsLog extends ConduitActiveSchema { private static _instance: ChatParticipantsLog; _id: string; - action: 'add' | 'remove' | 'create' | 'join' | 'leave'; + action: 'added' | 'remove' | 'create' | 'join' | 'leave'; user: string | User; chatRoom: string | ChatRoom; createdAt: Date; diff --git a/modules/chat/src/routes/InvitationRoutes.ts b/modules/chat/src/routes/InvitationRoutes.ts index 5c4feca30..3a055a2b2 100644 --- a/modules/chat/src/routes/InvitationRoutes.ts +++ b/modules/chat/src/routes/InvitationRoutes.ts @@ -11,7 +11,7 @@ import { ConduitString, RoutingManager, } from '@conduitplatform/module-tools'; -import { ChatRoom, InvitationToken } from '../models/index.js'; +import { ChatParticipantsLog, ChatRoom, InvitationToken } from '../models/index.js'; import { isNil } from 'lodash-es'; import { status } from '@grpc/grpc-js'; @@ -122,6 +122,11 @@ export class InvitationRoutes { if (!isNil(invitationTokenDoc) && accepted) { chatRoom.participants.push(user); await ChatRoom.getInstance().findByIdAndUpdate(chatRoom._id, chatRoom); + await ChatParticipantsLog.getInstance().create({ + user: user._id, + action: 'join', + chatRoom: invitationTokenDoc.room as string, + }); message = 'Invitation accepted'; } else { message = 'Invitation declined'; @@ -166,6 +171,11 @@ export class InvitationRoutes { if (!isNil(invitationTokenDoc) && accepted) { (chatRoom.participants as string[]).push(receiver as string); await ChatRoom.getInstance().findByIdAndUpdate(roomId, chatRoom); + await ChatParticipantsLog.getInstance().create({ + user: user._id, + action: 'join', + chatRoom: roomId, + }); message = 'Invitation accepted'; } else { message = 'Invitation declined'; @@ -220,8 +230,9 @@ export class InvitationRoutes { .catch((e: Error) => { throw new GrpcError(status.INTERNAL, e.message); }); - - const count = invitations.length; + const count = await InvitationToken.getInstance().countDocuments({ + receiver: user._id, + }); return { invitations, count }; } diff --git a/modules/chat/src/routes/index.ts b/modules/chat/src/routes/index.ts index 69dda8f3b..14f255509 100644 --- a/modules/chat/src/routes/index.ts +++ b/modules/chat/src/routes/index.ts @@ -12,6 +12,7 @@ import { } from '@conduitplatform/grpc-sdk'; import { ConduitNumber, + ConduitObjectId, ConduitString, ConfigController, GrpcServer, @@ -66,7 +67,7 @@ export class ChatRoutes { ); } try { - usersToBeAdded = await validateUsersInput(this.grpcSdk, users); + usersToBeAdded = await validateUsersInput(users); } catch (e) { throw new GrpcError(status.INTERNAL, (e as Error).message); } @@ -92,15 +93,15 @@ export class ChatRoutes { participantsLog: [participantsLog._id], })) as ChatRoom; const serverConfig = await this.grpcSdk.config.get('router'); - await sendInvitations( - usersToBeAdded, - user, + await sendInvitations({ + users: usersToBeAdded, + sender: user, room, - serverConfig.hostUrl, - this.sendEmail, - this.sendPushNotification, - this.grpcSdk, - ).catch((e: Error) => { + url: serverConfig.hostUrl, + sendEmail: this.sendEmail, + sendNotification: this.sendPushNotification, + grpcSdk: this.grpcSdk, + }).catch((e: Error) => { throw new GrpcError(status.INTERNAL, e.message); }); } else { @@ -118,7 +119,7 @@ export class ChatRoutes { }, ...users.map((userId: string) => ({ user: userId, - action: 'join' as 'join', + action: 'join', chatRoom: room._id, })), ]); @@ -153,7 +154,7 @@ export class ChatRoutes { throw new GrpcError(status.NOT_FOUND, "Room doesn't exist"); } try { - usersToBeAdded = await validateUsersInput(this.grpcSdk, users); + usersToBeAdded = await validateUsersInput(users); } catch (e) { throw new GrpcError(status.INTERNAL, (e as Error).message); } @@ -169,15 +170,15 @@ export class ChatRoutes { const config = await this.grpcSdk.config.get('chat'); if (config.explicit_room_joins.enabled) { const serverConfig = await this.grpcSdk.config.get('router'); - const ret = await sendInvitations( - usersToBeAdded, - user, + const ret = await sendInvitations({ + users: usersToBeAdded, + sender: user, room, - serverConfig.hostUrl, - this.sendEmail, - this.sendPushNotification, - this.grpcSdk, - ).catch((e: Error) => { + url: serverConfig.hostUrl, + sendEmail: this.sendEmail, + sendNotification: this.sendPushNotification, + grpcSdk: this.grpcSdk, + }).catch((e: Error) => { throw new GrpcError(status.INTERNAL, e.message); }); return ret!; @@ -185,7 +186,7 @@ export class ChatRoutes { const participantsLog = await ChatParticipantsLog.getInstance().createMany( users.map((userId: string) => ({ user: userId, - action: 'join' as 'join', + action: 'added', chatRoom: room._id, })), ); @@ -223,7 +224,7 @@ export class ChatRoutes { room.participants.splice(index, 1); const participantsLog = await ChatParticipantsLog.getInstance().create({ user: user._id, - action: 'leave' as 'leave', + action: 'leave', chatRoom: room._id, }); @@ -263,6 +264,57 @@ export class ChatRoutes { return 'Ok'; } + async removeFromRoom(call: ParsedRouterRequest): Promise { + const { roomId } = call.request.urlParams as { roomId: string }; + const { user } = call.request.context as { user: User }; + const { users } = call.request.bodyParams as { users: string[] }; + + if (users.length === 0) { + throw new GrpcError( + status.INVALID_ARGUMENT, + 'Users is required and must be a non-empty array', + ); + } + + const room = await ChatRoom.getInstance().findOne({ _id: roomId }); + if (isNil(room)) throw new GrpcError(status.NOT_FOUND, "Room doesn't exist"); + + if (room.creator !== user._id) + throw new GrpcError( + status.PERMISSION_DENIED, + "User doesn't have permissions to remove users from room!", + ); + + const index = (room.participants as string[]).indexOf(user._id); + if (index > -1) + throw new GrpcError(status.INVALID_ARGUMENT, "Users can't remove self from room!"); + + for (const user of users) { + if ((room.participants as string[]).indexOf(user) === -1) + throw new GrpcError(status.NOT_FOUND, 'User is not participant of room!'); + } + + room.participants = (room.participants as string[]).flatMap(participant => { + if (users.indexOf(participant) > -1) return []; + return participant; + }); + users.map(async user => { + const log = await ChatParticipantsLog.getInstance().create({ + user: user, + action: 'remove', + chatRoom: room._id, + }); + room.participantsLog.push(log); + }); + await ChatRoom.getInstance() + .findByIdAndUpdate(room._id, room) + .catch((e: Error) => { + throw new GrpcError(status.INTERNAL, e.message); + }); + + return 'OK'; + } + async getMessages(call: ParsedRouterRequest): Promise { const { roomId, skip, limit } = call.request.params; const { user } = call.request.context; @@ -507,7 +559,7 @@ export class ChatRoutes { description: `Creates a new room.`, bodyParams: { roomName: ConduitString.Required, - users: [TYPE.String], + users: [ConduitObjectId.Required], }, middlewares: ['authMiddleware'], }, @@ -523,7 +575,7 @@ export class ChatRoutes { action: ConduitRouteActions.UPDATE, description: `Adds users to a chat room.`, urlParams: { - roomId: ConduitString.Required, + roomId: ConduitObjectId.Required, }, bodyParams: { users: [TYPE.String], @@ -538,9 +590,9 @@ export class ChatRoutes { { path: '/leave/:roomId', action: ConduitRouteActions.UPDATE, - description: `Removes current user from a chat room.`, + description: `Context user leaves chat room.`, urlParams: { - roomId: ConduitString.Required, + roomId: ConduitObjectId.Required, }, middlewares: ['authMiddleware'], }, @@ -548,13 +600,30 @@ export class ChatRoutes { this.leaveRoom.bind(this), ); + this._routingManager.route( + { + path: '/room/:roomId/remove', + action: ConduitRouteActions.UPDATE, + description: `Room Creator removes users from chat room.`, + urlParams: { + roomId: ConduitObjectId.Required, + }, + bodyParams: { + users: [ConduitObjectId.Required], + }, + middlewares: ['authMiddleware'], + }, + new ConduitRouteReturnDefinition('RemoveFromRoomResponse', 'String'), + this.removeFromRoom.bind(this), + ); + this._routingManager.route( { path: '/rooms/:id', action: ConduitRouteActions.GET, description: `Returns a chat room.`, urlParams: { - id: ConduitString.Required, + id: ConduitObjectId.Required, }, middlewares: ['authMiddleware'], }, diff --git a/modules/chat/src/utils/index.ts b/modules/chat/src/utils/index.ts index 25090448a..7951be80e 100644 --- a/modules/chat/src/utils/index.ts +++ b/modules/chat/src/utils/index.ts @@ -4,7 +4,7 @@ import { ConduitGrpcSdk, GrpcError, UntypedArray } from '@conduitplatform/grpc-s import { ChatRoom, InvitationToken, User } from '../models/index.js'; import { v4 as uuid } from 'uuid'; -export async function validateUsersInput(grpcSdk: ConduitGrpcSdk, users: UntypedArray) { +export async function validateUsersInput(users: UntypedArray) { const uniqueUsers = Array.from(new Set(users)); let errorMessage: string | null = null; const usersToBeAdded = (await User.getInstance() @@ -28,15 +28,16 @@ export async function validateUsersInput(grpcSdk: ConduitGrpcSdk, users: Untyped return usersToBeAdded; } -export async function sendInvitations( - users: User[], - sender: User, - room: ChatRoom, - url: string, - sendEmail: boolean, - sendNotification: boolean, - grpcSdk: ConduitGrpcSdk, -) { +export async function sendInvitations(args: { + users: User[]; + sender: User; + room: ChatRoom; + url: string; + sendEmail: boolean; + sendNotification: boolean; + grpcSdk: ConduitGrpcSdk; +}) { + const { room, users, sender, url, sendEmail, sendNotification, grpcSdk } = args; const roomId = room._id; for (const invitedUser of users) { const invitationsCount = await InvitationToken.getInstance().countDocuments({ @@ -56,7 +57,7 @@ export async function sendInvitations( token: uuid(), room: roomId, }); - if (sendEmail) { + if (sendEmail && grpcSdk.isAvailable('email')) { const result = { invitationToken, hostUrl: url }; const acceptLink = `${result.hostUrl}/hook/chat/invitations/accept/${result.invitationToken.token}`; const declineLink = `${result.hostUrl}/hook/chat/invitations/decline/${result.invitationToken.token}`; @@ -76,7 +77,7 @@ export async function sendInvitations( throw new Error(e.message); }); } - if (sendNotification) { + if (sendNotification && grpcSdk.isAvailable('pushNotifications')) { const body = `User ${sender._id} has invited you to join in room ${room.name}`; const title = 'You have an invitation request!'; await grpcSdk @@ -88,15 +89,3 @@ export async function sendInvitations( } return 'Invitations sent'; } - -export function populateArray(pop: string | string[] | undefined) { - if (!pop) return pop; - if (typeof pop === 'string' && pop.indexOf(',') !== -1) { - pop = pop.split(','); - } else if (Array.isArray(pop)) { - return pop; - } else { - pop = [pop]; - } - return pop; -} From b114836622ea4ea152dce1fac50c99a5f84770a6 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 12:43:22 +0300 Subject: [PATCH 02/14] feat(module-tools): support multi-service descriptors --- libraries/module-tools/src/ManagedModule.ts | 29 ++++++++++++++----- .../src/interfaces/ConduitService.ts | 10 ++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/libraries/module-tools/src/ManagedModule.ts b/libraries/module-tools/src/ManagedModule.ts index 3536d1a27..fb472df32 100644 --- a/libraries/module-tools/src/ManagedModule.ts +++ b/libraries/module-tools/src/ManagedModule.ts @@ -158,14 +158,27 @@ export abstract class ManagedModule extends ConduitServiceModule { async startGrpcServer() { if (this.service) { - this._serviceName = this.service.protoDescription.substring( - this.service.protoDescription.indexOf('.') + 1, - ); - await this.grpcServer.addService( - this.service.protoPath, - this.service.protoDescription, - this.service.functions, - ); + // singular service + if (this.service.protoDescription.includes('.')) { + this._serviceName = this.service.protoDescription.substring( + this.service.protoDescription.indexOf('.') + 1, + ); + await this.grpcServer.addService( + this.service.protoPath, + this.service.protoDescription, + this.service.functions as { [name: string]: Function }, + ); + } else { + const packageName = this.service.protoDescription; + for (const service of Object.keys(this.service.functions)) { + this._serviceName = packageName + '.' + service; + await this.grpcServer.addService( + this.service.protoPath, + this._serviceName, + this.service.functions[service] as { [name: string]: Function }, + ); + } + } } RoutingManager.ClientController = new RoutingController(); RoutingManager.AdminController = new RoutingController(); diff --git a/libraries/module-tools/src/interfaces/ConduitService.ts b/libraries/module-tools/src/interfaces/ConduitService.ts index e93b2079b..5494e8e93 100644 --- a/libraries/module-tools/src/interfaces/ConduitService.ts +++ b/libraries/module-tools/src/interfaces/ConduitService.ts @@ -1,12 +1,14 @@ import { GrpcRequest, GrpcResponse } from '@conduitplatform/grpc-sdk'; +export type ServiceFunction = ( + call: GrpcRequest, + callback: GrpcResponse, +) => void | Promise; + export interface ConduitService { readonly protoPath: string; readonly protoDescription: string; functions: { - [p: string]: ( - call: GrpcRequest, - callback: GrpcResponse, - ) => void | Promise; + [p: string]: ServiceFunction | { [p: string]: ServiceFunction }; }; } From ef77325144f4877f9f34a61d1d23f6cd446780a5 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 15:43:07 +0300 Subject: [PATCH 03/14] feat: modules merge --- modules/{email => comms}/Dockerfile | 0 modules/{email => comms}/README.mdx | 0 modules/{email => comms}/build.sh | 0 modules/{email => comms}/package.json | 12 +- modules/comms/src/Comms.ts | 99 ++++++++++++ modules/comms/src/admin/index.ts | 34 +++++ modules/comms/src/comms.proto | 143 ++++++++++++++++++ modules/comms/src/config/comms.ts | 7 + modules/comms/src/config/email.ts | 130 ++++++++++++++++ modules/comms/src/config/index.ts | 16 ++ modules/comms/src/config/push.ts | 32 ++++ modules/comms/src/config/sms.ts | 71 +++++++++ modules/comms/src/index.ts | 4 + modules/comms/src/interfaces/CommService.ts | 14 ++ modules/{email => comms}/src/metrics/index.ts | 14 ++ .../src => comms/src/modules/email}/Email.ts | 89 ++++++----- .../src/modules/email}/admin/index.ts | 44 +++--- .../modules/email}/email-provider/README.md | 0 .../modules/email}/email-provider/index.ts | 0 .../interfaces/CreateEmailTemplate.ts | 0 .../interfaces/DeleteEmailTemplate.ts | 0 .../email-provider/interfaces/EmailBuilder.ts | 0 .../email-provider/interfaces/EmailOptions.ts | 0 .../interfaces/EmailSendTransport.ts | 0 .../email-provider/interfaces/Template.ts | 0 .../interfaces/TemplateDocument.ts | 0 .../interfaces/TemplateOptions.ts | 0 .../interfaces/UpdateEmailTemplate.ts | 0 .../email}/email-provider/interfaces/Var.ts | 0 .../interfaces/mailgun/MailgunEmailOptions.ts | 0 .../interfaces/mailgun/MailgunTemplate.ts | 0 .../mandrill/MandrillEmailOptions.ts | 0 .../interfaces/mandrill/MandrillTemplate.ts | 0 .../sendgrid/SendgridEmailOptions.ts | 0 .../interfaces/sendgrid/SendgridTemplate.ts | 0 .../models/EmailBuilderClass.ts | 0 .../models/EmailProviderClass.ts | 0 .../transports/mailgun/MailgunProvider.ts | 0 .../transports/mailgun/mailgun.config.ts | 0 .../transports/mailgun/mailgun.ts | 0 .../transports/mailgun/mailgunMailBuilder.ts | 0 .../transports/mandrill/MandrilProvider.ts | 0 .../transports/mandrill/mandrill.config.ts | 0 .../transports/mandrill/mandrillBuilder.ts | 0 .../nodemailer/nodemailerBuilder.ts | 0 .../transports/sendgrid/SendgridProvider.ts | 0 .../transports/sendgrid/sendgrid.config.ts | 0 .../sendgrid/sendgridMailBuilder.ts | 0 .../transports/smtp/SmtpProvider.ts | 0 .../utils/getHandleBarsValues.ts | 0 .../email}/email-provider/utils/htmlCheck.ts | 0 .../email}/email-provider/utils/index.ts | 0 .../interfaces/RegisterTemplateParams.ts | 0 .../email}/interfaces/SendEmailParams.ts | 0 .../src/modules/email}/interfaces/index.ts | 0 .../email}/jobs/cleanupStoredEmails.ts | 0 .../src/modules/email}/jobs/index.ts | 0 .../src/modules/email}/migrations/index.ts | 0 .../src/modules/email}/models/EmailRecord.ts | 0 .../email}/models/EmailTemplate.schema.ts | 0 .../src/modules/email}/models/index.ts | 0 .../modules/email}/services/email.service.ts | 4 +- .../src/modules/email}/utils/index.ts | 0 .../src/modules/email}/utils/storeEmail.ts | 8 +- .../src/modules/push}/PushNotifications.ts | 101 ++++++------- .../src/modules/push}/admin/index.ts | 24 +-- .../push}/handlers/notification-tokens.ts | 0 .../push}/interfaces/IFirebaseSettings.ts | 0 .../push}/interfaces/IOneSignalSettings.ts | 0 .../push}/interfaces/ISendNotification.ts | 0 .../src/modules/push}/migrations/index.ts | 0 .../push}/models/Notification.schema.ts | 0 .../push}/models/NotificationToken.schema.ts | 0 .../src/modules/push}/models/User.model.ts | 0 .../src/modules/push}/models/index.ts | 0 .../push}/providers/Firebase.provider.ts | 0 .../push}/providers/OneSignal.provider.ts | 0 .../modules/push}/providers/base.provider.ts | 0 .../modules/push}/providers/utils/index.ts | 0 .../src/modules/push}/routes/index.ts | 21 +-- .../{sms/src => comms/src/modules/sms}/Sms.ts | 73 +++++---- .../src/modules/sms}/admin/index.ts | 12 +- .../modules/sms}/interfaces/ISmsProvider.ts | 0 .../src/modules/sms}/providers/awsSns.ts | 0 .../src/modules/sms}/providers/clickSend.ts | 0 .../src/modules/sms}/providers/messageBird.ts | 0 .../src/modules/sms}/providers/twilio.ts | 0 .../src/modules/sms}/utils/index.ts | 0 modules/comms/src/router/index.ts | 33 ++++ modules/{email => comms}/tsconfig.json | 0 modules/email/src/config/config.ts | 128 ---------------- modules/email/src/config/index.ts | 7 - modules/email/src/email.proto | 54 ------- modules/email/src/index.ts | 4 - modules/push-notifications/.gitignore | 2 - modules/push-notifications/Dockerfile | 19 --- modules/push-notifications/README.mdx | 22 --- modules/push-notifications/build.sh | 20 --- modules/push-notifications/package.json | 52 ------- .../push-notifications/src/config/config.ts | 34 ----- .../push-notifications/src/config/index.ts | 7 - modules/push-notifications/src/index.ts | 4 - .../push-notifications/src/metrics/index.ts | 11 -- .../src/push-notifications.proto | 56 ------- modules/push-notifications/tsconfig.json | 69 --------- modules/sms/Dockerfile | 19 --- modules/sms/README.mdx | 22 --- modules/sms/build.sh | 20 --- modules/sms/package.json | 52 ------- modules/sms/src/config/config.ts | 69 --------- modules/sms/src/config/index.ts | 7 - modules/sms/src/index.ts | 4 - modules/sms/src/metrics/index.ts | 11 -- modules/sms/src/sms.proto | 34 ----- modules/sms/tsconfig.json | 69 --------- 115 files changed, 788 insertions(+), 993 deletions(-) rename modules/{email => comms}/Dockerfile (100%) rename modules/{email => comms}/README.mdx (100%) rename modules/{email => comms}/build.sh (100%) rename modules/{email => comms}/package.json (83%) create mode 100644 modules/comms/src/Comms.ts create mode 100644 modules/comms/src/admin/index.ts create mode 100644 modules/comms/src/comms.proto create mode 100644 modules/comms/src/config/comms.ts create mode 100644 modules/comms/src/config/email.ts create mode 100644 modules/comms/src/config/index.ts create mode 100644 modules/comms/src/config/push.ts create mode 100644 modules/comms/src/config/sms.ts create mode 100644 modules/comms/src/index.ts create mode 100644 modules/comms/src/interfaces/CommService.ts rename modules/{email => comms}/src/metrics/index.ts (54%) rename modules/{email/src => comms/src/modules/email}/Email.ts (78%) rename modules/{email/src => comms/src/modules/email}/admin/index.ts (95%) rename modules/{email/src => comms/src/modules/email}/email-provider/README.md (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/CreateEmailTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/DeleteEmailTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/EmailBuilder.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/EmailOptions.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/EmailSendTransport.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/Template.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/TemplateDocument.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/TemplateOptions.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/UpdateEmailTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/Var.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/mailgun/MailgunEmailOptions.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/mailgun/MailgunTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/mandrill/MandrillEmailOptions.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/mandrill/MandrillTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/sendgrid/SendgridEmailOptions.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/interfaces/sendgrid/SendgridTemplate.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/models/EmailBuilderClass.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/models/EmailProviderClass.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mailgun/MailgunProvider.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mailgun/mailgun.config.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mailgun/mailgun.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mailgun/mailgunMailBuilder.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mandrill/MandrilProvider.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mandrill/mandrill.config.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/mandrill/mandrillBuilder.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/nodemailer/nodemailerBuilder.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/sendgrid/SendgridProvider.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/sendgrid/sendgrid.config.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/sendgrid/sendgridMailBuilder.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/transports/smtp/SmtpProvider.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/utils/getHandleBarsValues.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/utils/htmlCheck.ts (100%) rename modules/{email/src => comms/src/modules/email}/email-provider/utils/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/interfaces/RegisterTemplateParams.ts (100%) rename modules/{email/src => comms/src/modules/email}/interfaces/SendEmailParams.ts (100%) rename modules/{email/src => comms/src/modules/email}/interfaces/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/jobs/cleanupStoredEmails.ts (100%) rename modules/{email/src => comms/src/modules/email}/jobs/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/migrations/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/models/EmailRecord.ts (100%) rename modules/{email/src => comms/src/modules/email}/models/EmailTemplate.schema.ts (100%) rename modules/{email/src => comms/src/modules/email}/models/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/services/email.service.ts (98%) rename modules/{email/src => comms/src/modules/email}/utils/index.ts (100%) rename modules/{email/src => comms/src/modules/email}/utils/storeEmail.ts (85%) rename modules/{push-notifications/src => comms/src/modules/push}/PushNotifications.ts (77%) rename modules/{push-notifications/src => comms/src/modules/push}/admin/index.ts (94%) rename modules/{push-notifications/src => comms/src/modules/push}/handlers/notification-tokens.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/interfaces/IFirebaseSettings.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/interfaces/IOneSignalSettings.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/interfaces/ISendNotification.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/migrations/index.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/models/Notification.schema.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/models/NotificationToken.schema.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/models/User.model.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/models/index.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/providers/Firebase.provider.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/providers/OneSignal.provider.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/providers/base.provider.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/providers/utils/index.ts (100%) rename modules/{push-notifications/src => comms/src/modules/push}/routes/index.ts (85%) rename modules/{sms/src => comms/src/modules/sms}/Sms.ts (74%) rename modules/{sms/src => comms/src/modules/sms}/admin/index.ts (77%) rename modules/{sms/src => comms/src/modules/sms}/interfaces/ISmsProvider.ts (100%) rename modules/{sms/src => comms/src/modules/sms}/providers/awsSns.ts (100%) rename modules/{sms/src => comms/src/modules/sms}/providers/clickSend.ts (100%) rename modules/{sms/src => comms/src/modules/sms}/providers/messageBird.ts (100%) rename modules/{sms/src => comms/src/modules/sms}/providers/twilio.ts (100%) rename modules/{sms/src => comms/src/modules/sms}/utils/index.ts (100%) create mode 100644 modules/comms/src/router/index.ts rename modules/{email => comms}/tsconfig.json (100%) delete mode 100644 modules/email/src/config/config.ts delete mode 100644 modules/email/src/config/index.ts delete mode 100644 modules/email/src/email.proto delete mode 100644 modules/email/src/index.ts delete mode 100644 modules/push-notifications/.gitignore delete mode 100644 modules/push-notifications/Dockerfile delete mode 100644 modules/push-notifications/README.mdx delete mode 100644 modules/push-notifications/build.sh delete mode 100644 modules/push-notifications/package.json delete mode 100644 modules/push-notifications/src/config/config.ts delete mode 100644 modules/push-notifications/src/config/index.ts delete mode 100644 modules/push-notifications/src/index.ts delete mode 100644 modules/push-notifications/src/metrics/index.ts delete mode 100644 modules/push-notifications/src/push-notifications.proto delete mode 100644 modules/push-notifications/tsconfig.json delete mode 100644 modules/sms/Dockerfile delete mode 100644 modules/sms/README.mdx delete mode 100644 modules/sms/build.sh delete mode 100644 modules/sms/package.json delete mode 100644 modules/sms/src/config/config.ts delete mode 100644 modules/sms/src/config/index.ts delete mode 100644 modules/sms/src/index.ts delete mode 100644 modules/sms/src/metrics/index.ts delete mode 100644 modules/sms/src/sms.proto delete mode 100644 modules/sms/tsconfig.json diff --git a/modules/email/Dockerfile b/modules/comms/Dockerfile similarity index 100% rename from modules/email/Dockerfile rename to modules/comms/Dockerfile diff --git a/modules/email/README.mdx b/modules/comms/README.mdx similarity index 100% rename from modules/email/README.mdx rename to modules/comms/README.mdx diff --git a/modules/email/build.sh b/modules/comms/build.sh similarity index 100% rename from modules/email/build.sh rename to modules/comms/build.sh diff --git a/modules/email/package.json b/modules/comms/package.json similarity index 83% rename from modules/email/package.json rename to modules/comms/package.json index 6f1d1b777..d8744c4a4 100644 --- a/modules/email/package.json +++ b/modules/comms/package.json @@ -46,9 +46,19 @@ "nodemailer": "^6.9.10", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mandrill-transport": "^1.2.0", - "nodemailer-sendgrid": "^1.0.3" + "nodemailer-sendgrid": "^1.0.3", + "@aws-sdk/client-sns": "^3.627.1", + "bluebird": "^3.7.2", + "clicksend": "^5.0.79", + "messagebird": "^4.0.1", + "otp-generator": "^4.0.1", + "twilio": "5.3.0", + "@onesignal/node-onesignal": "^1.0.0-beta9", + "firebase-admin": "^12.4.0" }, "devDependencies": { + "@types/bluebird": "^3.5.42", + "@types/otp-generator": "^4.0.2", "@types/convict": "^6.1.6", "@types/lodash-es": "^4.17.12", "@types/mailgun-js": "^0.22.18", diff --git a/modules/comms/src/Comms.ts b/modules/comms/src/Comms.ts new file mode 100644 index 000000000..238e722a6 --- /dev/null +++ b/modules/comms/src/Comms.ts @@ -0,0 +1,99 @@ +import { ConduitGrpcSdk, HealthCheckStatus } from '@conduitplatform/grpc-sdk'; +import AppConfigSchema, { Config } from './config/index.js'; +import { AdminHandlers } from './admin/index.js'; +import path from 'path'; +import metricsSchema from './metrics/index.js'; +import { ManagedModule, ServiceFunction } from '@conduitplatform/module-tools'; +import { fileURLToPath } from 'node:url'; +import Sms from './modules/sms/Sms.js'; +import { CommService } from './interfaces/CommService.js'; +import { ClientRouteHandlers } from './router/index.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default class Comms extends ManagedModule { + configSchema = AppConfigSchema; + protected metricsSchema = metricsSchema; + private readonly smsService: CommService; + private readonly emailService: CommService; + private readonly pushService: CommService; + + constructor() { + super('sms'); + this.updateHealth(HealthCheckStatus.UNKNOWN, true); + AdminHandlers.getInstance(this.grpcServer, this.grpcSdk); + ClientRouteHandlers.getInstance(this.grpcServer, this.grpcSdk); + this.smsService = Sms.getInstance(this.grpcSdk); + // this.emailService = Email.getInstance(this.grpcSdk); + // this.pushService = Sms.getInstance(this.grpcSdk); + this.service = { + protoPath: path.resolve(__dirname, 'comms.proto'), + protoDescription: 'comms', + functions: { + Comms: {}, + Sms: { + ...(this.smsService.rpcFunctions as { [key: string]: ServiceFunction }), + }, + Email: { + ...(this.emailService.rpcFunctions as { [key: string]: ServiceFunction }), + }, + PushNotifications: { + ...(this.pushService.rpcFunctions as { [key: string]: ServiceFunction }), + }, + }, + }; + } + + async preConfig(config: any) { + let modifiedConfig = config; + modifiedConfig = + (await this.smsService.preConfig?.(modifiedConfig)) ?? modifiedConfig; + modifiedConfig = + (await this.emailService.preConfig?.(modifiedConfig)) ?? modifiedConfig; + modifiedConfig = + (await this.pushService.preConfig?.(modifiedConfig)) ?? modifiedConfig; + return modifiedConfig; + } + + async onServerStart() { + await this.smsService.onServerStart?.(); + await this.emailService.onServerStart?.(); + await this.pushService.onServerStart?.(); + } + + async onConfig() { + await this.smsService.onConfig?.(); + await this.emailService.onConfig?.(); + await this.pushService.onConfig?.(); + let oneHealthy = false; + if (this.smsService.health === HealthCheckStatus.SERVING) { + oneHealthy = true; + } + if (this.emailService.health === HealthCheckStatus.SERVING) { + oneHealthy = true; + } + if (this.pushService.health === HealthCheckStatus.SERVING) { + oneHealthy = true; + } + if (oneHealthy) { + this.updateHealth(HealthCheckStatus.SERVING); + } else { + this.updateHealth(HealthCheckStatus.NOT_SERVING); + } + await AdminHandlers.getInstance().registerAdminRoutes(); + + this.grpcSdk + .waitForExistence('router') + .then(() => ClientRouteHandlers.getInstance().registerRoutes()) + .catch(e => { + ConduitGrpcSdk.Logger.error(e.message); + }); + } + + async initializeMetrics() { + await this.smsService.initializeMetrics?.(); + await this.emailService.initializeMetrics?.(); + await this.pushService.initializeMetrics?.(); + } +} diff --git a/modules/comms/src/admin/index.ts b/modules/comms/src/admin/index.ts new file mode 100644 index 000000000..c3915c1e0 --- /dev/null +++ b/modules/comms/src/admin/index.ts @@ -0,0 +1,34 @@ +import { ConduitGrpcSdk } from '@conduitplatform/grpc-sdk'; +import { GrpcServer, RoutingManager } from '@conduitplatform/module-tools'; +import Sms from '../modules/sms/Sms.js'; +import Email from '../modules/email/Email.js'; +import PushNotifications from '../modules/push/PushNotifications.js'; + +export class AdminHandlers { + private readonly routingManager: RoutingManager; + private static instance: AdminHandlers | null = null; + + private constructor( + readonly server: GrpcServer, + readonly grpcSdk: ConduitGrpcSdk, + ) { + this.routingManager = new RoutingManager(grpcSdk.admin, server); + } + + public static getInstance(server?: GrpcServer, grpcSdk?: ConduitGrpcSdk) { + if (!AdminHandlers.instance) { + if (!server || !grpcSdk) throw new Error('Server and grpcSdk must be provided'); + AdminHandlers.instance = new AdminHandlers(server!, grpcSdk!); + } + return AdminHandlers.instance; + } + + async registerAdminRoutes() { + this.routingManager.clear(); + await Sms.getInstance().registerAdminRoutes(this.routingManager); + // Email.getInstance().registerAdminRoutes(this.routingManager); + // PushNotifications.getInstance().registerAdminRoutes(this.routingManager); + + await this.routingManager.registerRoutes(); + } +} diff --git a/modules/comms/src/comms.proto b/modules/comms/src/comms.proto new file mode 100644 index 000000000..2e431f14f --- /dev/null +++ b/modules/comms/src/comms.proto @@ -0,0 +1,143 @@ +syntax = 'proto3'; +package comms; +//------ SMS ------ +message SendSmsRequest { + string to = 1; + string message = 2; +} + +message SendSmsResponse { + string message = 1; +} + +message SendVerificationCodeRequest { + string to = 1; +} + +message SendVerificationCodeResponse { + string verificationSid = 1; +} + +message VerifyRequest { + string verificationSid = 1; + string code = 2; +} + +message VerifyResponse { + bool verified = 1; +} + +service Sms { + rpc SendSms(SendSmsRequest) returns (SendSmsResponse); + rpc SendVerificationCode(SendVerificationCodeRequest) returns (SendVerificationCodeResponse); + rpc Verify(VerifyRequest) returns (VerifyResponse); + +} +//------ EMAIL ------ +message RegisterTemplateRequest { + string name = 1; + string subject = 2; + string body = 3; + repeated string variables = 4; + optional string sender = 5; +} + +message RegisterTemplateResponse { + string template = 1; +} + +message SendEmailRequest { + string templateName = 1; + SendEmailParams params = 2; + message SendEmailParams { + string email = 1; + string variables = 2; + optional string sender = 3; + repeated string cc = 4; + optional string replyTo = 5; + repeated string attachments = 6; + } +} + +message SendEmailResponse { + string sentMessageInfo = 1; +} + +message ResendEmailRequest { + string emailRecordId = 1; +} + +message ResendEmailResponse { + string sentMessageInfo = 1; +} + +message GetEmailStatusRequest { + string messageId = 1; +} + +message GetEmailStatusResponse { + string statusInfo = 1; +} + +service Email { + rpc RegisterTemplate(RegisterTemplateRequest) returns (RegisterTemplateResponse); + rpc SendEmail(SendEmailRequest) returns (SendEmailResponse); + rpc ResendEmail(ResendEmailRequest) returns (ResendEmailResponse); + rpc GetEmailStatus(GetEmailStatusRequest) returns (GetEmailStatusResponse); +} + +//------ PushNotifications ------ + +message SetNotificationTokenRequest { + string token = 1; + string platform = 2; + string userId = 3; +} + +message SetNotificationTokenResponse { + string newTokenDocument = 1; +} + +message GetNotificationTokensRequest { + string userId = 1; +} + +message GetNotificationTokensResponse { + repeated string tokenDocuments = 1; +} + +message SendNotificationRequest { + string sendTo = 1; + optional string title = 2; + optional string platform = 3; + optional string body = 4; + optional string data = 5; + optional bool doNotStore = 6; + optional bool isSilent = 7; +} + +message SendNotificationToManyDevicesRequest { + repeated string sendTo = 1; + optional string title = 2; + optional string body = 3; + optional string data = 4; + optional string platform = 5; + optional bool doNotStore = 6; + optional bool isSilent = 7; +} + +message SendManyNotificationsRequest { + repeated SendNotificationRequest notifications = 1; +} + +message SendNotificationResponse { + string message = 1; +} + +service PushNotifications { + rpc SetNotificationToken(SetNotificationTokenRequest) returns (SetNotificationTokenResponse); + rpc GetNotificationTokens(GetNotificationTokensRequest) returns (GetNotificationTokensResponse); + rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse); + rpc SendNotificationToManyDevices(SendNotificationToManyDevicesRequest) returns (SendNotificationResponse); + rpc SendManyNotifications(SendManyNotificationsRequest) returns (SendNotificationResponse); +} diff --git a/modules/comms/src/config/comms.ts b/modules/comms/src/config/comms.ts new file mode 100644 index 000000000..41df2be98 --- /dev/null +++ b/modules/comms/src/config/comms.ts @@ -0,0 +1,7 @@ +export default { + doc: 'The options for the conduit comms service', + migrationComplete: { + format: 'Boolean', + default: false, + }, +}; diff --git a/modules/comms/src/config/email.ts b/modules/comms/src/config/email.ts new file mode 100644 index 000000000..55da034b6 --- /dev/null +++ b/modules/comms/src/config/email.ts @@ -0,0 +1,130 @@ +export default { + email: { + doc: 'The options for the conduit email provider', + active: { + format: 'Boolean', + default: false, + }, + transport: { + format: 'String', + default: 'smtp', + }, + sendingDomain: { + format: 'String', + default: 'conduit.com', + }, + transportSettings: { + mailgun: { + apiKey: { + doc: 'The email service API key', + format: 'String', + default: '', + }, + host: { + doc: 'The host for email service', + format: 'String', + default: '', + }, + proxy: { + doc: 'The email proxy', + format: 'String', + default: '', + }, + }, + smtp: { + port: { + doc: 'The port the SMTP server is listening on', + format: 'Number', + default: -1, + }, + secure: { + format: 'Boolean', + default: false, + }, + ignoreTls: { + format: 'Boolean', + default: false, + }, + host: { + doc: 'The SMTP server address', + format: 'String', + default: '', + }, + auth: { + username: { + format: 'String', + default: '', + }, + password: { + format: 'String', + default: '', + }, + method: { + format: 'String', + default: 'PLAIN', + }, + }, + }, + mandrill: { + apiKey: { + doc: 'The Mandrill API key', + format: 'String', + default: '', + }, + }, + sendgrid: { + residency: { + doc: 'Sets the host for sendgrid provider. SendGrid Default: global', + format: 'String', + default: '', + }, + apiKey: { + doc: 'The SendGrid API key', + format: 'String', + default: '', + }, + }, + }, + storeEmails: { + enabled: { + doc: 'Defines if sent email info should be stored in database', + format: 'Boolean', + default: false, + }, + storage: { + enabled: { + doc: 'Defines if email content should be stored in storage', + format: 'Boolean', + default: false, + }, + container: { + doc: 'The storage container for emails', + format: 'String', + default: 'conduit', + }, + folder: { + doc: 'The storage folder for emails', + format: 'String', + default: 'cnd-stored-emails', + }, + }, + cleanupSettings: { + enabled: { + doc: 'Settings for deleting old stored emails', + format: 'Boolean', + default: false, + }, + repeat: { + doc: 'Time in milliseconds to repeat the cleanup job', + format: 'Number', + default: 6 * 60 * 60 * 1000, + }, + limit: { + doc: 'Amount of stored emails to be deleted upon cleanup', + format: 'Number', + default: 100, + }, + }, + }, + }, +}; diff --git a/modules/comms/src/config/index.ts b/modules/comms/src/config/index.ts new file mode 100644 index 000000000..c44d2269b --- /dev/null +++ b/modules/comms/src/config/index.ts @@ -0,0 +1,16 @@ +import convict from 'convict'; +import emailConfig from './email.js'; +import pushConfig from './push.js'; +import smsConfig from './sms.js'; +import generalConfig from './comms.js'; + +const AppConfigSchema = { + ...generalConfig, + ...emailConfig, + ...pushConfig, + ...smsConfig, +}; +const config = convict(AppConfigSchema); +const configProperties = config.getProperties(); +export type Config = typeof configProperties; +export default AppConfigSchema; diff --git a/modules/comms/src/config/push.ts b/modules/comms/src/config/push.ts new file mode 100644 index 000000000..9e1cc13af --- /dev/null +++ b/modules/comms/src/config/push.ts @@ -0,0 +1,32 @@ +export default { + push: { + providerName: { + format: 'String', + default: 'basic', + }, + firebase: { + projectId: { + format: 'String', + default: '', + }, + privateKey: { + format: 'String', + default: '', + }, + clientEmail: { + format: 'String', + default: '', + }, + }, + onesignal: { + appId: { + format: 'String', + default: '', + }, + apiKey: { + format: 'String', + default: '', + }, + }, + }, +}; diff --git a/modules/comms/src/config/sms.ts b/modules/comms/src/config/sms.ts new file mode 100644 index 000000000..058b701a9 --- /dev/null +++ b/modules/comms/src/config/sms.ts @@ -0,0 +1,71 @@ +export default { + sms: { + doc: 'The options for the conduit sms provider', + active: { + format: 'Boolean', + default: false, + }, + providerName: { + format: 'String', + default: 'twilio', + }, + twilio: { + phoneNumber: { + format: 'String', + default: '', + }, + accountSID: { + format: 'String', + default: '', + }, + authToken: { + format: 'String', + default: '', + }, + verify: { + active: { + format: 'Boolean', + default: false, + }, + serviceSid: { + format: 'String', + default: '', + }, + }, + }, + awsSns: { + region: { + format: 'String', + default: '', + }, + accessKeyId: { + format: 'String', + default: '', + }, + secretAccessKey: { + format: 'String', + default: '', + }, + }, + messageBird: { + accessKeyId: { + format: 'String', + default: '', + }, + originatorName: { + format: 'String', + default: '', + }, + }, + clickSend: { + username: { + format: 'String', + default: '', + }, + clicksendApiKey: { + format: 'String', + default: '', + }, + }, + }, +}; diff --git a/modules/comms/src/index.ts b/modules/comms/src/index.ts new file mode 100644 index 000000000..efb4e627d --- /dev/null +++ b/modules/comms/src/index.ts @@ -0,0 +1,4 @@ +import CommsModule from './Comms.js'; + +const comms = new CommsModule(); +comms.start(); diff --git a/modules/comms/src/interfaces/CommService.ts b/modules/comms/src/interfaces/CommService.ts new file mode 100644 index 000000000..95e2b5b1a --- /dev/null +++ b/modules/comms/src/interfaces/CommService.ts @@ -0,0 +1,14 @@ +import { ConduitService, RoutingManager } from '@conduitplatform/module-tools'; +import { HealthCheckStatus } from '@conduitplatform/grpc-sdk'; + +export interface CommService { + preConfig?: (config: any) => Promise; + onConfig?: () => Promise; + onServerStart?: () => Promise; + initializeMetrics?: () => Promise; + registerRoutes?: (_routingManager: RoutingManager) => Promise | void; + registerAdminRoutes?: (_routingManager: RoutingManager) => Promise | void; + + get rpcFunctions(): ConduitService['functions']; + get health(): HealthCheckStatus; +} diff --git a/modules/email/src/metrics/index.ts b/modules/comms/src/metrics/index.ts similarity index 54% rename from modules/email/src/metrics/index.ts rename to modules/comms/src/metrics/index.ts index 715c87309..68438ea4d 100644 --- a/modules/email/src/metrics/index.ts +++ b/modules/comms/src/metrics/index.ts @@ -15,4 +15,18 @@ export default { help: 'Tracks the total number of emails sent', }, }, + pushNotifications: { + type: MetricType.Counter, + config: { + name: 'push_notifications_sent_total', + help: 'Tracks the total number of push notifications sent', + }, + }, + smsSent: { + type: MetricType.Counter, + config: { + name: 'sms_sent_total', + help: 'Tracks the total number of sms sent', + }, + }, }; diff --git a/modules/email/src/Email.ts b/modules/comms/src/modules/email/Email.ts similarity index 78% rename from modules/email/src/Email.ts rename to modules/comms/src/modules/email/Email.ts index c14085e30..36275a2b2 100644 --- a/modules/email/src/Email.ts +++ b/modules/comms/src/modules/email/Email.ts @@ -6,7 +6,6 @@ import { HealthCheckStatus, } from '@conduitplatform/grpc-sdk'; import path from 'path'; -import AppConfigSchema, { Config } from './config/index.js'; import { AdminHandlers } from './admin/index.js'; import { EmailService } from './services/email.service.js'; import { EmailProvider } from './email-provider/index.js'; @@ -23,29 +22,21 @@ import { ResendEmailResponse, SendEmailRequest, SendEmailResponse, -} from './protoTypes/email.js'; -import metricsSchema from './metrics/index.js'; -import { ConfigController, ManagedModule } from '@conduitplatform/module-tools'; +} from '../../protoTypes/comms.js'; +import { ConfigController, RoutingManager } from '@conduitplatform/module-tools'; import { ISendEmailParams } from './interfaces/index.js'; -import { fileURLToPath } from 'node:url'; import { Queue, Worker } from 'bullmq'; import { Cluster, Redis } from 'ioredis'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +import { CommService } from '../../interfaces/CommService.js'; +import { Config } from '../../config/index.js'; -export default class Email extends ManagedModule { - configSchema = AppConfigSchema; - service = { - protoPath: path.resolve(__dirname, 'email.proto'), - protoDescription: 'email.Email', - functions: { - registerTemplate: this.registerTemplate.bind(this), - sendEmail: this.sendEmail.bind(this), - resendEmail: this.resendEmail.bind(this), - getEmailStatus: this.getEmailStatus.bind(this), - }, +export default class Email implements CommService { + readonly functions = { + registerTemplate: this.registerTemplate.bind(this), + sendEmail: this.sendEmail.bind(this), + resendEmail: this.resendEmail.bind(this), + getEmailStatus: this.getEmailStatus.bind(this), }; - protected metricsSchema = metricsSchema; private isRunning: boolean = false; private adminRouter: AdminHandlers; private database: DatabaseProvider; @@ -54,9 +45,29 @@ export default class Email extends ManagedModule { private redisConnection: Redis | Cluster; private emailCleanupQueue: Queue | null = null; - constructor() { - super('email'); - this.updateHealth(HealthCheckStatus.UNKNOWN, true); + private static _instance: Email; + private _health: HealthCheckStatus = HealthCheckStatus.UNKNOWN; + + private constructor(private readonly grpcSdk: ConduitGrpcSdk) {} + + static getInstance(grpcSdk?: ConduitGrpcSdk) { + if (!Email._instance) { + if (!grpcSdk) throw new Error('GrpcSdk must be provided'); + Email._instance = new Email(grpcSdk); + } + return Email._instance; + } + + registerAdminRoutes(routingManager: RoutingManager) { + this.adminRouter.registerAdminRoutes(routingManager); + } + + get rpcFunctions() { + return this.functions; + } + + get health() { + return this._health; } async onServerStart() { @@ -68,15 +79,15 @@ export default class Email extends ManagedModule { } async preConfig(config: Config) { - if (config.transportSettings?.sendgrid.hasOwnProperty('apiUser')) { + if (config.email.transportSettings?.sendgrid.hasOwnProperty('apiUser')) { delete ( config as Config & { transportSettings: { sendgrid: { apiUser?: string } } } ).transportSettings.sendgrid.apiUser; } if ( - isNil(config.active) || - isNil(config.transport) || - isNil(config.transportSettings) + isNil(config.email.active) || + isNil(config.email.transport) || + isNil(config.email.transportSettings) ) { throw new Error('Invalid configuration given'); } @@ -84,23 +95,26 @@ export default class Email extends ManagedModule { } async onConfig() { - if (!ConfigController.getInstance().config.active) { - this.updateHealth(HealthCheckStatus.NOT_SERVING); + if (!ConfigController.getInstance().config.email.active) { + this._health = HealthCheckStatus.NOT_SERVING; } else { if (!this.isRunning) { await this.initEmailProvider(); this.emailService = new EmailService(this.grpcSdk, this.emailProvider); - this.adminRouter = new AdminHandlers(this.grpcServer, this.grpcSdk); + this.adminRouter = new AdminHandlers(this.grpcSdk); this.adminRouter.setEmailService(this.emailService); this.isRunning = true; } else { await this.initEmailProvider(ConfigController.getInstance().config); this.emailService.updateProvider(this.emailProvider); } - this.updateHealth(HealthCheckStatus.SERVING); + this._health = HealthCheckStatus.SERVING; const config = ConfigController.getInstance().config as Config; - if (config.storeEmails.storage.enabled && !this.grpcSdk.isAvailable('storage')) { + if ( + config.email.storeEmails.storage.enabled && + !this.grpcSdk.isAvailable('storage') + ) { ConduitGrpcSdk.Logger.warn( 'Failed to enable email storing. Storage module not serving.', ); @@ -150,7 +164,7 @@ export default class Email extends ManagedModule { cc: call.request.params!.cc, replyTo: call.request.params!.replyTo, attachments: call.request.params!.attachments, - sendingDomain: emailConfig?.sendingDomain ?? '', + sendingDomain: emailConfig?.email.sendingDomain ?? '', }; let errorMessage: string | null = null; @@ -214,7 +228,10 @@ export default class Email extends ManagedModule { connection: this.redisConnection, }); await this.emailCleanupQueue.drain(true); - if (!config.storeEmails.enabled || !config.storeEmails.cleanupSettings.enabled) { + if ( + !config.email.storeEmails.enabled || + !config.email.storeEmails.cleanupSettings.enabled + ) { await this.emailCleanupQueue.close(); return; } @@ -249,12 +266,12 @@ export default class Email extends ManagedModule { await this.emailCleanupQueue.add( 'cleanup', { - limit: config.storeEmails.cleanupSettings.limit, - deleteStorageFiles: config.storeEmails.storage.enabled, + limit: config.email.storeEmails.cleanupSettings.limit, + deleteStorageFiles: config.email.storeEmails.storage.enabled, }, { repeat: { - every: config.storeEmails.cleanupSettings.repeat, + every: config.email.storeEmails.cleanupSettings.repeat, }, }, ); diff --git a/modules/email/src/admin/index.ts b/modules/comms/src/modules/email/admin/index.ts similarity index 95% rename from modules/email/src/admin/index.ts rename to modules/comms/src/modules/email/admin/index.ts index c71f800ce..0b285933c 100644 --- a/modules/email/src/admin/index.ts +++ b/modules/comms/src/modules/email/admin/index.ts @@ -14,7 +14,6 @@ import { ConduitJson, ConduitNumber, ConduitString, - GrpcServer, RoutingManager, } from '@conduitplatform/module-tools'; import { status } from '@grpc/grpc-js'; @@ -23,23 +22,16 @@ import { isNil } from 'lodash-es'; import { getHandleBarsValues } from '../email-provider/utils/index.js'; import { EmailService } from '../services/email.service.js'; import { EmailRecord, EmailTemplate } from '../models/index.js'; -import { Config } from '../config/index.js'; import { Template } from '../email-provider/interfaces/Template.js'; import { TemplateDocument } from '../email-provider/interfaces/TemplateDocument.js'; import escapeStringRegexp from 'escape-string-regexp'; +import { Config } from '../../../config/index.js'; export class AdminHandlers { private emailService: EmailService; - private readonly routingManager: RoutingManager; - - constructor( - private readonly server: GrpcServer, - private readonly grpcSdk: ConduitGrpcSdk, - ) { - this.routingManager = new RoutingManager(grpcSdk.admin, server); - this.registerAdminRoutes(); - } + + constructor(private readonly grpcSdk: ConduitGrpcSdk) {} setEmailService(emailService: EmailService) { this.emailService = emailService; @@ -354,7 +346,7 @@ export class AdminHandlers { const emailConfig: Config = await this.grpcSdk.config .get('email') .catch(() => ConduitGrpcSdk.Logger.error('Failed to get sending domain')); - sender = sender + `@${emailConfig?.sendingDomain ?? 'conduit.com'}`; + sender = sender + `@${emailConfig?.email.sendingDomain ?? 'conduit.com'}`; } if (templateName) { const templateFound = await EmailTemplate.getInstance().findOne({ @@ -434,9 +426,8 @@ export class AdminHandlers { return { records, count }; } - private registerAdminRoutes() { - this.routingManager.clear(); - this.routingManager.route( + registerAdminRoutes(routingManager: RoutingManager) { + routingManager.route( { path: '/templates', action: ConduitRouteActions.GET, @@ -454,7 +445,7 @@ export class AdminHandlers { }), this.getTemplates.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/templates', action: ConduitRouteActions.POST, @@ -473,7 +464,7 @@ export class AdminHandlers { }), this.createTemplate.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/templates/:id', action: ConduitRouteActions.PATCH, @@ -492,7 +483,7 @@ export class AdminHandlers { }), this.patchTemplate.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/templates', action: ConduitRouteActions.DELETE, @@ -506,7 +497,7 @@ export class AdminHandlers { }), this.deleteTemplates.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/templates/:id', action: ConduitRouteActions.DELETE, @@ -520,7 +511,7 @@ export class AdminHandlers { }), this.deleteTemplate.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/templates/upload', action: ConduitRouteActions.POST, @@ -534,7 +525,7 @@ export class AdminHandlers { }), this.uploadTemplate.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/externalTemplates', action: ConduitRouteActions.GET, @@ -551,7 +542,7 @@ export class AdminHandlers { }), this.getExternalTemplates.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/syncExternalTemplates', action: ConduitRouteActions.UPDATE, @@ -563,7 +554,7 @@ export class AdminHandlers { }), this.syncExternalTemplates.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/send', action: ConduitRouteActions.POST, @@ -582,7 +573,7 @@ export class AdminHandlers { }), this.sendEmail.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/resend', action: ConduitRouteActions.POST, @@ -596,7 +587,7 @@ export class AdminHandlers { }), this.resendEmail.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/status', action: ConduitRouteActions.GET, @@ -610,7 +601,7 @@ export class AdminHandlers { }), this.getEmailStatus.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/record', action: ConduitRouteActions.GET, @@ -635,6 +626,5 @@ export class AdminHandlers { }), this.getEmailRecords.bind(this), ); - this.routingManager.registerRoutes(); } } diff --git a/modules/email/src/email-provider/README.md b/modules/comms/src/modules/email/email-provider/README.md similarity index 100% rename from modules/email/src/email-provider/README.md rename to modules/comms/src/modules/email/email-provider/README.md diff --git a/modules/email/src/email-provider/index.ts b/modules/comms/src/modules/email/email-provider/index.ts similarity index 100% rename from modules/email/src/email-provider/index.ts rename to modules/comms/src/modules/email/email-provider/index.ts diff --git a/modules/email/src/email-provider/interfaces/CreateEmailTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/CreateEmailTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/CreateEmailTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/CreateEmailTemplate.ts diff --git a/modules/email/src/email-provider/interfaces/DeleteEmailTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/DeleteEmailTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/DeleteEmailTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/DeleteEmailTemplate.ts diff --git a/modules/email/src/email-provider/interfaces/EmailBuilder.ts b/modules/comms/src/modules/email/email-provider/interfaces/EmailBuilder.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/EmailBuilder.ts rename to modules/comms/src/modules/email/email-provider/interfaces/EmailBuilder.ts diff --git a/modules/email/src/email-provider/interfaces/EmailOptions.ts b/modules/comms/src/modules/email/email-provider/interfaces/EmailOptions.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/EmailOptions.ts rename to modules/comms/src/modules/email/email-provider/interfaces/EmailOptions.ts diff --git a/modules/email/src/email-provider/interfaces/EmailSendTransport.ts b/modules/comms/src/modules/email/email-provider/interfaces/EmailSendTransport.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/EmailSendTransport.ts rename to modules/comms/src/modules/email/email-provider/interfaces/EmailSendTransport.ts diff --git a/modules/email/src/email-provider/interfaces/Template.ts b/modules/comms/src/modules/email/email-provider/interfaces/Template.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/Template.ts rename to modules/comms/src/modules/email/email-provider/interfaces/Template.ts diff --git a/modules/email/src/email-provider/interfaces/TemplateDocument.ts b/modules/comms/src/modules/email/email-provider/interfaces/TemplateDocument.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/TemplateDocument.ts rename to modules/comms/src/modules/email/email-provider/interfaces/TemplateDocument.ts diff --git a/modules/email/src/email-provider/interfaces/TemplateOptions.ts b/modules/comms/src/modules/email/email-provider/interfaces/TemplateOptions.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/TemplateOptions.ts rename to modules/comms/src/modules/email/email-provider/interfaces/TemplateOptions.ts diff --git a/modules/email/src/email-provider/interfaces/UpdateEmailTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/UpdateEmailTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/UpdateEmailTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/UpdateEmailTemplate.ts diff --git a/modules/email/src/email-provider/interfaces/Var.ts b/modules/comms/src/modules/email/email-provider/interfaces/Var.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/Var.ts rename to modules/comms/src/modules/email/email-provider/interfaces/Var.ts diff --git a/modules/email/src/email-provider/interfaces/mailgun/MailgunEmailOptions.ts b/modules/comms/src/modules/email/email-provider/interfaces/mailgun/MailgunEmailOptions.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/mailgun/MailgunEmailOptions.ts rename to modules/comms/src/modules/email/email-provider/interfaces/mailgun/MailgunEmailOptions.ts diff --git a/modules/email/src/email-provider/interfaces/mailgun/MailgunTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/mailgun/MailgunTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/mailgun/MailgunTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/mailgun/MailgunTemplate.ts diff --git a/modules/email/src/email-provider/interfaces/mandrill/MandrillEmailOptions.ts b/modules/comms/src/modules/email/email-provider/interfaces/mandrill/MandrillEmailOptions.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/mandrill/MandrillEmailOptions.ts rename to modules/comms/src/modules/email/email-provider/interfaces/mandrill/MandrillEmailOptions.ts diff --git a/modules/email/src/email-provider/interfaces/mandrill/MandrillTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/mandrill/MandrillTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/mandrill/MandrillTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/mandrill/MandrillTemplate.ts diff --git a/modules/email/src/email-provider/interfaces/sendgrid/SendgridEmailOptions.ts b/modules/comms/src/modules/email/email-provider/interfaces/sendgrid/SendgridEmailOptions.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/sendgrid/SendgridEmailOptions.ts rename to modules/comms/src/modules/email/email-provider/interfaces/sendgrid/SendgridEmailOptions.ts diff --git a/modules/email/src/email-provider/interfaces/sendgrid/SendgridTemplate.ts b/modules/comms/src/modules/email/email-provider/interfaces/sendgrid/SendgridTemplate.ts similarity index 100% rename from modules/email/src/email-provider/interfaces/sendgrid/SendgridTemplate.ts rename to modules/comms/src/modules/email/email-provider/interfaces/sendgrid/SendgridTemplate.ts diff --git a/modules/email/src/email-provider/models/EmailBuilderClass.ts b/modules/comms/src/modules/email/email-provider/models/EmailBuilderClass.ts similarity index 100% rename from modules/email/src/email-provider/models/EmailBuilderClass.ts rename to modules/comms/src/modules/email/email-provider/models/EmailBuilderClass.ts diff --git a/modules/email/src/email-provider/models/EmailProviderClass.ts b/modules/comms/src/modules/email/email-provider/models/EmailProviderClass.ts similarity index 100% rename from modules/email/src/email-provider/models/EmailProviderClass.ts rename to modules/comms/src/modules/email/email-provider/models/EmailProviderClass.ts diff --git a/modules/email/src/email-provider/transports/mailgun/MailgunProvider.ts b/modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts similarity index 100% rename from modules/email/src/email-provider/transports/mailgun/MailgunProvider.ts rename to modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts diff --git a/modules/email/src/email-provider/transports/mailgun/mailgun.config.ts b/modules/comms/src/modules/email/email-provider/transports/mailgun/mailgun.config.ts similarity index 100% rename from modules/email/src/email-provider/transports/mailgun/mailgun.config.ts rename to modules/comms/src/modules/email/email-provider/transports/mailgun/mailgun.config.ts diff --git a/modules/email/src/email-provider/transports/mailgun/mailgun.ts b/modules/comms/src/modules/email/email-provider/transports/mailgun/mailgun.ts similarity index 100% rename from modules/email/src/email-provider/transports/mailgun/mailgun.ts rename to modules/comms/src/modules/email/email-provider/transports/mailgun/mailgun.ts diff --git a/modules/email/src/email-provider/transports/mailgun/mailgunMailBuilder.ts b/modules/comms/src/modules/email/email-provider/transports/mailgun/mailgunMailBuilder.ts similarity index 100% rename from modules/email/src/email-provider/transports/mailgun/mailgunMailBuilder.ts rename to modules/comms/src/modules/email/email-provider/transports/mailgun/mailgunMailBuilder.ts diff --git a/modules/email/src/email-provider/transports/mandrill/MandrilProvider.ts b/modules/comms/src/modules/email/email-provider/transports/mandrill/MandrilProvider.ts similarity index 100% rename from modules/email/src/email-provider/transports/mandrill/MandrilProvider.ts rename to modules/comms/src/modules/email/email-provider/transports/mandrill/MandrilProvider.ts diff --git a/modules/email/src/email-provider/transports/mandrill/mandrill.config.ts b/modules/comms/src/modules/email/email-provider/transports/mandrill/mandrill.config.ts similarity index 100% rename from modules/email/src/email-provider/transports/mandrill/mandrill.config.ts rename to modules/comms/src/modules/email/email-provider/transports/mandrill/mandrill.config.ts diff --git a/modules/email/src/email-provider/transports/mandrill/mandrillBuilder.ts b/modules/comms/src/modules/email/email-provider/transports/mandrill/mandrillBuilder.ts similarity index 100% rename from modules/email/src/email-provider/transports/mandrill/mandrillBuilder.ts rename to modules/comms/src/modules/email/email-provider/transports/mandrill/mandrillBuilder.ts diff --git a/modules/email/src/email-provider/transports/nodemailer/nodemailerBuilder.ts b/modules/comms/src/modules/email/email-provider/transports/nodemailer/nodemailerBuilder.ts similarity index 100% rename from modules/email/src/email-provider/transports/nodemailer/nodemailerBuilder.ts rename to modules/comms/src/modules/email/email-provider/transports/nodemailer/nodemailerBuilder.ts diff --git a/modules/email/src/email-provider/transports/sendgrid/SendgridProvider.ts b/modules/comms/src/modules/email/email-provider/transports/sendgrid/SendgridProvider.ts similarity index 100% rename from modules/email/src/email-provider/transports/sendgrid/SendgridProvider.ts rename to modules/comms/src/modules/email/email-provider/transports/sendgrid/SendgridProvider.ts diff --git a/modules/email/src/email-provider/transports/sendgrid/sendgrid.config.ts b/modules/comms/src/modules/email/email-provider/transports/sendgrid/sendgrid.config.ts similarity index 100% rename from modules/email/src/email-provider/transports/sendgrid/sendgrid.config.ts rename to modules/comms/src/modules/email/email-provider/transports/sendgrid/sendgrid.config.ts diff --git a/modules/email/src/email-provider/transports/sendgrid/sendgridMailBuilder.ts b/modules/comms/src/modules/email/email-provider/transports/sendgrid/sendgridMailBuilder.ts similarity index 100% rename from modules/email/src/email-provider/transports/sendgrid/sendgridMailBuilder.ts rename to modules/comms/src/modules/email/email-provider/transports/sendgrid/sendgridMailBuilder.ts diff --git a/modules/email/src/email-provider/transports/smtp/SmtpProvider.ts b/modules/comms/src/modules/email/email-provider/transports/smtp/SmtpProvider.ts similarity index 100% rename from modules/email/src/email-provider/transports/smtp/SmtpProvider.ts rename to modules/comms/src/modules/email/email-provider/transports/smtp/SmtpProvider.ts diff --git a/modules/email/src/email-provider/utils/getHandleBarsValues.ts b/modules/comms/src/modules/email/email-provider/utils/getHandleBarsValues.ts similarity index 100% rename from modules/email/src/email-provider/utils/getHandleBarsValues.ts rename to modules/comms/src/modules/email/email-provider/utils/getHandleBarsValues.ts diff --git a/modules/email/src/email-provider/utils/htmlCheck.ts b/modules/comms/src/modules/email/email-provider/utils/htmlCheck.ts similarity index 100% rename from modules/email/src/email-provider/utils/htmlCheck.ts rename to modules/comms/src/modules/email/email-provider/utils/htmlCheck.ts diff --git a/modules/email/src/email-provider/utils/index.ts b/modules/comms/src/modules/email/email-provider/utils/index.ts similarity index 100% rename from modules/email/src/email-provider/utils/index.ts rename to modules/comms/src/modules/email/email-provider/utils/index.ts diff --git a/modules/email/src/interfaces/RegisterTemplateParams.ts b/modules/comms/src/modules/email/interfaces/RegisterTemplateParams.ts similarity index 100% rename from modules/email/src/interfaces/RegisterTemplateParams.ts rename to modules/comms/src/modules/email/interfaces/RegisterTemplateParams.ts diff --git a/modules/email/src/interfaces/SendEmailParams.ts b/modules/comms/src/modules/email/interfaces/SendEmailParams.ts similarity index 100% rename from modules/email/src/interfaces/SendEmailParams.ts rename to modules/comms/src/modules/email/interfaces/SendEmailParams.ts diff --git a/modules/email/src/interfaces/index.ts b/modules/comms/src/modules/email/interfaces/index.ts similarity index 100% rename from modules/email/src/interfaces/index.ts rename to modules/comms/src/modules/email/interfaces/index.ts diff --git a/modules/email/src/jobs/cleanupStoredEmails.ts b/modules/comms/src/modules/email/jobs/cleanupStoredEmails.ts similarity index 100% rename from modules/email/src/jobs/cleanupStoredEmails.ts rename to modules/comms/src/modules/email/jobs/cleanupStoredEmails.ts diff --git a/modules/email/src/jobs/index.ts b/modules/comms/src/modules/email/jobs/index.ts similarity index 100% rename from modules/email/src/jobs/index.ts rename to modules/comms/src/modules/email/jobs/index.ts diff --git a/modules/email/src/migrations/index.ts b/modules/comms/src/modules/email/migrations/index.ts similarity index 100% rename from modules/email/src/migrations/index.ts rename to modules/comms/src/modules/email/migrations/index.ts diff --git a/modules/email/src/models/EmailRecord.ts b/modules/comms/src/modules/email/models/EmailRecord.ts similarity index 100% rename from modules/email/src/models/EmailRecord.ts rename to modules/comms/src/modules/email/models/EmailRecord.ts diff --git a/modules/email/src/models/EmailTemplate.schema.ts b/modules/comms/src/modules/email/models/EmailTemplate.schema.ts similarity index 100% rename from modules/email/src/models/EmailTemplate.schema.ts rename to modules/comms/src/modules/email/models/EmailTemplate.schema.ts diff --git a/modules/email/src/models/index.ts b/modules/comms/src/modules/email/models/index.ts similarity index 100% rename from modules/email/src/models/index.ts rename to modules/comms/src/modules/email/models/index.ts diff --git a/modules/email/src/services/email.service.ts b/modules/comms/src/modules/email/services/email.service.ts similarity index 98% rename from modules/email/src/services/email.service.ts rename to modules/comms/src/modules/email/services/email.service.ts index 4fc39ebbe..4830bd9ec 100644 --- a/modules/email/src/services/email.service.ts +++ b/modules/comms/src/modules/email/services/email.service.ts @@ -9,9 +9,9 @@ import { Attachment } from 'nodemailer/lib/mailer'; import { Template } from '../email-provider/interfaces/Template.js'; import { ConduitGrpcSdk, GrpcError } from '@conduitplatform/grpc-sdk'; import { ConfigController } from '@conduitplatform/module-tools'; -import { Config } from '../config/index.js'; import { status } from '@grpc/grpc-js'; import { storeEmail } from '../utils/index.js'; +import { Config } from '../../../config/index.js'; export class EmailService { constructor( @@ -142,7 +142,7 @@ export class EmailService { const messageId = this.emailer._transport?.getMessageId(sentMessageInfo); const config = ConfigController.getInstance().config as Config; - if (config.storeEmails.enabled) { + if (config.email.storeEmails.enabled) { await storeEmail(this.grpcSdk, messageId, templateFound, contentFileId, { body: bodyString, subject: subjectString, diff --git a/modules/email/src/utils/index.ts b/modules/comms/src/modules/email/utils/index.ts similarity index 100% rename from modules/email/src/utils/index.ts rename to modules/comms/src/modules/email/utils/index.ts diff --git a/modules/email/src/utils/storeEmail.ts b/modules/comms/src/modules/email/utils/storeEmail.ts similarity index 85% rename from modules/email/src/utils/storeEmail.ts rename to modules/comms/src/modules/email/utils/storeEmail.ts index 83c790df2..fbd8d8f59 100644 --- a/modules/email/src/utils/storeEmail.ts +++ b/modules/comms/src/modules/email/utils/storeEmail.ts @@ -3,8 +3,8 @@ import axios from 'axios'; import { EmailRecord, EmailTemplate } from '../models/index.js'; import { ConduitGrpcSdk } from '@conduitplatform/grpc-sdk'; import { ConfigController } from '@conduitplatform/module-tools'; -import { Config } from '../config/index.js'; import { ISendEmailParams } from '../interfaces/index.js'; +import { Config } from '../../../config/index.js'; export async function storeEmail( grpcSdk: ConduitGrpcSdk, @@ -15,11 +15,11 @@ export async function storeEmail( ) { const config = ConfigController.getInstance().config as Config; let newContentFile; - if (!contentFileId && config.storeEmails.storage.enabled) { + if (!contentFileId && config.email.storeEmails.storage.enabled) { newContentFile = await grpcSdk.storage!.createFileByUrl( randomUUID(), - config.storeEmails.storage.folder, - config.storeEmails.storage.container, + config.email.storeEmails.storage.folder, + config.email.storeEmails.storage.container, ); const buffer = Buffer.from(JSON.stringify(params)); await axios.put(newContentFile.uploadUrl, buffer, { diff --git a/modules/push-notifications/src/PushNotifications.ts b/modules/comms/src/modules/push/PushNotifications.ts similarity index 77% rename from modules/push-notifications/src/PushNotifications.ts rename to modules/comms/src/modules/push/PushNotifications.ts index b33032434..98bc74ec9 100644 --- a/modules/push-notifications/src/PushNotifications.ts +++ b/modules/comms/src/modules/push/PushNotifications.ts @@ -1,23 +1,21 @@ import { + ConduitGrpcSdk, DatabaseProvider, GrpcRequest, GrpcResponse, HealthCheckStatus, } from '@conduitplatform/grpc-sdk'; -import AppConfigSchema, { Config } from './config/index.js'; import { AdminHandlers } from './admin/index.js'; import { PushNotificationsRoutes } from './routes/index.js'; import * as models from './models/index.js'; import { FirebaseProvider } from './providers/Firebase.provider.js'; import { BaseNotificationProvider } from './providers/base.provider.js'; -import path from 'path'; import { isNil } from 'lodash-es'; import { status } from '@grpc/grpc-js'; import { ISendNotification } from './interfaces/ISendNotification.js'; import { runMigrations } from './migrations/index.js'; -import metricsSchema from './metrics/index.js'; import { OneSignalProvider } from './providers/OneSignal.provider.js'; -import { ConfigController, ManagedModule } from '@conduitplatform/module-tools'; +import { ConfigController, RoutingManager } from '@conduitplatform/module-tools'; import { GetNotificationTokensRequest, GetNotificationTokensResponse, @@ -27,26 +25,18 @@ import { SendNotificationToManyDevicesRequest, SetNotificationTokenRequest, SetNotificationTokenResponse, -} from './protoTypes/push-notifications.js'; -import { fileURLToPath } from 'node:url'; +} from '../../protoTypes/comms.js'; +import { CommService } from '../../interfaces/CommService.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -export default class PushNotifications extends ManagedModule { - configSchema = AppConfigSchema; - service = { - protoPath: path.resolve(__dirname, 'push-notifications.proto'), - protoDescription: 'pushnotifications.PushNotifications', - functions: { - setNotificationToken: this.setNotificationToken.bind(this), - getNotificationTokens: this.getNotificationTokens.bind(this), - sendNotification: this.sendNotification.bind(this), - sendNotificationToManyDevices: this.sendToManyDevices.bind(this), - sendManyNotifications: this.sendMany.bind(this), - }, +export default class PushNotifications implements CommService { + readonly functions = { + setNotificationToken: this.setNotificationToken.bind(this), + getNotificationTokens: this.getNotificationTokens.bind(this), + sendNotification: this.sendNotification.bind(this), + sendNotificationToManyDevices: this.sendToManyDevices.bind(this), + sendManyNotifications: this.sendMany.bind(this), }; - protected metricsSchema = metricsSchema; + private isRunning = false; private canServe = false; private authServing = false; @@ -55,9 +45,33 @@ export default class PushNotifications extends ManagedModule { private database!: DatabaseProvider; private _provider: BaseNotificationProvider | undefined; - constructor() { - super('pushNotifications'); - this.updateHealth(HealthCheckStatus.UNKNOWN, true); + private static _instance: PushNotifications; + private _health: HealthCheckStatus = HealthCheckStatus.UNKNOWN; + + private constructor(private readonly grpcSdk: ConduitGrpcSdk) {} + + static getInstance(grpcSdk?: ConduitGrpcSdk) { + if (!PushNotifications._instance) { + if (!grpcSdk) throw new Error('GrpcSdk must be provided'); + PushNotifications._instance = new PushNotifications(grpcSdk); + } + return PushNotifications._instance; + } + + get rpcFunctions() { + return this.functions; + } + + get health() { + return this._health; + } + + registerAdminRoutes(routingManager: RoutingManager) { + this.adminRouter.registerAdminRoutes(routingManager); + } + + registerRoutes(routingManager: RoutingManager) { + this.userRouter.registerRoutes(routingManager); } async onServerStart() { @@ -71,23 +85,16 @@ export default class PushNotifications extends ManagedModule { } async onConfig() { - if (!ConfigController.getInstance().config.active) { + try { + await this.enableModule(); + this.canServe = true; + this.updateHealthState(HealthCheckStatus.SERVING); + } catch (e) { this.canServe = false; this.updateHealthState(HealthCheckStatus.NOT_SERVING); - } else { - try { - await this.enableModule(); - this.canServe = true; - this.updateHealthState(HealthCheckStatus.SERVING); - } catch (e) { - this.canServe = false; - this.updateHealthState(HealthCheckStatus.NOT_SERVING); - } } } - async initializeMetrics() {} - // gRPC Service async setNotificationToken( call: GrpcRequest, @@ -221,7 +228,7 @@ export default class PushNotifications extends ManagedModule { depState === requestedState && requestedState === HealthCheckStatus.SERVING ? HealthCheckStatus.SERVING : HealthCheckStatus.NOT_SERVING; - this.updateHealth(nextState); + this._health = nextState; } private async enableModule() { @@ -230,22 +237,8 @@ export default class PushNotifications extends ManagedModule { if (!this._provider || !this._provider?.isInitialized) { throw new Error('Provider failed to initialize'); } - if (this.grpcSdk.isAvailable('router')) { - this.userRouter = new PushNotificationsRoutes(this.grpcServer, this.grpcSdk); - } else { - this.grpcSdk.monitorModule('router', serving => { - if (serving) { - this.userRouter = new PushNotificationsRoutes(this.grpcServer, this.grpcSdk); - this.grpcSdk.unmonitorModule('router'); - } - }); - } - - this.adminRouter = new AdminHandlers( - this.grpcServer, - this.grpcSdk, - this._provider!, - ); + this.userRouter = new PushNotificationsRoutes(this.grpcSdk); + this.adminRouter = new AdminHandlers(this.grpcSdk, this._provider!); this.isRunning = true; } else { await this.initProvider(); diff --git a/modules/push-notifications/src/admin/index.ts b/modules/comms/src/modules/push/admin/index.ts similarity index 94% rename from modules/push-notifications/src/admin/index.ts rename to modules/comms/src/modules/push/admin/index.ts index 6f68e00cd..00de21328 100644 --- a/modules/push-notifications/src/admin/index.ts +++ b/modules/comms/src/modules/push/admin/index.ts @@ -14,27 +14,22 @@ import { ConduitNumber, ConduitObjectId, ConduitString, - GrpcServer, RoutingManager, } from '@conduitplatform/module-tools'; import { status } from '@grpc/grpc-js'; import { isNil } from 'lodash-es'; import { BaseNotificationProvider } from '../providers/base.provider.js'; -import { NotificationToken } from '../models/index.js'; import { ISendNotification } from '../interfaces/ISendNotification.js'; +import { NotificationToken } from '../models/index.js'; export class AdminHandlers { private provider: BaseNotificationProvider; - private readonly routingManager: RoutingManager; constructor( - private readonly server: GrpcServer, private readonly grpcSdk: ConduitGrpcSdk, provider: BaseNotificationProvider, ) { this.provider = provider; - this.routingManager = new RoutingManager(this.grpcSdk.admin, this.server); - this.registerAdminRoutes(); } updateProvider(provider: BaseNotificationProvider) { @@ -162,10 +157,8 @@ export class AdminHandlers { return { tokens, count }; } - private registerAdminRoutes() { - this.routingManager.clear(); - - this.routingManager.route( + registerAdminRoutes(routingManager: RoutingManager) { + routingManager.route( { path: '/send', action: ConduitRouteActions.POST, @@ -183,7 +176,7 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('SendNotification', 'String'), this.sendNotification.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/sendToManyDevices', action: ConduitRouteActions.POST, @@ -201,7 +194,7 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('SendNotificationToManyDevices', 'String'), this.sendNotificationToManyDevices.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/sendMany', action: ConduitRouteActions.POST, @@ -225,7 +218,7 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('SendManyNotifications', 'String'), this.sendManyNotifications.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/token', action: ConduitRouteActions.GET, @@ -245,7 +238,7 @@ export class AdminHandlers { }), this.getNotificationTokens.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/token/:id', action: ConduitRouteActions.GET, @@ -260,7 +253,7 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('GetNotificationToken', NotificationToken.name), this.getNotificationToken.bind(this), ); - this.routingManager.route( + routingManager.route( { path: '/token/user/:userId', action: ConduitRouteActions.GET, @@ -277,6 +270,5 @@ export class AdminHandlers { }), this.getNotificationTokenByUserId.bind(this), ); - this.routingManager.registerRoutes(); } } diff --git a/modules/push-notifications/src/handlers/notification-tokens.ts b/modules/comms/src/modules/push/handlers/notification-tokens.ts similarity index 100% rename from modules/push-notifications/src/handlers/notification-tokens.ts rename to modules/comms/src/modules/push/handlers/notification-tokens.ts diff --git a/modules/push-notifications/src/interfaces/IFirebaseSettings.ts b/modules/comms/src/modules/push/interfaces/IFirebaseSettings.ts similarity index 100% rename from modules/push-notifications/src/interfaces/IFirebaseSettings.ts rename to modules/comms/src/modules/push/interfaces/IFirebaseSettings.ts diff --git a/modules/push-notifications/src/interfaces/IOneSignalSettings.ts b/modules/comms/src/modules/push/interfaces/IOneSignalSettings.ts similarity index 100% rename from modules/push-notifications/src/interfaces/IOneSignalSettings.ts rename to modules/comms/src/modules/push/interfaces/IOneSignalSettings.ts diff --git a/modules/push-notifications/src/interfaces/ISendNotification.ts b/modules/comms/src/modules/push/interfaces/ISendNotification.ts similarity index 100% rename from modules/push-notifications/src/interfaces/ISendNotification.ts rename to modules/comms/src/modules/push/interfaces/ISendNotification.ts diff --git a/modules/push-notifications/src/migrations/index.ts b/modules/comms/src/modules/push/migrations/index.ts similarity index 100% rename from modules/push-notifications/src/migrations/index.ts rename to modules/comms/src/modules/push/migrations/index.ts diff --git a/modules/push-notifications/src/models/Notification.schema.ts b/modules/comms/src/modules/push/models/Notification.schema.ts similarity index 100% rename from modules/push-notifications/src/models/Notification.schema.ts rename to modules/comms/src/modules/push/models/Notification.schema.ts diff --git a/modules/push-notifications/src/models/NotificationToken.schema.ts b/modules/comms/src/modules/push/models/NotificationToken.schema.ts similarity index 100% rename from modules/push-notifications/src/models/NotificationToken.schema.ts rename to modules/comms/src/modules/push/models/NotificationToken.schema.ts diff --git a/modules/push-notifications/src/models/User.model.ts b/modules/comms/src/modules/push/models/User.model.ts similarity index 100% rename from modules/push-notifications/src/models/User.model.ts rename to modules/comms/src/modules/push/models/User.model.ts diff --git a/modules/push-notifications/src/models/index.ts b/modules/comms/src/modules/push/models/index.ts similarity index 100% rename from modules/push-notifications/src/models/index.ts rename to modules/comms/src/modules/push/models/index.ts diff --git a/modules/push-notifications/src/providers/Firebase.provider.ts b/modules/comms/src/modules/push/providers/Firebase.provider.ts similarity index 100% rename from modules/push-notifications/src/providers/Firebase.provider.ts rename to modules/comms/src/modules/push/providers/Firebase.provider.ts diff --git a/modules/push-notifications/src/providers/OneSignal.provider.ts b/modules/comms/src/modules/push/providers/OneSignal.provider.ts similarity index 100% rename from modules/push-notifications/src/providers/OneSignal.provider.ts rename to modules/comms/src/modules/push/providers/OneSignal.provider.ts diff --git a/modules/push-notifications/src/providers/base.provider.ts b/modules/comms/src/modules/push/providers/base.provider.ts similarity index 100% rename from modules/push-notifications/src/providers/base.provider.ts rename to modules/comms/src/modules/push/providers/base.provider.ts diff --git a/modules/push-notifications/src/providers/utils/index.ts b/modules/comms/src/modules/push/providers/utils/index.ts similarity index 100% rename from modules/push-notifications/src/providers/utils/index.ts rename to modules/comms/src/modules/push/providers/utils/index.ts diff --git a/modules/push-notifications/src/routes/index.ts b/modules/comms/src/modules/push/routes/index.ts similarity index 85% rename from modules/push-notifications/src/routes/index.ts rename to modules/comms/src/modules/push/routes/index.ts index 9b7a55704..1defcf839 100644 --- a/modules/push-notifications/src/routes/index.ts +++ b/modules/comms/src/modules/push/routes/index.ts @@ -10,7 +10,6 @@ import { ConduitDate, ConduitNumber, ConduitString, - GrpcServer, RoutingManager, } from '@conduitplatform/module-tools'; @@ -18,20 +17,13 @@ import { Notification, NotificationToken } from '../models/index.js'; export class PushNotificationsRoutes { private readonly handlers: NotificationTokensHandler; - private _routingManager: RoutingManager; - constructor( - readonly server: GrpcServer, - private readonly grpcSdk: ConduitGrpcSdk, - ) { + constructor(private readonly grpcSdk: ConduitGrpcSdk) { this.handlers = new NotificationTokensHandler(); - this._routingManager = new RoutingManager(this.grpcSdk.router!, server); - this.registeredRoutes(); } - async registeredRoutes() { - this._routingManager.clear(); - this._routingManager.route( + async registerRoutes(_routingManager: RoutingManager) { + _routingManager.route( { bodyParams: { token: ConduitString.Required, @@ -47,7 +39,7 @@ export class PushNotificationsRoutes { }), this.handlers.setNotificationToken.bind(this.handlers), ); - this._routingManager.route( + _routingManager.route( { queryParams: { platform: ConduitString.Optional, @@ -60,7 +52,7 @@ export class PushNotificationsRoutes { new ConduitRouteReturnDefinition('SetNotificationTokenResponse', 'String'), this.handlers.clearNotificationTokens.bind(this.handlers), ); - this._routingManager.route( + _routingManager.route( { queryParams: { read: ConduitBoolean.Optional, @@ -80,7 +72,7 @@ export class PushNotificationsRoutes { }), this.handlers.getUserNotifications.bind(this.handlers), ); - this._routingManager.route( + _routingManager.route( { bodyParams: { before: ConduitDate.Optional, @@ -94,6 +86,5 @@ export class PushNotificationsRoutes { new ConduitRouteReturnDefinition('ReadNotificationsResponse', 'String'), this.handlers.readUserNotification.bind(this.handlers), ); - await this._routingManager.registerRoutes(); } } diff --git a/modules/sms/src/Sms.ts b/modules/comms/src/modules/sms/Sms.ts similarity index 74% rename from modules/sms/src/Sms.ts rename to modules/comms/src/modules/sms/Sms.ts index aa0ff2293..f98e06ccf 100644 --- a/modules/sms/src/Sms.ts +++ b/modules/comms/src/modules/sms/Sms.ts @@ -4,16 +4,16 @@ import { GrpcRequest, HealthCheckStatus, } from '@conduitplatform/grpc-sdk'; -import AppConfigSchema, { Config } from './config/index.js'; import { AdminHandlers } from './admin/index.js'; import { ISmsProvider } from './interfaces/ISmsProvider.js'; import { TwilioProvider } from './providers/twilio.js'; import { AwsSnsProvider } from './providers/awsSns.js'; import { messageBirdProvider } from './providers/messageBird.js'; import { clickSendProvider } from './providers/clickSend.js'; -import path from 'path'; import { isNil } from 'lodash-es'; import { status } from '@grpc/grpc-js'; +import { ConfigController, RoutingManager } from '@conduitplatform/module-tools'; +import { CommService } from '../../interfaces/CommService.js'; import { SendSmsRequest, SendSmsResponse, @@ -21,39 +21,36 @@ import { SendVerificationCodeResponse, VerifyRequest, VerifyResponse, -} from './protoTypes/sms.js'; -import metricsSchema from './metrics/index.js'; -import { ConfigController, ManagedModule } from '@conduitplatform/module-tools'; -import { fileURLToPath } from 'node:url'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -export default class Sms extends ManagedModule { - configSchema = AppConfigSchema; - service = { - protoPath: path.resolve(__dirname, 'sms.proto'), - protoDescription: 'sms.Sms', - functions: { - sendSms: this.sendSms.bind(this), - sendVerificationCode: this.sendVerificationCode.bind(this), - verify: this.verify.bind(this), - }, +} from '../../protoTypes/comms.js'; + +export default class Sms implements CommService { + readonly functions = { + sendSms: this.sendSms.bind(this), + sendVerificationCode: this.sendVerificationCode.bind(this), + verify: this.verify.bind(this), }; - protected metricsSchema = metricsSchema; + private isRunning: boolean = false; private adminRouter: AdminHandlers; private _provider: ISmsProvider | undefined; + private static _instance: Sms; + private _health: HealthCheckStatus = HealthCheckStatus.UNKNOWN; + + private constructor(private readonly grpcSdk: ConduitGrpcSdk) {} - constructor() { - super('sms'); - this.updateHealth(HealthCheckStatus.UNKNOWN, true); + static getInstance(grpcSdk?: ConduitGrpcSdk) { + if (!Sms._instance) { + if (!grpcSdk) throw new Error('GrpcSdk must be provided'); + Sms._instance = new Sms(grpcSdk); + } + return Sms._instance; } async preConfig(config: any) { if ( - isNil(config.active) || - isNil(config.providerName) || - isNil(config[config.providerName]) + isNil(config.sms.active) || + isNil(config.sms.providerName) || + isNil(config.sms[config.sms.providerName]) ) { throw new Error('Invalid configuration given'); } @@ -61,15 +58,25 @@ export default class Sms extends ManagedModule { } async onConfig() { - if (!ConfigController.getInstance().config.active) { - this.updateHealth(HealthCheckStatus.NOT_SERVING); + if (!ConfigController.getInstance().config.sms.active) { + this._health = HealthCheckStatus.NOT_SERVING; } else { - this.adminRouter = new AdminHandlers(this.grpcServer, this.grpcSdk, this._provider); + this.adminRouter = new AdminHandlers(this.grpcSdk, this._provider); await this.initProvider(); } } - async initializeMetrics() {} + registerAdminRoutes(routingManager: RoutingManager) { + this.adminRouter.registerAdminRoutes(routingManager); + } + + get rpcFunctions() { + return this.functions; + } + + get health() { + return this._health; + } // gRPC Service async sendSms( @@ -176,8 +183,8 @@ export default class Sms extends ManagedModule { this.adminRouter.updateProvider(this._provider!); this.isRunning = true; - this.updateHealth( - this._provider ? HealthCheckStatus.SERVING : HealthCheckStatus.NOT_SERVING, - ); + this._health = this._provider + ? HealthCheckStatus.SERVING + : HealthCheckStatus.NOT_SERVING; } } diff --git a/modules/sms/src/admin/index.ts b/modules/comms/src/modules/sms/admin/index.ts similarity index 77% rename from modules/sms/src/admin/index.ts rename to modules/comms/src/modules/sms/admin/index.ts index 761fe0777..50aac785b 100644 --- a/modules/sms/src/admin/index.ts +++ b/modules/comms/src/modules/sms/admin/index.ts @@ -6,23 +6,19 @@ import { ParsedRouterRequest, UnparsedRouterResponse, } from '@conduitplatform/grpc-sdk'; -import { ConduitString, GrpcServer, RoutingManager } from '@conduitplatform/module-tools'; +import { ConduitString, RoutingManager } from '@conduitplatform/module-tools'; import { status } from '@grpc/grpc-js'; import { isNil } from 'lodash-es'; import { ISmsProvider } from '../interfaces/ISmsProvider.js'; export class AdminHandlers { private provider: ISmsProvider | undefined; - private readonly routingManager: RoutingManager; constructor( - private readonly server: GrpcServer, private readonly grpcSdk: ConduitGrpcSdk, provider: ISmsProvider | undefined, ) { this.provider = provider; - this.routingManager = new RoutingManager(this.grpcSdk.admin, this.server); - this.registerAdminRoutes(); } updateProvider(provider: ISmsProvider) { @@ -45,9 +41,8 @@ export class AdminHandlers { return 'SMS sent'; } - private registerAdminRoutes() { - this.routingManager.clear(); - this.routingManager.route( + registerAdminRoutes(routingManager: RoutingManager) { + routingManager.route( { path: '/send', action: ConduitRouteActions.POST, @@ -60,6 +55,5 @@ export class AdminHandlers { new ConduitRouteReturnDefinition('SendSMS', 'String'), this.sendSms.bind(this), ); - this.routingManager.registerRoutes(); } } diff --git a/modules/sms/src/interfaces/ISmsProvider.ts b/modules/comms/src/modules/sms/interfaces/ISmsProvider.ts similarity index 100% rename from modules/sms/src/interfaces/ISmsProvider.ts rename to modules/comms/src/modules/sms/interfaces/ISmsProvider.ts diff --git a/modules/sms/src/providers/awsSns.ts b/modules/comms/src/modules/sms/providers/awsSns.ts similarity index 100% rename from modules/sms/src/providers/awsSns.ts rename to modules/comms/src/modules/sms/providers/awsSns.ts diff --git a/modules/sms/src/providers/clickSend.ts b/modules/comms/src/modules/sms/providers/clickSend.ts similarity index 100% rename from modules/sms/src/providers/clickSend.ts rename to modules/comms/src/modules/sms/providers/clickSend.ts diff --git a/modules/sms/src/providers/messageBird.ts b/modules/comms/src/modules/sms/providers/messageBird.ts similarity index 100% rename from modules/sms/src/providers/messageBird.ts rename to modules/comms/src/modules/sms/providers/messageBird.ts diff --git a/modules/sms/src/providers/twilio.ts b/modules/comms/src/modules/sms/providers/twilio.ts similarity index 100% rename from modules/sms/src/providers/twilio.ts rename to modules/comms/src/modules/sms/providers/twilio.ts diff --git a/modules/sms/src/utils/index.ts b/modules/comms/src/modules/sms/utils/index.ts similarity index 100% rename from modules/sms/src/utils/index.ts rename to modules/comms/src/modules/sms/utils/index.ts diff --git a/modules/comms/src/router/index.ts b/modules/comms/src/router/index.ts new file mode 100644 index 000000000..17dbd2c73 --- /dev/null +++ b/modules/comms/src/router/index.ts @@ -0,0 +1,33 @@ +import { ConduitGrpcSdk } from '@conduitplatform/grpc-sdk'; +import { GrpcServer, RoutingManager } from '@conduitplatform/module-tools'; +import Sms from '../modules/sms/Sms.js'; +import { CommService } from '../interfaces/CommService.js'; + +export class ClientRouteHandlers { + private readonly routingManager: RoutingManager; + private static instance: ClientRouteHandlers | null = null; + + private constructor( + readonly server: GrpcServer, + readonly grpcSdk: ConduitGrpcSdk, + ) { + this.routingManager = new RoutingManager(grpcSdk.router!, server); + } + + public static getInstance(server?: GrpcServer, grpcSdk?: ConduitGrpcSdk) { + if (!ClientRouteHandlers.instance) { + if (!server || !grpcSdk) throw new Error('Server and grpcSdk must be provided'); + ClientRouteHandlers.instance = new ClientRouteHandlers(server!, grpcSdk!); + } + return ClientRouteHandlers.instance; + } + + async registerRoutes() { + this.routingManager.clear(); + await (Sms.getInstance() as CommService).registerRoutes?.(this.routingManager); + // await Email.getInstance().registerAdminRoutes(this.routingManager); + // await PushNotifications.getInstance().registerAdminRoutes(this.routingManager); + + await this.routingManager.registerRoutes(); + } +} diff --git a/modules/email/tsconfig.json b/modules/comms/tsconfig.json similarity index 100% rename from modules/email/tsconfig.json rename to modules/comms/tsconfig.json diff --git a/modules/email/src/config/config.ts b/modules/email/src/config/config.ts deleted file mode 100644 index 052c828d5..000000000 --- a/modules/email/src/config/config.ts +++ /dev/null @@ -1,128 +0,0 @@ -export default { - doc: 'The options for the conduit email provider', - active: { - format: 'Boolean', - default: false, - }, - transport: { - format: 'String', - default: 'smtp', - }, - sendingDomain: { - format: 'String', - default: 'conduit.com', - }, - transportSettings: { - mailgun: { - apiKey: { - doc: 'The email service API key', - format: 'String', - default: '', - }, - host: { - doc: 'The host for email service', - format: 'String', - default: '', - }, - proxy: { - doc: 'The email proxy', - format: 'String', - default: '', - }, - }, - smtp: { - port: { - doc: 'The port the SMTP server is listening on', - format: 'Number', - default: -1, - }, - secure: { - format: 'Boolean', - default: false, - }, - ignoreTls: { - format: 'Boolean', - default: false, - }, - host: { - doc: 'The SMTP server address', - format: 'String', - default: '', - }, - auth: { - username: { - format: 'String', - default: '', - }, - password: { - format: 'String', - default: '', - }, - method: { - format: 'String', - default: 'PLAIN', - }, - }, - }, - mandrill: { - apiKey: { - doc: 'The Mandrill API key', - format: 'String', - default: '', - }, - }, - sendgrid: { - residency: { - doc: 'Sets the host for sendgrid provider. SendGrid Default: global', - format: 'String', - default: '', - }, - apiKey: { - doc: 'The SendGrid API key', - format: 'String', - default: '', - }, - }, - }, - storeEmails: { - enabled: { - doc: 'Defines if sent email info should be stored in database', - format: 'Boolean', - default: false, - }, - storage: { - enabled: { - doc: 'Defines if email content should be stored in storage', - format: 'Boolean', - default: false, - }, - container: { - doc: 'The storage container for emails', - format: 'String', - default: 'conduit', - }, - folder: { - doc: 'The storage folder for emails', - format: 'String', - default: 'cnd-stored-emails', - }, - }, - cleanupSettings: { - enabled: { - doc: 'Settings for deleting old stored emails', - format: 'Boolean', - default: false, - }, - repeat: { - doc: 'Time in milliseconds to repeat the cleanup job', - format: 'Number', - default: 6 * 60 * 60 * 1000, - }, - limit: { - doc: 'Amount of stored emails to be deleted upon cleanup', - format: 'Number', - default: 100, - }, - }, - }, -}; diff --git a/modules/email/src/config/index.ts b/modules/email/src/config/index.ts deleted file mode 100644 index 97068378e..000000000 --- a/modules/email/src/config/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import convict from 'convict'; -import AppConfigSchema from './config.js'; - -const config = convict(AppConfigSchema); -const configProperties = config.getProperties(); -export type Config = typeof configProperties; -export default AppConfigSchema; diff --git a/modules/email/src/email.proto b/modules/email/src/email.proto deleted file mode 100644 index 66216ac65..000000000 --- a/modules/email/src/email.proto +++ /dev/null @@ -1,54 +0,0 @@ -syntax = 'proto3'; -package email; - -message RegisterTemplateRequest { - string name = 1; - string subject = 2; - string body = 3; - repeated string variables = 4; - optional string sender = 5; -} - -message RegisterTemplateResponse { - string template = 1; -} - -message SendEmailRequest { - string templateName = 1; - SendEmailParams params = 2; - message SendEmailParams { - string email = 1; - string variables = 2; - optional string sender = 3; - repeated string cc = 4; - optional string replyTo = 5; - repeated string attachments = 6; - } -} - -message SendEmailResponse { - string sentMessageInfo = 1; -} - -message ResendEmailRequest { - string emailRecordId = 1; -} - -message ResendEmailResponse { - string sentMessageInfo = 1; -} - -message GetEmailStatusRequest { - string messageId = 1; -} - -message GetEmailStatusResponse { - string statusInfo = 1; -} - -service Email { - rpc RegisterTemplate(RegisterTemplateRequest) returns (RegisterTemplateResponse); - rpc SendEmail(SendEmailRequest) returns (SendEmailResponse); - rpc ResendEmail(ResendEmailRequest) returns (ResendEmailResponse); - rpc GetEmailStatus(GetEmailStatusRequest) returns (GetEmailStatusResponse); -} diff --git a/modules/email/src/index.ts b/modules/email/src/index.ts deleted file mode 100644 index 96c53b076..000000000 --- a/modules/email/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import EmailModule from './Email.js'; - -const email = new EmailModule(); -email.start(); diff --git a/modules/push-notifications/.gitignore b/modules/push-notifications/.gitignore deleted file mode 100644 index f06235c46..000000000 --- a/modules/push-notifications/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/modules/push-notifications/Dockerfile b/modules/push-notifications/Dockerfile deleted file mode 100644 index 25bbf2c13..000000000 --- a/modules/push-notifications/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM conduit-builder:latest - -WORKDIR /app - -COPY --from=conduit-base:latest /app/modules/push-notifications /app/modules/push-notifications - -RUN yarn install --production --pure-lockfile --non-interactive && yarn cache clean - -WORKDIR /app/modules/push-notifications - -ENV CONDUIT_SERVER conduit_server - -ENV SERVICE_URL 0.0.0.0:5000 - -ENV GRPC_PORT 5000 - -EXPOSE 5000 - -CMD ["yarn", "start"] diff --git a/modules/push-notifications/README.mdx b/modules/push-notifications/README.mdx deleted file mode 100644 index bbdec386f..000000000 --- a/modules/push-notifications/README.mdx +++ /dev/null @@ -1,22 +0,0 @@ -# Push Notifications Module - -Handles sending push notifications to users. - -## Features ✔️ - -- Compatible with Firebase Push Notification Service -- Easily configurable -- Failover baked-in - -## Requirements ⚡ - -- [Database](../database) module - -## Environment Variables 📃 - -| Variable | Description | Required | Example | -|:--------------------:|:-----------------------------------------------------| :------: |:------------------:| -| `CONDUIT_SERVER` | Conduit Core's address and port | True | `0.0.0.0:55152` | -| `SERVICE_URL` | This should be where this service listens on. If behind a LB it should point to the LB's IP/DNS | False | `0.0.0.0:55190` | -| `GRPC_PORT` | The port number the gRPC server will listen to | False | `55190` | -| `GRPC_KEY` | Specifying a secret enables gRPC signed request protection (**use across modules**) | False | `someRandomSecret` | diff --git a/modules/push-notifications/build.sh b/modules/push-notifications/build.sh deleted file mode 100644 index 6b5d21623..000000000 --- a/modules/push-notifications/build.sh +++ /dev/null @@ -1,20 +0,0 @@ -rm -rf ./src/protoTypes -mkdir ./src/protoTypes - - -cp ./src/*.proto ./src/protoTypes - -cd ./src/protoTypes || exit - -echo "Generating typescript code" -protoc \ - --plugin=protoc-gen-ts_proto=../../node_modules/.bin/protoc-gen-ts_proto \ - --ts_proto_opt=esModuleInterop=true \ - --ts_proto_opt=outputServices=generic-definitions,useExactTypes=false \ - --ts_proto_out=./ \ - --ts_proto_opt=importSuffix=.js \ - --ts_proto_opt=snakeToCamel=false \ - ./*.proto - -echo "Cleaning up folders" -rm -rf ./*.proto diff --git a/modules/push-notifications/package.json b/modules/push-notifications/package.json deleted file mode 100644 index 504eb9698..000000000 --- a/modules/push-notifications/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@conduitplatform/push-notifications", - "version": "1.0.1", - "description": "Handles sending push notifications to users", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "type": "module", - "private": true, - "engines": { - "node": ">=14" - }, - "scripts": { - "start": "node dist/index.js", - "prepublish": "npm run build", - "prebuild": "npm run generateTypes", - "build": "rimraf dist && tsc", - "postbuild": "copyfiles -u 1 src/**/*.proto src/*.proto src/**/*.json ./dist/", - "prepare": "npm run build", - "generateTypes": "sh build.sh", - "build:docker": "docker build -t ghcr.io/conduitplatform/push-notifications:latest -f ./Dockerfile ../../ && docker push ghcr.io/conduitplatform/push-notifications:latest" - }, - "author": "", - "license": "ISC", - "directories": { - "lib": "src" - }, - "publishConfig": { - "registry": "https://npm.pkg.github.com/" - }, - "files": [ - "src" - ], - "dependencies": { - "@conduitplatform/grpc-sdk": "*", - "@conduitplatform/module-tools": "*", - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.6", - "@onesignal/node-onesignal": "^1.0.0-beta9", - "convict": "^6.2.4", - "firebase-admin": "^12.4.0", - "lodash-es": "^4.17.21" - }, - "devDependencies": { - "@types/convict": "^6.1.6", - "@types/lodash-es": "^4.17.12", - "@types/node": "20.11.24", - "copyfiles": "^2.4.1", - "ts-proto": "^1.167.1", - "rimraf": "^5.0.5", - "typescript": "~5.6.2" - } -} diff --git a/modules/push-notifications/src/config/config.ts b/modules/push-notifications/src/config/config.ts deleted file mode 100644 index b1a196aa3..000000000 --- a/modules/push-notifications/src/config/config.ts +++ /dev/null @@ -1,34 +0,0 @@ -export default { - active: { - format: 'Boolean', - default: true, - }, - providerName: { - format: 'String', - default: 'basic', - }, - firebase: { - projectId: { - format: 'String', - default: '', - }, - privateKey: { - format: 'String', - default: '', - }, - clientEmail: { - format: 'String', - default: '', - }, - }, - onesignal: { - appId: { - format: 'String', - default: '', - }, - apiKey: { - format: 'String', - default: '', - }, - }, -}; diff --git a/modules/push-notifications/src/config/index.ts b/modules/push-notifications/src/config/index.ts deleted file mode 100644 index 97068378e..000000000 --- a/modules/push-notifications/src/config/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import convict from 'convict'; -import AppConfigSchema from './config.js'; - -const config = convict(AppConfigSchema); -const configProperties = config.getProperties(); -export type Config = typeof configProperties; -export default AppConfigSchema; diff --git a/modules/push-notifications/src/index.ts b/modules/push-notifications/src/index.ts deleted file mode 100644 index dc1e895c7..000000000 --- a/modules/push-notifications/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import PushNotificationsModule from './PushNotifications.js'; - -const pushNotifications = new PushNotificationsModule(); -pushNotifications.start(); diff --git a/modules/push-notifications/src/metrics/index.ts b/modules/push-notifications/src/metrics/index.ts deleted file mode 100644 index f380e35ba..000000000 --- a/modules/push-notifications/src/metrics/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MetricType } from '@conduitplatform/grpc-sdk'; - -export default { - pushNotifications: { - type: MetricType.Counter, - config: { - name: 'push_notifications_sent_total', - help: 'Tracks the total number of push notifications sent', - }, - }, -}; diff --git a/modules/push-notifications/src/push-notifications.proto b/modules/push-notifications/src/push-notifications.proto deleted file mode 100644 index d32e28ca2..000000000 --- a/modules/push-notifications/src/push-notifications.proto +++ /dev/null @@ -1,56 +0,0 @@ -syntax = 'proto3'; -package pushnotifications; - -message SetNotificationTokenRequest { - string token = 1; - string platform = 2; - string userId = 3; -} - -message SetNotificationTokenResponse { - string newTokenDocument = 1; -} - -message GetNotificationTokensRequest { - string userId = 1; -} - -message GetNotificationTokensResponse { - repeated string tokenDocuments = 1; -} - -message SendNotificationRequest { - string sendTo = 1; - optional string title = 2; - optional string platform = 3; - optional string body = 4; - optional string data = 5; - optional bool doNotStore = 6; - optional bool isSilent = 7; -} - -message SendNotificationToManyDevicesRequest { - repeated string sendTo = 1; - optional string title = 2; - optional string body = 3; - optional string data = 4; - optional string platform = 5; - optional bool doNotStore = 6; - optional bool isSilent = 7; -} - -message SendManyNotificationsRequest { - repeated SendNotificationRequest notifications = 1; -} - -message SendNotificationResponse { - string message = 1; -} - -service PushNotifications { - rpc SetNotificationToken(SetNotificationTokenRequest) returns (SetNotificationTokenResponse); - rpc GetNotificationTokens(GetNotificationTokensRequest) returns (GetNotificationTokensResponse); - rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse); - rpc SendNotificationToManyDevices(SendNotificationToManyDevicesRequest) returns (SendNotificationResponse); - rpc SendManyNotifications(SendManyNotificationsRequest) returns (SendNotificationResponse); -} diff --git a/modules/push-notifications/tsconfig.json b/modules/push-notifications/tsconfig.json deleted file mode 100644 index fefcee0ac..000000000 --- a/modules/push-notifications/tsconfig.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "compilerOptions": { - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "ESNext", - "module": "NodeNext", - "resolveJsonModule": true, - "skipLibCheck": true, - /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true /* Generates corresponding '.d.ts' file. */, - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true /* Generates corresponding '.map' file. */, - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist" /* Redirect output structure to the directory. */, - // "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - "removeComments": true /* Do not emit comments to output. */, - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - "moduleResolution": "NodeNext", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} diff --git a/modules/sms/Dockerfile b/modules/sms/Dockerfile deleted file mode 100644 index b7458072c..000000000 --- a/modules/sms/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM conduit-builder:latest - -WORKDIR /app - -COPY --from=conduit-base:latest /app/modules/sms /app/modules/sms - -RUN yarn install --production --pure-lockfile --non-interactive && yarn cache clean - -WORKDIR /app/modules/sms - -ENV CONDUIT_SERVER conduit_server - -ENV SERVICE_URL 0.0.0.0:5000 - -ENV GRPC_PORT 5000 - -EXPOSE 5000 - -CMD ["yarn", "start"] diff --git a/modules/sms/README.mdx b/modules/sms/README.mdx deleted file mode 100644 index 95fdf1dd1..000000000 --- a/modules/sms/README.mdx +++ /dev/null @@ -1,22 +0,0 @@ -# SMS Module - -This module provides support for SMS messages.
-Messages are delivered using third party providers. - -## Features ✔️ - -- Sending SMS -- Twilio support - -## Requirements ⚡ - -- [Database](../database) module - -## Environment Variables 📃 - -| Variable | Description | Required | Example | -|:--------------------:|:-----------------------------------------------------| :------: |:------------------:| -| `CONDUIT_SERVER` | Conduit Core's address and port | True | `0.0.0.0:55152` | -| `SERVICE_URL` | This should be where this service listens on. If behind a LB it should point to the LB's IP/DNS | False | `0.0.0.0:55190` | -| `GRPC_PORT` | The port number the gRPC server will listen to | False | `55190` | -| `GRPC_KEY` | Specifying a secret enables gRPC signed request protection (**use across modules**) | False | `someRandomSecret` | diff --git a/modules/sms/build.sh b/modules/sms/build.sh deleted file mode 100644 index 6b5d21623..000000000 --- a/modules/sms/build.sh +++ /dev/null @@ -1,20 +0,0 @@ -rm -rf ./src/protoTypes -mkdir ./src/protoTypes - - -cp ./src/*.proto ./src/protoTypes - -cd ./src/protoTypes || exit - -echo "Generating typescript code" -protoc \ - --plugin=protoc-gen-ts_proto=../../node_modules/.bin/protoc-gen-ts_proto \ - --ts_proto_opt=esModuleInterop=true \ - --ts_proto_opt=outputServices=generic-definitions,useExactTypes=false \ - --ts_proto_out=./ \ - --ts_proto_opt=importSuffix=.js \ - --ts_proto_opt=snakeToCamel=false \ - ./*.proto - -echo "Cleaning up folders" -rm -rf ./*.proto diff --git a/modules/sms/package.json b/modules/sms/package.json deleted file mode 100644 index 77d1a4348..000000000 --- a/modules/sms/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@conduitplatform/sms", - "version": "1.0.0", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "private": true, - "type": "module", - "engines": { - "node": ">=14" - }, - "scripts": { - "start": "node dist/index.js", - "postbuild": "copyfiles -u 1 src/**/*.proto src/*.proto src/**/*.json ./dist/", - "prebuild": "npm run generateTypes", - "build": "rimraf dist && tsc", - "prepare": "npm run build", - "generateTypes": "sh build.sh", - "build:docker": "docker build -t ghcr.io/conduitplatform/sms:latest -f ./Dockerfile ../../ && docker push ghcr.io/conduitplatform/sms:latest" - }, - "directories": { - "lib": "src" - }, - "files": [ - "src" - ], - "license": "ISC", - "dependencies": { - "@aws-sdk/client-sns": "^3.627.1", - "@conduitplatform/grpc-sdk": "*", - "@conduitplatform/module-tools": "*", - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.6", - "bluebird": "^3.7.2", - "clicksend": "^5.0.79", - "convict": "^6.2.4", - "lodash-es": "^4.17.21", - "messagebird": "^4.0.1", - "otp-generator": "^4.0.1", - "twilio": "5.3.0" - }, - "devDependencies": { - "@types/bluebird": "^3.5.42", - "@types/otp-generator": "^4.0.2", - "@types/convict": "^6.1.6", - "@types/lodash-es": "^4.17.12", - "@types/node": "20.11.24", - "copyfiles": "^2.4.1", - "rimraf": "^5.0.5", - "ts-proto": "^1.167.1", - "typescript": "~5.6.2" - } -} diff --git a/modules/sms/src/config/config.ts b/modules/sms/src/config/config.ts deleted file mode 100644 index 1a1a0b77f..000000000 --- a/modules/sms/src/config/config.ts +++ /dev/null @@ -1,69 +0,0 @@ -export default { - doc: 'The options for the conduit sms provider', - active: { - format: 'Boolean', - default: false, - }, - providerName: { - format: 'String', - default: 'twilio', - }, - twilio: { - phoneNumber: { - format: 'String', - default: '', - }, - accountSID: { - format: 'String', - default: '', - }, - authToken: { - format: 'String', - default: '', - }, - verify: { - active: { - format: 'Boolean', - default: false, - }, - serviceSid: { - format: 'String', - default: '', - }, - }, - }, - awsSns: { - region: { - format: 'String', - default: '', - }, - accessKeyId: { - format: 'String', - default: '', - }, - secretAccessKey: { - format: 'String', - default: '', - }, - }, - messageBird: { - accessKeyId: { - format: 'String', - default: '', - }, - originatorName: { - format: 'String', - default: '', - }, - }, - clickSend: { - username: { - format: 'String', - default: '', - }, - clicksendApiKey: { - format: 'String', - default: '', - }, - }, -}; diff --git a/modules/sms/src/config/index.ts b/modules/sms/src/config/index.ts deleted file mode 100644 index 97068378e..000000000 --- a/modules/sms/src/config/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import convict from 'convict'; -import AppConfigSchema from './config.js'; - -const config = convict(AppConfigSchema); -const configProperties = config.getProperties(); -export type Config = typeof configProperties; -export default AppConfigSchema; diff --git a/modules/sms/src/index.ts b/modules/sms/src/index.ts deleted file mode 100644 index 65974b54c..000000000 --- a/modules/sms/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import SmsModule from './Sms.js'; - -const sms = new SmsModule(); -sms.start(); diff --git a/modules/sms/src/metrics/index.ts b/modules/sms/src/metrics/index.ts deleted file mode 100644 index 5227cdb8e..000000000 --- a/modules/sms/src/metrics/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MetricType } from '@conduitplatform/grpc-sdk'; - -export default { - smsSent: { - type: MetricType.Counter, - config: { - name: 'sms_sent_total', - help: 'Tracks the total number of sms sent', - }, - }, -}; diff --git a/modules/sms/src/sms.proto b/modules/sms/src/sms.proto deleted file mode 100644 index 472dc0d43..000000000 --- a/modules/sms/src/sms.proto +++ /dev/null @@ -1,34 +0,0 @@ -syntax = 'proto3'; -package sms; - -message SendSmsRequest { - string to = 1; - string message = 2; -} - -message SendSmsResponse { - string message = 1; -} - -message SendVerificationCodeRequest { - string to = 1; -} - -message SendVerificationCodeResponse { - string verificationSid = 1; -} - -message VerifyRequest { - string verificationSid = 1; - string code = 2; -} - -message VerifyResponse { - bool verified = 1; -} - -service Sms { - rpc SendSms(SendSmsRequest) returns (SendSmsResponse); - rpc SendVerificationCode(SendVerificationCodeRequest) returns (SendVerificationCodeResponse); - rpc Verify(VerifyRequest) returns (VerifyResponse); -} diff --git a/modules/sms/tsconfig.json b/modules/sms/tsconfig.json deleted file mode 100644 index fefcee0ac..000000000 --- a/modules/sms/tsconfig.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "compilerOptions": { - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "ESNext", - "module": "NodeNext", - "resolveJsonModule": true, - "skipLibCheck": true, - /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true /* Generates corresponding '.d.ts' file. */, - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true /* Generates corresponding '.map' file. */, - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist" /* Redirect output structure to the directory. */, - // "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - "removeComments": true /* Do not emit comments to output. */, - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - "moduleResolution": "NodeNext", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} From de8cfa5698501eada68e5beb70a7432672f8638e Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 15:51:30 +0300 Subject: [PATCH 04/14] feat(comms): feature available request --- modules/comms/package.json | 4 ++-- modules/comms/src/Comms.ts | 29 +++++++++++++++++++++++++++-- modules/comms/src/comms.proto | 13 +++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/comms/package.json b/modules/comms/package.json index d8744c4a4..75bad0547 100644 --- a/modules/comms/package.json +++ b/modules/comms/package.json @@ -1,5 +1,5 @@ { - "name": "@conduitplatform/email", + "name": "@conduitplatform/comms", "version": "1.0.1", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "prebuild": "npm run generateTypes", "build": "rimraf dist && tsc", "prepare": "npm run build", - "build:docker": "docker build -t ghcr.io/conduitplatform/email:latest -f ./Dockerfile ../../ && docker push ghcr.io/conduitplatform/email:latest", + "build:docker": "docker build -t ghcr.io/conduitplatform/comms:latest -f ./Dockerfile ../../ && docker push ghcr.io/conduitplatform/comms:latest", "generateTypes": "sh build.sh" }, "directories": { diff --git a/modules/comms/src/Comms.ts b/modules/comms/src/Comms.ts index 238e722a6..87ba317df 100644 --- a/modules/comms/src/Comms.ts +++ b/modules/comms/src/Comms.ts @@ -1,4 +1,9 @@ -import { ConduitGrpcSdk, HealthCheckStatus } from '@conduitplatform/grpc-sdk'; +import { + ConduitGrpcSdk, + GrpcCallback, + GrpcRequest, + HealthCheckStatus, +} from '@conduitplatform/grpc-sdk'; import AppConfigSchema, { Config } from './config/index.js'; import { AdminHandlers } from './admin/index.js'; import path from 'path'; @@ -8,6 +13,7 @@ import { fileURLToPath } from 'node:url'; import Sms from './modules/sms/Sms.js'; import { CommService } from './interfaces/CommService.js'; import { ClientRouteHandlers } from './router/index.js'; +import { FeatureAvailableRequest, FeatureAvailableResponse } from './protoTypes/comms.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -31,7 +37,9 @@ export default class Comms extends ManagedModule { protoPath: path.resolve(__dirname, 'comms.proto'), protoDescription: 'comms', functions: { - Comms: {}, + Comms: { + featureAvailable: this.featureAvailable.bind(this), + }, Sms: { ...(this.smsService.rpcFunctions as { [key: string]: ServiceFunction }), }, @@ -96,4 +104,21 @@ export default class Comms extends ManagedModule { await this.emailService.initializeMetrics?.(); await this.pushService.initializeMetrics?.(); } + + // gRPC Service + async featureAvailable( + call: GrpcRequest, + callback: GrpcCallback, + ) { + const feature = call.request.serviceName; + let available = false; + if (feature === 'sms') { + available = this.smsService.health === HealthCheckStatus.SERVING; + } else if (feature === 'email') { + available = this.emailService.health === HealthCheckStatus.SERVING; + } else if (feature === 'push') { + available = this.pushService.health === HealthCheckStatus.SERVING; + } + callback(null, { available }); + } } diff --git a/modules/comms/src/comms.proto b/modules/comms/src/comms.proto index 2e431f14f..023a9789b 100644 --- a/modules/comms/src/comms.proto +++ b/modules/comms/src/comms.proto @@ -1,5 +1,18 @@ syntax = 'proto3'; package comms; + +message FeatureAvailableRequest{ + string serviceName = 1; +} + +message FeatureAvailableResponse{ + bool available = 1; +} + +service Comms { + rpc FeatureAvailable(FeatureAvailableRequest) returns (FeatureAvailableResponse); +} + //------ SMS ------ message SendSmsRequest { string to = 1; From b1b9bbb39cbbbb2cc52e5b3fe5b9c88ece94c330 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:38:14 +0300 Subject: [PATCH 05/14] fix(comms): wrong module name on start --- modules/comms/src/Comms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/comms/src/Comms.ts b/modules/comms/src/Comms.ts index 87ba317df..0b8346a71 100644 --- a/modules/comms/src/Comms.ts +++ b/modules/comms/src/Comms.ts @@ -26,7 +26,7 @@ export default class Comms extends ManagedModule { private readonly pushService: CommService; constructor() { - super('sms'); + super('comms'); this.updateHealth(HealthCheckStatus.UNKNOWN, true); AdminHandlers.getInstance(this.grpcServer, this.grpcSdk); ClientRouteHandlers.getInstance(this.grpcServer, this.grpcSdk); From 2105306eb96f73db40233c9c06f543b99c44a7d8 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:38:32 +0300 Subject: [PATCH 06/14] feat(comms): init admin and client routes for all services --- modules/comms/src/admin/index.ts | 5 ++--- modules/comms/src/router/index.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/comms/src/admin/index.ts b/modules/comms/src/admin/index.ts index c3915c1e0..a8765aef0 100644 --- a/modules/comms/src/admin/index.ts +++ b/modules/comms/src/admin/index.ts @@ -26,9 +26,8 @@ export class AdminHandlers { async registerAdminRoutes() { this.routingManager.clear(); await Sms.getInstance().registerAdminRoutes(this.routingManager); - // Email.getInstance().registerAdminRoutes(this.routingManager); - // PushNotifications.getInstance().registerAdminRoutes(this.routingManager); - + await Email.getInstance().registerAdminRoutes(this.routingManager); + await PushNotifications.getInstance().registerAdminRoutes(this.routingManager); await this.routingManager.registerRoutes(); } } diff --git a/modules/comms/src/router/index.ts b/modules/comms/src/router/index.ts index 17dbd2c73..dcd5ad402 100644 --- a/modules/comms/src/router/index.ts +++ b/modules/comms/src/router/index.ts @@ -2,6 +2,8 @@ import { ConduitGrpcSdk } from '@conduitplatform/grpc-sdk'; import { GrpcServer, RoutingManager } from '@conduitplatform/module-tools'; import Sms from '../modules/sms/Sms.js'; import { CommService } from '../interfaces/CommService.js'; +import Email from '../modules/email/Email.js'; +import PushNotifications from '../modules/push/PushNotifications.js'; export class ClientRouteHandlers { private readonly routingManager: RoutingManager; @@ -25,8 +27,10 @@ export class ClientRouteHandlers { async registerRoutes() { this.routingManager.clear(); await (Sms.getInstance() as CommService).registerRoutes?.(this.routingManager); - // await Email.getInstance().registerAdminRoutes(this.routingManager); - // await PushNotifications.getInstance().registerAdminRoutes(this.routingManager); + await (Email.getInstance() as CommService).registerAdminRoutes?.(this.routingManager); + await (PushNotifications.getInstance() as CommService).registerAdminRoutes?.( + this.routingManager, + ); await this.routingManager.registerRoutes(); } From ac5c053fd2a0c7b2f2637fa8e653b84218e799c3 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:38:58 +0300 Subject: [PATCH 07/14] refactor(grpc-sdk): introduce comms module, move email,sms and push services to comms --- libraries/grpc-sdk/src/index.ts | 34 +++------------- .../src/modules/{ => comms}/email/index.ts | 4 +- libraries/grpc-sdk/src/modules/comms/index.ts | 39 +++++++++++++++++++ .../{ => comms}/pushNotifications/index.ts | 4 +- .../{ => comms}/pushNotifications/types.ts | 0 .../src/modules/{ => comms}/sms/index.ts | 4 +- libraries/grpc-sdk/src/modules/index.ts | 4 +- 7 files changed, 52 insertions(+), 37 deletions(-) rename libraries/grpc-sdk/src/modules/{ => comms}/email/index.ts (92%) create mode 100644 libraries/grpc-sdk/src/modules/comms/index.ts rename libraries/grpc-sdk/src/modules/{ => comms}/pushNotifications/index.ts (96%) rename libraries/grpc-sdk/src/modules/{ => comms}/pushNotifications/types.ts (100%) rename libraries/grpc-sdk/src/modules/{ => comms}/sms/index.ts (88%) diff --git a/libraries/grpc-sdk/src/index.ts b/libraries/grpc-sdk/src/index.ts index 93c906823..a3c3b64fe 100644 --- a/libraries/grpc-sdk/src/index.ts +++ b/libraries/grpc-sdk/src/index.ts @@ -3,13 +3,11 @@ import { Authentication, Authorization, Chat, + Comms, Config, Core, DatabaseProvider, - Email, - PushNotifications, Router, - SMS, Storage, } from './modules/index.js'; import crypto from 'crypto'; @@ -51,11 +49,9 @@ class ConduitGrpcSdk { router: Router, database: DatabaseProvider, storage: Storage, - email: Email, - pushNotifications: PushNotifications, + comms: Comms, authentication: Authentication, authorization: Authorization, - sms: SMS, chat: Chat, }; private _dynamicModules: { [key: string]: CompatServiceDefinition } = {}; @@ -213,20 +209,11 @@ class ConduitGrpcSdk { } } - get emailProvider(): Email | null { - if (this._modules['email']) { - return this._modules['email'] as Email; + get comms(): Comms | null { + if (this._modules['comms']) { + return this._modules['comms'] as Comms; } else { - ConduitGrpcSdk.Logger.warn('Email provider not up yet!'); - return null; - } - } - - get pushNotifications(): PushNotifications | null { - if (this._modules['pushNotifications']) { - return this._modules['pushNotifications'] as PushNotifications; - } else { - ConduitGrpcSdk.Logger.warn('Push notifications module not up yet!'); + ConduitGrpcSdk.Logger.warn('Comms not up yet!'); return null; } } @@ -249,15 +236,6 @@ class ConduitGrpcSdk { } } - get sms(): SMS | null { - if (this._modules['sms']) { - return this._modules['sms'] as SMS; - } else { - ConduitGrpcSdk.Logger.warn('SMS module not up yet!'); - return null; - } - } - get chat(): Chat | null { if (this._modules['chat']) { return this._modules['chat'] as Chat; diff --git a/libraries/grpc-sdk/src/modules/email/index.ts b/libraries/grpc-sdk/src/modules/comms/email/index.ts similarity index 92% rename from libraries/grpc-sdk/src/modules/email/index.ts rename to libraries/grpc-sdk/src/modules/comms/email/index.ts index 61e52cf13..1f930490f 100644 --- a/libraries/grpc-sdk/src/modules/email/index.ts +++ b/libraries/grpc-sdk/src/modules/comms/email/index.ts @@ -1,5 +1,5 @@ -import { ConduitModule } from '../../classes/index.js'; -import { EmailDefinition } from '../../protoUtils/email.js'; +import { ConduitModule } from '../../../classes/index.js'; +import { EmailDefinition } from '../../../protoUtils/index.js'; export class Email extends ConduitModule { constructor( diff --git a/libraries/grpc-sdk/src/modules/comms/index.ts b/libraries/grpc-sdk/src/modules/comms/index.ts new file mode 100644 index 000000000..9b1ae38cb --- /dev/null +++ b/libraries/grpc-sdk/src/modules/comms/index.ts @@ -0,0 +1,39 @@ +import { ConduitModule } from '../../classes/index.js'; +import { CommsDefinition } from '../../protoUtils/index.js'; +import { Email } from './email'; +import { SMS } from './sms'; +import { PushNotifications } from './pushNotifications'; + +export class Comms extends ConduitModule { + private readonly _email: Email; + private readonly _sms: SMS; + private readonly _pushNotifications: PushNotifications; + + constructor( + private readonly moduleName: string, + url: string, + grpcToken?: string, + ) { + super(moduleName, 'comms', url, grpcToken); + this.initializeClient(CommsDefinition); + this._email = new Email(moduleName, url, grpcToken); + this._sms = new SMS(moduleName, url, grpcToken); + this._pushNotifications = new PushNotifications(moduleName, url, grpcToken); + } + + get email() { + return this._email; + } + + get sms() { + return this._sms; + } + + get pushNotifications() { + return this._pushNotifications; + } + + featureAvailable(name: string) { + return this.client!.featureAvailable({ serviceName: name }); + } +} diff --git a/libraries/grpc-sdk/src/modules/pushNotifications/index.ts b/libraries/grpc-sdk/src/modules/comms/pushNotifications/index.ts similarity index 96% rename from libraries/grpc-sdk/src/modules/pushNotifications/index.ts rename to libraries/grpc-sdk/src/modules/comms/pushNotifications/index.ts index 72f5bd769..c5bddcc07 100644 --- a/libraries/grpc-sdk/src/modules/pushNotifications/index.ts +++ b/libraries/grpc-sdk/src/modules/comms/pushNotifications/index.ts @@ -1,8 +1,8 @@ -import { ConduitModule } from '../../classes/index.js'; +import { ConduitModule } from '../../../classes/index.js'; import { PushNotificationsDefinition, SendNotificationResponse, -} from '../../protoUtils/index.js'; +} from '../../../protoUtils/index.js'; import { SendNotificationOptions } from './types'; import { isNil } from 'lodash'; diff --git a/libraries/grpc-sdk/src/modules/pushNotifications/types.ts b/libraries/grpc-sdk/src/modules/comms/pushNotifications/types.ts similarity index 100% rename from libraries/grpc-sdk/src/modules/pushNotifications/types.ts rename to libraries/grpc-sdk/src/modules/comms/pushNotifications/types.ts diff --git a/libraries/grpc-sdk/src/modules/sms/index.ts b/libraries/grpc-sdk/src/modules/comms/sms/index.ts similarity index 88% rename from libraries/grpc-sdk/src/modules/sms/index.ts rename to libraries/grpc-sdk/src/modules/comms/sms/index.ts index 31942e7b8..34fec34ca 100644 --- a/libraries/grpc-sdk/src/modules/sms/index.ts +++ b/libraries/grpc-sdk/src/modules/comms/sms/index.ts @@ -1,10 +1,10 @@ -import { ConduitModule } from '../../classes/index.js'; +import { ConduitModule } from '../../../classes/index.js'; import { SendSmsResponse, SendVerificationCodeResponse, SmsDefinition, VerifyResponse, -} from '../../protoUtils/sms.js'; +} from '../../../protoUtils/index.js'; export class SMS extends ConduitModule { constructor( diff --git a/libraries/grpc-sdk/src/modules/index.ts b/libraries/grpc-sdk/src/modules/index.ts index 16831e4d2..645b33271 100644 --- a/libraries/grpc-sdk/src/modules/index.ts +++ b/libraries/grpc-sdk/src/modules/index.ts @@ -1,12 +1,10 @@ export * from './storage/index.js'; export * from './router/index.js'; -export * from './email/index.js'; export * from './database/index.js'; export * from './config/index.js'; +export * from './comms/index.js'; export * from './core/index.js'; export * from './admin/index.js'; -export * from './pushNotifications/index.js'; -export * from './sms/index.js'; export * from './chat/index.js'; export * from './authorization/index.js'; export * from './authentication/index.js'; From 3d8b592ae75093e33000150b5a545f5264dcf2a2 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:51:59 +0300 Subject: [PATCH 08/14] feat(comms): config migration from email,sms,push to the comms module --- modules/comms/src/Comms.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/modules/comms/src/Comms.ts b/modules/comms/src/Comms.ts index 0b8346a71..93d3083b0 100644 --- a/modules/comms/src/Comms.ts +++ b/modules/comms/src/Comms.ts @@ -53,8 +53,27 @@ export default class Comms extends ManagedModule { }; } - async preConfig(config: any) { + async preConfig(config: Config) { let modifiedConfig = config; + if (!config.migrationComplete) { + const sms = await this.grpcSdk.config.get('sms'); + const email = await this.grpcSdk.config.get('email'); + const push = await this.grpcSdk.config.get('pushNotifications'); + modifiedConfig = { + ...config, + sms: { + ...sms, + }, + email: { + ...email, + }, + push: { + ...push, + }, + migrationComplete: true, + }; + } + modifiedConfig = (await this.smsService.preConfig?.(modifiedConfig)) ?? modifiedConfig; modifiedConfig = @@ -116,7 +135,7 @@ export default class Comms extends ManagedModule { available = this.smsService.health === HealthCheckStatus.SERVING; } else if (feature === 'email') { available = this.emailService.health === HealthCheckStatus.SERVING; - } else if (feature === 'push') { + } else if (feature === 'pushNotifications') { available = this.pushService.health === HealthCheckStatus.SERVING; } callback(null, { available }); From 6b694b884de20f429aea90f88980337190b1b606 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:52:17 +0300 Subject: [PATCH 09/14] feat(database): schema migration from email,sms,push to comms --- modules/database/src/migrations/comms.migration.ts | 13 +++++++++++++ modules/database/src/migrations/index.ts | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 modules/database/src/migrations/comms.migration.ts diff --git a/modules/database/src/migrations/comms.migration.ts b/modules/database/src/migrations/comms.migration.ts new file mode 100644 index 000000000..5d9ea7e41 --- /dev/null +++ b/modules/database/src/migrations/comms.migration.ts @@ -0,0 +1,13 @@ +import { DatabaseAdapter } from '../adapters/DatabaseAdapter.js'; +import { MongooseSchema } from '../adapters/mongoose-adapter/MongooseSchema.js'; +import { SequelizeSchema } from '../adapters/sequelize-adapter/SequelizeSchema.js'; + +export async function migrateCommsSchemas( + adapter: DatabaseAdapter, +) { + const model = adapter.getSchemaModel('_DeclaredSchema').model; + await model.updateMany( + { ownerModule: { $in: ['email', 'sms', 'pushNotifications'] } }, + { ownerModule: 'comms' }, + ); +} diff --git a/modules/database/src/migrations/index.ts b/modules/database/src/migrations/index.ts index c87b9895d..2d8ddda4f 100644 --- a/modules/database/src/migrations/index.ts +++ b/modules/database/src/migrations/index.ts @@ -5,6 +5,7 @@ import { migrateCrudOperations } from './crudOperations.migration.js'; import { migrateSecurityClients } from './securityClients.migration.js'; import { migrateCustomEndpoints } from './customEndpoints.migration.js'; import { migrateSystemSchemasCms } from './systemSchemasCms.migration.js'; +import { migrateCommsSchemas } from './comms.migration.js'; export async function runMigrations( adapter: DatabaseAdapter, @@ -13,4 +14,5 @@ export async function runMigrations( await migrateSecurityClients(adapter); await migrateCustomEndpoints(adapter); await migrateSystemSchemasCms(adapter); + await migrateCommsSchemas(adapter); } From 57f13e62db30e490e1f25a75ce24bac4207f90bf Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:54:53 +0300 Subject: [PATCH 10/14] feat(comms): move admin routes to respective sub-routes --- .../comms/src/modules/email/admin/index.ts | 24 +++++++++---------- modules/comms/src/modules/push/admin/index.ts | 12 +++++----- modules/comms/src/modules/sms/admin/index.ts | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/comms/src/modules/email/admin/index.ts b/modules/comms/src/modules/email/admin/index.ts index 0b285933c..25f90d09c 100644 --- a/modules/comms/src/modules/email/admin/index.ts +++ b/modules/comms/src/modules/email/admin/index.ts @@ -429,7 +429,7 @@ export class AdminHandlers { registerAdminRoutes(routingManager: RoutingManager) { routingManager.route( { - path: '/templates', + path: '/email/templates', action: ConduitRouteActions.GET, description: `Returns queried templates and their total count.`, queryParams: { @@ -447,7 +447,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/templates', + path: '/email/templates', action: ConduitRouteActions.POST, description: `Creates a new email template.`, bodyParams: { @@ -466,7 +466,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/templates/:id', + path: '/email/templates/:id', action: ConduitRouteActions.PATCH, description: `Updates an email template.`, urlParams: { @@ -485,7 +485,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/templates', + path: '/email/templates', action: ConduitRouteActions.DELETE, description: `Deletes queried email templates.`, queryParams: { @@ -499,7 +499,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/templates/:id', + path: '/email/templates/:id', action: ConduitRouteActions.DELETE, description: `Deletes an email template.`, urlParams: { @@ -513,7 +513,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/templates/upload', + path: '/email/templates/upload', action: ConduitRouteActions.POST, description: `Uploads a local email template to remote provider.`, bodyParams: { @@ -527,7 +527,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/externalTemplates', + path: '/email/externalTemplates', action: ConduitRouteActions.GET, description: `Returns external email templates and their total count.`, queryParams: { @@ -544,7 +544,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/syncExternalTemplates', + path: '/email/syncExternalTemplates', action: ConduitRouteActions.UPDATE, description: `Synchronizes local email templates from remote provider.`, }, @@ -556,7 +556,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/send', + path: '/email/send', action: ConduitRouteActions.POST, description: `Sends an email.`, bodyParams: { @@ -575,7 +575,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/resend', + path: '/email/resend', action: ConduitRouteActions.POST, description: `Resends an email (only if stored in storage).`, bodyParams: { @@ -589,7 +589,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/status', + path: '/email/status', action: ConduitRouteActions.GET, description: `Returns the latest status of a sent email.`, queryParams: { @@ -603,7 +603,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/record', + path: '/email/record', action: ConduitRouteActions.GET, description: `Returns records of stored sent emails.`, queryParams: { diff --git a/modules/comms/src/modules/push/admin/index.ts b/modules/comms/src/modules/push/admin/index.ts index 00de21328..e16924ae5 100644 --- a/modules/comms/src/modules/push/admin/index.ts +++ b/modules/comms/src/modules/push/admin/index.ts @@ -160,7 +160,7 @@ export class AdminHandlers { registerAdminRoutes(routingManager: RoutingManager) { routingManager.route( { - path: '/send', + path: '/push/send', action: ConduitRouteActions.POST, description: `Sends a notification.`, bodyParams: { @@ -178,7 +178,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/sendToManyDevices', + path: '/push/sendToManyDevices', action: ConduitRouteActions.POST, description: `Sends a notification to multiple devices.`, bodyParams: { @@ -196,7 +196,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/sendMany', + path: '/push/sendMany', action: ConduitRouteActions.POST, description: `Sends many notifications to many devices.`, bodyParams: { @@ -220,7 +220,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/token', + path: '/push/token', action: ConduitRouteActions.GET, description: `Get and search notification tokens.`, queryParams: { @@ -240,7 +240,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/token/:id', + path: '/push/token/:id', action: ConduitRouteActions.GET, description: `Get a notification token by id.`, urlParams: { @@ -255,7 +255,7 @@ export class AdminHandlers { ); routingManager.route( { - path: '/token/user/:userId', + path: '/push/token/user/:userId', action: ConduitRouteActions.GET, description: `Returns a user's notification token.`, urlParams: { diff --git a/modules/comms/src/modules/sms/admin/index.ts b/modules/comms/src/modules/sms/admin/index.ts index 50aac785b..155603e21 100644 --- a/modules/comms/src/modules/sms/admin/index.ts +++ b/modules/comms/src/modules/sms/admin/index.ts @@ -44,7 +44,7 @@ export class AdminHandlers { registerAdminRoutes(routingManager: RoutingManager) { routingManager.route( { - path: '/send', + path: '/sms/send', action: ConduitRouteActions.POST, description: `Sends sms.`, bodyParams: { From 5fdd69765c711d1807bea3308a06b1ee5662fa81 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 17:56:02 +0300 Subject: [PATCH 11/14] feat(comms): move client routes to respective sub-routes --- .../comms/src/modules/push/routes/index.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/comms/src/modules/push/routes/index.ts b/modules/comms/src/modules/push/routes/index.ts index 1defcf839..5363e2f60 100644 --- a/modules/comms/src/modules/push/routes/index.ts +++ b/modules/comms/src/modules/push/routes/index.ts @@ -25,14 +25,15 @@ export class PushNotificationsRoutes { async registerRoutes(_routingManager: RoutingManager) { _routingManager.route( { + path: '/push/token', + action: ConduitRouteActions.POST, + description: `Sets the given notification token for a user.`, bodyParams: { token: ConduitString.Required, platform: ConduitString.Required, }, - action: ConduitRouteActions.POST, - description: `Sets the given notification token for a user.`, + middlewares: ['authMiddleware'], - path: '/token', }, new ConduitRouteReturnDefinition('SetNotificationTokenResponse', { newTokenDocument: NotificationToken.getInstance().fields, // @type-inconsistency @@ -41,29 +42,31 @@ export class PushNotificationsRoutes { ); _routingManager.route( { + path: '/token', + action: ConduitRouteActions.DELETE, + description: `Removes tokens for a user (optionally for specific platform). This effectively disables push notifications for the user, but not the notification center.`, queryParams: { platform: ConduitString.Optional, }, - action: ConduitRouteActions.DELETE, - description: `Removes tokens for a user (optionally for specific platform). This effectively disables push notifications for the user, but not the notification center.`, + middlewares: ['authMiddleware'], - path: '/token', }, new ConduitRouteReturnDefinition('SetNotificationTokenResponse', 'String'), this.handlers.clearNotificationTokens.bind(this.handlers), ); _routingManager.route( { + path: '/push/notifications', + action: ConduitRouteActions.GET, queryParams: { read: ConduitBoolean.Optional, skip: ConduitNumber.Optional, limit: ConduitNumber.Optional, platform: ConduitString.Optional, }, - action: ConduitRouteActions.GET, + description: `Get User notifications`, middlewares: ['authMiddleware'], - path: '/notifications', }, new ConduitRouteReturnDefinition('GetNotificationsResponse', { notifications: [Notification.name], @@ -74,14 +77,15 @@ export class PushNotificationsRoutes { ); _routingManager.route( { + path: '/push/notifications', + action: ConduitRouteActions.PATCH, + description: `Read user notifications. If before is provided, any notification before that date will be marked as read. If id is provided, only that notification will be marked as read.`, bodyParams: { before: ConduitDate.Optional, id: ConduitString.Optional, }, - action: ConduitRouteActions.PATCH, - description: `Read user notifications. If before is provided, any notification before that date will be marked as read. If id is provided, only that notification will be marked as read.`, + middlewares: ['authMiddleware'], - path: '/notifications', }, new ConduitRouteReturnDefinition('ReadNotificationsResponse', 'String'), this.handlers.readUserNotification.bind(this.handlers), From 4ac11a5da553e10ffb47b4a1fbbc4333f1a9428b Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 19:57:04 +0300 Subject: [PATCH 12/14] feat(grpc-sdk): expose email,sms and push sub-services --- libraries/grpc-sdk/src/modules/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/grpc-sdk/src/modules/index.ts b/libraries/grpc-sdk/src/modules/index.ts index 645b33271..577b982c2 100644 --- a/libraries/grpc-sdk/src/modules/index.ts +++ b/libraries/grpc-sdk/src/modules/index.ts @@ -3,6 +3,9 @@ export * from './router/index.js'; export * from './database/index.js'; export * from './config/index.js'; export * from './comms/index.js'; +export * from './comms/email/index.js'; +export * from './comms/sms/index.js'; +export * from './comms/pushNotifications/index.js'; export * from './core/index.js'; export * from './admin/index.js'; export * from './chat/index.js'; From ef357eeae270d91e392fee7e8617931e919a4fbf Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Wed, 18 Sep 2024 20:13:00 +0300 Subject: [PATCH 13/14] refactor(authentication,chat,forms): use comms --- .../testing-tools/src/mock-module/index.ts | 6 ++-- modules/authentication/src/Authentication.ts | 8 +++-- modules/authentication/src/handlers/local.ts | 11 ++++--- .../authentication/src/handlers/magicLink.ts | 8 +++-- modules/authentication/src/handlers/phone.ts | 6 ++-- modules/authentication/src/handlers/team.ts | 22 +++++++++---- modules/authentication/src/handlers/twoFa.ts | 6 +++- modules/authentication/src/utils/index.ts | 2 +- modules/chat/src/routes/index.ts | 31 +++++++++++-------- modules/chat/src/utils/index.ts | 18 ++++++++--- modules/forms/src/Forms.ts | 8 +++-- modules/forms/src/routes/index.ts | 4 +-- 12 files changed, 85 insertions(+), 45 deletions(-) diff --git a/libraries/testing-tools/src/mock-module/index.ts b/libraries/testing-tools/src/mock-module/index.ts index 6f7c5b4f4..736a47e34 100644 --- a/libraries/testing-tools/src/mock-module/index.ts +++ b/libraries/testing-tools/src/mock-module/index.ts @@ -3,14 +3,14 @@ import { Channel, Client, createChannel, createClientFactory } from 'nice-grpc'; import { getModuleNameInterceptor } from './utils'; import { HealthCheckResponse, HealthDefinition } from '../protoUtils/grpc_health_check'; import { EventEmitter } from 'events'; -import { EmailDefinition } from '../protoUtils/email'; +import { EmailDefinition } from '../protoUtils/comms'; import { RouterDefinition } from '../protoUtils/router'; import { DatabaseProviderDefinition } from '../protoUtils/database'; import { StorageDefinition } from '../protoUtils/storage'; -import { PushNotificationsDefinition } from '../protoUtils/push-notifications'; +import { PushNotificationsDefinition } from '../protoUtils/comms'; import { AuthenticationDefinition } from '../protoUtils/authentication'; import { AuthorizationDefinition } from '../protoUtils/authorization'; -import { SmsDefinition } from '../protoUtils/sms'; +import { SmsDefinition } from '../protoUtils/comms'; import { ChatDefinition } from '../protoUtils/chat'; export default class MockModule { diff --git a/modules/authentication/src/Authentication.ts b/modules/authentication/src/Authentication.ts index e1c11bddc..7fd850c4f 100644 --- a/modules/authentication/src/Authentication.ts +++ b/modules/authentication/src/Authentication.ts @@ -12,6 +12,7 @@ import AppConfigSchema, { Config } from './config/index.js'; import { AdminHandlers } from './admin/index.js'; import { AuthenticationRoutes } from './routes/index.js'; import * as models from './models/index.js'; +import { User } from './models/index.js'; import { AuthUtils } from './utils/index.js'; import { TokenType } from './constants/index.js'; import { v4 as uuid } from 'uuid'; @@ -50,7 +51,6 @@ import { User as UserAuthz } from './authz/index.js'; import { handleAuthentication } from './routes/middleware.js'; import { fileURLToPath } from 'node:url'; import { TeamsHandler } from './handlers/team.js'; -import { User } from './models/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -313,7 +313,9 @@ export default class Authentication extends ManagedModule { } const sendEmail = ConfigController.getInstance().config.local.verification.send_email; - const emailAvailable = this.grpcSdk.isAvailable('email'); + const emailAvailable = + this.grpcSdk.isAvailable('comms') && + (await this.grpcSdk.comms?.featureAvailable('email')); if (verify && sendEmail && emailAvailable) { const serverConfig = await this.grpcSdk.config.get('router'); const url = serverConfig.hostUrl; @@ -324,7 +326,7 @@ export default class Authentication extends ManagedModule { }); const result = { verificationToken, hostUrl: url }; const link = `${result.hostUrl}/hook/authentication/verify-email/${result.verificationToken.token}`; - await this.grpcSdk.emailProvider!.sendEmail('EmailVerification', { + await this.grpcSdk.comms?.email!.sendEmail('EmailVerification', { email: user.email, variables: { link, diff --git a/modules/authentication/src/handlers/local.ts b/modules/authentication/src/handlers/local.ts index 0b1fd4804..b31122233 100644 --- a/modules/authentication/src/handlers/local.ts +++ b/modules/authentication/src/handlers/local.ts @@ -709,11 +709,12 @@ export class LocalHandlers implements IAuthenticationStrategy { private async initDbAndEmail() { const config = ConfigController.getInstance().config; - if (config.local.verification.send_email && this.grpcSdk.isAvailable('email')) { - this.emailModule = this.grpcSdk.emailProvider!; - } - - if (config.local.verification.send_email && this.grpcSdk.isAvailable('email')) { + if ( + config.local.verification.send_email && + this.grpcSdk.isAvailable('comms') && + this.grpcSdk.comms?.featureAvailable('email') + ) { + this.emailModule = this.grpcSdk.comms.email!; this.registerTemplates(); } this.initialized = true; diff --git a/modules/authentication/src/handlers/magicLink.ts b/modules/authentication/src/handlers/magicLink.ts index 0708cbadd..a33c68a79 100644 --- a/modules/authentication/src/handlers/magicLink.ts +++ b/modules/authentication/src/handlers/magicLink.ts @@ -30,8 +30,12 @@ export class MagicLinkHandlers implements IAuthenticationStrategy { async validate(): Promise { const config = ConfigController.getInstance().config; - if (config.magic_link.enabled && this.grpcSdk.isAvailable('email')) { - this.emailModule = this.grpcSdk.emailProvider!; + if ( + config.magic_link.enabled && + this.grpcSdk.isAvailable('comms') && + (await this.grpcSdk.comms!.featureAvailable('email')) + ) { + this.emailModule = this.grpcSdk.comms?.email!; const success = await this.registerTemplate() .then(() => true) .catch(e => { diff --git a/modules/authentication/src/handlers/phone.ts b/modules/authentication/src/handlers/phone.ts index f1bd92a79..8de97f0e3 100644 --- a/modules/authentication/src/handlers/phone.ts +++ b/modules/authentication/src/handlers/phone.ts @@ -31,9 +31,11 @@ export class PhoneHandlers implements IAuthenticationStrategy { async validate(): Promise { const config = ConfigController.getInstance().config; - const isAvailable = this.grpcSdk.isAvailable('sms'); + const isAvailable = + this.grpcSdk.isAvailable('comms') && + (await this.grpcSdk.comms!.featureAvailable('sms')); if (config.phoneAuthentication.enabled && isAvailable) { - this.sms = this.grpcSdk.sms!; + this.sms = this.grpcSdk.comms!.sms!; ConduitGrpcSdk.Logger.log('Phone authentication is available'); return (this.initialized = true); } else { diff --git a/modules/authentication/src/handlers/team.ts b/modules/authentication/src/handlers/team.ts index c7b2bd2e3..dd58d9494 100644 --- a/modules/authentication/src/handlers/team.ts +++ b/modules/authentication/src/handlers/team.ts @@ -539,14 +539,19 @@ export class TeamsHandler implements IAuthenticationStrategy { role, inviter: user, }); - if (email && config.teams.invites.sendEmail && this.grpcSdk.isAvailable('email')) { + if ( + email && + config.teams.invites.sendEmail && + this.grpcSdk.isAvailable('comms') && + (await this.grpcSdk.comms?.featureAvailable('email')) + ) { let link = !isEmpty(redirectUri) ? AuthUtils.validateRedirectUri(redirectUri) : config.teams.invites.inviteUrl; link += `?invitationToken=${invitation.token}`; await this.grpcSdk - .emailProvider!.sendEmail('TeamInvite', { + .comms!.email!.sendEmail('TeamInvite', { email: email, variables: { link, @@ -554,7 +559,7 @@ export class TeamsHandler implements IAuthenticationStrategy { inviterName: user.name, }, }) - .catch(e => { + .catch((e: Error) => { ConduitGrpcSdk.Logger.error(e); }); } @@ -854,14 +859,19 @@ export class TeamsHandler implements IAuthenticationStrategy { } } if (config.teams.invites.enabled && config.teams.invites.sendEmail) { - if (!config.teams.invites.sendEmail || !this.grpcSdk.isAvailable('email')) { + if ( + !config.teams.invites.sendEmail || + !this.grpcSdk.isAvailable('comms') || + !(await this.grpcSdk.comms!.featureAvailable('email')) + ) { ConduitGrpcSdk.Logger.warn( 'Team invites are enabled, but email sending is disabled. No invites will be sent.', ); } if (config.teams.invites.sendEmail) { - this.grpcSdk.onceModuleUp('email', async () => { - await this.grpcSdk.emailProvider!.registerTemplate(TeamInviteTemplate); + this.grpcSdk.onceModuleUp('comms', async () => { + // email doesn't have to be generally serving to user registerTemplate + await this.grpcSdk.comms!.email.registerTemplate(TeamInviteTemplate); }); } } diff --git a/modules/authentication/src/handlers/twoFa.ts b/modules/authentication/src/handlers/twoFa.ts index 01b739fbb..e2b6f4b6f 100644 --- a/modules/authentication/src/handlers/twoFa.ts +++ b/modules/authentication/src/handlers/twoFa.ts @@ -37,11 +37,15 @@ export class TwoFa implements IAuthenticationStrategy { return false; } if (authConfig.twoFa.enabled && authConfig.twoFa.methods.sms) { - if (!this.grpcSdk.isAvailable('sms')) { + if ( + !this.grpcSdk.isAvailable('comms') || + !(await this.grpcSdk.comms!.featureAvailable('sms')) + ) { ConduitGrpcSdk.Logger.error('SMS module not available'); return false; } } + this.smsModule = this.grpcSdk.comms!.sms!; ConduitGrpcSdk.Logger.log('TwoFactor authentication is available'); return true; } diff --git a/modules/authentication/src/utils/index.ts b/modules/authentication/src/utils/index.ts index 0a68df0ef..7162b24ee 100644 --- a/modules/authentication/src/utils/index.ts +++ b/modules/authentication/src/utils/index.ts @@ -92,7 +92,7 @@ export namespace AuthUtils { token: Token, code: string, ): Promise { - const verified = await grpcSdk.sms!.verify(token.data.verification, code); + const verified = await grpcSdk.comms!.sms!.verify(token.data.verification, code); if (!verified.verified) { return false; } diff --git a/modules/chat/src/routes/index.ts b/modules/chat/src/routes/index.ts index 14f255509..d718133dc 100644 --- a/modules/chat/src/routes/index.ts +++ b/modules/chat/src/routes/index.ts @@ -40,20 +40,25 @@ export class ChatRoutes { } async registerTemplates() { - this.grpcSdk.config - .get('email') - .then(() => { - const promises = Object.values(templates).map((template: any) => { - return this.grpcSdk.emailProvider!.registerTemplate(template); - }); - return Promise.all(promises); - }) - .then(() => { - ConduitGrpcSdk.Logger.log('Email templates registered'); - }) - .catch(() => { - ConduitGrpcSdk.Logger.error('Internal error while registering email templates'); + if ( + this.grpcSdk.isAvailable('comms') && + this.grpcSdk.comms?.featureAvailable('email') + ) { + const promises = Object.values(templates).map((template: any) => { + return this.grpcSdk.comms?.email!.registerTemplate(template); }); + return Promise.all(promises) + .then(() => { + ConduitGrpcSdk.Logger.log('Email templates registered'); + }) + .catch(() => { + ConduitGrpcSdk.Logger.error('Internal error while registering email templates'); + }); + } else { + ConduitGrpcSdk.Logger.error( + 'Could not register email templates, email not available', + ); + } } async createRoom(call: ParsedRouterRequest): Promise { diff --git a/modules/chat/src/utils/index.ts b/modules/chat/src/utils/index.ts index 7951be80e..5ef431655 100644 --- a/modules/chat/src/utils/index.ts +++ b/modules/chat/src/utils/index.ts @@ -57,14 +57,18 @@ export async function sendInvitations(args: { token: uuid(), room: roomId, }); - if (sendEmail && grpcSdk.isAvailable('email')) { + if ( + sendEmail && + grpcSdk.isAvailable('comms') && + grpcSdk.comms?.featureAvailable('email') + ) { const result = { invitationToken, hostUrl: url }; const acceptLink = `${result.hostUrl}/hook/chat/invitations/accept/${result.invitationToken.token}`; const declineLink = `${result.hostUrl}/hook/chat/invitations/decline/${result.invitationToken.token}`; const roomName = room.name; const userName = sender.email; - await grpcSdk - .emailProvider!.sendEmail('ChatRoomInvitation', { + await grpcSdk.comms?.email + ?.sendEmail('ChatRoomInvitation', { email: invitedUser.email, variables: { acceptLink, @@ -77,10 +81,14 @@ export async function sendInvitations(args: { throw new Error(e.message); }); } - if (sendNotification && grpcSdk.isAvailable('pushNotifications')) { + if ( + sendNotification && + grpcSdk.isAvailable('comms') && + grpcSdk.comms?.featureAvailable('pushNotifications') + ) { const body = `User ${sender._id} has invited you to join in room ${room.name}`; const title = 'You have an invitation request!'; - await grpcSdk + await grpcSdk.comms .pushNotifications!.sendNotification(invitedUser._id, title, body) .catch((e: Error) => { throw new Error(e.message); diff --git a/modules/forms/src/Forms.ts b/modules/forms/src/Forms.ts index 589e62ef6..856dc01a3 100644 --- a/modules/forms/src/Forms.ts +++ b/modules/forms/src/Forms.ts @@ -50,8 +50,12 @@ export default class Forms extends ManagedModule { this.updateHealth(HealthCheckStatus.NOT_SERVING); } else { if (!this.isRunning) { - if (!this.grpcSdk.isAvailable('email')) return; - await this.grpcSdk.emailProvider!.registerTemplate(FormSubmissionTemplate); + if ( + !this.grpcSdk.isAvailable('comms') || + !this.grpcSdk.comms?.featureAvailable('email') + ) + return; + await this.grpcSdk.comms.email!.registerTemplate(FormSubmissionTemplate); this.formController = new FormsController(this.grpcSdk); this.adminRouter = new AdminHandlers( this.grpcServer, diff --git a/modules/forms/src/routes/index.ts b/modules/forms/src/routes/index.ts index 9b06f8df3..98f8cbc6b 100644 --- a/modules/forms/src/routes/index.ts +++ b/modules/forms/src/routes/index.ts @@ -94,8 +94,8 @@ export class FormsRoutes { Object.keys(data).forEach(r => { text += `
${r}: ${data[r]}`; }); - await this.grpcSdk - .emailProvider!.sendEmail('FormSubmission', { + await this.grpcSdk.comms?.email + ?.sendEmail('FormSubmission', { email: form.forwardTo, replyTo: form.emailField ? data[form.emailField] : null, variables: { From 4f1eb2139f45aa87c8ccb32db4a4a1eb61c48963 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Thu, 19 Sep 2024 13:40:39 +0300 Subject: [PATCH 14/14] refactor(comms): remove deprecated mailgun library feat(comms): update code for new mailgun library --- modules/comms/package.json | 20 +- .../transports/mailgun/MailgunProvider.ts | 126 +++-- yarn.lock | 445 ++---------------- 3 files changed, 111 insertions(+), 480 deletions(-) diff --git a/modules/comms/package.json b/modules/comms/package.json index 75bad0547..906444cfc 100644 --- a/modules/comms/package.json +++ b/modules/comms/package.json @@ -28,44 +28,44 @@ }, "license": "ISC", "dependencies": { + "@aws-sdk/client-sns": "^3.627.1", "@conduitplatform/grpc-sdk": "*", "@conduitplatform/module-tools": "*", "@grpc/grpc-js": "^1.10.9", "@grpc/proto-loader": "^0.7.6", + "@onesignal/node-onesignal": "^1.0.0-beta9", "@sendgrid/client": "^8.0.0", "@types/nodemailer-sendgrid": "^1.0.3", "await-to-js": "^3.0.0", "axios": "^1.7.4", + "bluebird": "^3.7.2", "bullmq": "^5.12.14", + "clicksend": "^5.0.79", "convict": "^6.2.4", "escape-string-regexp": "^4.0.0", + "firebase-admin": "^12.4.0", + "form-data": "^4.0.0", "handlebars": "^4.7.8", "lodash-es": "^4.17.21", - "mailgun-js": "^0.22.0", + "mailgun.js": "^10.2.3", "mandrill-api": "^1.0.45", + "messagebird": "^4.0.1", "nodemailer": "^6.9.10", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mandrill-transport": "^1.2.0", "nodemailer-sendgrid": "^1.0.3", - "@aws-sdk/client-sns": "^3.627.1", - "bluebird": "^3.7.2", - "clicksend": "^5.0.79", - "messagebird": "^4.0.1", "otp-generator": "^4.0.1", - "twilio": "5.3.0", - "@onesignal/node-onesignal": "^1.0.0-beta9", - "firebase-admin": "^12.4.0" + "twilio": "5.3.0" }, "devDependencies": { "@types/bluebird": "^3.5.42", - "@types/otp-generator": "^4.0.2", "@types/convict": "^6.1.6", "@types/lodash-es": "^4.17.12", - "@types/mailgun-js": "^0.22.18", "@types/mandrill-api": "^1.0.34", "@types/node": "20.11.24", "@types/nodemailer": "^6.4.14", "@types/nodemailer-mailgun-transport": "^1.4.3", + "@types/otp-generator": "^4.0.2", "@types/smtp-server": "^3.5.7", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", diff --git a/modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts b/modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts index 0a034bf4c..645be6b67 100644 --- a/modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts +++ b/modules/comms/src/modules/email/email-provider/transports/mailgun/MailgunProvider.ts @@ -1,8 +1,6 @@ -import { to } from 'await-to-js'; import { createTransport, SentMessageInfo } from 'nodemailer'; import { Options } from 'nodemailer/lib/mailer'; import { CreateEmailTemplate } from '../../interfaces/CreateEmailTemplate.js'; -import { Template } from '../../interfaces/Template.js'; import { UpdateEmailTemplate } from '../../interfaces/UpdateEmailTemplate.js'; import { EmailBuilderClass } from '../../models/EmailBuilderClass.js'; import { EmailProviderClass } from '../../models/EmailProviderClass.js'; @@ -10,13 +8,15 @@ import { getHandleBarsValues } from '../../utils/index.js'; import { initialize as initializeMailgun } from './mailgun.js'; import { MailgunConfig } from './mailgun.config.js'; import { MailgunMailBuilder } from './mailgunMailBuilder.js'; -import mailgun, { Mailgun } from 'mailgun-js'; import { DeleteEmailTemplate } from '../../interfaces/DeleteEmailTemplate.js'; -import { MailgunTemplate } from '../../interfaces/mailgun/MailgunTemplate.js'; import { Indexable } from '@conduitplatform/grpc-sdk'; +import Mailgun, { Interfaces } from 'mailgun.js'; +import formData from 'form-data'; +import { Template } from '../../interfaces/Template.js'; +import { YesNo } from 'mailgun.js/Enums'; export class MailgunProvider extends EmailProviderClass { - protected _mailgunSdk: Mailgun; + protected _mailgunSdk: Interfaces.IMailgunClient; private domain: string; private apiKey: string; @@ -24,40 +24,42 @@ export class MailgunProvider extends EmailProviderClass { super(createTransport(initializeMailgun(mailgunSettings))); this.domain = mailgunSettings.auth.domain; this.apiKey = mailgunSettings.auth.api_key; - this._mailgunSdk = mailgun({ - apiKey: this.apiKey, - domain: this.domain, - host: mailgunSettings.host, + this._mailgunSdk = new Mailgun.default(formData).client({ + username: 'api', + key: this.apiKey, + url: mailgunSettings.host, }); } async listTemplates(): Promise { - const templates = await this._mailgunSdk.get(`/${this.domain}/templates`); - const retList: Template[] = templates.items.map( - async (element: Template) => await this.getTemplateInfo(element.name), + const templates = await this._mailgunSdk.domains.domainTemplates.list(this.domain); + const retList: Promise