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
3 changes: 3 additions & 0 deletions backend/core-api/src/commands/migrateContentType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const command = async () => {
}

console.log(`Process finished at: ${new Date().toISOString()}`);

await client.close();
process.exit();
Copy link

Choose a reason for hiding this comment

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

Call client.close() before process.exit() for proper cleanup of the MongoDB connection.

Suggested change
process.exit();
await client.close(); process.exit();

};

command();
44 changes: 44 additions & 0 deletions backend/core-api/src/commands/migrateTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as dotenv from 'dotenv';

dotenv.config();

import { Collection, Db, MongoClient } from 'mongodb';

const { MONGO_URL = 'mongodb://localhost:27017/erxes?directConnection=true' } =
process.env;

if (!MONGO_URL) {
throw new Error(`Environment variable MONGO_URL not set.`);
}

const client = new MongoClient(MONGO_URL);

let db: Db;
let Tags: Collection;

const command = async () => {
await client.connect();
db = client.db() as Db;

Tags = db.collection('tags');

try {
await Tags.updateMany(
{},
{
$unset: { scopeBrandIds: '' },
$set: { isGroup: false, parentId: '', order: '' }, // flatten parentId: no nested parent tags allowed
},
);
} catch (e) {
console.log(`Error occurred: ${e.message}`);
await client.close();
}

Copy link

Choose a reason for hiding this comment

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

Ensure client connection is closed on success too (e.g., in a finally block) to avoid leaving open connections.

console.log(`Process finished at: ${new Date().toISOString()}`);

await client.close();
process.exit();
};

command();
26 changes: 26 additions & 0 deletions backend/core-api/src/modules/contacts/trpc/customer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,31 @@ export const customerRouter = t.router({
data: doc,
});
}),

tag: t.procedure.input(z.any()).mutation(async ({ ctx, input }) => {
Copy link

Choose a reason for hiding this comment

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

Avoid using z.any() for input and remove debug console.log; add stricter validation and remove logging of raw inputs in production.

const { action, _ids, tagIds, targetIds } = input;
const { models } = ctx;

let response = {};

if (action === 'count') {
response = await models.Customers.countDocuments({
tagIds: { $in: _ids },
});
}

if (action === 'tagObject') {
await models.Customers.updateMany(
{ _id: { $in: targetIds } },
{ $set: { tagIds } },
);

response = await models.Customers.find({
_id: { $in: targetIds },
}).lean();
}

return response;
Copy link

Choose a reason for hiding this comment

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

Consider handling unexpected 'action' values in the 'tag' procedure (e.g. add a default case or error throw) to avoid returning an empty response.

}),
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const documentSchema = new Schema(
name: { type: String },
content: { type: String },
replacer: { type: String },
code: { type: String },
code: { 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.

💡 Verification agent

🧩 Analysis chain

Unique index on an optional field will cause duplicate-key errors; use a partial index and drop "optional".

  • MongoDB unique indexes on non-present/null values are tricky; with the current definition, inserts can collide on null/absent code. Also, Mongoose does not recognize "optional"; use required: false (or omit) and a partialFilterExpression to index only docs that have a code.

Apply:

-    code: { type: String, optional: true },
+    code: { type: String, required: false, trim: true },

-documentSchema.index({ code: 1 }, { unique: true });
+documentSchema.index(
+  { code: 1 },
+  { unique: true, partialFilterExpression: { code: { $exists: true } } }
+);

Verification (run in mongosh against the target DB):

Also applies to: 18-18


🏁 Script executed:

db.getCollection('documents').getIndexes()
# Expect the 'code_1' index to have { unique: true, partialFilterExpression: { code: { $exists: true } } }

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Print the 'code' field definition and surrounding lines
rg -n "code:\s*\{" -C2 backend/core-api/src/modules/documents/db/definitions/documents.ts
# Print all index definitions on documentSchema
rg -n "documentSchema\.index" -C3 backend/core-api/src/modules/documents/db/definitions/documents.ts

Length of output: 398


Use required:false (not optional) and add a partial filter to the unique ‘code’ index

Replace the field and index definitions as follows:

-    code: { type: String, optional: true },
+    code: { type: String, required: false, trim: true },

- documentSchema.index({ code: 1 }, { unique: true });
+ documentSchema.index(
+   { code: 1 },
+   { unique: true, partialFilterExpression: { code: { $exists: true } } }
+ );

After deployment, in the Mongo shell run:

db.getCollection('documents').getIndexes();

and confirm that the code_1 index includes your partialFilterExpression.

🤖 Prompt for AI Agents
In backend/core-api/src/modules/documents/db/definitions/documents.ts around
line 11, change the field definition from optional:true to required:false and
update the unique index on code to include a partialFilterExpression so the
unique constraint only applies when code exists (e.g. partialFilterExpression: {
code: { $exists: true } }), then deploy and run
db.getCollection('documents').getIndexes() in the Mongo shell to confirm the
code_1 index includes the partialFilterExpression.

},
{
timestamps: true,
Expand Down
1 change: 1 addition & 0 deletions backend/core-api/src/modules/tags/@types/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface ITagFilterQueryParams
type: string;
tagIds?: string[];
parentId?: string;
isGroup?: boolean;
ids: string[];
excludeIds: boolean;
}
28 changes: 8 additions & 20 deletions backend/core-api/src/modules/tags/db/definitions/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,20 @@ export const tagSchema = schemaWrapper(
new Schema(
{
_id: mongooseStringRandomId,
name: { type: String, label: 'Name' },
type: {
type: String,
label: 'Type',
index: true,
},
name: { type: String, label: 'Name', unique: true },
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Global unique on name is risky; scope uniqueness.

unique: true enforces cross-tenant/global uniqueness and blocks same-name tags under different groups. Prefer a compound unique index (name, parentId) or include tenant scope (e.g., organizationId).

-      name: { type: String, label: 'Name', unique: true },
+      name: { type: String, label: 'Name' },
@@
-tagSchema.index({ _id: 1, name: 1, parentId: 1 });
+tagSchema.index({ _id: 1, name: 1, parentId: 1 });
+// Enforce uniqueness only within the same parent (adjust to include tenant scope if applicable)
+tagSchema.index({ name: 1, parentId: 1 }, { unique: true });

If a unique index on name already exists in prod, we’ll need a migration to drop it before adding the compound index. I can prepare it.


🏁 Script executed:

#!/bin/bash
# Locate the tags.ts schema definition
fd -t f tags.ts

# Preview the top of the file to see field definitions
sed -n '1,200p' backend/core-api/src/modules/tags/db/definitions/tags.ts

# Inspect the schema object and index declarations
rg -n "tagSchema" -C3 backend/core-api/src/modules/tags/db/definitions/tags.ts
rg -n "index" -C3 backend/core-api/src/modules/tags/db/definitions/tags.ts

# Check for tenant or hierarchical scope fields
rg -n "parentId" -C3 backend/core-api/src/modules/tags/db/definitions/tags.ts
rg -n "organizationId" -C3 backend/core-api/src/modules/tags/db/definitions/tags.ts

Length of output: 2612


Scope tag name uniqueness to its parent (and tenant)

  • In backend/core-api/src/modules/tags/db/definitions/tags.ts (line 8), remove the field-level unique: true on name.
  • Add a compound unique index:
    tagSchema.index({ name: 1, parentId: 1 }, { unique: true });
    (Include tenant/organizationId in the index if this collection is multi-tenant.)
  • If a standalone unique index on name already exists in production, drop it via a migration before creating the new compound index.

colorCode: { type: String, label: 'Color code' },
parentId: { type: String, label: 'Parent' },
relatedIds: { type: [String], label: 'Children tag ids' },
isGroup: { type: Boolean, label: 'Is group', default: false },
type: { type: String, label: 'Content type' },
description: { type: String, label: 'Description' },
objectCount: { type: Number, label: 'Object count' },
order: { type: String, label: 'Order', index: true },
parentId: {
type: String,
optional: true,
index: true,
label: 'Parent',
},
relatedIds: {
type: [String],
optional: true,
label: 'Children tag ids',
},
order: { type: String, label: 'Order' },
},
{
timestamps: true,
},
),
);

// for tags query. increases search speed, avoids in-memory sorting
tagSchema.index({ _id: 1, type: 1, order: 1, name: 1, createdAt: 1 });
tagSchema.index({ _id: 1, name: 1, parentId: 1, type: 1 });
Loading