mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
fix: refactor context to use service pattern (#721)
This commit is contained in:
parent
4f1d4df4b8
commit
6389385f61
@ -1,11 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const {
|
|
||||||
CONTEXT_FIELD_CREATED,
|
|
||||||
CONTEXT_FIELD_UPDATED,
|
|
||||||
CONTEXT_FIELD_DELETED,
|
|
||||||
} = require('../event-type');
|
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
@ -26,20 +20,10 @@ const mapRow = row => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class ContextFieldStore {
|
class ContextFieldStore {
|
||||||
constructor(db, customContextFields, eventStore, getLogger) {
|
constructor(db, customContextFields, getLogger) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.logger = getLogger('context-field-store.js');
|
this.logger = getLogger('context-field-store.js');
|
||||||
this._createFromConfig(customContextFields);
|
this._createFromConfig(customContextFields);
|
||||||
|
|
||||||
eventStore.on(CONTEXT_FIELD_CREATED, event =>
|
|
||||||
this._createContextField(event.data),
|
|
||||||
);
|
|
||||||
eventStore.on(CONTEXT_FIELD_UPDATED, event =>
|
|
||||||
this._updateContextField(event.data),
|
|
||||||
);
|
|
||||||
eventStore.on(CONTEXT_FIELD_DELETED, event => {
|
|
||||||
this._deleteContextField(event.data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createFromConfig(customContextFields) {
|
async _createFromConfig(customContextFields) {
|
||||||
@ -48,12 +32,12 @@ class ContextFieldStore {
|
|||||||
'Create custom context fields',
|
'Create custom context fields',
|
||||||
customContextFields,
|
customContextFields,
|
||||||
);
|
);
|
||||||
const conextFields = await this.getAll();
|
const contextFields = await this.getAll();
|
||||||
customContextFields
|
customContextFields
|
||||||
.filter(c => !conextFields.some(cf => cf.name === c.name))
|
.filter(c => !contextFields.some(cf => cf.name === c.name))
|
||||||
.forEach(async field => {
|
.forEach(async field => {
|
||||||
try {
|
try {
|
||||||
await this._createContextField(field);
|
await this.create(field);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(e);
|
this.logger.error(e);
|
||||||
}
|
}
|
||||||
@ -89,39 +73,20 @@ class ContextFieldStore {
|
|||||||
.then(mapRow);
|
.then(mapRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createContextField(contextField) {
|
async create(contextField) {
|
||||||
return this.db(TABLE)
|
return this.db(TABLE).insert(this.fieldToRow(contextField));
|
||||||
.insert(this.fieldToRow(contextField))
|
|
||||||
.catch(err =>
|
|
||||||
this.logger.error(
|
|
||||||
'Could not insert contextField, error: ',
|
|
||||||
err,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateContextField(data) {
|
async update(data) {
|
||||||
return this.db(TABLE)
|
return this.db(TABLE)
|
||||||
.where({ name: data.name })
|
.where({ name: data.name })
|
||||||
.update(this.fieldToRow(data))
|
.update(this.fieldToRow(data));
|
||||||
.catch(err =>
|
|
||||||
this.logger.error(
|
|
||||||
'Could not update context field, error: ',
|
|
||||||
err,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _deleteContextField({ name }) {
|
async delete(name) {
|
||||||
return this.db(TABLE)
|
return this.db(TABLE)
|
||||||
.where({ name })
|
.where({ name })
|
||||||
.del()
|
.del();
|
||||||
.catch(err => {
|
|
||||||
this.logger.error(
|
|
||||||
'Could not delete context field, error: ',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,6 @@ module.exports.createStores = (config, eventBus) => {
|
|||||||
contextFieldStore: new ContextFieldStore(
|
contextFieldStore: new ContextFieldStore(
|
||||||
db,
|
db,
|
||||||
config.customContextFields,
|
config.customContextFields,
|
||||||
eventStore,
|
|
||||||
getLogger,
|
getLogger,
|
||||||
),
|
),
|
||||||
settingStore: new SettingStore(db, getLogger),
|
settingStore: new SettingStore(db, getLogger),
|
||||||
|
@ -2,17 +2,9 @@
|
|||||||
|
|
||||||
const Controller = require('../controller');
|
const Controller = require('../controller');
|
||||||
|
|
||||||
const { contextSchema, nameSchema } = require('./context-schema');
|
|
||||||
const NameExistsError = require('../../error/name-exists-error');
|
|
||||||
const { handleErrors } = require('./util');
|
const { handleErrors } = require('./util');
|
||||||
const extractUser = require('../../extract-user');
|
const extractUser = require('../../extract-user');
|
||||||
|
|
||||||
const {
|
|
||||||
CONTEXT_FIELD_CREATED,
|
|
||||||
CONTEXT_FIELD_UPDATED,
|
|
||||||
CONTEXT_FIELD_DELETED,
|
|
||||||
} = require('../../event-type');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
CREATE_CONTEXT_FIELD,
|
CREATE_CONTEXT_FIELD,
|
||||||
UPDATE_CONTEXT_FIELD,
|
UPDATE_CONTEXT_FIELD,
|
||||||
@ -20,11 +12,10 @@ const {
|
|||||||
} = require('../../permissions');
|
} = require('../../permissions');
|
||||||
|
|
||||||
class ContextController extends Controller {
|
class ContextController extends Controller {
|
||||||
constructor(config) {
|
constructor(config, { contextService }) {
|
||||||
super(config);
|
super(config);
|
||||||
this.logger = config.getLogger('/admin-api/feature.js');
|
this.logger = config.getLogger('/admin-api/feature.js');
|
||||||
this.eventStore = config.stores.eventStore;
|
this.contextService = contextService;
|
||||||
this.store = config.stores.contextFieldStore;
|
|
||||||
|
|
||||||
this.get('/', this.getContextFields);
|
this.get('/', this.getContextFields);
|
||||||
this.post('/', this.createContextField, CREATE_CONTEXT_FIELD);
|
this.post('/', this.createContextField, CREATE_CONTEXT_FIELD);
|
||||||
@ -43,7 +34,7 @@ class ContextController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getContextFields(req, res) {
|
async getContextFields(req, res) {
|
||||||
const fields = await this.store.getAll();
|
const fields = await this.contextService.getAll();
|
||||||
res.status(200)
|
res.status(200)
|
||||||
.json(fields)
|
.json(fields)
|
||||||
.end();
|
.end();
|
||||||
@ -52,7 +43,9 @@ class ContextController extends Controller {
|
|||||||
async getContextField(req, res) {
|
async getContextField(req, res) {
|
||||||
try {
|
try {
|
||||||
const name = req.params.contextField;
|
const name = req.params.contextField;
|
||||||
const contextField = await this.store.get(name);
|
const contextField = await this.contextService.getContextField(
|
||||||
|
name,
|
||||||
|
);
|
||||||
res.json(contextField).end();
|
res.json(contextField).end();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(404).json({ error: 'Could not find context field' });
|
res.status(404).json({ error: 'Could not find context field' });
|
||||||
@ -60,17 +53,11 @@ class ContextController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createContextField(req, res) {
|
async createContextField(req, res) {
|
||||||
const { name } = req.body;
|
const value = req.body;
|
||||||
const userName = extractUser(req);
|
const userName = extractUser(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.validateUniqueName(name);
|
await this.contextService.createContextField(value, userName);
|
||||||
const value = await contextSchema.validateAsync(req.body);
|
|
||||||
await this.eventStore.store({
|
|
||||||
type: CONTEXT_FIELD_CREATED,
|
|
||||||
createdBy: userName,
|
|
||||||
data: value,
|
|
||||||
});
|
|
||||||
res.status(201).end();
|
res.status(201).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrors(res, this.logger, error);
|
handleErrors(res, this.logger, error);
|
||||||
@ -80,21 +67,15 @@ class ContextController extends Controller {
|
|||||||
async updateContextField(req, res) {
|
async updateContextField(req, res) {
|
||||||
const name = req.params.contextField;
|
const name = req.params.contextField;
|
||||||
const userName = extractUser(req);
|
const userName = extractUser(req);
|
||||||
const updatedContextField = req.body;
|
const contextField = req.body;
|
||||||
|
|
||||||
updatedContextField.name = name;
|
contextField.name = name;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.store.get(name);
|
await this.contextService.updateContextField(
|
||||||
|
contextField,
|
||||||
const value = await contextSchema.validateAsync(
|
userName,
|
||||||
updatedContextField,
|
|
||||||
);
|
);
|
||||||
await this.eventStore.store({
|
|
||||||
type: CONTEXT_FIELD_UPDATED,
|
|
||||||
createdBy: userName,
|
|
||||||
data: value,
|
|
||||||
});
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrors(res, this.logger, error);
|
handleErrors(res, this.logger, error);
|
||||||
@ -103,40 +84,21 @@ class ContextController extends Controller {
|
|||||||
|
|
||||||
async deleteContextField(req, res) {
|
async deleteContextField(req, res) {
|
||||||
const name = req.params.contextField;
|
const name = req.params.contextField;
|
||||||
|
const userName = extractUser(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.store.get(name);
|
await this.contextService.deleteContextField(name, userName);
|
||||||
await this.eventStore.store({
|
|
||||||
type: CONTEXT_FIELD_DELETED,
|
|
||||||
createdBy: extractUser(req),
|
|
||||||
data: { name },
|
|
||||||
});
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrors(res, this.logger, error);
|
handleErrors(res, this.logger, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateUniqueName(name) {
|
|
||||||
let msg;
|
|
||||||
try {
|
|
||||||
await this.store.get(name);
|
|
||||||
msg = 'A context field with that name already exist';
|
|
||||||
} catch (error) {
|
|
||||||
// No conflict, everything ok!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interntional throw here!
|
|
||||||
throw new NameExistsError(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(req, res) {
|
async validate(req, res) {
|
||||||
const { name } = req.body;
|
const { name } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await nameSchema.validateAsync({ name });
|
await this.contextService.validateName(name);
|
||||||
await this.validateUniqueName(name);
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleErrors(res, this.logger, error);
|
handleErrors(res, this.logger, error);
|
||||||
|
@ -4,6 +4,7 @@ const test = require('ava');
|
|||||||
const supertest = require('supertest');
|
const supertest = require('supertest');
|
||||||
const { EventEmitter } = require('events');
|
const { EventEmitter } = require('events');
|
||||||
const store = require('../../../test/fixtures/store');
|
const store = require('../../../test/fixtures/store');
|
||||||
|
const { createServices } = require('../../services');
|
||||||
const getLogger = require('../../../test/fixtures/no-logger');
|
const getLogger = require('../../../test/fixtures/no-logger');
|
||||||
const getApp = require('../../app');
|
const getApp = require('../../app');
|
||||||
|
|
||||||
@ -12,14 +13,17 @@ const eventBus = new EventEmitter();
|
|||||||
function getSetup() {
|
function getSetup() {
|
||||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||||
const stores = store.createStores();
|
const stores = store.createStores();
|
||||||
const app = getApp({
|
const config = {
|
||||||
baseUriPath: base,
|
baseUriPath: base,
|
||||||
stores,
|
stores,
|
||||||
eventBus,
|
eventBus,
|
||||||
extendedPermissions: false,
|
extendedPermissions: false,
|
||||||
customContextFields: [{ name: 'tenantId' }],
|
customContextFields: [{ name: 'tenantId' }],
|
||||||
getLogger,
|
getLogger,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const services = createServices(stores, config);
|
||||||
|
const app = getApp(config, services);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const joi = require('joi');
|
const joi = require('joi');
|
||||||
const { nameType } = require('./util');
|
const { nameType } = require('../routes/admin-api/util');
|
||||||
|
|
||||||
const nameSchema = joi.object().keys({ name: nameType });
|
const nameSchema = joi.object().keys({ name: nameType });
|
||||||
|
|
92
lib/services/context-service.js
Normal file
92
lib/services/context-service.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { contextSchema, nameSchema } = require('./context-schema');
|
||||||
|
const NameExistsError = require('../error/name-exists-error');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CONTEXT_FIELD_CREATED,
|
||||||
|
CONTEXT_FIELD_UPDATED,
|
||||||
|
CONTEXT_FIELD_DELETED,
|
||||||
|
} = require('../event-type');
|
||||||
|
|
||||||
|
class ContextService {
|
||||||
|
constructor(
|
||||||
|
{ projectStore, eventStore, contextFieldStore },
|
||||||
|
{ getLogger },
|
||||||
|
) {
|
||||||
|
this.projectStore = projectStore;
|
||||||
|
this.eventStore = eventStore;
|
||||||
|
this.contextFieldStore = contextFieldStore;
|
||||||
|
this.logger = getLogger('services/context-service.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
return this.contextFieldStore.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContextField(name) {
|
||||||
|
return this.contextFieldStore.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createContextField(value, userName) {
|
||||||
|
// validations
|
||||||
|
await this.validateUniqueName(value);
|
||||||
|
const contextField = await contextSchema.validateAsync(value);
|
||||||
|
|
||||||
|
// creations
|
||||||
|
await this.contextFieldStore.create(value);
|
||||||
|
await this.eventStore.store({
|
||||||
|
type: CONTEXT_FIELD_CREATED,
|
||||||
|
createdBy: userName,
|
||||||
|
data: contextField,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateContextField(updatedContextField, userName) {
|
||||||
|
// validations
|
||||||
|
await this.contextFieldStore.get(updatedContextField.name);
|
||||||
|
const value = await contextSchema.validateAsync(updatedContextField);
|
||||||
|
|
||||||
|
// update
|
||||||
|
await this.contextFieldStore.update(value);
|
||||||
|
await this.eventStore.store({
|
||||||
|
type: CONTEXT_FIELD_UPDATED,
|
||||||
|
createdBy: userName,
|
||||||
|
data: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteContextField(name, userName) {
|
||||||
|
// validate existence
|
||||||
|
await this.contextFieldStore.get(name);
|
||||||
|
|
||||||
|
// delete
|
||||||
|
await this.contextFieldStore.delete(name);
|
||||||
|
await this.eventStore.store({
|
||||||
|
type: CONTEXT_FIELD_DELETED,
|
||||||
|
createdBy: userName,
|
||||||
|
data: { name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateUniqueName({ name }) {
|
||||||
|
let msg;
|
||||||
|
try {
|
||||||
|
await this.contextFieldStore.get(name);
|
||||||
|
msg = 'A context field with that name already exist';
|
||||||
|
} catch (error) {
|
||||||
|
// No conflict, everything ok!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentional throw here!
|
||||||
|
throw new NameExistsError(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateName(name) {
|
||||||
|
await nameSchema.validateAsync({ name });
|
||||||
|
await this.validateUniqueName({ name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ContextService;
|
@ -6,6 +6,7 @@ const TagTypeService = require('./tag-type-service');
|
|||||||
const TagService = require('./tag-service');
|
const TagService = require('./tag-service');
|
||||||
const StrategyService = require('./strategy-service');
|
const StrategyService = require('./strategy-service');
|
||||||
const AddonService = require('./addon-service');
|
const AddonService = require('./addon-service');
|
||||||
|
const ContextService = require('./context-service');
|
||||||
|
|
||||||
module.exports.createServices = (stores, config) => {
|
module.exports.createServices = (stores, config) => {
|
||||||
const featureToggleService = new FeatureToggleService(stores, config);
|
const featureToggleService = new FeatureToggleService(stores, config);
|
||||||
@ -16,6 +17,7 @@ module.exports.createServices = (stores, config) => {
|
|||||||
const tagService = new TagService(stores, config);
|
const tagService = new TagService(stores, config);
|
||||||
const clientMetricsService = new ClientMetricsService(stores, config);
|
const clientMetricsService = new ClientMetricsService(stores, config);
|
||||||
const addonService = new AddonService(stores, config, tagTypeService);
|
const addonService = new AddonService(stores, config, tagTypeService);
|
||||||
|
const contextService = new ContextService(stores, config);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addonService,
|
addonService,
|
||||||
@ -26,5 +28,6 @@ module.exports.createServices = (stores, config) => {
|
|||||||
tagTypeService,
|
tagTypeService,
|
||||||
tagService,
|
tagService,
|
||||||
clientMetricsService,
|
clientMetricsService,
|
||||||
|
contextService,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -36,7 +36,7 @@ function createStrategies(store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createContextFields(store) {
|
function createContextFields(store) {
|
||||||
return dbState.contextFields.map(c => store._createContextField(c));
|
return dbState.contextFields.map(c => store.create(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createApplications(store) {
|
function createApplications(store) {
|
||||||
|
10
test/fixtures/fake-context-store.js
vendored
10
test/fixtures/fake-context-store.js
vendored
@ -3,7 +3,7 @@
|
|||||||
const NotFoundError = require('../../lib/error/notfound-error');
|
const NotFoundError = require('../../lib/error/notfound-error');
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
const _contextFields = [
|
let _contextFields = [
|
||||||
{ name: 'environment' },
|
{ name: 'environment' },
|
||||||
{ name: 'userId' },
|
{ name: 'userId' },
|
||||||
{ name: 'appName' },
|
{ name: 'appName' },
|
||||||
@ -19,5 +19,13 @@ module.exports = () => {
|
|||||||
return Promise.reject(NotFoundError);
|
return Promise.reject(NotFoundError);
|
||||||
},
|
},
|
||||||
create: contextField => _contextFields.push(contextField),
|
create: contextField => _contextFields.push(contextField),
|
||||||
|
update: field => {
|
||||||
|
_contextFields = _contextFields.map(c =>
|
||||||
|
c.name === field.name ? field : c,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
delete: name => {
|
||||||
|
_contextFields = _contextFields.filter(c => c.name !== name);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user