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,110 @@
import mongoose, { Document, Model, Schema, model } from "mongoose";
import {
CreateMessageConversationParams,
FindMessageConversationsParams,
IMessageConversation,
IMessageConversationsAdapter,
PaginateMessageConversationsParams,
PaginateMessageConversationsResults,
} from "./MessagesConversations.Interface";
import EmployeesList from "../Employees/Employee";
export interface IMessageConversationDocument extends Omit<IMessageConversation, "id">, Document {}
export class MessageConversationsAdapterMongoose implements IMessageConversationsAdapter {
schema: Schema;
conversationList: Model<IMessageConversationDocument>;
constructor() {
this.schema = new Schema({
userIdFrom: { type: Schema.Types.ObjectId, required: false, ref: "User" },
userIdTo: { type: Schema.Types.ObjectId, required: false, ref: "User" },
companyId: { type: Schema.Types.ObjectId, required: false, ref: "Companie" },
type: { type: String, required: true },
});
this.conversationList = model<IMessageConversationDocument>(
"MessagesConversation",
this.schema
);
}
public async create(data: CreateMessageConversationParams): Promise<IMessageConversation> {
return await this.conversationList.create(data);
}
public async delete(id: string): Promise<void> {
await this.conversationList.findByIdAndDelete(id).exec();
}
public async find(
filters: Omit<FindMessageConversationsParams, "sessionUser">
): Promise<IMessageConversation[]> {
return this.conversationList.find(filters).exec();
}
public async findOne(
filters: Omit<FindMessageConversationsParams, "sessionUser">
): Promise<IMessageConversationDocument | null> {
return this.conversationList.findOne(filters).exec();
}
// Método para búsqueda con expresiones regulares y paginación
public async paginate(
data: PaginateMessageConversationsParams
): Promise<PaginateMessageConversationsResults> {
const skip = (data.page - 1) * data.limit;
// Construye los criterios de búsqueda
const searchCriteria: mongoose.FilterQuery<IMessageConversation> = {};
// 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 }];
}
//Busca las organizaciones en las que el usuario es colaborador
const collaboratorOrgs = await EmployeesList.employees.find({
userId: data.sessionUser,
});
const orgIds = collaboratorOrgs.map((org) => org.companyId);
searchCriteria.$or = [
{ userIdFrom: data.sessionUser },
{ userIdTo: data.sessionUser },
{ companyId: { $in: orgIds } },
];
// Filtra por categoryId si está presente
if (data.userIdFrom !== undefined) {
searchCriteria.userIdFrom = data.userIdFrom;
}
if (data.userIdTo !== undefined) {
searchCriteria.userIdTo = data.userIdTo;
}
if (data.companyId !== undefined) {
searchCriteria.companyId = data.companyId;
}
// Realiza la consulta con paginación
const results = await this.conversationList
.find(searchCriteria)
.sort({ notificationDate: -1 })
.skip(skip)
.limit(data.limit)
.exec();
// Cuenta el total de documentos que coinciden con los criterios de búsqueda
const count = await this.conversationList.countDocuments(searchCriteria).exec();
return {
data: results,
page: data.page,
pages: Math.ceil(count / data.limit),
};
}
}
@@ -0,0 +1,133 @@
export enum MESSAGE_CONVERSATION_TYPES {
"USER_TO_USER" = "user-to-user",
"USER_TO_ORG" = "user-to-org",
}
export type FindMessageConversationsParams = {
_id?: string;
userIdFrom?: string;
userIdTo?: string;
companyId?: string;
sessionUser: string;
};
export type PaginateMessageConversationsParams = {
query?: string;
userIdFrom?: string;
userIdTo?: string;
companyId?: string;
page: number;
limit: number;
sessionUser: string;
};
export type PaginateMessageConversationsResults = {
data: IMessageConversation[];
page: number;
pages: number;
};
export type FindConversationsResults = {
data: FindConversationItem[];
page: number;
pages: number;
};
export type CreateMessageConversationParams = {
userIdFrom?: string;
userIdTo?: string;
companyId?: string;
type: MESSAGE_CONVERSATION_TYPES;
sessionUser: string;
};
export type DeleteMessageConversationParams = {
id: string;
sessionUser: string;
};
export interface IMessageConversation {
id?: string;
userIdFrom?: string; //Indica quien envía el mensaje
userIdTo?: string; // Indica a que usuario va dirigido el mensaje.
// Cuando el mensaje va dirigido a una organizacion,
// habrá un colaborador de la organizacion que de una respuesta
// y ese id de usuario se almacenara en userIdTo.
companyId?: string; //Indica que el mensaje va dirigido a una organizacion.
type: MESSAGE_CONVERSATION_TYPES;
newMessages: number; //Indica si hay nuevos mensajes en la conversacion.
}
export interface FindConversationItem {
conversation: IMessageConversation;
name: string;
description: string;
image: string;
orderWeight: number;
}
export type PaginateFindContactsParams = {
query?: string;
page: number;
limit: number;
sessionUser: string;
};
export type PaginateFindContactsResults = {
data: FindConversationItem[];
page: number;
pages: number;
};
export type MarkConversationAsReadParams = {
conversationId: string;
sessionUser: string;
};
export type CountUnreadConversationParams = {
conversationId: string;
sessionUser: string;
};
export type ConvertU2UMessageConversationParams = {
conversationId: string;
sessionUser: string;
};
export type DeleteConversationsByCompanyParams = {
companyId: string;
};
export type DeleteConversationsByUserParams = {
userId: string;
};
export interface IMessageConversationsAdapter {
create(data: CreateMessageConversationParams): Promise<IMessageConversation>;
delete(id: string): Promise<void>;
find(
filters: Omit<FindMessageConversationsParams, "sessionUser">
): Promise<IMessageConversation[]>;
findOne(
filters: Omit<FindMessageConversationsParams, "sessionUser">
): Promise<IMessageConversation | null>;
paginate(
filters: PaginateMessageConversationsParams
): Promise<PaginateMessageConversationsResults>;
}
export interface IMessageConversationsManager {
conversations: IMessageConversationsAdapter;
createConversation(data: CreateMessageConversationParams): Promise<IMessageConversation>;
deleteConversation(data: DeleteMessageConversationParams): Promise<void>;
paginateConversations(
data: PaginateMessageConversationsParams
): Promise<PaginateMessageConversationsResults>;
paginateFindContacts(data: PaginateFindContactsParams): Promise<PaginateFindContactsResults>;
findConversations(data: PaginateFindContactsParams): Promise<FindConversationsResults>;
markConversationAsRead(data: MarkConversationAsReadParams): Promise<void>;
countUnreadMessages(data: CountUnreadConversationParams): Promise<number>;
convertUserToUser(data: ConvertU2UMessageConversationParams): Promise<void>;
deleteConversationsByCompany(data: DeleteConversationsByCompanyParams): Promise<void>;
deleteConversationsByUser(data: DeleteConversationsByUserParams): Promise<void>;
}
@@ -0,0 +1,504 @@
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import {
ConvertU2UMessageConversationParams,
CountUnreadConversationParams,
CreateMessageConversationParams,
DeleteConversationsByCompanyParams,
DeleteConversationsByUserParams,
DeleteMessageConversationParams,
FindConversationItem,
FindConversationsResults,
IMessageConversation,
IMessageConversationsManager,
MarkConversationAsReadParams,
MESSAGE_CONVERSATION_TYPES,
PaginateFindContactsParams,
PaginateFindContactsResults,
PaginateMessageConversationsParams,
PaginateMessageConversationsResults,
} from "./MessagesConversations.Interface";
import { MessageConversationsAdapterMongoose } from "./MessagesConversations.Adapter.Mongoose";
import EmployeesList from "../Employees/Employee";
import ClientsList from "../Clients/Clients";
import getAvatar from "../../helpers/getAvatar";
import UserList from "../Users/Users";
import MessagesList from "../Messages/Messages";
import { isNull } from "../../helpers/IsNull";
import SystemNoficationsList from "../SystemNotifications/SystemNotification";
import mongoose from "mongoose";
type GetConversationParams = {
userId: string;
};
class MessageConversationsManager implements IMessageConversationsManager {
conversations: MessageConversationsAdapterMongoose;
constructor() {
this.conversations = new MessageConversationsAdapterMongoose();
}
public async deleteConversationsByUser(data: DeleteConversationsByUserParams): Promise<void> {
const conversationsFrom = await this.conversations.find({ userIdFrom: data.userId });
for (const conv of conversationsFrom) {
await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
}
const conversationsTo = await this.conversations.find({ userIdTo: data.userId });
for (const conv of conversationsTo) {
await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
}
await this.conversations.conversationList.deleteMany({ userIdFrom: data.userId });
await this.conversations.conversationList.deleteMany({ userIdTo: data.userId });
}
public async deleteConversationsByCompany(
data: DeleteConversationsByCompanyParams
): Promise<void> {
const conversations = await this.conversations.find({ companyId: data.companyId });
for (const conv of conversations) {
await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
}
await this.conversations.conversationList.deleteMany({ companyId: data.companyId });
}
public async createConversation(
data: CreateMessageConversationParams
): Promise<IMessageConversation> {
if (data.userIdFrom) {
const userFromCheck = await UsersManager.users.findOne({ _id: data.userIdFrom });
if (!userFromCheck) {
throw new Error("Usuario invalido");
}
}
if (data.userIdTo) {
const userToCheck = await UsersManager.users.findOne({ _id: data.userIdTo });
if (!userToCheck) {
throw new Error("Usuario invalido");
}
}
if (data.companyId) {
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
if (!companyCheck) {
throw new Error("La compañia no existe");
}
}
const newConversation = await this.conversations.create(data);
return newConversation;
}
public async deleteConversation(data: DeleteMessageConversationParams): Promise<void> {
const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!checkUser) {
throw new Error("Usuario invalido");
}
const checkConversation = await this.conversations.conversationList.findOne({
_id: data.id,
});
if (!checkConversation) {
throw new Error("Conversacion invalida");
}
if (checkConversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_USER) {
if (String(checkConversation.userIdFrom) !== data.sessionUser) {
throw new Error("No tienes permiso para eliminar esta conversacion");
}
} else if (checkConversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_ORG) {
if (String(checkConversation.userIdFrom) !== data.sessionUser) {
throw new Error("No tienes permiso para eliminar esta conversacion");
}
}
await this.conversations.delete(data.id);
}
public async paginateConversations(
filters: PaginateMessageConversationsParams
): Promise<PaginateMessageConversationsResults> {
return await this.conversations.paginate(filters);
}
private async getUserRelationWithMe(userId: string, sessionUser: string): Promise<string> {
const myOrgs = await EmployeesList.employees.find({ userId: sessionUser });
for (const org of myOrgs) {
const organization = await CompaniesManager.companies.findOne({ _id: org.companyId });
if (!organization) {
continue;
}
//Buscamos si es un colaborador en una de mis organizaciones.
const employee = await EmployeesList.employees.findOne({
userId,
companyId: org.companyId,
});
if (employee) {
return "Colaborador en " + organization.name;
}
//Buscamos si es un cliente en una de mis organizaciones.
const client = await ClientsList.clients.findOne({
userId: userId,
companyId: org.companyId,
});
if (client) {
return "Cliente de " + organization.name;
}
}
const orgsAsClient = await ClientsList.clients.find({
userId: sessionUser,
});
for (const organization of orgsAsClient) {
const checkColaIsCollaborator = await EmployeesList.employees.findOne({
companyId: organization.companyId,
userId: userId,
});
if (checkColaIsCollaborator) {
const org = await CompaniesManager.companies.findOne({
_id: organization.companyId,
});
if (!org) {
continue;
}
return "Miembro de " + org.name;
}
}
return "Otros contactos";
}
public async findConversations(
data: PaginateFindContactsParams
): Promise<FindConversationsResults> {
const items: FindConversationItem[] = [];
const conversations = await this.conversations.paginate(data);
for (const conversation of conversations.data) {
let contactName = "";
let contactUserId: string | undefined = "";
let contactAvatar: string = "";
let conversationName = "";
let conversationDescription = "";
let conversationImage = "";
if (conversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_USER) {
if (conversation.userIdFrom == data.sessionUser) {
const user = await UserList.users.findOne({ _id: conversation.userIdTo });
if (!user) {
continue;
}
contactName = UserList.getUserFullName(user);
contactUserId = String(conversation.userIdTo);
contactAvatar = user.avatar ? user.avatar : "";
} else if (conversation.userIdTo == data.sessionUser) {
const user = await UserList.users.findOne({ _id: conversation.userIdFrom });
if (!user) {
continue;
}
contactName = UserList.getUserFullName(user);
contactUserId = String(conversation.userIdFrom);
contactAvatar = user.avatar ? user.avatar : "";
}
if (contactUserId) {
conversationName = contactName;
conversationDescription = await this.getUserRelationWithMe(
contactUserId,
data.sessionUser
);
conversationImage = getAvatar(contactUserId, contactAvatar, contactName);
}
} else if (conversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_ORG) {
if (conversation.userIdFrom == data.sessionUser) {
const org = await CompaniesManager.companies.findOne({
_id: conversation.companyId,
});
if (!org) {
continue;
}
conversationName = org.name;
conversationDescription = org.description;
conversationImage = isNull<string>(org.iconFile, org.name);
} else if (conversation.companyId) {
const user = await UserList.users.findOne({ _id: conversation.userIdFrom });
if (!user) {
continue;
}
conversationName = UserList.getUserFullName(user);
conversationDescription = "Consulta a la organización";
conversationImage = getAvatar(user.id, user.avatar, conversationName);
}
}
let unreadCount = 0;
if (conversation && conversation.id) {
unreadCount = await this.countUnreadMessages({
conversationId: conversation.id,
sessionUser: data.sessionUser,
});
}
items.push({
conversation: {
id: conversation.id,
userIdFrom: conversation.userIdFrom,
userIdTo: conversation.userIdTo,
companyId: conversation.companyId,
type: conversation.type,
newMessages: unreadCount,
},
name: conversationName,
description: conversationDescription,
image: conversationImage,
orderWeight: 1,
});
}
return {
data: items,
page: conversations.page,
pages: conversations.pages,
} as FindConversationsResults;
}
private async getConversationIfExists(
data: GetConversationParams
): Promise<IMessageConversation | null> {
let conversation = await this.conversations.conversationList.findOne({
userIdFrom: data.userId,
});
if (!conversation) {
conversation = await this.conversations.conversationList.findOne({
userIdTo: data.userId,
});
}
return conversation;
}
public async paginateFindContacts(
data: PaginateFindContactsParams
): Promise<PaginateFindContactsResults> {
const items: FindConversationItem[] = [];
//Mostrar las organizaciones en las que el usuario es cliente.
const companiesAsClient = await CompaniesManager.getByClientId({
clientId: data.sessionUser,
sessionUser: data.sessionUser,
});
for (const org of companiesAsClient) {
items.push({
conversation: {
companyId: org.id,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_ORG,
newMessages: 0,
},
name: org.name,
description: org.description,
image: org.logoFile,
orderWeight: 1,
});
//Mostrar los colaboradores de esa organizacion.
const employeesByOrganization = await EmployeesList.findByCompanyId({
companyId: org.id,
});
for (const employee of employeesByOrganization) {
const conversation = await this.getConversationIfExists({
userId: employee.userId,
});
let unreadCount = 0;
if (conversation && conversation.id) {
unreadCount = await this.countUnreadMessages({
conversationId: conversation.id,
sessionUser: data.sessionUser,
});
}
items.push({
conversation: conversation
? ({
id: conversation.id,
userIdFrom: conversation.userIdFrom,
userIdTo: conversation.userIdTo,
companyId: conversation.companyId,
type: conversation.type,
newMessages: unreadCount,
} as IMessageConversation)
: {
userIdTo: employee.userId,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
newMessages: 0,
},
name: employee.fullName,
description: org.name,
image: employee.avatar,
orderWeight: 2,
});
}
}
//Mostras los clientes y colaboradores de las organizaciones donde el usuario es colaborador.
const companiesAsCollaborator = await EmployeesList.employees.find({
userId: data.sessionUser,
});
for (const org of companiesAsCollaborator) {
const organization = await CompaniesManager.companies.findOne({ _id: org.companyId });
if (!organization) {
continue;
}
//Mostras los clientes de las organizaciones donde el usuario es colaborador.
const organizationClients = await ClientsList.clients.find({
companyId: org.companyId,
});
for (const client of organizationClients) {
if (!client.userId) {
continue;
}
const clientName = ClientsList.getClientFullName(client);
items.push({
conversation: {
userIdFrom: data.sessionUser,
userIdTo: client.userId,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
newMessages: 0,
},
name: clientName,
description: "Cliente de " + organization.name,
image: getAvatar(client.userId, "", clientName),
orderWeight: 2,
});
}
//Mostrar los colaboradores de las organizaciones donde el usuario es colaborador.
const organizationEmployees = await EmployeesList.employees.find({
companyId: org.companyId,
});
for (const employee of organizationEmployees) {
const employeeUser = await UserList.users.findOne({ _id: employee.userId });
if (!employeeUser) {
continue;
}
if (employee.userId == data.sessionUser) {
continue;
}
const employeeName = UserList.getUserFullName(employeeUser);
items.push({
conversation: {
userIdFrom: data.sessionUser,
userIdTo: employee.userId,
type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
newMessages: 0,
},
name: employeeName,
description: "Colaborador en " + organization.name,
image: getAvatar(employee.userId, "", employeeName),
orderWeight: 2,
});
}
}
const query = data.query ? data.query.toUpperCase() : "";
return {
data: items.filter(
(i) =>
String(i.name + i.description)
.toUpperCase()
.indexOf(query) >= 0
),
page: 1,
pages: 1,
} as PaginateFindContactsResults;
}
public async markConversationAsRead(data: MarkConversationAsReadParams): Promise<void> {
const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!checkUser) {
throw new Error("Usuario invalido");
}
const checkConversation = await this.conversations.conversationList.findOne({
_id: data.conversationId,
});
if (!checkConversation) {
throw new Error("Conversacion invalida");
}
await MessagesList.markAsRead({
conversationId: data.conversationId,
sessionUser: data.sessionUser,
});
await SystemNoficationsList.deleteConversationNotifications({
conversationId: data.conversationId,
userId: data.sessionUser,
sessionUser: data.sessionUser,
});
}
public async convertUserToUser(data: ConvertU2UMessageConversationParams): Promise<void> {
const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!checkUser) {
throw new Error("Usuario invalido");
}
const checkConversation = await this.conversations.conversationList.findOne({
_id: data.conversationId,
});
if (!checkConversation) {
throw new Error("Conversacion invalida");
}
checkConversation.type = MESSAGE_CONVERSATION_TYPES.USER_TO_USER;
checkConversation.userIdTo = data.sessionUser;
await checkConversation.save();
}
public async countUnreadMessages(data: CountUnreadConversationParams): Promise<number> {
const searchCriteria: mongoose.FilterQuery<IMessageConversation> = {};
searchCriteria.conversationId = data.conversationId;
searchCriteria.readed = false;
searchCriteria.userId = { $ne: data.sessionUser };
const unreadMessages = await MessagesList.messages.find(searchCriteria);
return unreadMessages.length;
}
}
const MessageConversationList = new MessageConversationsManager();
export default MessageConversationList;