strapi-content-sync-pro 1.0.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/LICENSE +21 -0
- package/README.md +206 -0
- package/admin/src/components/ConfigTab.jsx +1038 -0
- package/admin/src/components/ContentTypesTab.jsx +160 -0
- package/admin/src/components/HelpTab.jsx +945 -0
- package/admin/src/components/LogsTab.jsx +136 -0
- package/admin/src/components/MediaTab.jsx +557 -0
- package/admin/src/components/SyncProfilesTab.jsx +715 -0
- package/admin/src/components/SyncTab.jsx +988 -0
- package/admin/src/index.js +31 -0
- package/admin/src/pages/App/index.jsx +129 -0
- package/admin/src/pluginId.js +3 -0
- package/package.json +84 -0
- package/server/src/bootstrap.js +151 -0
- package/server/src/config/index.js +5 -0
- package/server/src/content-types/index.js +7 -0
- package/server/src/content-types/sync-log/schema.json +24 -0
- package/server/src/controllers/alerts.js +59 -0
- package/server/src/controllers/config.js +292 -0
- package/server/src/controllers/content-type-discovery.js +9 -0
- package/server/src/controllers/dependencies.js +109 -0
- package/server/src/controllers/index.js +29 -0
- package/server/src/controllers/ping.js +7 -0
- package/server/src/controllers/sync-config.js +26 -0
- package/server/src/controllers/sync-enforcement.js +323 -0
- package/server/src/controllers/sync-execution.js +134 -0
- package/server/src/controllers/sync-log.js +18 -0
- package/server/src/controllers/sync-media.js +158 -0
- package/server/src/controllers/sync-profiles.js +182 -0
- package/server/src/controllers/sync.js +31 -0
- package/server/src/destroy.js +7 -0
- package/server/src/index.js +21 -0
- package/server/src/middlewares/verify-signature.js +32 -0
- package/server/src/register.js +7 -0
- package/server/src/routes/index.js +111 -0
- package/server/src/services/alerts.js +437 -0
- package/server/src/services/config.js +68 -0
- package/server/src/services/content-type-discovery.js +41 -0
- package/server/src/services/dependency-resolver.js +284 -0
- package/server/src/services/index.js +30 -0
- package/server/src/services/ping.js +7 -0
- package/server/src/services/sync-config.js +45 -0
- package/server/src/services/sync-enforcement.js +362 -0
- package/server/src/services/sync-execution.js +541 -0
- package/server/src/services/sync-log.js +56 -0
- package/server/src/services/sync-media.js +963 -0
- package/server/src/services/sync-profiles.js +380 -0
- package/server/src/services/sync.js +248 -0
- package/server/src/utils/applier.js +89 -0
- package/server/src/utils/comparator.js +83 -0
- package/server/src/utils/fetcher.js +142 -0
- package/server/src/utils/hmac.js +37 -0
- package/server/src/utils/pagination.js +51 -0
- package/server/src/utils/sync-guard.js +29 -0
- package/server/src/utils/sync-id.js +16 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'strapi-content-sync-pro';
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
async get(ctx) {
|
|
7
|
+
const configService = strapi.plugin(PLUGIN_ID).service('config');
|
|
8
|
+
const config = await configService.getConfig({ safe: true });
|
|
9
|
+
|
|
10
|
+
ctx.body = { data: config };
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GET /config/test
|
|
15
|
+
* Test connectivity to the remote server using stored credentials
|
|
16
|
+
*/
|
|
17
|
+
async test(ctx) {
|
|
18
|
+
const configService = strapi.plugin(PLUGIN_ID).service('config');
|
|
19
|
+
const config = await configService.getConfig({ safe: false });
|
|
20
|
+
|
|
21
|
+
if (!config || !config.baseUrl) {
|
|
22
|
+
return ctx.body = {
|
|
23
|
+
data: {
|
|
24
|
+
success: false,
|
|
25
|
+
stage: 'config',
|
|
26
|
+
message: 'Remote server URL is not configured',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!config.apiToken) {
|
|
32
|
+
return ctx.body = {
|
|
33
|
+
data: {
|
|
34
|
+
success: false,
|
|
35
|
+
stage: 'config',
|
|
36
|
+
message: 'API token is not configured',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Step 1: Basic reachability via public ping endpoint
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
let pingStatus = null;
|
|
44
|
+
try {
|
|
45
|
+
const pingRes = await fetch(`${config.baseUrl}/api/${PLUGIN_ID}/ping`, {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
});
|
|
48
|
+
pingStatus = pingRes.status;
|
|
49
|
+
if (!pingRes.ok) {
|
|
50
|
+
return ctx.body = {
|
|
51
|
+
data: {
|
|
52
|
+
success: false,
|
|
53
|
+
stage: 'ping',
|
|
54
|
+
message: `Remote server returned ${pingRes.status} on /ping. The plugin may not be installed on the remote server.`,
|
|
55
|
+
latency: Date.now() - startTime,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return ctx.body = {
|
|
61
|
+
data: {
|
|
62
|
+
success: false,
|
|
63
|
+
stage: 'network',
|
|
64
|
+
message: `Cannot reach remote server: ${err.message}`,
|
|
65
|
+
latency: Date.now() - startTime,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pingLatency = Date.now() - startTime;
|
|
71
|
+
|
|
72
|
+
// Step 2: Verify API token works against an authenticated endpoint
|
|
73
|
+
let authWorks = false;
|
|
74
|
+
let remoteInfo = null;
|
|
75
|
+
try {
|
|
76
|
+
const infoRes = await fetch(`${config.baseUrl}/api/${PLUGIN_ID}/enforcement/local-info`, {
|
|
77
|
+
method: 'GET',
|
|
78
|
+
headers: { Authorization: `Bearer ${config.apiToken}` },
|
|
79
|
+
});
|
|
80
|
+
if (infoRes.ok) {
|
|
81
|
+
authWorks = true;
|
|
82
|
+
const body = await infoRes.json().catch(() => ({}));
|
|
83
|
+
remoteInfo = body?.data || null;
|
|
84
|
+
} else if (infoRes.status === 401 || infoRes.status === 403) {
|
|
85
|
+
return ctx.body = {
|
|
86
|
+
data: {
|
|
87
|
+
success: false,
|
|
88
|
+
stage: 'auth',
|
|
89
|
+
message: `API token rejected by remote server (${infoRes.status}). Verify the token is valid and has Full Access.`,
|
|
90
|
+
latency: pingLatency,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
} else if (infoRes.status === 404) {
|
|
94
|
+
return ctx.body = {
|
|
95
|
+
data: {
|
|
96
|
+
success: false,
|
|
97
|
+
stage: 'plugin',
|
|
98
|
+
message: 'Remote server reachable but /enforcement/local-info not found. Ensure the plugin is installed and updated on the remote server.',
|
|
99
|
+
latency: pingLatency,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return ctx.body = {
|
|
105
|
+
data: {
|
|
106
|
+
success: false,
|
|
107
|
+
stage: 'auth',
|
|
108
|
+
message: `Error validating API token: ${err.message}`,
|
|
109
|
+
latency: pingLatency,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ctx.body = {
|
|
115
|
+
data: {
|
|
116
|
+
success: true,
|
|
117
|
+
stage: 'complete',
|
|
118
|
+
message: authWorks
|
|
119
|
+
? 'Connection successful and API token validated'
|
|
120
|
+
: 'Reachable but API token could not be validated',
|
|
121
|
+
latency: pingLatency,
|
|
122
|
+
remoteInfo,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
async set(ctx) {
|
|
128
|
+
const { body } = ctx.request;
|
|
129
|
+
|
|
130
|
+
if (!body || typeof body !== 'object') {
|
|
131
|
+
return ctx.badRequest('Request body must be a JSON object');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const configService = strapi.plugin(PLUGIN_ID).service('config');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const saved = await configService.setConfig(body);
|
|
138
|
+
|
|
139
|
+
const sanitized = { ...saved };
|
|
140
|
+
if (sanitized.apiToken) {
|
|
141
|
+
sanitized.apiToken = '••••••••';
|
|
142
|
+
}
|
|
143
|
+
if (sanitized.sharedSecret) {
|
|
144
|
+
sanitized.sharedSecret = '••••••••';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ctx.body = { data: sanitized };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return ctx.badRequest(err.message);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* POST /config/remote-login
|
|
155
|
+
* Proxy login to remote Strapi and retrieve/create API token
|
|
156
|
+
*/
|
|
157
|
+
async remoteLogin(ctx) {
|
|
158
|
+
const { baseUrl, email, password } = ctx.request.body;
|
|
159
|
+
|
|
160
|
+
if (!baseUrl || !email || !password) {
|
|
161
|
+
return ctx.badRequest('baseUrl, email, and password are required');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Step 1: Login to remote Strapi admin
|
|
166
|
+
const loginResponse = await fetch(`${baseUrl}/admin/login`, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
email,
|
|
173
|
+
password,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!loginResponse.ok) {
|
|
178
|
+
const errorBody = await loginResponse.json().catch(() => ({}));
|
|
179
|
+
const errorMessage = errorBody?.error?.message || `Login failed with status ${loginResponse.status}`;
|
|
180
|
+
return ctx.throw(loginResponse.status, errorMessage);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const loginData = await loginResponse.json();
|
|
184
|
+
const adminJwt = loginData.data?.token;
|
|
185
|
+
|
|
186
|
+
if (!adminJwt) {
|
|
187
|
+
return ctx.throw(500, 'No token received from remote server');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Step 2: Check for existing token or create new one
|
|
191
|
+
let apiToken = null;
|
|
192
|
+
const TOKEN_NAME = 'strapi-data-sync-plugin'; // Fixed name for easy identification
|
|
193
|
+
|
|
194
|
+
// First, list existing API tokens to see if one exists for the plugin
|
|
195
|
+
const listTokensResponse = await fetch(`${baseUrl}/admin/api-tokens`, {
|
|
196
|
+
method: 'GET',
|
|
197
|
+
headers: {
|
|
198
|
+
Authorization: `Bearer ${adminJwt}`,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
let existingTokenId = null;
|
|
203
|
+
if (listTokensResponse.ok) {
|
|
204
|
+
const tokensData = await listTokensResponse.json();
|
|
205
|
+
const existingToken = tokensData.data?.find(t =>
|
|
206
|
+
t.name === TOKEN_NAME ||
|
|
207
|
+
t.name?.startsWith('strapi-data-sync') ||
|
|
208
|
+
t.name?.startsWith('data-sync-auto')
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (existingToken) {
|
|
212
|
+
existingTokenId = existingToken.id;
|
|
213
|
+
// Delete the old token so we can create a fresh one with the access key
|
|
214
|
+
await fetch(`${baseUrl}/admin/api-tokens/${existingTokenId}`, {
|
|
215
|
+
method: 'DELETE',
|
|
216
|
+
headers: {
|
|
217
|
+
Authorization: `Bearer ${adminJwt}`,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Step 3: Create a new API token with full access
|
|
224
|
+
const createTokenResponse = await fetch(`${baseUrl}/admin/api-tokens`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
Authorization: `Bearer ${adminJwt}`,
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
name: TOKEN_NAME,
|
|
232
|
+
description: 'Auto-generated token for Strapi-to-Strapi Data Sync plugin',
|
|
233
|
+
type: 'full-access',
|
|
234
|
+
lifespan: null, // No expiration
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (!createTokenResponse.ok) {
|
|
239
|
+
const errorBody = await createTokenResponse.json().catch(() => ({}));
|
|
240
|
+
const errorMessage = errorBody?.error?.message || 'Failed to create API token';
|
|
241
|
+
return ctx.throw(createTokenResponse.status, errorMessage);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const tokenData = await createTokenResponse.json();
|
|
245
|
+
apiToken = tokenData.data?.accessKey;
|
|
246
|
+
|
|
247
|
+
if (!apiToken) {
|
|
248
|
+
return ctx.throw(500, 'Failed to retrieve API token from created token');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Step 4: Optionally get remote instance ID if plugin is installed
|
|
252
|
+
let remoteInstanceId = null;
|
|
253
|
+
try {
|
|
254
|
+
const remoteConfigResponse = await fetch(`${baseUrl}/api/${PLUGIN_ID}/config`, {
|
|
255
|
+
method: 'GET',
|
|
256
|
+
headers: {
|
|
257
|
+
Authorization: `Bearer ${apiToken}`,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (remoteConfigResponse.ok) {
|
|
262
|
+
const remoteConfig = await remoteConfigResponse.json();
|
|
263
|
+
remoteInstanceId = remoteConfig.data?.instanceId;
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// Remote plugin might not be installed or configured
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Step 5: Save the token to local config
|
|
270
|
+
const configService = strapi.plugin(PLUGIN_ID).service('config');
|
|
271
|
+
await configService.setConfig({
|
|
272
|
+
baseUrl,
|
|
273
|
+
apiToken,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
ctx.body = {
|
|
277
|
+
data: {
|
|
278
|
+
success: true,
|
|
279
|
+
apiToken: '••••••••', // Don't send actual token back to frontend
|
|
280
|
+
tokenName: TOKEN_NAME,
|
|
281
|
+
instanceId: remoteInstanceId,
|
|
282
|
+
message: 'Successfully authenticated and created API token',
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (err.status) {
|
|
287
|
+
throw err; // Re-throw Koa errors
|
|
288
|
+
}
|
|
289
|
+
ctx.throw(500, err.message || 'Remote login failed');
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'strapi-content-sync-pro';
|
|
4
|
+
|
|
5
|
+
module.exports = ({ strapi }) => ({
|
|
6
|
+
/**
|
|
7
|
+
* GET /dependencies/all
|
|
8
|
+
* Get dependency analysis for all enabled content types
|
|
9
|
+
*/
|
|
10
|
+
async analyzeAll(ctx) {
|
|
11
|
+
try {
|
|
12
|
+
const dependencyResolver = strapi.plugin(PLUGIN_ID).service('dependencyResolver');
|
|
13
|
+
const syncConfig = strapi.plugin(PLUGIN_ID).service('syncConfig');
|
|
14
|
+
|
|
15
|
+
const config = await syncConfig.getSyncConfig();
|
|
16
|
+
const enabledTypes = (config.contentTypes || [])
|
|
17
|
+
.filter((ct) => ct.enabled)
|
|
18
|
+
.map((ct) => ct.uid);
|
|
19
|
+
|
|
20
|
+
const allDependencies = {};
|
|
21
|
+
|
|
22
|
+
for (const uid of enabledTypes) {
|
|
23
|
+
try {
|
|
24
|
+
const analysis = dependencyResolver.analyzeContentType(uid);
|
|
25
|
+
allDependencies[uid] = analysis.relations || [];
|
|
26
|
+
} catch (err) {
|
|
27
|
+
// Skip if content type doesn't exist
|
|
28
|
+
allDependencies[uid] = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ctx.body = { data: allDependencies };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
ctx.throw(500, err.message);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* GET /dependencies/:uid
|
|
40
|
+
* Get dependency analysis for a content type
|
|
41
|
+
*/
|
|
42
|
+
async analyze(ctx) {
|
|
43
|
+
const { uid } = ctx.params;
|
|
44
|
+
try {
|
|
45
|
+
const analysis = strapi.plugin(PLUGIN_ID).service('dependencyResolver').analyzeContentType(uid);
|
|
46
|
+
ctx.body = { data: analysis };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
ctx.throw(400, err.message);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* GET /dependencies/:uid/graph
|
|
54
|
+
* Get dependency graph for a content type
|
|
55
|
+
*/
|
|
56
|
+
async getGraph(ctx) {
|
|
57
|
+
const { uid } = ctx.params;
|
|
58
|
+
const depth = parseInt(ctx.query.depth, 10) || 1;
|
|
59
|
+
try {
|
|
60
|
+
const graph = strapi.plugin(PLUGIN_ID).service('dependencyResolver').buildDependencyGraph(uid, depth);
|
|
61
|
+
ctx.body = { data: graph };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
ctx.throw(400, err.message);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* GET /dependencies/:uid/sync-order
|
|
69
|
+
* Get sync order for a content type
|
|
70
|
+
*/
|
|
71
|
+
async getSyncOrder(ctx) {
|
|
72
|
+
const { uid } = ctx.params;
|
|
73
|
+
const depth = parseInt(ctx.query.depth, 10) || 1;
|
|
74
|
+
try {
|
|
75
|
+
const order = strapi.plugin(PLUGIN_ID).service('dependencyResolver').getSyncOrder(uid, depth);
|
|
76
|
+
ctx.body = { data: order };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
ctx.throw(400, err.message);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* GET /dependencies/:uid/summary
|
|
84
|
+
* Get dependency summary for UI
|
|
85
|
+
*/
|
|
86
|
+
async getSummary(ctx) {
|
|
87
|
+
const { uid } = ctx.params;
|
|
88
|
+
const depth = parseInt(ctx.query.depth, 10) || 1;
|
|
89
|
+
try {
|
|
90
|
+
const summary = strapi.plugin(PLUGIN_ID).service('dependencyResolver').getDependencySummary(uid, depth);
|
|
91
|
+
ctx.body = { data: summary };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
ctx.throw(400, err.message);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* POST /dependencies/clear-cache
|
|
99
|
+
* Clear dependency cache
|
|
100
|
+
*/
|
|
101
|
+
async clearCache(ctx) {
|
|
102
|
+
try {
|
|
103
|
+
strapi.plugin(PLUGIN_ID).service('dependencyResolver').clearCache();
|
|
104
|
+
ctx.body = { data: { success: true } };
|
|
105
|
+
} catch (err) {
|
|
106
|
+
ctx.throw(500, err.message);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ping = require('./ping');
|
|
4
|
+
const config = require('./config');
|
|
5
|
+
const contentTypeDiscovery = require('./content-type-discovery');
|
|
6
|
+
const syncConfig = require('./sync-config');
|
|
7
|
+
const sync = require('./sync');
|
|
8
|
+
const syncLog = require('./sync-log');
|
|
9
|
+
const syncProfiles = require('./sync-profiles');
|
|
10
|
+
const syncExecution = require('./sync-execution');
|
|
11
|
+
const syncEnforcement = require('./sync-enforcement');
|
|
12
|
+
const syncMedia = require('./sync-media');
|
|
13
|
+
const alerts = require('./alerts');
|
|
14
|
+
const dependencies = require('./dependencies');
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
ping,
|
|
18
|
+
config,
|
|
19
|
+
contentTypeDiscovery,
|
|
20
|
+
syncConfig,
|
|
21
|
+
sync,
|
|
22
|
+
syncLog,
|
|
23
|
+
syncProfiles,
|
|
24
|
+
syncExecution,
|
|
25
|
+
syncEnforcement,
|
|
26
|
+
syncMedia,
|
|
27
|
+
alerts,
|
|
28
|
+
dependencies,
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
async get(ctx) {
|
|
5
|
+
const service = strapi.plugin('strapi-content-sync-pro').service('syncConfig');
|
|
6
|
+
const config = await service.getSyncConfig();
|
|
7
|
+
ctx.body = { data: config };
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
async set(ctx) {
|
|
11
|
+
const { body } = ctx.request;
|
|
12
|
+
|
|
13
|
+
if (!body || typeof body !== 'object') {
|
|
14
|
+
return ctx.badRequest('Request body must be a JSON object');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const service = strapi.plugin('strapi-content-sync-pro').service('syncConfig');
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const saved = await service.setSyncConfig(body);
|
|
21
|
+
ctx.body = { data: saved };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return ctx.badRequest(err.message);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|