first commit

This commit is contained in:
2026-07-16 20:48:43 -03:00
commit 5696cee264
1111 changed files with 322270 additions and 0 deletions
@@ -0,0 +1,107 @@
import mongoose, { Document, Model, Schema, model } from "mongoose";
import {
CreateMessageParams,
FindMessageParams,
IMessage,
IMessagesAdapter,
PaginateMessageParams,
PaginateMessageResults,
} from "./Messages.Interface";
export interface IMessageDocument extends Omit<IMessage, "id">, Document {}
export class MessagesAdapterMongoose implements IMessagesAdapter {
schema: Schema;
messageList: Model<IMessageDocument>;
constructor() {
this.schema = new Schema({
conversationId: {
type: Schema.Types.ObjectId,
required: true,
ref: "MessagesConversation",
},
userId: {
type: Schema.Types.ObjectId,
required: true,
ref: "User",
},
message: { type: String, required: true },
messageDate: { type: Date, required: true },
readed: { type: Boolean, default: false },
});
this.messageList = model<IMessageDocument>("Message", this.schema);
}
public async create(data: CreateMessageParams): Promise<IMessage> {
return await this.messageList.create({
conversationId: data.conversationId,
userId: data.sessionUser,
message: data.message,
messageDate: new Date(),
readed: false,
});
}
public async delete(id: string): Promise<void> {
await this.messageList.findByIdAndDelete(id).exec();
}
public async find(filters: Omit<FindMessageParams, "sessionUser">): Promise<IMessage[]> {
return this.messageList.find(filters).exec();
}
public async findOne(
filters: Omit<FindMessageParams, "sessionUser">
): Promise<IMessage | null> {
return this.messageList.findOne(filters).exec();
}
// Método para búsqueda con expresiones regulares y paginación
public async paginate(data: PaginateMessageParams): Promise<PaginateMessageResults> {
const skip = (data.page - 1) * data.limit;
// Construye los criterios de búsqueda
const searchCriteria: mongoose.FilterQuery<IMessage> = {};
// Agrega filtro por query usando expresiones regulares para 'name' y 'description'
if (data.query) {
const regex = new RegExp(data.query, "i");
searchCriteria.$or = [{ subject: regex }, { message: regex }];
}
// Filtra por categoryId si está presente
if (data.conversationId !== undefined) {
searchCriteria.conversationId = data.conversationId;
}
if (data.readed) {
searchCriteria.readed = data.readed;
}
// Realiza la consulta con paginación
const results = await this.messageList
.find(searchCriteria)
.sort({ messageDate: -1 })
.skip(skip)
.limit(data.limit)
.exec();
// Cuenta el total de documentos que coinciden con los criterios de búsqueda
const count = await this.messageList.countDocuments(searchCriteria).exec();
return {
data: results,
page: data.page,
pages: Math.ceil(count / data.limit),
};
}
public async markAsRead(data: FindMessageParams): Promise<void> {
await this.messageList.updateMany(
{ conversationId: data.conversationId },
{ $set: { readed: true } }
);
}
}
@@ -0,0 +1,78 @@
import {
CreateMessageConversationParams,
IMessageConversation,
MESSAGE_CONVERSATION_TYPES,
} from "../MessageConversations/MessagesConversations.Interface";
export type FindMessageParams = {
id?: string;
conversationId?: string;
messageDate?: Date;
readed?: boolean;
sessionUser: string;
};
export type PaginateMessageParams = {
query?: string;
conversationId?: string;
dateFrom?: Date;
dateTo?: Date;
readed?: boolean;
page: number;
limit: number;
sessionUser: string;
};
export type PaginateMessageResults = {
data: IMessage[];
page: number;
pages: number;
};
export type SendMessageParams = Omit<CreateMessageConversationParams, "type"> & {
conversationId?: string;
type?: MESSAGE_CONVERSATION_TYPES;
message: string;
};
export type CreateMessageParams = {
conversationId: string;
message: string;
sessionUser: string;
};
export type DeleteMessageParams = {
id: string;
sessionUser: string;
};
export interface IMessage {
id?: string;
conversationId?: string;
messageDate?: Date; //Fecha de envío del mensaje
readed: boolean; //Indica si el mensaje fue leido por el receptor.
message: string; //Contenido del mensaje.
userId: string; //Propietario del mensaje.
}
export interface ISendMessageResult {
message: IMessage;
conversation: IMessageConversation;
}
export interface IMessagesAdapter {
create(data: CreateMessageParams): Promise<IMessage>;
delete(id: string): Promise<void>;
find(filters: FindMessageParams): Promise<IMessage[]>;
findOne(filters: FindMessageParams): Promise<IMessage | null>;
paginate(filters: PaginateMessageParams): Promise<PaginateMessageResults>;
markAsRead(data: FindMessageParams): Promise<void>;
}
export interface IMessageManager {
messages: IMessagesAdapter;
sendMessage(data: SendMessageParams): Promise<ISendMessageResult>;
deleteMessage(data: DeleteMessageParams): Promise<void>;
paginateMessages(data: PaginateMessageParams): Promise<PaginateMessageResults>;
markAsRead(data: FindMessageParams): Promise<void>;
}
+211
View File
@@ -0,0 +1,211 @@
import UsersManager from "../Users/Users";
import {
DeleteMessageParams,
FindMessageParams,
IMessageManager,
ISendMessageResult,
PaginateMessageParams,
PaginateMessageResults,
SendMessageParams,
} from "./Messages.Interface";
import { MessagesAdapterMongoose } from "./Messages.Adapter.Mongoose";
import {
IMessageConversation,
MESSAGE_CONVERSATION_TYPES,
} from "../MessageConversations/MessagesConversations.Interface";
import MessageConversationList from "../MessageConversations/MessagesConversations";
import { NotificationsManager } from "../Notifications/Notifications";
import EmployeesList from "../Employees/Employee";
import CompaniesList from "../Companies/Companies";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";
class MessageManager implements IMessageManager {
messages: MessagesAdapterMongoose;
constructor() {
this.messages = new MessagesAdapterMongoose();
}
public async sendMessage(data: SendMessageParams): Promise<ISendMessageResult> {
/*
* Cuando envio un mensaje tengo dos opciones:
* 1. Enviar un mensaje a una conversacion existente
* - En este caso debo proporcionar un conversationId.
* 2. Crear una nueva conversacion
* - En este caso debo proporcionar el tipo de conversacion que se va ha crear
* y los datos necesarios para crearla.
*/
if (data.sessionUser) {
const userCheck = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!userCheck) {
throw new Error("Usuario invalido");
}
}
let conversation: IMessageConversation | null = null;
if (data.conversationId) {
conversation = await MessageConversationList.conversations.findOne({
_id: data.conversationId,
});
if (!conversation) {
throw new Error("Conversacion invalida");
}
}
if (!data.conversationId) {
if (!data.type) {
throw new Error("El tipo de la conversación es requerido");
}
if (data.type === MESSAGE_CONVERSATION_TYPES.USER_TO_USER) {
conversation = await MessageConversationList.conversations.findOne({
userIdFrom: data.sessionUser,
userIdTo: data.userIdTo,
});
if (!conversation) {
conversation = await MessageConversationList.createConversation({
userIdFrom: data.sessionUser,
userIdTo: data.userIdTo,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
sessionUser: data.sessionUser,
});
}
} else if (data.type === MESSAGE_CONVERSATION_TYPES.USER_TO_ORG) {
conversation = await MessageConversationList.conversations.findOne({
userIdFrom: data.sessionUser,
companyId: data.companyId,
});
if (!conversation) {
conversation = await MessageConversationList.createConversation({
userIdFrom: data.sessionUser,
companyId: data.companyId,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_ORG,
sessionUser: data.sessionUser,
});
}
}
}
if (!conversation) {
throw new Error("No se pudo crear la conversacion");
}
if (!conversation.id) {
throw new Error("No se pudo crear la conversacion");
}
//Cuando alguien contesta la conversacion se pasa a user to user.
//De esta manera el userTo pasa a ser el que contesto la conversacion (data.sessionUser).
if (
conversation.companyId &&
!conversation.userIdTo &&
conversation.userIdFrom != data.sessionUser
) {
//Convertir la conversation en user to user
await MessageConversationList.convertUserToUser({
conversationId: conversation.id,
sessionUser: data.sessionUser,
});
}
const newMessage = await this.messages.create({
conversationId: conversation.id,
message: data.message,
sessionUser: data.sessionUser,
});
const shortMessage =
data.message.length > 100 ? data.message.substring(0, 100) + "..." : data.message;
if (conversation.userIdTo) {
const toUser =
String(data.sessionUser) === String(conversation.userIdTo)
? conversation.userIdFrom
: conversation.userIdTo;
await NotificationsManager.sendSystemNotification({
userId: String(toUser),
conversationId: conversation.id,
subject: "Nuevo mensaje recibido",
message: shortMessage,
type: NotificationType.MESSAGE,
code: String((newMessage as any)._id)
});
}
if (!conversation.userIdTo && conversation.companyId) {
const org = await CompaniesList.companies.findOne({
_id: conversation.companyId,
});
if (org) {
const employees = await EmployeesList.employees.find({
companyId: conversation.companyId,
});
for (const employee of employees) {
await NotificationsManager.sendSystemNotification({
userId: String(employee.userId),
conversationId: conversation.id,
subject: "Nueva consulta a la organización " + org.name,
message: shortMessage,
type: NotificationType.MESSAGE,
code: String((newMessage as any)._id)
});
}
}
}
return {
message: newMessage,
conversation: {
id: conversation.id,
userIdFrom: conversation.userIdFrom,
userIdTo: conversation.userIdTo,
companyId: conversation.companyId,
type: conversation.type,
newMessages: conversation.newMessages,
},
};
}
public async deleteMessage(data: DeleteMessageParams): Promise<void> {
const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!checkUser) {
throw new Error("Usuario invalido");
}
const messacheCheck = await this.messages.messageList.findOne({
_id: data.id,
});
if (!messacheCheck) {
throw new Error("Mensaje invalido");
}
if (String(messacheCheck.userId) !== data.sessionUser) {
throw new Error("No tienes permiso para eliminar este mensaje");
}
await this.messages.delete(data.id);
}
public async paginateMessages(filters: PaginateMessageParams): Promise<PaginateMessageResults> {
return await this.messages.paginate(filters);
}
public async markAsRead(data: FindMessageParams): Promise<void> {
const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!checkUser) {
throw new Error("Usuario invalido");
}
this.messages.markAsRead(data);
}
}
const MessagesList = new MessageManager();
export default MessagesList;