Skip to content

Commit 63f0774

Browse files
authored
feat(): Backport metadata management (medusajs#11469)
FIXES FRMW-2915 **What** Backport metadata management. all the metadata get preserved unless a specific empty string is provided for the key which in turn would remove that key from the metadata
1 parent a5ff1b9 commit 63f0774

File tree

7 files changed

+171
-4
lines changed

7 files changed

+171
-4
lines changed

.changeset/kind-chairs-cough.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@medusajs/utils": patch
3+
---
4+
5+
feat(utils): Backport metadata management

integration-tests/http/__tests__/customer/admin/customer.spec.ts

+46-2
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ medusaIntegrationTestRunner({
216216
first_name: "newf",
217217
last_name: "newl",
218218
219-
metadata: { foo: "bar" },
219+
metadata: { foo: "bar", bar: "bar", baz: "baz" },
220220
},
221221
adminHeaders
222222
)
@@ -230,7 +230,51 @@ medusaIntegrationTestRunner({
230230
first_name: "newf",
231231
last_name: "newl",
232232
233-
metadata: { foo: "bar" },
233+
metadata: { foo: "bar", bar: "bar", baz: "baz" },
234+
})
235+
)
236+
})
237+
238+
it("should correctly update customer metadata", async () => {
239+
let response = await api.post(
240+
`/admin/customers/${customer3.id}`,
241+
{
242+
first_name: "newf",
243+
last_name: "newl",
244+
245+
metadata: { foo: "bar", bar: "bar", baz: "baz" },
246+
},
247+
adminHeaders
248+
)
249+
250+
expect(response.data.customer).toEqual(
251+
expect.objectContaining({
252+
first_name: "newf",
253+
last_name: "newl",
254+
255+
metadata: { foo: "bar", bar: "bar", baz: "baz" },
256+
})
257+
)
258+
259+
response = await api
260+
.post(
261+
`/admin/customers/${customer3.id}`,
262+
{
263+
metadata: { foo: "", bar: "bar2", baz2: "baz2" },
264+
},
265+
adminHeaders
266+
)
267+
.catch((err) => {
268+
console.log(err)
269+
})
270+
271+
expect(response.status).toEqual(200)
272+
expect(response.data.customer).toEqual(
273+
expect.objectContaining({
274+
first_name: "newf",
275+
last_name: "newl",
276+
277+
metadata: { bar: "bar2", baz: "baz", baz2: "baz2" },
234278
})
235279
)
236280
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { mergeMetadata } from "../merge-metadata"
2+
3+
describe("mergeMetadata", () => {
4+
it("should merge simple key-value pairs", () => {
5+
const metadata = {
6+
key1: "value1",
7+
key2: "value2",
8+
}
9+
const metadataToMerge = {
10+
key2: "new-value2",
11+
key3: "value3",
12+
}
13+
14+
const result = mergeMetadata(metadata, metadataToMerge)
15+
16+
expect(result).toEqual({
17+
key1: "value1",
18+
key2: "new-value2",
19+
key3: "value3",
20+
})
21+
})
22+
23+
it("should remove keys with empty string values", () => {
24+
const metadata = {
25+
key1: "value1",
26+
key2: "value2",
27+
key3: "value3",
28+
}
29+
const metadataToMerge = {
30+
key2: "",
31+
}
32+
33+
const result = mergeMetadata(metadata, metadataToMerge)
34+
35+
expect(result).toEqual({
36+
key1: "value1",
37+
key3: "value3",
38+
})
39+
})
40+
})

packages/core/utils/src/common/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,4 @@ export * from "./upper-case-first"
8181
export * from "./validate-handle"
8282
export * from "./wrap-handler"
8383
export * from "./merge-plugin-modules"
84+
export * from "./merge-metadata"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Merges two metadata objects. The key from the original metadata object is
3+
* preserved if the key is not present in the metadata to merge. If the key
4+
* is present in the metadata to merge, the value from the metadata to merge
5+
* is used. If the key in the metadata to merge is an empty string, the key
6+
* is removed from the merged metadata object.
7+
*
8+
* @param metadata - The base metadata object.
9+
* @param metadataToMerge - The metadata object to merge.
10+
* @returns The merged metadata object.
11+
*/
12+
export function mergeMetadata(
13+
metadata: Record<string, any>,
14+
metadataToMerge: Record<string, any>
15+
) {
16+
const merged = { ...metadata }
17+
18+
for (const [key, value] of Object.entries(metadataToMerge)) {
19+
if (value === "") {
20+
delete merged[key]
21+
continue
22+
}
23+
24+
// NOTE: If we want to handle the same behaviour on nested objects. We should then conside arrays as well.
25+
// if (value && typeof value === "object") {
26+
// merged[key] =
27+
// merged[key] && typeof merged[key] === "object"
28+
// ? mergeMetadata(merged[key], value)
29+
// : { ...value }
30+
// continue
31+
// }
32+
33+
merged[key] = value
34+
}
35+
36+
return merged
37+
}

packages/core/utils/src/modules-sdk/__tests__/medusa-internal-service.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ describe("Internal Module Service Factory", () => {
4545
let instance
4646

4747
beforeEach(() => {
48-
jest.clearAllMocks()
4948
instance = new IMedusaInternalService(containerMock)
5049
})
5150

51+
afterEach(() => {
52+
jest.clearAllMocks()
53+
})
54+
5255
it("should throw model id undefined error on retrieve if id is not defined", async () => {
5356
const err = await instance.retrieve().catch((e) => e)
5457
expect(err.message).toBe("model - id must be defined")
@@ -201,7 +204,7 @@ describe("Internal Module Service Factory", () => {
201204
updateData,
202205
])
203206

204-
const result = await instance.update(updateData)
207+
const result = await instance.update({ selector: {}, data: updateData })
205208
expect(result).toEqual([updateData])
206209
})
207210

@@ -223,6 +226,32 @@ describe("Internal Module Service Factory", () => {
223226
])
224227
})
225228

229+
it("should update entities metadata successfully", async () => {
230+
const updateData = {
231+
id: "1",
232+
name: "UpdatedItem",
233+
metadata: { key1: "", key2: "key2" },
234+
}
235+
const entitiesToUpdate = [
236+
{ id: "1", name: "Item", metadata: { key1: "value1" } },
237+
]
238+
239+
containerMock[modelRepositoryName].find.mockClear()
240+
containerMock[modelRepositoryName].find.mockResolvedValueOnce(
241+
entitiesToUpdate
242+
)
243+
244+
await instance.update({ selector: {}, data: updateData })
245+
expect(
246+
containerMock[modelRepositoryName].update.mock.calls[0][0][0].update
247+
).toEqual({
248+
...updateData,
249+
metadata: {
250+
key2: "key2",
251+
},
252+
})
253+
})
254+
226255
it("should delete entity successfully", async () => {
227256
await instance.delete("1")
228257
expect(containerMock[modelRepositoryName].delete).toHaveBeenCalledWith(

packages/core/utils/src/modules-sdk/medusa-internal-service.ts

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
isString,
1818
lowerCaseFirst,
1919
MedusaError,
20+
mergeMetadata,
2021
} from "../common"
2122
import { FreeTextSearchFilterKeyPrefix } from "../dal"
2223
import { DmlEntity, toMikroORMEntity } from "../dml"
@@ -350,6 +351,16 @@ export function MedusaInternalService<
350351
return []
351352
}
352353

354+
// Manage metadata if needed
355+
toUpdateData.forEach(({ entity, update }) => {
356+
if (isPresent(update.metadata)) {
357+
entity.metadata = update.metadata = mergeMetadata(
358+
entity.metadata ?? {},
359+
update.metadata
360+
)
361+
}
362+
})
363+
353364
return await this[propertyRepositoryName].update(
354365
toUpdateData,
355366
sharedContext

0 commit comments

Comments
 (0)