openvibers/openvibe.vision Commits
/openapi.json: the service's public API as OpenAPI 3.1 (SEO-3) (#77)
Changes
@@@ -10,6 +10,7 @@ module.exports = {api: "/api/v1",docs: "/developers",mcp: { url: "/mcp" },++ openapi: '/openapi.json',policy: {auth: ["none", "Network OIDC"],},@@@ -92,6 +92,25 @@ async function createApp(opts = {}) {const ctx = { config, s, keys, sso, principal, store, ocr, ai, vision, faces, faceLibraries, consentRegistry, models, networkTools, accountData, accountSend, eventOutbox, eventPublisher, log, fetchImpl };const app = express();++ // GET /openapi.json (SEO-3): this service's public API as OpenAPI 3.1, generated from the contracts++ // (openvibe-contracts); the operations they mark internal-only are left out.++ app.get('/openapi.json', (_req, res) => {++ const doc = require('openvibe-contracts').openapi.document('vision');++ if (!doc) return res.status(404).type('text/plain').send('No API document.');++ const METHODS = new Set(['get', 'put', 'post', 'delete', 'patch', 'head', 'options']);++ const paths = {};++ for (const [p, item] of Object.entries(doc.paths || {})) {++ const kept = {};++ for (const [k, op] of Object.entries(item || {})) {++ if (!METHODS.has(k)) { kept[k] = op; continue; }++ const vis = [].concat((op && op['x-openvibe-visibility']) || []);++ if (vis.length && vis.every((v) => v === 'internal')) continue;++ kept[k] = op;++ }++ if (Object.keys(kept).some((k) => METHODS.has(k))) paths[p] = kept;++ }++ res.set('Cache-Control', 'public, max-age=3600').set('Access-Control-Allow-Origin', '*').json({ ...doc, paths });++ });// /.well-known/agent.json (openvibe-shared/agent-layer, SEO-2): what this site serves, for agents.require('openvibe-shared/agent-layer').mount(app, { descriptor: require('./agent-descriptor') });app.disable('x-powered-by');nnew file mode 100644@@@ -0,0 +1,13 @@++'use strict';++// SEO-3: /openapi.json answered 404 here (11-03). It is the contracts' OpenAPI 3.1 document for this service++// (openvibe-contracts), without the operations they mark internal-only.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const doc = require('openvibe-contracts').openapi.document('vision');++assert.ok(doc && doc.openapi && /^3\.1/.test(doc.openapi), 'the contracts carry an OpenAPI 3.1 document for vision');++const src = fs.readFileSync(path.join(__dirname, '..', 'server/app.js'), 'utf8');++assert.match(src, /app\.get\('\/openapi\.json'/, 'the route is mounted');++assert.match(src, /openapi\.document\('vision'\)/);++assert.match(src, /vis\.every\(\(v\) => v === 'internal'\)\) continue;/, 'internal-only operations are left out');++console.log('openapi.json: ok');
@@ -10,6 +10,7 @@ module.exports = {
@@ -10,6 +10,7 @@ module.exports = {
api: "/api/v1",
api: "/api/v1",
docs: "/developers",
docs: "/developers",
mcp: { url: "/mcp" },
mcp: { url: "/mcp" },
+ openapi: '/openapi.json',
policy: {
policy: {
auth: ["none", "Network OIDC"],
auth: ["none", "Network OIDC"],
},
},
@@ -92,6 +92,25 @@ async function createApp(opts = {}) {
@@ -92,6 +92,25 @@ async function createApp(opts = {}) {
const ctx = { config, s, keys, sso, principal, store, ocr, ai, vision, faces, faceLibraries, consentRegistry, models, networkTools, accountData, accountSend, eventOutbox, eventPublisher, log, fetchImpl };
const ctx = { config, s, keys, sso, principal, store, ocr, ai, vision, faces, faceLibraries, consentRegistry, models, networkTools, accountData, accountSend, eventOutbox, eventPublisher, log, fetchImpl };
const app = express();
const app = express();
+ // GET /openapi.json (SEO-3): this service's public API as OpenAPI 3.1, generated from the contracts
+ // (openvibe-contracts); the operations they mark internal-only are left out.
+ app.get('/openapi.json', (_req, res) => {
+ const doc = require('openvibe-contracts').openapi.document('vision');
+ if (!doc) return res.status(404).type('text/plain').send('No API document.');
+ const METHODS = new Set(['get', 'put', 'post', 'delete', 'patch', 'head', 'options']);
+ const paths = {};
+ for (const [p, item] of Object.entries(doc.paths || {})) {
+ const kept = {};
+ for (const [k, op] of Object.entries(item || {})) {
+ if (!METHODS.has(k)) { kept[k] = op; continue; }
+ const vis = [].concat((op && op['x-openvibe-visibility']) || []);
+ if (vis.length && vis.every((v) => v === 'internal')) continue;
+ kept[k] = op;
+ }
+ if (Object.keys(kept).some((k) => METHODS.has(k))) paths[p] = kept;
+ }
+ res.set('Cache-Control', 'public, max-age=3600').set('Access-Control-Allow-Origin', '*').json({ ...doc, paths });
+ });
// /.well-known/agent.json (openvibe-shared/agent-layer, SEO-2): what this site serves, for agents.
// /.well-known/agent.json (openvibe-shared/agent-layer, SEO-2): what this site serves, for agents.
require('openvibe-shared/agent-layer').mount(app, { descriptor: require('./agent-descriptor') });
require('openvibe-shared/agent-layer').mount(app, { descriptor: require('./agent-descriptor') });
app.disable('x-powered-by');
app.disable('x-powered-by');
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+'use strict';
+// SEO-3: /openapi.json answered 404 here (11-03). It is the contracts' OpenAPI 3.1 document for this service
+// (openvibe-contracts), without the operations they mark internal-only.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const doc = require('openvibe-contracts').openapi.document('vision');
+assert.ok(doc && doc.openapi && /^3\.1/.test(doc.openapi), 'the contracts carry an OpenAPI 3.1 document for vision');
+const src = fs.readFileSync(path.join(__dirname, '..', 'server/app.js'), 'utf8');
+assert.match(src, /app\.get\('\/openapi\.json'/, 'the route is mounted');
+assert.match(src, /openapi\.document\('vision'\)/);
+assert.match(src, /vis\.every\(\(v\) => v === 'internal'\)\) continue;/, 'internal-only operations are left out');
+console.log('openapi.json: ok');