Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/controllers/config.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2025 Eclipse Foundation.
//
// This program and the accompanying materials are made available under the
// terms of the MIT License which is available at
// https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: MIT

const httpStatus = require('http-status');
const catchAsync = require('../utils/catchAsync');
const configService = require('../services/config.service');
const pick = require('../utils/pick');

/**
* Get configuration by key
*/
const getConfig = catchAsync(async (req, res) => {
const config = await configService.getConfig(req.params.key);
res.json(config);
});

/**
* Get all configurations by category
*/
const getConfigsByCategory = catchAsync(async (req, res) => {
const filter = pick(req.query, ['category']);
const configs = await configService.getConfigsByCategory(filter.category);
res.json(configs);
});

/**
* Get all configurations by group
*/
const getConfigsByGroup = catchAsync(async (req, res) => {
const configs = await configService.getConfigsByGroup(req.params.group);
res.json(configs);
});

/**
* Get all available providers
*/
const getAllProviders = catchAsync(async (req, res) => {
const providers = await configService.getAllProviders();
res.json(providers);
});

/**
* Get provider configuration (generic)
*/
const getProviderConfig = catchAsync(async (req, res) => {
const shouldMask = req.query.mask !== 'false'; // Default to true, only false if explicitly set to 'false'
const config = await configService.getProviderConfig(req.params.provider, shouldMask);
// Return empty object if no config found, instead of 404
// This allows the frontend to display the form with empty fields
res.json(config || {});
});

/**
* Set provider configuration (generic)
*/
const setProviderConfig = catchAsync(async (req, res) => {
const { provider } = req.params;
const { config: configData, schema } = req.body;
const result = await configService.setProviderConfig(provider, configData, schema);
res.json(result);
});

/**
* Set/Update configuration
*/
const setConfig = catchAsync(async (req, res) => {
const { key, value, category, description, isSecret } = req.body;
const config = await configService.setConfig(key, value, {
category,
description,
isSecret,
});
res.status(200).json(config);
});

/**
* Update configuration by key
*/
const updateConfig = catchAsync(async (req, res) => {
const config = await configService.updateConfig(req.params.key, req.body);
res.json(config);
});

/**
* Delete configuration by key
*/
const deleteConfig = catchAsync(async (req, res) => {
await configService.deleteConfig(req.params.key);
res.status(httpStatus.NO_CONTENT).send();
});

module.exports = {
getConfig,
getConfigsByCategory,
getConfigsByGroup,
getAllProviders,
getProviderConfig,
setConfig,
setProviderConfig,
updateConfig,
deleteConfig,
};
98 changes: 98 additions & 0 deletions src/models/config.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) 2025 Eclipse Foundation.
//
// This program and the accompanying materials are made available under the
// terms of the MIT License which is available at
// https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: MIT

const mongoose = require('mongoose');
const { toJSON } = require('./plugins');

const configSchema = new mongoose.Schema(
{
key: {
type: String,
required: true,
unique: true,
trim: true,
index: true,
},
value: {
type: String,
required: true,
},
category: {
type: String,
default: 'general',
index: true,
},
description: {
type: String,
default: '',
},
isSecret: {
type: Boolean,
default: false,
},
group: {
type: String,
default: null,
index: true,
comment: 'Grouping identifier for related configs (e.g., "azure_speech", "openai", "gemini")',
},
metadata: {
type: mongoose.Schema.Types.Mixed,
default: {},
comment: 'Additional metadata for the config (e.g., provider info, display name, validation rules)',
},
},
{
timestamps: true,
}
);

configSchema.plugin(toJSON);

/**
* Get configuration by key
* @param {string} key - Config key
* @returns {Promise<string|null>} Config value
*/
configSchema.statics.getConfig = async function (key) {
const config = await this.findOne({ key });
return config ? config.value : null;
};

/**
* Set configuration value
* @param {string} key - Config key
* @param {string} value - Config value
* @param {object} options - Additional options (category, description, isSecret, group, metadata)
* @returns {Promise<object>} Updated/created config
*/
configSchema.statics.setConfig = async function (key, value, options = {}) {
const update = {
value,
...options,
};

const config = await this.findOneAndUpdate(
{ key },
update,
{ upsert: true, new: true, runValidators: true }
);

return config;
};

/**
* Get configurations by group
* @param {string} group - Group identifier
* @returns {Promise<object[]>} Array of config objects
*/
configSchema.statics.getConfigsByGroup = async function (group) {
return this.find({ group });
};

module.exports = mongoose.model('Config', configSchema);
1 change: 1 addition & 0 deletions src/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ module.exports.Schema = require('./schema.model');
module.exports.Relation = require('./relation.model');
module.exports.Instance = require('./instance.model');
module.exports.InstanceRelation = require('./instanceRelation.model');
module.exports.Config = require('./config.model');
45 changes: 45 additions & 0 deletions src/routes/v2/config.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2025 Eclipse Foundation.
//
// This program and the accompanying materials are made available under the
// terms of the MIT License which is available at
// https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: MIT

const express = require('express');
const router = express.Router();
const configController = require('../../controllers/config.controller');
const validate = require('../../middlewares/validate');
const { configValidation } = require('../../validations');
const auth = require('../../middlewares/auth');
const { PERMISSIONS } = require('../../config/roles');
const { checkPermission } = require('../../middlewares/permission');

// Admin-only endpoints for managing configurations
router
.route('/')
.get(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.getConfigsByCategory), configController.getConfigsByCategory)
.post(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.setConfig), configController.setConfig);

// Get all available providers (admin only)
router.get('/providers', auth(), checkPermission(PERMISSIONS.ADMIN), configController.getAllProviders);

// Generic provider config endpoints (admin only) - must be before /:key route
router
.route('/provider/:provider')
.get(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.getProviderConfig), configController.getProviderConfig)
.post(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.setProviderConfig), configController.setProviderConfig);

// Group-based config endpoint (admin only) - must be before /:key route
router
.route('/group/:group')
.get(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.getConfigsByGroup), configController.getConfigsByGroup);

// Single config by key (admin only) - this should be last since it matches any string
router
.route('/:key')
.get(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.getConfig), configController.getConfig)
.put(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.updateConfig), configController.updateConfig)
.delete(auth(), checkPermission(PERMISSIONS.ADMIN), validate(configValidation.deleteConfig), configController.deleteConfig);

module.exports = router;
5 changes: 5 additions & 0 deletions src/routes/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const schemaRoute = require('./schema.route');
const relationRoute = require('./relation.route');
const instanceRoute = require('./instance.route');
const instanceRelationRoute = require('./instanceRelation.route');
const configRoute = require('./config.route');

const router = express.Router();

Expand Down Expand Up @@ -106,6 +107,10 @@ const defaultRoutes = [
path: '/inventory/instance-relations',
route: instanceRelationRoute,
},
{
path: '/configs',
route: configRoute,
},
];

defaultRoutes.forEach((route) => {
Expand Down
Loading