1
0
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:
Ivar Conradi Østhus 2021-02-12 10:23:43 +01:00 committed by GitHub
parent 4f1d4df4b8
commit 6389385f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 105 deletions

View File

@ -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,
);
});
} }
} }

View File

@ -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),

View File

@ -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);

View File

@ -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,

View File

@ -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 });

View 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;

View File

@ -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,
}; };
}; };

View File

@ -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) {

View File

@ -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);
},
}; };
}; };