diff --git a/forge/db/models/MQTTTopicSchema.js b/forge/db/models/MQTTTopicSchema.js index 9b8164cbb5..4291fb7725 100644 --- a/forge/db/models/MQTTTopicSchema.js +++ b/forge/db/models/MQTTTopicSchema.js @@ -10,7 +10,20 @@ module.exports = { name: 'MQTTTopicSchema', schema: { topic: { type: DataTypes.STRING, allowNull: false }, - metadata: { type: DataTypes.TEXT, allowNull: true }, + metadata: { + type: DataTypes.TEXT, + allowNull: true, + get () { + const rawValue = this.getDataValue('metadata') + if (rawValue === undefined || rawValue === null) { + return rawValue + } + return JSON.parse(rawValue) + }, + set (value) { + this.setDataValue('metadata', JSON.stringify(value)) + } + }, inferredSchema: { type: DataTypes.TEXT, allowNull: true } }, indexes: [ diff --git a/forge/db/views/MQTTTopicSchema.js b/forge/db/views/MQTTTopicSchema.js index 8dff71fcca..1a4f7840d2 100644 --- a/forge/db/views/MQTTTopicSchema.js +++ b/forge/db/views/MQTTTopicSchema.js @@ -4,7 +4,7 @@ module.exports = { const cleaned = { id: result.hashid, topic: result.topic, - metadata: result.metadata ? JSON.parse(result.metadata) : { }, + metadata: result.metadata || { }, inferredSchema: result.inferredSchema ? JSON.parse(result.inferredSchema) : undefined } return cleaned diff --git a/forge/ee/routes/teamBroker/3rdPartyBroker.js b/forge/ee/routes/teamBroker/3rdPartyBroker.js index 0e9b62f158..980b2cda8e 100644 --- a/forge/ee/routes/teamBroker/3rdPartyBroker.js +++ b/forge/ee/routes/teamBroker/3rdPartyBroker.js @@ -1,6 +1,4 @@ module.exports = async function (app) { - app.addHook('preHandler', app.verifySession) - app.addHook('preHandler', async (request, reply) => { if (request.params.teamId !== undefined || request.params.teamSlug !== undefined) { // let teamId = request.params.teamId @@ -465,7 +463,7 @@ module.exports = async function (app) { topicObj.inferredSchema = JSON.stringify(topicInfo.type) } if (Object.hasOwn(topicInfo, 'metadata')) { - topicObj.metadata = JSON.stringify(topicInfo.metadata) + topicObj.metadata = topicInfo.metadata } try { await app.db.models.MQTTTopicSchema.upsert(topicObj, { @@ -524,7 +522,7 @@ module.exports = async function (app) { const topic = await app.db.models.MQTTTopicSchema.get(request.params.teamId, brokerId, request.params.topicId) if (topic) { if (request.body.metadata) { - topic.metadata = JSON.stringify(request.body.metadata) + topic.metadata = request.body.metadata await topic.save() } reply.status(201).send(app.db.views.MQTTTopicSchema.clean(topic)) diff --git a/forge/ee/routes/teamBroker/index.js b/forge/ee/routes/teamBroker/index.js index 48efb39d37..f52b50a4d2 100644 --- a/forge/ee/routes/teamBroker/index.js +++ b/forge/ee/routes/teamBroker/index.js @@ -1,8 +1,6 @@ const schemaApi = require('./schema') module.exports = async function (app) { - app.addHook('preHandler', app.verifySession) - app.addHook('preHandler', async (request, reply) => { if (request.params.teamId !== undefined || request.params.teamSlug !== undefined) { // let teamId = request.params.teamId diff --git a/forge/ee/routes/teamBroker/schema.js b/forge/ee/routes/teamBroker/schema.js index 2be7386588..6722f7fdce 100644 --- a/forge/ee/routes/teamBroker/schema.js +++ b/forge/ee/routes/teamBroker/schema.js @@ -1,9 +1,50 @@ const YAML = require('yaml') module.exports = async function (app) { - app.get('/team-broker/schema.yml', async (request, reply) => { - const topics = await app.db.models.MQTTTopicSchema.getTeamBroker(request.team.hashid) - const list = topics.topics.map(t => t.topic) - list.sort() + app.addHook('preHandler', async (request, reply) => { + if (request.params.teamId !== undefined || request.params.teamSlug !== undefined) { + // let teamId = request.params.teamId + if (request.params.teamSlug) { + // If :teamSlug is provided, need to lookup the team to get + // its id for subsequent checks + request.team = await app.db.models.Team.bySlug(request.params.teamSlug) + if (!request.team) { + reply.code(404).send({ code: 'not_found', error: 'Not Found' }) + return + } + // teamId = request.team.hashid + } + + if (!request.team) { + // For a :teamId route, we can now lookup the full team object + request.team = await app.db.models.Team.byId(request.params.teamId) + if (!request.team) { + reply.code(404).send({ code: 'not_found', error: 'Not Found' }) + return + } + + const teamType = await request.team.getTeamType() + if (!teamType.getFeatureProperty('teamBroker', false)) { + reply.code(404).send({ code: 'not_found', error: 'Not Found' }) + return // eslint-disable-line no-useless-return + } + } + + if (request.params.brokerId && request.params.brokerId !== 'team-broker') { + request.broker = await app.db.models.BrokerCredentials.byId(request.params.brokerId) + if (!request.broker) { + reply.code(404).send({ code: 'not_found', error: 'Not Found' }) + return // eslint-disable-line no-useless-return + } + } + } + if (!request.teamMembership && request.session.User) { + request.teamMembership = await request.session.User.getTeamMembership(request.team.id) + } + }) + + app.get('/:brokerId/schema.yml', { + preHandler: app.needsPermission('broker:topics:list') + }, async (request, reply) => { const schema = { asyncapi: '3.0.0', info: { @@ -12,33 +53,63 @@ module.exports = async function (app) { description: 'An auto-generated schema of the topics being used on the team broker' } } - // Add the team-broker details - // Figure out the hostname for the team broker - let teamBrokerHost = app.config.broker?.teamBroker?.host - if (!teamBrokerHost) { - // No explict value set, default to broker.${domain} - if (app.config.domain) { - teamBrokerHost = `broker.${app.config.domain}` + let topics + const isTeamBroker = request.params.brokerId === 'team-broker' + if (isTeamBroker) { + schema.info.title = `${request.team.name} Team Broker` + schema.info.description = 'An auto-generated schema of the topics being used on the team broker' + topics = await app.db.models.MQTTTopicSchema.getTeamBroker(request.team.hashid) + // Figure out the hostname for the team broker + let teamBrokerHost = app.config.broker?.teamBroker?.host + if (!teamBrokerHost) { + // No explict value set, default to broker.${domain} + if (app.config.domain) { + teamBrokerHost = `broker.${app.config.domain}` + } } - } - if (teamBrokerHost) { + if (teamBrokerHost) { + schema.servers = { + 'team-broker': { + host: teamBrokerHost, + protocol: 'mqtt', + security: [{ + type: 'userPassword' + }] + } + } + } + } else { + schema.info.title = `${request.broker.name}` + schema.info.description = `An auto-generated schema of the topics being used on the '${request.broker.name}' broker` + topics = await app.db.models.MQTTTopicSchema.byBroker(request.broker.id) + schema.servers = { - 'team-broker': { - host: teamBrokerHost, - protocol: 'mqtt', - security: [{ + [request.broker.name]: { + host: request.broker.host + ':' + request.broker.port, + protocol: 'mqtt' + } + } + if (request.broker.credentials) { + const creds = JSON.parse(request.broker.credentials) + if (creds.username && creds.password) { + schema.servers[request.broker.name].security = [{ type: 'userPassword' }] } } } - if (list.length > 0) { + const topicList = topics.topics + topicList.sort((A, B) => A.topic.localeCompare(B.topic)) + if (topicList.length > 0) { schema.channels = {} - list.forEach(topic => { - schema.channels[topic] = { - address: topic + topicList.forEach(topicObj => { + schema.channels[topicObj.topic] = { + address: topicObj.topic + } + if (topicObj.metadata?.description) { + schema.channels[topicObj.topic].description = topicObj.metadata?.description } }) } diff --git a/frontend/src/pages/team/Brokers/Hierarchy/index.vue b/frontend/src/pages/team/Brokers/Hierarchy/index.vue index e71b21f601..b03cdbb8d1 100644 --- a/frontend/src/pages/team/Brokers/Hierarchy/index.vue +++ b/frontend/src/pages/team/Brokers/Hierarchy/index.vue @@ -42,7 +42,7 @@ -