@isardsat/editorial-server 6.15.0 → 6.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/routes/data.js +169 -1
- package/package.json +3 -3
package/dist/routes/data.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
|
2
|
-
import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, getOptionsReference, } from "@isardsat/editorial-common";
|
|
2
|
+
import { EditorialDataItemSchema, EditorialDataSchema, EditorialDiffResponseSchema, EditorialSchemaSchema, getOptionsReference, } from "@isardsat/editorial-common";
|
|
3
3
|
import { generateMetaSchema } from "../lib/utils/schema.js";
|
|
4
4
|
function createCache() {
|
|
5
5
|
let schemaCache = null;
|
|
@@ -117,6 +117,55 @@ function resolveCollectionReferences(collection, schema, itemType, content, orig
|
|
|
117
117
|
}
|
|
118
118
|
return resolvedCollection;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Compares two items and returns the list of fields that have changed.
|
|
122
|
+
* Excludes metadata fields like 'updatedAt'.
|
|
123
|
+
*/
|
|
124
|
+
function getChangedFields(previewItem, productionItem) {
|
|
125
|
+
const changedFields = [];
|
|
126
|
+
const excludedFields = ["updatedAt", "createdAt"];
|
|
127
|
+
// Get all unique keys from both items
|
|
128
|
+
const allKeys = new Set([
|
|
129
|
+
...Object.keys(previewItem),
|
|
130
|
+
...Object.keys(productionItem),
|
|
131
|
+
]);
|
|
132
|
+
for (const key of allKeys) {
|
|
133
|
+
if (excludedFields.includes(key))
|
|
134
|
+
continue;
|
|
135
|
+
const previewValue = previewItem[key];
|
|
136
|
+
const productionValue = productionItem[key];
|
|
137
|
+
if (!deepEqual(previewValue, productionValue)) {
|
|
138
|
+
changedFields.push(key);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return changedFields;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Deep equality comparison for values.
|
|
145
|
+
*/
|
|
146
|
+
function deepEqual(value1, value2) {
|
|
147
|
+
if (value1 === value2)
|
|
148
|
+
return true;
|
|
149
|
+
if (value1 == null || value2 == null)
|
|
150
|
+
return false;
|
|
151
|
+
if (typeof value1 !== typeof value2)
|
|
152
|
+
return false;
|
|
153
|
+
if (typeof value1 !== "object") {
|
|
154
|
+
return value1 === value2;
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(value1) !== Array.isArray(value2))
|
|
157
|
+
return false;
|
|
158
|
+
if (Array.isArray(value1)) {
|
|
159
|
+
if (value1.length !== value2.length)
|
|
160
|
+
return false;
|
|
161
|
+
return value1.every((item, index) => deepEqual(item, value2[index]));
|
|
162
|
+
}
|
|
163
|
+
const keys1 = Object.keys(value1);
|
|
164
|
+
const keys2 = Object.keys(value2);
|
|
165
|
+
if (keys1.length !== keys2.length)
|
|
166
|
+
return false;
|
|
167
|
+
return keys1.every((key) => deepEqual(value1[key], value2[key]));
|
|
168
|
+
}
|
|
120
169
|
export function createDataRoutes(config, storage) {
|
|
121
170
|
const app = new OpenAPIHono();
|
|
122
171
|
const cache = createCache();
|
|
@@ -553,5 +602,124 @@ export function createDataRoutes(config, storage) {
|
|
|
553
602
|
const metaSchema = generateMetaSchema({ allowedExtraFields });
|
|
554
603
|
return c.json(metaSchema);
|
|
555
604
|
});
|
|
605
|
+
app.openapi(createRoute({
|
|
606
|
+
method: "get",
|
|
607
|
+
path: "/diff",
|
|
608
|
+
summary: "Get differences between preview and production data",
|
|
609
|
+
responses: {
|
|
610
|
+
200: {
|
|
611
|
+
content: {
|
|
612
|
+
"application/json": {
|
|
613
|
+
schema: EditorialDiffResponseSchema,
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
description: "Get differences between preview and production data",
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
tags: ["Data"],
|
|
620
|
+
}), async (c) => {
|
|
621
|
+
// Fetch both preview and production content
|
|
622
|
+
const [previewContent, productionContent, schema] = await Promise.all([
|
|
623
|
+
cache.getContent(storage, { production: false }),
|
|
624
|
+
cache.getContent(storage, { production: true }),
|
|
625
|
+
cache.getSchema(storage),
|
|
626
|
+
]);
|
|
627
|
+
const result = {
|
|
628
|
+
collections: {},
|
|
629
|
+
singles: {},
|
|
630
|
+
};
|
|
631
|
+
// Get all item types from both preview and production
|
|
632
|
+
const allItemTypes = new Set([
|
|
633
|
+
...Object.keys(previewContent),
|
|
634
|
+
...Object.keys(productionContent),
|
|
635
|
+
]);
|
|
636
|
+
for (const itemType of allItemTypes) {
|
|
637
|
+
const previewCollection = previewContent[itemType] || {};
|
|
638
|
+
const productionCollection = productionContent[itemType] || {};
|
|
639
|
+
const itemSchema = schema[itemType];
|
|
640
|
+
// Check if this is a "singleton" type (only one item allowed)
|
|
641
|
+
const isSingleton = itemSchema?.singleton === true;
|
|
642
|
+
if (isSingleton) {
|
|
643
|
+
// Compare the singleton item
|
|
644
|
+
const previewKeys = Object.keys(previewCollection);
|
|
645
|
+
const productionKeys = Object.keys(productionCollection);
|
|
646
|
+
const singletonKey = previewKeys[0] || productionKeys[0];
|
|
647
|
+
if (singletonKey) {
|
|
648
|
+
const previewItem = previewCollection[singletonKey];
|
|
649
|
+
const productionItem = productionCollection[singletonKey];
|
|
650
|
+
if (previewItem && !productionItem) {
|
|
651
|
+
// Singleton exists in preview but not in production = added
|
|
652
|
+
result.singles[itemType] = {
|
|
653
|
+
status: "added",
|
|
654
|
+
preview: previewItem,
|
|
655
|
+
updatedAt: previewItem.updatedAt,
|
|
656
|
+
changedFields: Object.keys(previewItem).filter((k) => !["updatedAt", "createdAt"].includes(k)),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
else if (!previewItem && productionItem) {
|
|
660
|
+
// Singleton exists in production but not in preview = deleted
|
|
661
|
+
result.singles[itemType] = {
|
|
662
|
+
status: "deleted",
|
|
663
|
+
production: productionItem,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
else if (previewItem?.updatedAt !== productionItem?.updatedAt) {
|
|
667
|
+
// Singleton has different updatedAt = modified
|
|
668
|
+
const changedFields = getChangedFields(previewItem, productionItem);
|
|
669
|
+
result.singles[itemType] = {
|
|
670
|
+
status: "modified",
|
|
671
|
+
preview: previewItem,
|
|
672
|
+
production: productionItem,
|
|
673
|
+
updatedAt: previewItem?.updatedAt,
|
|
674
|
+
changedFields,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
// Handle collections
|
|
681
|
+
const added = [];
|
|
682
|
+
const modified = [];
|
|
683
|
+
const deleted = [];
|
|
684
|
+
// Find added and modified items
|
|
685
|
+
for (const [id, previewItem] of Object.entries(previewCollection)) {
|
|
686
|
+
const productionItem = productionCollection[id];
|
|
687
|
+
if (!productionItem) {
|
|
688
|
+
// Item exists in preview but not in production = added
|
|
689
|
+
added.push({
|
|
690
|
+
id,
|
|
691
|
+
item: previewItem,
|
|
692
|
+
updatedAt: previewItem.updatedAt,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else if (previewItem.updatedAt !== productionItem.updatedAt) {
|
|
696
|
+
// Item has different updatedAt = modified (not yet published)
|
|
697
|
+
const changedFields = getChangedFields(previewItem, productionItem);
|
|
698
|
+
modified.push({
|
|
699
|
+
id,
|
|
700
|
+
preview: previewItem,
|
|
701
|
+
production: productionItem,
|
|
702
|
+
updatedAt: previewItem.updatedAt,
|
|
703
|
+
changedFields,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// Find deleted items (exist in production but not in preview)
|
|
708
|
+
for (const [id, productionItem] of Object.entries(productionCollection)) {
|
|
709
|
+
if (!previewCollection[id]) {
|
|
710
|
+
deleted.push({
|
|
711
|
+
id,
|
|
712
|
+
item: productionItem,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// Only include collections with changes
|
|
717
|
+
if (added.length > 0 || modified.length > 0 || deleted.length > 0) {
|
|
718
|
+
result.collections[itemType] = { added, modified, deleted };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return c.json(result);
|
|
723
|
+
});
|
|
556
724
|
return app;
|
|
557
725
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isardsat/editorial-server",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"hono": "^4.9.8",
|
|
15
15
|
"yaml": "^2.8.1",
|
|
16
16
|
"zod": "^4.1.11",
|
|
17
|
-
"@isardsat/editorial-
|
|
18
|
-
"@isardsat/editorial-
|
|
17
|
+
"@isardsat/editorial-common": "^6.17.0",
|
|
18
|
+
"@isardsat/editorial-admin": "^6.17.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@tsconfig/node22": "^22.0.0",
|