@intranefr/superbackend 1.5.2 → 1.6.3
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/cookies.txt +6 -0
- package/cookies1.txt +6 -0
- package/cookies2.txt +6 -0
- package/cookies3.txt +6 -0
- package/cookies4.txt +5 -0
- package/cookies_old.txt +5 -0
- package/cookies_old_test.txt +6 -0
- package/cookies_super.txt +5 -0
- package/cookies_super_test.txt +6 -0
- package/cookies_test.txt +6 -0
- package/index.js +9 -0
- package/manage.js +745 -0
- package/package.json +6 -2
- package/plugins/core-waiting-list-migration/README.md +118 -0
- package/plugins/core-waiting-list-migration/index.js +438 -0
- package/plugins/global-settings-presets/index.js +20 -0
- package/plugins/hello-cli/index.js +17 -0
- package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
- package/plugins/ui-components-seeder/components/suiToast.js +186 -0
- package/plugins/ui-components-seeder/index.js +31 -0
- package/public/js/admin-ui-components-preview.js +281 -0
- package/public/js/admin-ui-components.js +408 -0
- package/public/js/llm-provider-model-picker.js +193 -0
- package/public/test-iframe-fix.html +63 -0
- package/public/test-iframe.html +14 -0
- package/src/admin/endpointRegistry.js +68 -0
- package/src/controllers/admin.controller.js +36 -10
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminDataCleanup.controller.js +45 -0
- package/src/controllers/adminLlm.controller.js +19 -8
- package/src/controllers/adminLogin.controller.js +269 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminPlugins.controller.js +55 -0
- package/src/controllers/adminRegistry.controller.js +106 -0
- package/src/controllers/adminScripts.controller.js +138 -0
- package/src/controllers/adminStats.controller.js +4 -4
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/controllers/registry.controller.js +32 -0
- package/src/controllers/waitingList.controller.js +52 -74
- package/src/helpers/mongooseHelper.js +6 -6
- package/src/helpers/scriptBase.js +2 -2
- package/src/middleware/auth.js +71 -1
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +584 -176
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/GlobalSetting.js +11 -1
- package/src/models/Markdown.js +75 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- package/src/models/UiComponent.js +2 -0
- package/src/models/User.js +1 -1
- package/src/routes/admin.routes.js +3 -3
- package/src/routes/adminAgents.routes.js +13 -0
- package/src/routes/adminAssets.routes.js +11 -11
- package/src/routes/adminBlog.routes.js +2 -2
- package/src/routes/adminBlogAi.routes.js +2 -2
- package/src/routes/adminBlogAutomation.routes.js +2 -2
- package/src/routes/adminCache.routes.js +2 -2
- package/src/routes/adminConsoleManager.routes.js +2 -2
- package/src/routes/adminCrons.routes.js +2 -2
- package/src/routes/adminDataCleanup.routes.js +26 -0
- package/src/routes/adminDbBrowser.routes.js +2 -2
- package/src/routes/adminEjsVirtual.routes.js +2 -2
- package/src/routes/adminFeatureFlags.routes.js +6 -6
- package/src/routes/adminHeadless.routes.js +2 -2
- package/src/routes/adminHealthChecks.routes.js +2 -2
- package/src/routes/adminI18n.routes.js +2 -2
- package/src/routes/adminJsonConfigs.routes.js +8 -8
- package/src/routes/adminLlm.routes.js +8 -7
- package/src/routes/adminLogin.routes.js +23 -0
- package/src/routes/adminMarkdowns.routes.js +10 -0
- package/src/routes/adminMigration.routes.js +12 -12
- package/src/routes/adminPages.routes.js +2 -2
- package/src/routes/adminPlugins.routes.js +15 -0
- package/src/routes/adminProxy.routes.js +2 -2
- package/src/routes/adminRateLimits.routes.js +8 -8
- package/src/routes/adminRbac.routes.js +2 -2
- package/src/routes/adminRegistry.routes.js +24 -0
- package/src/routes/adminScripts.routes.js +6 -3
- package/src/routes/adminSeoConfig.routes.js +10 -10
- package/src/routes/adminTelegram.routes.js +14 -0
- package/src/routes/adminTerminals.routes.js +2 -2
- package/src/routes/adminUiComponents.routes.js +2 -2
- package/src/routes/adminUploadNamespaces.routes.js +7 -7
- package/src/routes/blogInternal.routes.js +2 -2
- package/src/routes/experiments.routes.js +2 -2
- package/src/routes/formsAdmin.routes.js +6 -6
- package/src/routes/globalSettings.routes.js +8 -8
- package/src/routes/internalExperiments.routes.js +2 -2
- package/src/routes/markdowns.routes.js +16 -0
- package/src/routes/notificationAdmin.routes.js +7 -7
- package/src/routes/orgAdmin.routes.js +16 -16
- package/src/routes/pages.routes.js +3 -3
- package/src/routes/registry.routes.js +11 -0
- package/src/routes/stripeAdmin.routes.js +12 -12
- package/src/routes/userAdmin.routes.js +7 -7
- package/src/routes/waitingListAdmin.routes.js +2 -2
- package/src/routes/workflows.routes.js +3 -3
- package/src/services/agent.service.js +546 -0
- package/src/services/agentHistory.service.js +345 -0
- package/src/services/agentTools.service.js +578 -0
- package/src/services/dataCleanup.service.js +286 -0
- package/src/services/jsonConfigs.service.js +284 -10
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/plugins.service.js +348 -0
- package/src/services/registry.service.js +452 -0
- package/src/services/scriptsRunner.service.js +328 -37
- package/src/services/telegram.service.js +130 -0
- package/src/services/uiComponents.service.js +180 -0
- package/src/services/waitingListJson.service.js +401 -0
- package/src/utils/rbac/rightsRegistry.js +118 -0
- package/test-access.js +63 -0
- package/test-iframe-fix.html +63 -0
- package/test-iframe.html +14 -0
- package/views/admin-403.ejs +92 -0
- package/views/admin-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard-home.ejs +52 -2
- package/views/admin-dashboard.ejs +179 -7
- package/views/admin-data-cleanup.ejs +357 -0
- package/views/admin-experiments.ejs +1 -1
- package/views/admin-login.ejs +286 -0
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-plugins-system.ejs +223 -0
- package/views/admin-scripts.ejs +221 -4
- package/views/admin-telegram.ejs +269 -0
- package/views/admin-ui-components.ejs +82 -402
- package/views/admin-users.ejs +207 -11
- package/views/partials/dashboard/nav-items.ejs +5 -0
- package/views/partials/llm-provider-model-picker.ejs +0 -161
- package/analysis-only.skill +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
const JsonConfig = require('../models/JsonConfig');
|
|
4
|
+
const { parseJsonOrThrow, clearJsonConfigCache } = require('./jsonConfigs.service');
|
|
5
|
+
|
|
6
|
+
const REGISTRY_CONFIG_KEY = 'open-registry-registries';
|
|
7
|
+
|
|
8
|
+
function sha256(value) {
|
|
9
|
+
return crypto.createHash('sha256').update(String(value || ''), 'utf8').digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function nowIso() {
|
|
13
|
+
return new Date().toISOString();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeRegistryId(id) {
|
|
17
|
+
return String(id || '').trim().toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeItem(item = {}) {
|
|
21
|
+
const createdAt = item.created_at || nowIso();
|
|
22
|
+
const updatedAt = nowIso();
|
|
23
|
+
const versions = Array.isArray(item.versions)
|
|
24
|
+
? item.versions.map((v) => Number(v)).filter((v) => Number.isInteger(v) && v > 0)
|
|
25
|
+
: [];
|
|
26
|
+
const latestVersion = Number(item.version);
|
|
27
|
+
if (Number.isInteger(latestVersion) && latestVersion > 0 && !versions.includes(latestVersion)) {
|
|
28
|
+
versions.push(latestVersion);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const safeVersions = Array.from(new Set(versions)).sort((a, b) => a - b);
|
|
32
|
+
const finalVersion = safeVersions.length > 0 ? safeVersions[safeVersions.length - 1] : 1;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
id: String(item.id || '').trim(),
|
|
36
|
+
name: String(item.name || item.id || '').trim(),
|
|
37
|
+
category: String(item.category || 'general').trim(),
|
|
38
|
+
version: finalVersion,
|
|
39
|
+
versions: safeVersions.length > 0 ? safeVersions : [finalVersion],
|
|
40
|
+
description: String(item.description || '').trim(),
|
|
41
|
+
public: item.public !== false,
|
|
42
|
+
tags: Array.isArray(item.tags) ? item.tags.map((t) => String(t).trim()).filter(Boolean) : [],
|
|
43
|
+
created_at: createdAt,
|
|
44
|
+
updated_at: updatedAt,
|
|
45
|
+
metadata: item.metadata && typeof item.metadata === 'object' && !Array.isArray(item.metadata)
|
|
46
|
+
? item.metadata
|
|
47
|
+
: {},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function ensureRegistryConfigDoc() {
|
|
52
|
+
const existing = await JsonConfig.findOne({
|
|
53
|
+
$or: [{ slug: REGISTRY_CONFIG_KEY }, { alias: REGISTRY_CONFIG_KEY }],
|
|
54
|
+
});
|
|
55
|
+
if (existing) return existing;
|
|
56
|
+
|
|
57
|
+
const payload = {
|
|
58
|
+
version: 1,
|
|
59
|
+
registries: {},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const doc = await JsonConfig.create({
|
|
63
|
+
title: 'Open Registry Registries',
|
|
64
|
+
slug: REGISTRY_CONFIG_KEY,
|
|
65
|
+
alias: REGISTRY_CONFIG_KEY,
|
|
66
|
+
publicEnabled: false,
|
|
67
|
+
cacheTtlSeconds: 0,
|
|
68
|
+
jsonRaw: JSON.stringify(payload, null, 2),
|
|
69
|
+
jsonHash: sha256(JSON.stringify(payload)),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
clearJsonConfigCache(REGISTRY_CONFIG_KEY);
|
|
73
|
+
return doc;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function getConfig() {
|
|
77
|
+
const doc = await ensureRegistryConfigDoc();
|
|
78
|
+
const data = parseJsonOrThrow(String(doc.jsonRaw || '{}'));
|
|
79
|
+
if (!data.registries || typeof data.registries !== 'object') {
|
|
80
|
+
data.registries = {};
|
|
81
|
+
}
|
|
82
|
+
return { doc, data };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function saveConfig(doc, data) {
|
|
86
|
+
doc.jsonRaw = JSON.stringify(data, null, 2);
|
|
87
|
+
doc.jsonHash = sha256(doc.jsonRaw);
|
|
88
|
+
await doc.save();
|
|
89
|
+
clearJsonConfigCache(REGISTRY_CONFIG_KEY);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sanitizeRegistryResponse(registry) {
|
|
93
|
+
return {
|
|
94
|
+
id: registry.id,
|
|
95
|
+
name: registry.name,
|
|
96
|
+
description: registry.description,
|
|
97
|
+
public: registry.public !== false,
|
|
98
|
+
categories: Array.isArray(registry.categories) ? registry.categories : [],
|
|
99
|
+
protocol_version: registry.protocol_version || '1.1.0',
|
|
100
|
+
version: registry.version || '1.0.0',
|
|
101
|
+
created_at: registry.created_at,
|
|
102
|
+
updated_at: registry.updated_at,
|
|
103
|
+
items_count: Object.keys(registry.items || {}).length,
|
|
104
|
+
tokens_count: Array.isArray(registry.tokens) ? registry.tokens.length : 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function ensureRegistry(registryInput) {
|
|
109
|
+
const { doc, data } = await getConfig();
|
|
110
|
+
const id = normalizeRegistryId(registryInput.id);
|
|
111
|
+
if (!id) throw new Error('registry id is required');
|
|
112
|
+
|
|
113
|
+
if (data.registries[id]) {
|
|
114
|
+
return data.registries[id];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const created = nowIso();
|
|
118
|
+
data.registries[id] = {
|
|
119
|
+
id,
|
|
120
|
+
name: String(registryInput.name || id),
|
|
121
|
+
description: String(registryInput.description || ''),
|
|
122
|
+
public: registryInput.public === true,
|
|
123
|
+
categories: Array.isArray(registryInput.categories) && registryInput.categories.length > 0
|
|
124
|
+
? registryInput.categories.map((c) => String(c).trim()).filter(Boolean)
|
|
125
|
+
: ['plugins'],
|
|
126
|
+
protocol_version: '1.1.0',
|
|
127
|
+
version: String(registryInput.version || '1.0.0'),
|
|
128
|
+
items: {},
|
|
129
|
+
tokens: [],
|
|
130
|
+
created_at: created,
|
|
131
|
+
updated_at: created,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await saveConfig(doc, data);
|
|
135
|
+
return data.registries[id];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function listRegistries() {
|
|
139
|
+
const { data } = await getConfig();
|
|
140
|
+
return Object.values(data.registries || {}).map(sanitizeRegistryResponse);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function getRegistry(registryId) {
|
|
144
|
+
const { data } = await getConfig();
|
|
145
|
+
return data.registries[normalizeRegistryId(registryId)] || null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function createRegistry(payload) {
|
|
149
|
+
const { doc, data } = await getConfig();
|
|
150
|
+
const id = normalizeRegistryId(payload.id);
|
|
151
|
+
if (!id) {
|
|
152
|
+
const err = new Error('id is required');
|
|
153
|
+
err.code = 'VALIDATION';
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
if (data.registries[id]) {
|
|
157
|
+
const err = new Error('registry already exists');
|
|
158
|
+
err.code = 'CONFLICT';
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const created = nowIso();
|
|
163
|
+
data.registries[id] = {
|
|
164
|
+
id,
|
|
165
|
+
name: String(payload.name || id),
|
|
166
|
+
description: String(payload.description || ''),
|
|
167
|
+
public: payload.public === true,
|
|
168
|
+
categories: Array.isArray(payload.categories) && payload.categories.length > 0
|
|
169
|
+
? payload.categories.map((c) => String(c).trim()).filter(Boolean)
|
|
170
|
+
: ['general'],
|
|
171
|
+
protocol_version: '1.1.0',
|
|
172
|
+
version: String(payload.version || '1.0.0'),
|
|
173
|
+
items: {},
|
|
174
|
+
tokens: [],
|
|
175
|
+
created_at: created,
|
|
176
|
+
updated_at: created,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
await saveConfig(doc, data);
|
|
180
|
+
return sanitizeRegistryResponse(data.registries[id]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function updateRegistry(registryId, patch) {
|
|
184
|
+
const { doc, data } = await getConfig();
|
|
185
|
+
const id = normalizeRegistryId(registryId);
|
|
186
|
+
const registry = data.registries[id];
|
|
187
|
+
if (!registry) {
|
|
188
|
+
const err = new Error('registry not found');
|
|
189
|
+
err.code = 'NOT_FOUND';
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (patch.name !== undefined) registry.name = String(patch.name || '').trim() || registry.name;
|
|
194
|
+
if (patch.description !== undefined) registry.description = String(patch.description || '');
|
|
195
|
+
if (patch.public !== undefined) registry.public = Boolean(patch.public);
|
|
196
|
+
if (patch.categories !== undefined && Array.isArray(patch.categories)) {
|
|
197
|
+
registry.categories = patch.categories.map((c) => String(c).trim()).filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
registry.updated_at = nowIso();
|
|
200
|
+
|
|
201
|
+
await saveConfig(doc, data);
|
|
202
|
+
return sanitizeRegistryResponse(registry);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function deleteRegistry(registryId) {
|
|
206
|
+
const { doc, data } = await getConfig();
|
|
207
|
+
const id = normalizeRegistryId(registryId);
|
|
208
|
+
if (!data.registries[id]) {
|
|
209
|
+
const err = new Error('registry not found');
|
|
210
|
+
err.code = 'NOT_FOUND';
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
delete data.registries[id];
|
|
214
|
+
await saveConfig(doc, data);
|
|
215
|
+
return { success: true };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function makeTokenValue() {
|
|
219
|
+
return crypto.randomBytes(24).toString('hex');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function createToken(registryId, payload = {}) {
|
|
223
|
+
const { doc, data } = await getConfig();
|
|
224
|
+
const id = normalizeRegistryId(registryId);
|
|
225
|
+
const registry = data.registries[id];
|
|
226
|
+
if (!registry) {
|
|
227
|
+
const err = new Error('registry not found');
|
|
228
|
+
err.code = 'NOT_FOUND';
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const tokenValue = makeTokenValue();
|
|
233
|
+
const token = {
|
|
234
|
+
id: crypto.randomBytes(8).toString('hex'),
|
|
235
|
+
name: String(payload.name || 'token'),
|
|
236
|
+
token_hash: sha256(tokenValue),
|
|
237
|
+
scopes: Array.isArray(payload.scopes) && payload.scopes.length > 0 ? payload.scopes : ['read'],
|
|
238
|
+
enabled: payload.enabled !== false,
|
|
239
|
+
created_at: nowIso(),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
registry.tokens = Array.isArray(registry.tokens) ? registry.tokens : [];
|
|
243
|
+
registry.tokens.push(token);
|
|
244
|
+
registry.updated_at = nowIso();
|
|
245
|
+
|
|
246
|
+
await saveConfig(doc, data);
|
|
247
|
+
return {
|
|
248
|
+
token: { ...token, token_hash: undefined },
|
|
249
|
+
tokenValue,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function deleteToken(registryId, tokenId) {
|
|
254
|
+
const { doc, data } = await getConfig();
|
|
255
|
+
const id = normalizeRegistryId(registryId);
|
|
256
|
+
const registry = data.registries[id];
|
|
257
|
+
if (!registry) {
|
|
258
|
+
const err = new Error('registry not found');
|
|
259
|
+
err.code = 'NOT_FOUND';
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const before = registry.tokens?.length || 0;
|
|
264
|
+
registry.tokens = (registry.tokens || []).filter((t) => t.id !== tokenId);
|
|
265
|
+
if ((registry.tokens || []).length === before) {
|
|
266
|
+
const err = new Error('token not found');
|
|
267
|
+
err.code = 'NOT_FOUND';
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
registry.updated_at = nowIso();
|
|
271
|
+
await saveConfig(doc, data);
|
|
272
|
+
return { success: true };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function hasValidToken(registry, authHeader) {
|
|
276
|
+
const expectedPrefix = 'Bearer ';
|
|
277
|
+
if (!authHeader || !String(authHeader).startsWith(expectedPrefix)) return false;
|
|
278
|
+
const raw = String(authHeader).slice(expectedPrefix.length).trim();
|
|
279
|
+
if (!raw) return false;
|
|
280
|
+
const hash = sha256(raw);
|
|
281
|
+
return (registry.tokens || []).some((token) => token.enabled !== false && token.token_hash === hash);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function applyListQuery(items, query = {}, includePrivate = false) {
|
|
285
|
+
const category = query.category ? String(query.category) : null;
|
|
286
|
+
const version = query.version !== undefined ? String(query.version) : null;
|
|
287
|
+
const minimal = String(query.minimal || 'false') === 'true';
|
|
288
|
+
const filter = query.filter ? String(query.filter).toLowerCase() : '';
|
|
289
|
+
|
|
290
|
+
let rows = items.filter((item) => includePrivate || item.public !== false);
|
|
291
|
+
|
|
292
|
+
if (category) rows = rows.filter((item) => item.category === category);
|
|
293
|
+
if (filter) {
|
|
294
|
+
rows = rows.filter((item) => {
|
|
295
|
+
const target = `${item.id} ${item.name} ${item.description} ${(item.tags || []).join(' ')}`.toLowerCase();
|
|
296
|
+
return target.includes(filter);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (version === 'latest') {
|
|
301
|
+
rows = rows.map((item) => ({ ...item, version: Math.max(...(item.versions || [item.version])) }));
|
|
302
|
+
} else if (version && Number.isInteger(Number(version))) {
|
|
303
|
+
const v = Number(version);
|
|
304
|
+
rows = rows.filter((item) => (item.versions || []).includes(v));
|
|
305
|
+
rows = rows.map((item) => ({ ...item, version: v }));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (minimal) {
|
|
309
|
+
rows = rows.map(({ metadata, ...rest }) => rest);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const page = Math.max(1, Number(query.page || 1));
|
|
313
|
+
const limit = Math.min(100, Math.max(1, Number(query.limit || 20)));
|
|
314
|
+
const totalItems = rows.length;
|
|
315
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / limit));
|
|
316
|
+
const start = (page - 1) * limit;
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
pagination: {
|
|
320
|
+
page,
|
|
321
|
+
limit,
|
|
322
|
+
total_items: totalItems,
|
|
323
|
+
total_pages: totalPages,
|
|
324
|
+
has_next: page < totalPages,
|
|
325
|
+
has_prev: page > 1,
|
|
326
|
+
category,
|
|
327
|
+
version: version || null,
|
|
328
|
+
},
|
|
329
|
+
items: rows.slice(start, start + limit),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function listItemsForRegistry(registryId, query = {}, authHeader) {
|
|
334
|
+
const registry = await getRegistry(registryId);
|
|
335
|
+
if (!registry) {
|
|
336
|
+
const err = new Error('registry not found');
|
|
337
|
+
err.code = 'NOT_FOUND';
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const canReadPrivate = registry.public === true ? true : hasValidToken(registry, authHeader);
|
|
342
|
+
const items = Object.values(registry.items || {});
|
|
343
|
+
const result = applyListQuery(items, query, canReadPrivate);
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
registry: {
|
|
347
|
+
name: registry.name,
|
|
348
|
+
version: registry.version || '1.0.0',
|
|
349
|
+
description: registry.description,
|
|
350
|
+
categories: registry.categories || [],
|
|
351
|
+
protocol_version: registry.protocol_version || '1.1.0',
|
|
352
|
+
},
|
|
353
|
+
pagination: result.pagination,
|
|
354
|
+
items: result.items,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function getAuthStatus(registryId, authHeader) {
|
|
359
|
+
const registry = await getRegistry(registryId);
|
|
360
|
+
if (!registry) {
|
|
361
|
+
const err = new Error('registry not found');
|
|
362
|
+
err.code = 'NOT_FOUND';
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (registry.public === true) {
|
|
367
|
+
return {
|
|
368
|
+
public: true,
|
|
369
|
+
requires_auth: false,
|
|
370
|
+
auth_type: 'none',
|
|
371
|
+
scope: 'read',
|
|
372
|
+
message: 'This registry is publicly accessible',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const valid = hasValidToken(registry, authHeader);
|
|
377
|
+
return {
|
|
378
|
+
public: false,
|
|
379
|
+
requires_auth: true,
|
|
380
|
+
auth_type: 'bearer',
|
|
381
|
+
scope: valid ? 'read' : 'none',
|
|
382
|
+
message: valid ? 'Authenticated access granted' : 'Token invalid or expired',
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function upsertItem(registryId, itemPayload) {
|
|
387
|
+
const { doc, data } = await getConfig();
|
|
388
|
+
const id = normalizeRegistryId(registryId);
|
|
389
|
+
const registry = data.registries[id];
|
|
390
|
+
if (!registry) {
|
|
391
|
+
const err = new Error('registry not found');
|
|
392
|
+
err.code = 'NOT_FOUND';
|
|
393
|
+
throw err;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const normalized = normalizeItem(itemPayload);
|
|
397
|
+
if (!normalized.id) {
|
|
398
|
+
const err = new Error('item id is required');
|
|
399
|
+
err.code = 'VALIDATION';
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const existing = registry.items?.[normalized.id];
|
|
404
|
+
if (existing) {
|
|
405
|
+
normalized.created_at = existing.created_at || normalized.created_at;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
registry.items = registry.items || {};
|
|
409
|
+
registry.items[normalized.id] = normalized;
|
|
410
|
+
registry.updated_at = nowIso();
|
|
411
|
+
await saveConfig(doc, data);
|
|
412
|
+
return normalized;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function deleteItem(registryId, itemId) {
|
|
416
|
+
const { doc, data } = await getConfig();
|
|
417
|
+
const id = normalizeRegistryId(registryId);
|
|
418
|
+
const registry = data.registries[id];
|
|
419
|
+
if (!registry) {
|
|
420
|
+
const err = new Error('registry not found');
|
|
421
|
+
err.code = 'NOT_FOUND';
|
|
422
|
+
throw err;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
registry.items = registry.items || {};
|
|
426
|
+
if (!registry.items[itemId]) {
|
|
427
|
+
const err = new Error('item not found');
|
|
428
|
+
err.code = 'NOT_FOUND';
|
|
429
|
+
throw err;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
delete registry.items[itemId];
|
|
433
|
+
registry.updated_at = nowIso();
|
|
434
|
+
await saveConfig(doc, data);
|
|
435
|
+
return { success: true };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = {
|
|
439
|
+
REGISTRY_CONFIG_KEY,
|
|
440
|
+
ensureRegistry,
|
|
441
|
+
listRegistries,
|
|
442
|
+
getRegistry,
|
|
443
|
+
createRegistry,
|
|
444
|
+
updateRegistry,
|
|
445
|
+
deleteRegistry,
|
|
446
|
+
createToken,
|
|
447
|
+
deleteToken,
|
|
448
|
+
listItemsForRegistry,
|
|
449
|
+
getAuthStatus,
|
|
450
|
+
upsertItem,
|
|
451
|
+
deleteItem,
|
|
452
|
+
};
|