strapi-content-sync-pro 1.0.2 → 1.0.4
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/README.md +67 -18
- package/admin/src/components/BulkTransferTab.jsx +880 -0
- package/admin/src/components/ConfigTab.jsx +25 -4
- package/admin/src/components/HelpTab.jsx +201 -15
- package/admin/src/components/MediaTab.jsx +7 -0
- package/admin/src/components/StatsTab.jsx +470 -0
- package/admin/src/components/SyncProfilesTab.jsx +63 -5
- package/admin/src/components/SyncTab.jsx +53 -7
- package/admin/src/pages/App/index.jsx +15 -1
- package/docs/clipchamp-screen-recording-script.md +0 -0
- package/docs/production-readiness-status.md +34 -0
- package/docs/production-readiness-test-matrix.md +151 -0
- package/docs/test-environments-setup-legacy.txt +60 -0
- package/package.json +13 -4
- package/server/src/content-types/index.js +2 -0
- package/server/src/content-types/sync-run-report/schema.json +26 -0
- package/server/src/controllers/bulk-transfer.js +141 -0
- package/server/src/controllers/config.js +48 -5
- package/server/src/controllers/index.js +4 -0
- package/server/src/controllers/sync-log.js +6 -0
- package/server/src/controllers/sync-media.js +19 -0
- package/server/src/controllers/sync-stats.js +51 -0
- package/server/src/controllers/sync.js +9 -3
- package/server/src/routes/index.js +28 -0
- package/server/src/services/bulk-transfer.js +837 -0
- package/server/src/services/config.js +18 -2
- package/server/src/services/index.js +4 -0
- package/server/src/services/sync-execution.js +102 -5
- package/server/src/services/sync-log.js +36 -0
- package/server/src/services/sync-media.js +224 -1
- package/server/src/services/sync-profiles.js +92 -4
- package/server/src/services/sync-stats.js +353 -0
- package/server/src/services/sync.js +323 -101
- package/server/src/utils/applier.js +120 -13
- package/server/src/utils/comparator.js +22 -6
- package/server/src/utils/fetcher.js +11 -2
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
import { Play, Clock, Cog, ArrowUp, ArrowDown } from '@strapi/icons';
|
|
27
27
|
import { useFetchClient } from '@strapi/strapi/admin';
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
|
|
29
31
|
const PLUGIN_ID = 'strapi-content-sync-pro';
|
|
30
32
|
|
|
31
33
|
const EXECUTION_MODE_OPTIONS = [
|
|
@@ -53,6 +55,7 @@ const SyncTab = () => {
|
|
|
53
55
|
const [executionStatus, setExecutionStatus] = useState([]);
|
|
54
56
|
const [globalSettings, setGlobalSettings] = useState({});
|
|
55
57
|
const [loading, setLoading] = useState(true);
|
|
58
|
+
const [syncMode, setSyncMode] = useState('paired');
|
|
56
59
|
|
|
57
60
|
// Filter and ordering
|
|
58
61
|
const [profileFilter, setProfileFilter] = useState('all');
|
|
@@ -84,17 +87,19 @@ const SyncTab = () => {
|
|
|
84
87
|
|
|
85
88
|
const loadData = async () => {
|
|
86
89
|
try {
|
|
87
|
-
const [profilesRes, statusRes, globalRes, depsRes] = await Promise.all([
|
|
90
|
+
const [profilesRes, statusRes, globalRes, depsRes, configRes] = await Promise.all([
|
|
88
91
|
get(`/${PLUGIN_ID}/sync-profiles`),
|
|
89
92
|
get(`/${PLUGIN_ID}/sync-execution/status`),
|
|
90
93
|
get(`/${PLUGIN_ID}/sync-execution/global-settings`),
|
|
91
94
|
get(`/${PLUGIN_ID}/dependencies/all`).catch(() => ({ data: { data: {} } })),
|
|
95
|
+
get(`/${PLUGIN_ID}/config`),
|
|
92
96
|
]);
|
|
93
97
|
const loadedProfiles = profilesRes.data.data || [];
|
|
94
98
|
setProfiles(loadedProfiles);
|
|
95
99
|
setExecutionStatus(statusRes.data.data || []);
|
|
96
100
|
setGlobalSettings(globalRes.data.data || {});
|
|
97
101
|
setDependencies(depsRes.data.data || {});
|
|
102
|
+
setSyncMode(configRes?.data?.data?.syncMode || 'paired');
|
|
98
103
|
|
|
99
104
|
// Load saved execution order or calculate from dependencies
|
|
100
105
|
const savedOrder = globalRes.data.data?.executionOrder || {};
|
|
@@ -325,7 +330,7 @@ const SyncTab = () => {
|
|
|
325
330
|
const openSettingsModal = async (profileId) => {
|
|
326
331
|
try {
|
|
327
332
|
const res = await get(`/${PLUGIN_ID}/sync-execution/settings/${profileId}`);
|
|
328
|
-
|
|
333
|
+
const settings = res.data.data || {
|
|
329
334
|
executionMode: 'on_demand',
|
|
330
335
|
scheduleType: 'interval',
|
|
331
336
|
scheduleInterval: 60,
|
|
@@ -333,7 +338,15 @@ const SyncTab = () => {
|
|
|
333
338
|
enabled: false,
|
|
334
339
|
syncDependencies: false,
|
|
335
340
|
dependencyDepth: 1,
|
|
336
|
-
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const profile = profiles.find((p) => p.id === profileId);
|
|
344
|
+
if (profile && profile.syncDeletions && settings.executionMode === 'live') {
|
|
345
|
+
settings.executionMode = 'on_demand';
|
|
346
|
+
settings.enabled = false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
setExecutionSettings(settings);
|
|
337
350
|
setSelectedProfile(profileId);
|
|
338
351
|
setSettingsModalOpen(true);
|
|
339
352
|
} catch (err) {
|
|
@@ -343,7 +356,19 @@ const SyncTab = () => {
|
|
|
343
356
|
|
|
344
357
|
const handleSaveExecutionSettings = async () => {
|
|
345
358
|
try {
|
|
346
|
-
|
|
359
|
+
const payload = { ...executionSettings };
|
|
360
|
+
if (syncMode === 'single_side' && payload.executionMode === 'live') {
|
|
361
|
+
payload.executionMode = 'on_demand';
|
|
362
|
+
payload.enabled = false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const selected = profiles.find((p) => p.id === selectedProfile);
|
|
366
|
+
if (selected?.syncDeletions && payload.executionMode === 'live') {
|
|
367
|
+
payload.executionMode = 'on_demand';
|
|
368
|
+
payload.enabled = false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await put(`/${PLUGIN_ID}/sync-execution/settings/${selectedProfile}`, payload);
|
|
347
372
|
setMessage({ type: 'success', text: 'Execution settings saved' });
|
|
348
373
|
setSettingsModalOpen(false);
|
|
349
374
|
loadData();
|
|
@@ -441,6 +466,22 @@ const SyncTab = () => {
|
|
|
441
466
|
</Flex>
|
|
442
467
|
</Flex>
|
|
443
468
|
|
|
469
|
+
{syncMode === 'single_side' && (
|
|
470
|
+
<Box paddingTop={4}>
|
|
471
|
+
<Alert variant="info" title="Single-side mode restrictions">
|
|
472
|
+
Live execution is disabled in single-side mode. Use on-demand or scheduled execution with pull-only profiles.
|
|
473
|
+
</Alert>
|
|
474
|
+
</Box>
|
|
475
|
+
)}
|
|
476
|
+
|
|
477
|
+
{profiles.some((p) => p.syncDeletions) && (
|
|
478
|
+
<Box paddingTop={4}>
|
|
479
|
+
<Alert variant="info" title="Deletion sync profile rules">
|
|
480
|
+
Profiles with deletion sync enabled run as explicit one-way operations. Avoid live mode for these profiles.
|
|
481
|
+
</Alert>
|
|
482
|
+
</Box>
|
|
483
|
+
)}
|
|
484
|
+
|
|
444
485
|
{message && (
|
|
445
486
|
<Box paddingTop={4}>
|
|
446
487
|
<Alert variant={message.type} closeLabel="Close" onClose={() => setMessage(null)}>
|
|
@@ -474,7 +515,7 @@ const SyncTab = () => {
|
|
|
474
515
|
value={profileFilter}
|
|
475
516
|
onChange={setProfileFilter}
|
|
476
517
|
size="S"
|
|
477
|
-
style={{ width:
|
|
518
|
+
style={{ width: 180 }}
|
|
478
519
|
>
|
|
479
520
|
{FILTER_OPTIONS.map(opt => (
|
|
480
521
|
<SingleSelectOption key={opt.value} value={opt.value}>
|
|
@@ -570,7 +611,7 @@ const SyncTab = () => {
|
|
|
570
611
|
<ArrowDown />
|
|
571
612
|
</IconButton>
|
|
572
613
|
</Flex>
|
|
573
|
-
<TextInput
|
|
614
|
+
<TextInput
|
|
574
615
|
value={order}
|
|
575
616
|
onChange={(e) => handleOrderChange(profile.id, e.target.value)}
|
|
576
617
|
style={{ width: 50, textAlign: 'center' }}
|
|
@@ -849,7 +890,11 @@ const SyncTab = () => {
|
|
|
849
890
|
onChange={(value) => setExecutionSettings((p) => ({ ...p, executionMode: value }))}
|
|
850
891
|
>
|
|
851
892
|
{EXECUTION_MODE_OPTIONS.map((opt) => (
|
|
852
|
-
<SingleSelectOption
|
|
893
|
+
<SingleSelectOption
|
|
894
|
+
key={opt.value}
|
|
895
|
+
value={opt.value}
|
|
896
|
+
disabled={syncMode === 'single_side' && opt.value === 'live'}
|
|
897
|
+
>
|
|
853
898
|
{opt.label}
|
|
854
899
|
</SingleSelectOption>
|
|
855
900
|
))}
|
|
@@ -858,6 +903,7 @@ const SyncTab = () => {
|
|
|
858
903
|
{executionSettings.executionMode === 'on_demand' && 'Sync only when manually triggered'}
|
|
859
904
|
{executionSettings.executionMode === 'scheduled' && 'Sync automatically at regular intervals'}
|
|
860
905
|
{executionSettings.executionMode === 'live' && 'Sync immediately when changes occur'}
|
|
906
|
+
{syncMode === 'single_side' && executionSettings.executionMode === 'live' && ' (not supported in single-side mode)'}
|
|
861
907
|
</Field.Hint>
|
|
862
908
|
</Field.Root>
|
|
863
909
|
</Box>
|
|
@@ -6,16 +6,20 @@ import { ConfigTab } from '../../components/ConfigTab';
|
|
|
6
6
|
import { ContentTypesTab } from '../../components/ContentTypesTab';
|
|
7
7
|
import { SyncTab } from '../../components/SyncTab';
|
|
8
8
|
import { LogsTab } from '../../components/LogsTab';
|
|
9
|
+
import { StatsTab } from '../../components/StatsTab';
|
|
9
10
|
import { HelpTab } from '../../components/HelpTab';
|
|
10
11
|
import { SyncProfilesTab } from '../../components/SyncProfilesTab';
|
|
11
12
|
import { MediaTab } from '../../components/MediaTab';
|
|
13
|
+
import BulkTransferTab from '../../components/BulkTransferTab';
|
|
12
14
|
|
|
13
15
|
const TABS = [
|
|
14
16
|
{ key: 'config', label: 'Configuration' },
|
|
15
17
|
{ key: 'content-types', label: 'Content Types' },
|
|
16
18
|
{ key: 'sync-profiles', label: 'Sync Profiles' },
|
|
17
19
|
{ key: 'sync', label: 'Sync' },
|
|
20
|
+
{ key: 'bulk-transfer', label: 'Bulk Transfer' },
|
|
18
21
|
{ key: 'media', label: 'Media' },
|
|
22
|
+
{ key: 'stats', label: 'Stats' },
|
|
19
23
|
{ key: 'logs', label: 'Logs' },
|
|
20
24
|
{ key: 'help', label: 'Help' },
|
|
21
25
|
];
|
|
@@ -27,8 +31,16 @@ const HomePage = () => {
|
|
|
27
31
|
<Main>
|
|
28
32
|
<Box padding={8} background="neutral100">
|
|
29
33
|
<Typography variant="alpha" tag="h1">
|
|
30
|
-
Content Sync Pro Plugin -
|
|
34
|
+
Content Sync Pro Plugin - Bulk Data Transfer, Live Sync, and Automated Content Replication between Strapi instances
|
|
31
35
|
</Typography>
|
|
36
|
+
<Box paddingTop={2}>
|
|
37
|
+
<Typography variant="omega" textColor="neutral600">
|
|
38
|
+
Copy, migrate, and keep content, media, users, and relations in sync across environments.
|
|
39
|
+
Run full one-click Bulk Transfers with selectable chunks, page-level progress, pause /
|
|
40
|
+
resume / cancel, and persisted run history — alongside profile-driven bi-directional sync,
|
|
41
|
+
field-level policies, scheduling, live hooks, and alerts.
|
|
42
|
+
</Typography>
|
|
43
|
+
</Box>
|
|
32
44
|
|
|
33
45
|
|
|
34
46
|
<Box paddingTop={4} paddingBottom={6}>
|
|
@@ -49,7 +61,9 @@ const HomePage = () => {
|
|
|
49
61
|
{activeTab === 'content-types' && <ContentTypesTab />}
|
|
50
62
|
{activeTab === 'sync-profiles' && <SyncProfilesTab />}
|
|
51
63
|
{activeTab === 'sync' && <SyncTab />}
|
|
64
|
+
{activeTab === 'bulk-transfer' && <BulkTransferTab />}
|
|
52
65
|
{activeTab === 'media' && <MediaTab />}
|
|
66
|
+
{activeTab === 'stats' && <StatsTab />}
|
|
53
67
|
{activeTab === 'logs' && <LogsTab />}
|
|
54
68
|
{activeTab === 'help' && <HelpTab />}
|
|
55
69
|
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Production Readiness Status — Content Sync Pro
|
|
2
|
+
|
|
3
|
+
## Current Verdict
|
|
4
|
+
**NO-GO (not yet fully production-ready)**
|
|
5
|
+
|
|
6
|
+
## Completed
|
|
7
|
+
- Implemented paired/single-side mode behavior and enforcement.
|
|
8
|
+
- Implemented Stats tab + before/after run reports.
|
|
9
|
+
- Implemented manual clear + retention limits for logs/reports.
|
|
10
|
+
- Added production-readiness test matrix:
|
|
11
|
+
- `docs/production-readiness-test-matrix.md`
|
|
12
|
+
- Added legacy environment notes copy:
|
|
13
|
+
- `docs/test-environments-setup-legacy.txt`
|
|
14
|
+
|
|
15
|
+
## Smoke Checks Passed
|
|
16
|
+
- `GET http://localhost:40101/api/strapi-content-sync-pro/ping` => 200
|
|
17
|
+
- `GET http://localhost:4010/api/strapi-content-sync-pro/ping` => 200
|
|
18
|
+
- Package test script passes (`npm run test`) — placeholder only.
|
|
19
|
+
|
|
20
|
+
## Blocking Items Before GO
|
|
21
|
+
1. Execute full P0 and P1 matrix scenarios in `docs/production-readiness-test-matrix.md`.
|
|
22
|
+
2. Capture evidence for each case (request/response, DB/file verification, screenshots).
|
|
23
|
+
3. Verify restart/recovery after plugin copy in target runtime path.
|
|
24
|
+
4. Validate single-side mode with remote plugin disabled.
|
|
25
|
+
5. Validate media restore scenarios after partial deletions.
|
|
26
|
+
6. Confirm retention pruning under load (high log/report volume).
|
|
27
|
+
|
|
28
|
+
## Required Release Gate
|
|
29
|
+
- P0 cases: 100% pass
|
|
30
|
+
- P1 cases: pass or accepted risk signed off
|
|
31
|
+
- No open critical defects
|
|
32
|
+
|
|
33
|
+
## Recommended Next Action
|
|
34
|
+
Run matrix execution in order: P0 -> P1 -> P2, then update this file with final **GO/NO-GO** sign-off.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Content Sync Pro — Production Readiness Test Matrix
|
|
2
|
+
|
|
3
|
+
This guide converts the environment notes from `test-enviroments-setup.txt` into a structured, repeatable validation matrix for release readiness.
|
|
4
|
+
|
|
5
|
+
## 1) Test Environments
|
|
6
|
+
|
|
7
|
+
### Remote Server
|
|
8
|
+
- Start:
|
|
9
|
+
- `D:\Rutba\ERP> npm run dev:strapi`
|
|
10
|
+
- URL: `http://localhost:4010`
|
|
11
|
+
- DB: MySQL (`pos_db`)
|
|
12
|
+
- Upload dir: `D:\Rutba\data\rutba-pos-files\uploads`
|
|
13
|
+
|
|
14
|
+
### Local Server
|
|
15
|
+
- Start:
|
|
16
|
+
- `D:\Rutba\ERP\pos-strapi> npm run dev`
|
|
17
|
+
- URL: `http://localhost:40101`
|
|
18
|
+
- DB: MySQL (`rutba_pos`)
|
|
19
|
+
- Upload dir: `D:\Rutba\data\rutba-pos-files\tmp\uploads`
|
|
20
|
+
|
|
21
|
+
### Shared Notes
|
|
22
|
+
- Same Strapi codebase, different `.env` values.
|
|
23
|
+
- Admin credentials (both):
|
|
24
|
+
- Email: `eharain@yahoo.com`
|
|
25
|
+
- Password: `At56ZNTxXf6JSTq`
|
|
26
|
+
- Plugin deploy target to test runtime updates:
|
|
27
|
+
- `D:\Rutba\ERP\pos-strapi\src\plugins\strapi-content-sync-pro`
|
|
28
|
+
- Restart may be required after plugin copy/update.
|
|
29
|
+
|
|
30
|
+
## 2) Scope of Validation
|
|
31
|
+
|
|
32
|
+
- Paired mode (plugin installed both sides)
|
|
33
|
+
- Single-side mode (plugin active on local only)
|
|
34
|
+
- Profiles (simple + advanced), dependency depth, execution modes
|
|
35
|
+
- Entity sync (CMS content, products, orders)
|
|
36
|
+
- Media sync + entity-linked media
|
|
37
|
+
- Stats tab snapshots and before/after reports
|
|
38
|
+
- Log/report retention and cleanup controls
|
|
39
|
+
- Failure/recovery behavior
|
|
40
|
+
|
|
41
|
+
## 3) Pass/Fail Exit Criteria
|
|
42
|
+
|
|
43
|
+
A build is **production-ready** only if all are true:
|
|
44
|
+
- Critical test cases pass (P0/P1 below)
|
|
45
|
+
- No data corruption in local/remote DBs
|
|
46
|
+
- No orphaned media references after sync runs
|
|
47
|
+
- Stats/report data is generated and retention controls work
|
|
48
|
+
- Plugin starts cleanly after restart in tested configurations
|
|
49
|
+
|
|
50
|
+
## 4) Test Matrix
|
|
51
|
+
|
|
52
|
+
| ID | Priority | Area | Scenario | Setup | Steps | Expected Result | Status | Evidence |
|
|
53
|
+
|---|---|---|---|---|---|---|---|---|
|
|
54
|
+
| P0-01 | P0 | Health | Plugin ping reachable both servers (paired) | Both running with plugin enabled | Call `/api/strapi-content-sync-pro/ping` on :40101 and :4010 | HTTP 200 + `{ "status": "ok" }` | TODO | |
|
|
55
|
+
| P0-02 | P0 | Connection | Connection test in paired mode | Local configured to remote | Run **Test Connection** in Configuration tab | Success, plugin endpoint validated | TODO | |
|
|
56
|
+
| P0-03 | P0 | Single-side | Connection test in single-side mode | Disable remote plugin load via env | Set local mode `single_side`, run Test Connection | Success without requiring remote plugin routes | TODO | |
|
|
57
|
+
| P0-04 | P0 | Profiles | Pull-only enforcement in single-side | Local mode `single_side` | Try creating push/bidirectional profile | UI/API enforce pull-only | TODO | |
|
|
58
|
+
| P0-05 | P0 | Execution | Live mode blocked in single-side | Local mode `single_side` | Try saving execution mode `live` | Rejected or normalized to non-live | TODO | |
|
|
59
|
+
| P0-06 | P0 | Data | Orders remote → local sync | Create new orders on remote | Run local pull/sync | Orders and order details appear local | TODO | |
|
|
60
|
+
| P0-07 | P0 | Data | CMS/offers local → remote sync (paired) | Create CMS/offers local | Run push/bidirectional | Entries appear remote correctly | TODO | |
|
|
61
|
+
| P0-08 | P0 | Media | Entity-linked media sync | Attach media to products/CMS | Run sync profile + media sync | Files + DB refs consistent on target | TODO | |
|
|
62
|
+
| P0-09 | P0 | Stats | Pre/post run report generation | Any enabled content types | Trigger sync run | Stats report contains before/after snapshots | TODO | |
|
|
63
|
+
| P0-10 | P0 | Retention | Manual clear logs/reports | Existing logs/reports | Use clear actions in Stats tab | Data removed, UI refreshed | TODO | |
|
|
64
|
+
| P1-01 | P1 | Dependencies | Min depth profile run | Profile depth=1 | Run profile | Only first-level dependencies handled | TODO | |
|
|
65
|
+
| P1-02 | P1 | Dependencies | Max depth profile run | Profile depth=5 | Run profile | Deep dependencies handled; no crash | TODO | |
|
|
66
|
+
| P1-03 | P1 | Media Recovery | Recreate deleted local media from remote | Delete selected local media rows/files | Run downward sync | Missing media restored | TODO | |
|
|
67
|
+
| P1-04 | P1 | Conflict | Latest/local/remote strategy behavior | Divergent edits both sides | Run with each strategy | Winner follows selected policy | TODO | |
|
|
68
|
+
| P1-05 | P1 | Restart | Restart resilience | Update plugin files + restart | Restart both servers | Plugin loads without route/service errors | TODO | |
|
|
69
|
+
| P1-06 | P1 | Retention Auto | Pruning by max entries | Set low limits | Trigger retention run | Old logs/reports pruned to limit | TODO | |
|
|
70
|
+
| P2-01 | P2 | Performance | Large dataset pagination behavior | Seed high row count | Run sync with page-size variations | Completes with bounded memory/errors | TODO | |
|
|
71
|
+
| P2-02 | P2 | UX | Stats readability | Run several syncs | Review report history | Clear before/after trend visibility | TODO | |
|
|
72
|
+
|
|
73
|
+
## 5) Detailed Execution Checklist
|
|
74
|
+
|
|
75
|
+
### A. Baseline + Connectivity
|
|
76
|
+
1. Confirm both servers are running.
|
|
77
|
+
2. Verify paired mode ping endpoints.
|
|
78
|
+
3. Configure local plugin connection settings.
|
|
79
|
+
4. Validate **Test Connection** in paired mode.
|
|
80
|
+
|
|
81
|
+
### B. Single-side Mode
|
|
82
|
+
1. Disable plugin loading on remote using env flag.
|
|
83
|
+
2. Keep local plugin enabled.
|
|
84
|
+
3. Set local `syncMode = single_side`.
|
|
85
|
+
4. Run connection test and verify success without remote plugin route dependency.
|
|
86
|
+
5. Verify profile direction and execution mode restrictions.
|
|
87
|
+
|
|
88
|
+
### C. Data Sync Scenarios
|
|
89
|
+
1. Create products with variations on source side; sync and verify target.
|
|
90
|
+
2. Create orders on remote; pull to local and verify details.
|
|
91
|
+
3. Create CMS content/offers local; push to remote and verify.
|
|
92
|
+
4. Run scenarios with different conflict strategies.
|
|
93
|
+
|
|
94
|
+
### D. Media Scenarios
|
|
95
|
+
1. Upload media and attach to products/CMS entities.
|
|
96
|
+
2. Sync and verify both file presence and DB links.
|
|
97
|
+
3. Delete a subset of local media rows/files.
|
|
98
|
+
4. Re-run downward sync; confirm restoration.
|
|
99
|
+
|
|
100
|
+
### E. Stats + Retention
|
|
101
|
+
1. Trigger sync; confirm report with before/after snapshots appears.
|
|
102
|
+
2. Verify columns: local/remote count, newest timestamps, newest side.
|
|
103
|
+
3. Use manual clear for logs and reports.
|
|
104
|
+
4. Configure low retention limits and run retention; verify pruning.
|
|
105
|
+
|
|
106
|
+
### F. Restart/Recovery
|
|
107
|
+
1. Copy plugin changes into Strapi plugin destination.
|
|
108
|
+
2. Restart both instances.
|
|
109
|
+
3. Re-check critical routes and one sync run.
|
|
110
|
+
|
|
111
|
+
## 6) Evidence Template
|
|
112
|
+
|
|
113
|
+
For each executed case, capture:
|
|
114
|
+
- Timestamp
|
|
115
|
+
- Server mode (paired/single-side)
|
|
116
|
+
- Profile ID/name and execution mode
|
|
117
|
+
- Request/response snippets (or screenshots)
|
|
118
|
+
- DB/file verification notes
|
|
119
|
+
- Result: Pass/Fail + defect link
|
|
120
|
+
|
|
121
|
+
Example note:
|
|
122
|
+
- `P0-09` | `2026-04-21 10:20` | `Pass`
|
|
123
|
+
- Report ID: `...`
|
|
124
|
+
- Before: products local=120 remote=115 newest=local
|
|
125
|
+
- After: products local=120 remote=120 newest=equal
|
|
126
|
+
|
|
127
|
+
## 7) Defect Severity Guidance
|
|
128
|
+
|
|
129
|
+
- **Critical**: Data loss/corruption, wrong direction writes, plugin cannot start
|
|
130
|
+
- **High**: Core sync path broken for common scenario
|
|
131
|
+
- **Medium**: Stats/reporting incorrect but sync still reliable
|
|
132
|
+
- **Low**: UI copy/formatting issues
|
|
133
|
+
|
|
134
|
+
## 8) Go/No-Go Summary
|
|
135
|
+
|
|
136
|
+
- Total cases executed:
|
|
137
|
+
- Passed:
|
|
138
|
+
- Failed:
|
|
139
|
+
- Blocked:
|
|
140
|
+
- Critical open defects:
|
|
141
|
+
- Decision: **GO / NO-GO**
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Current Session Smoke Result
|
|
146
|
+
|
|
147
|
+
- Ping local (`:40101`) = PASS
|
|
148
|
+
- Ping remote (`:4010`) = PASS
|
|
149
|
+
- Package test script (`npm run test`) = PASS (`No tests yet` placeholder)
|
|
150
|
+
|
|
151
|
+
A full production readiness sign-off requires completion of the matrix above.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
Remote server :
|
|
3
|
+
D:\>cd d:\Rutba\ERP
|
|
4
|
+
D:\Rutba\ERP>npm run dev:strapi
|
|
5
|
+
|
|
6
|
+
Database = mysql
|
|
7
|
+
Database name = pos_db
|
|
8
|
+
URL= http://localhost:4010
|
|
9
|
+
Upload dir =D:\Rutba\data\rutba-pos-files\uploads
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Local Server:
|
|
15
|
+
D:\Rutba\ERP>cd pos-strapi
|
|
16
|
+
D:\Rutba\ERP\pos-strapi>npm run dev
|
|
17
|
+
http://localhost:40101
|
|
18
|
+
|
|
19
|
+
Database = mysql │
|
|
20
|
+
Database name = rutba_pos
|
|
21
|
+
Upload Dir = D:\Rutba\data\rutba-pos-files\tmp\uploads
|
|
22
|
+
|
|
23
|
+
these two servers have same code base of strapi but are running using different .env settings
|
|
24
|
+
|
|
25
|
+
you can truncate or partially delete any of their database tables or enteries as well as files.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Please note that when the files are copied over the instance try to reboot but any of these can fail to start so you will have to restart. the restart takes about a minutes each time.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Both the servers admin user : eharain@yahoo.com and password : At56ZNTxXf6JSTq
|
|
33
|
+
|
|
34
|
+
kindly test different settings and different variations and ensure that the plugin works as desired.
|
|
35
|
+
|
|
36
|
+
the current servers are running in command prompt and you can stop them by pressing ctrl+c and start them again using the above commands. you can run these servers in you own power shels if you want.
|
|
37
|
+
the servers will not restrt unloss you copy the code changes in destination drrectory of the plugin that is :D:\Rutba\ERP\pos-strapi\src\plugins\strapi-content-sync-pro
|
|
38
|
+
|
|
39
|
+
to understand the entites read the contents of any server or in src directory of the strapi.
|
|
40
|
+
|
|
41
|
+
to test single-side mode disable the plugin loading by a special enviromment variable so that specific spin of strapi will not load the plugin and then you can test the sync in one direction.
|
|
42
|
+
|
|
43
|
+
Tests:
|
|
44
|
+
kindly create different sync profiles with least and max dependiceis and depth and try executing them.
|
|
45
|
+
|
|
46
|
+
kindly few media files and then sync also upload connect to entites and then sync and check if the media files are also synced properly with the entities.
|
|
47
|
+
|
|
48
|
+
Please also test the sync with different data in the tables and check if the sync is working as expected.
|
|
49
|
+
|
|
50
|
+
insert test data specially cms entites and then sync and check if the data is synced properly in the destination server.
|
|
51
|
+
|
|
52
|
+
create products and test the sync with different variations of products and check if the products are synced properly in the destination server.
|
|
53
|
+
|
|
54
|
+
create orders and test the sync with different variations of orders and check if the orders are synced properly in the destination server.
|
|
55
|
+
|
|
56
|
+
the orders will be created on remote server and should be synced to local server and then you can check the orders in local server and also check the order details and the products in the order and the media files connected to the products in the order.
|
|
57
|
+
|
|
58
|
+
the cms contenst and offers are created on local servers and set to remote server and then you can check the cms content and offers in remote server and also check the media files connected to the cms content and offers.
|
|
59
|
+
|
|
60
|
+
complete remove media enteries and few files on local server and then sync downward and check if these are created again. simmilar create new media each side and sync across.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-content-sync-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Strapi v5 plugin to copy, migrate, and live-sync content, media, and data between multiple Strapi environments with bi-directional sync, field-level policies, scheduling, and alerts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
|
-
"name": "Ejaz
|
|
7
|
+
"name": "Ejaz Hussain Arain",
|
|
8
8
|
"email": "eharain@yahoo.com",
|
|
9
|
-
"url": "https://
|
|
9
|
+
"url": "https://www.linkedin.com/in/ejazarain"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"strapi",
|
|
25
25
|
"strapi-plugin",
|
|
26
26
|
"strapi-v5",
|
|
27
|
+
"strapi pulgin to transfer content",
|
|
27
28
|
"sync",
|
|
29
|
+
"hot sync",
|
|
30
|
+
"real-time sync",
|
|
28
31
|
"live-sync",
|
|
29
32
|
"data-sync",
|
|
30
33
|
"content-sync",
|
|
@@ -39,7 +42,13 @@
|
|
|
39
42
|
"cross-environment",
|
|
40
43
|
"data-transfer",
|
|
41
44
|
"headless-cms",
|
|
42
|
-
"content-management"
|
|
45
|
+
"content-management",
|
|
46
|
+
"data transfer",
|
|
47
|
+
"bulk transfer",
|
|
48
|
+
"field-level policies",
|
|
49
|
+
"scheduling",
|
|
50
|
+
"alerts",
|
|
51
|
+
"bi-directional sync"
|
|
43
52
|
],
|
|
44
53
|
"exports": {
|
|
45
54
|
"./strapi-admin": {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const syncLogSchema = require('./sync-log/schema.json');
|
|
4
|
+
const syncRunReportSchema = require('./sync-run-report/schema.json');
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
'sync-log': { schema: syncLogSchema },
|
|
8
|
+
'sync-run-report': { schema: syncRunReportSchema },
|
|
7
9
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "collectionType",
|
|
3
|
+
"collectionName": "sync_run_reports",
|
|
4
|
+
"info": {
|
|
5
|
+
"singularName": "sync-run-report",
|
|
6
|
+
"pluralName": "sync-run-reports",
|
|
7
|
+
"displayName": "Sync Run Report"
|
|
8
|
+
},
|
|
9
|
+
"options": {},
|
|
10
|
+
"pluginOptions": {
|
|
11
|
+
"content-manager": { "visible": false },
|
|
12
|
+
"content-type-builder": { "visible": false }
|
|
13
|
+
},
|
|
14
|
+
"attributes": {
|
|
15
|
+
"runType": { "type": "string" },
|
|
16
|
+
"trigger": { "type": "string" },
|
|
17
|
+
"status": { "type": "string" },
|
|
18
|
+
"startedAt": { "type": "datetime" },
|
|
19
|
+
"completedAt": { "type": "datetime" },
|
|
20
|
+
"contentTypes": { "type": "json" },
|
|
21
|
+
"beforeStats": { "type": "json" },
|
|
22
|
+
"afterStats": { "type": "json" },
|
|
23
|
+
"summary": { "type": "json" },
|
|
24
|
+
"error": { "type": "text" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'strapi-content-sync-pro';
|
|
4
|
+
|
|
5
|
+
function svc(strapi) {
|
|
6
|
+
return strapi.plugin(PLUGIN_ID).service('bulkTransfer');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = ({ strapi }) => ({
|
|
10
|
+
async preview(ctx) {
|
|
11
|
+
try {
|
|
12
|
+
const body = ctx.request.body || {};
|
|
13
|
+
const data = await svc(strapi).preview({
|
|
14
|
+
direction: body.direction,
|
|
15
|
+
scopes: body.scopes,
|
|
16
|
+
});
|
|
17
|
+
ctx.body = { data };
|
|
18
|
+
} catch (err) {
|
|
19
|
+
ctx.throw(400, err.message);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async start(ctx) {
|
|
24
|
+
try {
|
|
25
|
+
const body = ctx.request.body || {};
|
|
26
|
+
const data = await svc(strapi).start(body);
|
|
27
|
+
ctx.body = { data };
|
|
28
|
+
} catch (err) {
|
|
29
|
+
ctx.throw(400, err.message);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async next(ctx) {
|
|
34
|
+
try {
|
|
35
|
+
const { jobId } = ctx.params;
|
|
36
|
+
const data = await svc(strapi).next(jobId);
|
|
37
|
+
ctx.body = { data };
|
|
38
|
+
} catch (err) {
|
|
39
|
+
ctx.throw(400, err.message);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async runAll(ctx) {
|
|
44
|
+
try {
|
|
45
|
+
const { jobId } = ctx.params;
|
|
46
|
+
const data = await svc(strapi).runToCompletion(jobId);
|
|
47
|
+
ctx.body = { data };
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ctx.throw(400, err.message);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async status(ctx) {
|
|
54
|
+
try {
|
|
55
|
+
const { jobId } = ctx.params;
|
|
56
|
+
const data = svc(strapi).getStatus(jobId);
|
|
57
|
+
if (!data) return ctx.notFound('Job not found');
|
|
58
|
+
ctx.body = { data };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
ctx.throw(500, err.message);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
async cancel(ctx) {
|
|
65
|
+
try {
|
|
66
|
+
const { jobId } = ctx.params;
|
|
67
|
+
const data = await svc(strapi).cancel(jobId);
|
|
68
|
+
ctx.body = { data };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
ctx.throw(400, err.message);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async pause(ctx) {
|
|
75
|
+
try {
|
|
76
|
+
const { jobId } = ctx.params;
|
|
77
|
+
const data = await svc(strapi).pause(jobId);
|
|
78
|
+
ctx.body = { data };
|
|
79
|
+
} catch (err) {
|
|
80
|
+
ctx.throw(400, err.message);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async resume(ctx) {
|
|
85
|
+
try {
|
|
86
|
+
const { jobId } = ctx.params;
|
|
87
|
+
const data = await svc(strapi).resume(jobId);
|
|
88
|
+
ctx.body = { data };
|
|
89
|
+
} catch (err) {
|
|
90
|
+
ctx.throw(400, err.message);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async list(ctx) {
|
|
95
|
+
try {
|
|
96
|
+
ctx.body = { data: svc(strapi).listJobs() };
|
|
97
|
+
} catch (err) {
|
|
98
|
+
ctx.throw(500, err.message);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async history(ctx) {
|
|
103
|
+
try {
|
|
104
|
+
const data = await svc(strapi).getHistory();
|
|
105
|
+
ctx.body = { data };
|
|
106
|
+
} catch (err) {
|
|
107
|
+
ctx.throw(500, err.message);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async clearHistory(ctx) {
|
|
112
|
+
try {
|
|
113
|
+
const data = await svc(strapi).clearHistory();
|
|
114
|
+
ctx.body = { data };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
ctx.throw(500, err.message);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async restart(ctx) {
|
|
121
|
+
try {
|
|
122
|
+
const { historyId } = ctx.params;
|
|
123
|
+
const body = ctx.request.body || {};
|
|
124
|
+
const data = await svc(strapi).restart(historyId, body);
|
|
125
|
+
ctx.body = { data };
|
|
126
|
+
} catch (err) {
|
|
127
|
+
ctx.throw(400, err.message);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async resumeFromHistory(ctx) {
|
|
132
|
+
try {
|
|
133
|
+
const { historyId } = ctx.params;
|
|
134
|
+
const body = ctx.request.body || {};
|
|
135
|
+
const data = await svc(strapi).resumeFromHistory(historyId, body);
|
|
136
|
+
ctx.body = { data };
|
|
137
|
+
} catch (err) {
|
|
138
|
+
ctx.throw(400, err.message);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|