Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions backend/core-api/src/connectionResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ import {
loadSegmentClass,
} from './modules/segments/db/models/Segments';

import {
IAutomationDocument,
IAutomationExecutionDocument,
} from 'erxes-api-shared/core-modules';
import {
IEmailDeliveryModel,
loadEmailDeliveryClass,
} from '~/modules/organization/team-member/db/models/EmailDeliveries';
import { IEmailDeliveriesDocument } from '~/modules/organization/team-member/types';
import {
IAutomationModel,
loadClass as loadAutomationClass,
Expand All @@ -138,10 +147,6 @@ import {
IExecutionModel,
loadClass as loadExecutionClass,
} from './modules/automations/db/models/Executions';
import {
IAutomationDocument,
IAutomationExecutionDocument,
} from 'erxes-api-shared/core-modules';
import { ILogModel, loadLogsClass } from './modules/logs/db/models/Logs';

export interface IModels {
Expand Down Expand Up @@ -176,6 +181,7 @@ export interface IModels {
Documents: IDocumentModel;
Automations: IAutomationModel;
AutomationExecutions: IExecutionModel;
EmailDeliveries: IEmailDeliveryModel;
Logs: ILogModel;
}

Expand Down Expand Up @@ -326,6 +332,11 @@ export const loadClasses = (
IExecutionModel
>('automations_executions', loadExecutionClass(models));

models.EmailDeliveries = db.model<
IEmailDeliveriesDocument,
IEmailDeliveryModel
>('email_deliveries', loadEmailDeliveryClass(models));

const db_name = db.name;

const logDb = db.useDb(`${db_name}_logs`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const queryParams = `
segment: String
type: String
ids: [String]
excludeIds: Boolean
excludeIds: [String]

tagIds: [String]
excludeTagIds: [String]
Expand Down
17 changes: 16 additions & 1 deletion backend/core-api/src/modules/contacts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export const generateFilter = async (params: any, models: IModels) => {
brandIds,
integrationIds,
integrationTypes,
status
status,
ids,
excludeIds,
} = params;

const filter = {};
Expand All @@ -30,6 +32,19 @@ export const generateFilter = async (params: any, models: IModels) => {
filter['searchText'] = { $regex: searchValue, $options: 'i' };
}

if (ids?.length || excludeIds?.length) {
if (ids?.length && excludeIds?.length) {
filter['_id'] = {
$in: ids,
$nin: excludeIds,
};
} else if (ids?.length) {
filter['_id'] = { $in: ids };
} else if (excludeIds?.length) {
filter['_id'] = { $nin: excludeIds };
}
}

if (brandIds || integrationIds || integrationTypes) {
const relatedIntegrationIdSet = new Set();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const EMAIL_DELIVERY_STATUS = {
PENDING: 'pending',
RECEIVED: 'received',
ALL: ['pending', 'received'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { mongooseStringRandomId } from 'erxes-api-shared/utils';
import { Schema } from 'mongoose';
import { EMAIL_DELIVERY_STATUS } from '~/modules/organization/team-member/constants';

export const emailDeliveriesSchema = new Schema({
_id: mongooseStringRandomId,
subject: { type: String },
body: { type: String },
to: { type: [String] },
cc: { type: [String], optional: true },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema for 'cc' (and similarly 'bcc') uses 'optional: true'. In Mongoose the standard option is 'required: false'. Verify that the intended optional behavior is achieved.

Suggested change
cc: { type: [String], optional: true },
cc: { type: [String], required: false },

bcc: { type: [String], optional: true },
Comment on lines +10 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The 'optional' property is not a standard Mongoose schema option.

Remove 'optional: true' from the schema fields, as Mongoose ignores this property. Fields are optional by default unless 'required: true' is specified.

Suggested change
cc: { type: [String], optional: true },
bcc: { type: [String], optional: true },
cc: { type: [String] },
bcc: { type: [String] },

Comment on lines +10 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix non-standard Mongoose optional field syntax.

The optional: true syntax is not standard Mongoose. Optional fields in Mongoose are defined by omitting required: true or using required: false.

-  cc: { type: [String], optional: true },
-  bcc: { type: [String], optional: true },
+  cc: { type: [String] },
+  bcc: { type: [String] },

Alternatively, if you want to be explicit about optional fields:

-  cc: { type: [String], optional: true },
-  bcc: { type: [String], optional: true },
+  cc: { type: [String], required: false },
+  bcc: { type: [String], required: false },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cc: { type: [String], optional: true },
bcc: { type: [String], optional: true },
cc: { type: [String] },
bcc: { type: [String] },
Suggested change
cc: { type: [String], optional: true },
bcc: { type: [String], optional: true },
cc: { type: [String], required: false },
bcc: { type: [String], required: false },
🤖 Prompt for AI Agents
In
backend/core-api/src/modules/organization/team-member/db/definitions/emailDeliveries.ts
around lines 10 to 11, the use of 'optional: true' for the cc and bcc fields is
not standard Mongoose syntax. Remove 'optional: true' and ensure these fields
are treated as optional by either omitting the 'required' property or explicitly
setting 'required: false' in their schema definitions.

attachments: { type: [Object] },
from: { type: String },
kind: { type: String },
customerId: { type: String },
userId: { type: String },
createdAt: { type: Date, default: Date.now },
status: { type: String, enum: EMAIL_DELIVERY_STATUS.ALL },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider setting a default value for 'status'.

Setting a default value like 'pending' for 'status' can help prevent missing or inconsistent values.

Suggested implementation:

  status: { type: String, enum: EMAIL_DELIVERY_STATUS.ALL, default: 'pending' },

If 'pending' is a value in EMAIL_DELIVERY_STATUS, consider using EMAIL_DELIVERY_STATUS.PENDING instead of the string literal 'pending' for better maintainability:

default: EMAIL_DELIVERY_STATUS.PENDING

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Model } from 'mongoose';
import { IModels } from '~/connectionResolvers';
import { emailDeliveriesSchema } from '~/modules/organization/team-member/db/definitions/emailDeliveries';
import {
IEmailDeliveries,
IEmailDeliveriesDocument,
} from '~/modules/organization/team-member/types';

export interface IEmailDeliveryModel extends Model<IEmailDeliveriesDocument> {
createEmailDelivery(doc: IEmailDeliveries): Promise<IEmailDeliveriesDocument>;
updateEmailDeliveryStatus(_id: string, status: string): Promise<void>;
}

export const loadEmailDeliveryClass = (models: IModels) => {
class EmailDelivery {
/**
* Create an EmailDelivery document
*/
public static async createEmailDelivery(doc: IEmailDeliveries) {
return models.EmailDeliveries.create({
...doc,
});
}

public static async updateEmailDeliveryStatus(_id: string, status: string) {
return models.EmailDeliveries.updateOne({ _id }, { $set: { status } });
}
}

emailDeliveriesSchema.loadClass(EmailDelivery);

return emailDeliveriesSchema;
};
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import * as bcrypt from 'bcryptjs';
import * as crypto from 'crypto';
import * as jwt from 'jsonwebtoken';
import { Model } from 'mongoose';
import * as crypto from 'crypto';

import { redis } from 'erxes-api-shared/utils';
import {
USER_ROLES,
userSchema,
userMovemmentSchema,
userSchema,
} from 'erxes-api-shared/core-modules';
import { redis } from 'erxes-api-shared/utils';

import { saveValidatedToken } from '@/auth/utils';
import { IModels } from '~/connectionResolvers';
import {
IUser,
IAppDocument,
IDetail,
IEmailSignature,
ILink,
IUserMovementDocument,
IUser,
IUserDocument,
IEmailSignature,
IAppDocument,
IUserMovementDocument,
} from 'erxes-api-shared/core-types';
import { IModels } from '~/connectionResolvers';

import { USER_MOVEMENT_STATUSES } from 'erxes-api-shared/core-modules';

Expand Down Expand Up @@ -322,6 +322,10 @@ export const loadUserClass = (models: IModels) => {
// Checking duplicated email
await models.Users.checkDuplication({ email });

if (!(await models.UsersGroups.findOne({ _id: groupId }))) {
throw new Error('Invalid group');
}

const { token, expires } = await User.generateToken();

this.checkPassword(password);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { IContext } from '~/connectionResolvers';
import {
IUser,
IDetail,
ILink,
IEmailSignature,
ILink,
IUser,
} from 'erxes-api-shared/core-types';
import { isEnabled, sendTRPCMessage } from 'erxes-api-shared/utils';
import { IContext } from '~/connectionResolvers';
import { sendInvitationEmail } from '~/modules/organization/team-member/utils';
import { resetPermissionsCache } from '~/modules/permissions/utils';

export interface IUsersEdit extends IUser {
channelIds?: string[];
Expand Down Expand Up @@ -198,7 +201,7 @@ export const userMutations = {
departmentId?: string;
}>;
},
{ models }: IContext,
{ models, subdomain }: IContext,
) {
for (const entry of entries) {
await models.Users.checkDuplication({ email: entry.email });
Expand All @@ -210,6 +213,8 @@ export const userMutations = {
if (docModified?.scopeBrandIds?.length) {
doc.brandIds = docModified.scopeBrandIds;
}

const token = await models.Users.invite(doc);
const createdUser = await models.Users.findOne({ email: entry.email });

if (entry.branchId) {
Expand All @@ -229,7 +234,21 @@ export const userMutations = {
},
);
}

if (entry.channelIds && (await isEnabled('frontline'))) {
sendTRPCMessage({
pluginName: 'frontline',
method: 'mutation',
module: 'inbox',
action: 'updateUserChannels',
input: { channelIds: entry.channelIds, userId: createdUser?._id },
});
}
Comment on lines +238 to +246
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for TRPC message sending.

The TRPC message is sent without error handling. If the frontline service is unavailable or the operation fails, it could cause the entire user invitation process to fail silently or throw an unhandled error.

      if (entry.channelIds && (await isEnabled('frontline'))) {
-        sendTRPCMessage({
-          pluginName: 'frontline',
-          method: 'mutation',
-          module: 'inbox',
-          action: 'updateUserChannels',
-          input: { channelIds: entry.channelIds, userId: createdUser?._id },
-        });
+        try {
+          await sendTRPCMessage({
+            pluginName: 'frontline',
+            method: 'mutation',
+            module: 'inbox',
+            action: 'updateUserChannels',
+            input: { channelIds: entry.channelIds, userId: createdUser?._id },
+          });
+        } catch (error) {
+          console.error('Failed to update user channels:', error);
+          // Continue with invitation process even if channel update fails
+        }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (entry.channelIds && (await isEnabled('frontline'))) {
sendTRPCMessage({
pluginName: 'frontline',
method: 'mutation',
module: 'inbox',
action: 'updateUserChannels',
input: { channelIds: entry.channelIds, userId: createdUser?._id },
});
}
if (entry.channelIds && (await isEnabled('frontline'))) {
try {
await sendTRPCMessage({
pluginName: 'frontline',
method: 'mutation',
module: 'inbox',
action: 'updateUserChannels',
input: { channelIds: entry.channelIds, userId: createdUser?._id },
});
} catch (error) {
console.error('Failed to update user channels:', error);
// Continue with invitation process even if channel update fails
}
}
🤖 Prompt for AI Agents
In backend/core-api/src/modules/organization/team-member/graphql/mutations.ts
around lines 238 to 246, the sendTRPCMessage call lacks error handling, risking
unhandled exceptions if the frontline service is down or the call fails. Wrap
the sendTRPCMessage invocation in a try-catch block to catch any errors, log or
handle them appropriately, and prevent the failure from affecting the overall
user invitation process.


sendInvitationEmail(models, subdomain, { email: entry.email, token });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): sendInvitationEmail is called without awaiting its result.

If you need to handle errors or ensure the email is sent before continuing, use 'await'. If fire-and-forget is intentional, please document this choice.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for invitation email sending.

The sendInvitationEmail call should be wrapped in error handling to prevent the entire invitation process from failing if email sending fails.

-      sendInvitationEmail(models, subdomain, { email: entry.email, token });
+      try {
+        await sendInvitationEmail(models, subdomain, { email: entry.email, token });
+      } catch (error) {
+        console.error('Failed to send invitation email:', error);
+        // Consider whether to continue or throw based on business requirements
+      }
🤖 Prompt for AI Agents
In backend/core-api/src/modules/organization/team-member/graphql/mutations.ts at
line 248, the call to sendInvitationEmail lacks error handling, which could
cause the entire invitation process to fail if email sending encounters an
error. Wrap the sendInvitationEmail call in a try-catch block to catch any
exceptions, log or handle the error appropriately, and allow the invitation
process to continue without interruption.

}

await resetPermissionsCache(models);
},

/*
Expand Down
39 changes: 39 additions & 0 deletions backend/core-api/src/modules/organization/team-member/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Document } from 'mongoose';

export interface IAttachmentParams {
data: string;
filename: string;
size: number;
mimeType: string;
}

export interface IEmailDeliveries {
subject: string;
body: string;
to: string[];
cc?: string[];
bcc?: string[];
attachments?: IAttachmentParams[];
from: string;
kind: string;
userId?: string;
customerId?: string;
status?: string;
}

export interface IEmailDeliveriesDocument extends IEmailDeliveries, Document {
id: string;
}

export interface IEmailParams {
toEmails?: string[];
fromEmail?: string;
title?: string;
customHtml?: string;
customHtmlData?: any;
template?: { name?: string; data?: any };
attachments?: object[];
modifier?: (data: any, email: string) => void;
transportMethod?: string;
getOrganizationDetail?: ({ subdomain }: { subdomain: string }) => any;
}
Loading