diff --git a/server/src/api/controllers/Settings.ts b/server/src/api/controllers/Settings.ts index a29cf574f..eddd3eae9 100644 --- a/server/src/api/controllers/Settings.ts +++ b/server/src/api/controllers/Settings.ts @@ -1,9 +1,13 @@ import { Router, Request, Response } from 'express'; -import { body, query, validationResult } from 'express-validator'; +import { body, query } from 'express-validator'; import { pick } from 'lodash'; import { IOptionDTO, IOptionsDTO } from 'interfaces'; import BaseController from 'api/controllers/BaseController'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; +import { + getDefinedOptions, + isDefinedOptionConfigurable, +} from 'utils'; export default class SettingsController extends BaseController{ /** @@ -14,12 +18,14 @@ export default class SettingsController extends BaseController{ router.post('/', this.saveSettingsValidationSchema, - asyncMiddleware(this.saveSettings.bind(this))); - + this.validationResult, + asyncMiddleware(this.saveSettings.bind(this)), + ); router.get('/', this.getSettingsSchema, - asyncMiddleware(this.getSettings.bind(this))); - + this.validationResult, + asyncMiddleware(this.getSettings.bind(this)), + ); return router; } @@ -29,9 +35,9 @@ export default class SettingsController extends BaseController{ get saveSettingsValidationSchema() { return [ body('options').isArray({ min: 1 }), - body('options.*.key').exists(), - body('options.*.value').exists(), - body('options.*.group').exists(), + body('options.*.key').exists().trim().escape().isLength({ min: 1 }), + body('options.*.value').exists().trim().escape().isLength({ min: 1 }), + body('options.*.group').exists().trim().escape().isLength({ min: 1 }), ]; } @@ -40,11 +46,32 @@ export default class SettingsController extends BaseController{ */ get getSettingsSchema() { return [ - query('key').optional(), - query('group').optional(), + query('key').optional().trim().escape(), + query('group').optional().trim().escape(), ]; } + /** + * Observes application configuration option, whether all options configured + * sets `app_configured` option. + * @param {Setting} settings + */ + observeAppConfigsComplete(settings) { + if (!settings.get('app_configured', false)) { + const definedOptions = getDefinedOptions(); + + const isNotConfigured = definedOptions.some((option) => { + const isDefined = isDefinedOptionConfigurable(option.key, option.group); + const hasStoredOption = settings.get({ key: option.key, group: option.group }); + + return (isDefined && !hasStoredOption); + }); + if (!isNotConfigured) { + settings.set('app_configured', true); + } + } + } + /** * Saves the given options to the storage. * @param {Request} req - @@ -71,7 +98,8 @@ export default class SettingsController extends BaseController{ optionsDTO.options.forEach((option: IOptionDTO) => { settings.set({ ...option }); }); - + this.observeAppConfigsComplete(settings); + return res.status(200).send({ type: 'success', code: 'OPTIONS.SAVED.SUCCESSFULLY', diff --git a/server/src/api/index.ts b/server/src/api/index.ts index 9ec4ca04a..3d574ca2d 100644 --- a/server/src/api/index.ts +++ b/server/src/api/index.ts @@ -41,6 +41,8 @@ import Licenses from 'api/controllers/Subscription/Licenses'; export default () => { const app = Router(); + // - Global routes. + // --------------------------- app.use(I18nMiddleware); app.use('/auth', Container.get(Authentication).router()); @@ -49,7 +51,24 @@ export default () => { app.use('/subscription', Container.get(Subscription).router()); app.use('/ping', Container.get(Ping).router()); app.use('/organization', Container.get(Organization).router()); + + // - Settings routes. + // --------------------------- + const settings = Router(); + settings.use(JWTAuth); + settings.use(AttachCurrentTenantUser); + settings.use(TenancyMiddleware); + settings.use(SubscriptionMiddleware('main')); + settings.use(EnsureTenantIsInitialized); + settings.use(SettingsMiddleware); + + settings.use('/settings', Container.get(Settings).router()); + + app.use('/', settings); + + // - Dashboard routes. + // --------------------------- const dashboard = Router(); dashboard.use(JWTAuth); @@ -73,7 +92,6 @@ export default () => { dashboard.use('/item_categories', Container.get(ItemCategories).router()); dashboard.use('/expenses', Container.get(Expenses).router()); dashboard.use('/financial_statements', FinancialStatements.router()); - dashboard.use('/settings', Container.get(Settings).router()); dashboard.use('/sales', Sales.router()); dashboard.use('/customers', Container.get(Customers).router()); dashboard.use('/vendors', Container.get(Vendors).router()); diff --git a/server/src/data/options.js b/server/src/data/options.js index 9e0e5ed00..e10fee599 100644 --- a/server/src/data/options.js +++ b/server/src/data/options.js @@ -5,12 +5,12 @@ export default { { key: 'name', type: 'string', - configure: true, + config: true, }, { key: 'base_currency', type: 'string', - configure: true, + config: true, }, { key: 'industry', @@ -23,22 +23,22 @@ export default { { key: 'fiscal_year', type: 'string', - configure: true, + // config: true, }, { key: 'language', type: 'string', - configure: true, + config: true, }, { key: 'time_zone', type: 'string', - configure: true, + // config: true, }, { key: 'date_format', type: 'string', - configure: true, + // config: true, }, ], }; \ No newline at end of file diff --git a/server/src/utils/index.js b/server/src/utils/index.js index b649db46c..8c03e6041 100644 --- a/server/src/utils/index.js +++ b/server/src/utils/index.js @@ -1,6 +1,7 @@ import bcrypt from 'bcryptjs'; import moment from 'moment'; import _ from 'lodash'; +import definedOptions from 'data/options'; const hashPassword = (password) => new Promise((resolve) => { @@ -155,6 +156,29 @@ const formatDateFields = (inputDTO, fields, format = 'YYYY-MM-DD') => { return _inputDTO; }; +const getDefinedOptions = () => { + const options = []; + + Object.keys(definedOptions).forEach((groupKey) => { + const groupOptions = definedOptions[groupKey]; + groupOptions.forEach((option) => { + options.push({ ...option, group: groupKey }); + }); + }); + return options; +}; + +const getDefinedOption = (key, group) => { + return definedOptions?.[group]?.find(option => option.key == key); +}; + +const isDefinedOptionConfigurable = (key, group) => { + const definedOption = getDefinedOption(key, group); + console.log(definedOption, 'definedOption'); + + return definedOption?.config || false; +}; + export { hashPassword, origin, @@ -168,4 +192,8 @@ export { getTotalDeep, applyMixins, formatDateFields, + + isDefinedOptionConfigurable, + getDefinedOption, + getDefinedOptions, };