1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01: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';
const {
CONTEXT_FIELD_CREATED,
CONTEXT_FIELD_UPDATED,
CONTEXT_FIELD_DELETED,
} = require('../event-type');
const COLUMNS = [
'name',
'description',
@ -26,20 +20,10 @@ const mapRow = row => ({
});
class ContextFieldStore {
constructor(db, customContextFields, eventStore, getLogger) {
constructor(db, customContextFields, getLogger) {
this.db = db;
this.logger = getLogger('context-field-store.js');
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) {
@ -48,12 +32,12 @@ class ContextFieldStore {
'Create custom context fields',
customContextFields,
);
const conextFields = await this.getAll();
const contextFields = await this.getAll();
customContextFields
.filter(c => !conextFields.some(cf => cf.name === c.name))
.filter(c => !contextFields.some(cf => cf.name === c.name))
.forEach(async field => {
try {
await this._createContextField(field);
await this.create(field);
} catch (e) {
this.logger.error(e);
}
@ -89,39 +73,20 @@ class ContextFieldStore {
.then(mapRow);
}
async _createContextField(contextField) {
return this.db(TABLE)
.insert(this.fieldToRow(contextField))
.catch(err =>
this.logger.error(
'Could not insert contextField, error: ',
err,
),
);
async create(contextField) {
return this.db(TABLE).insert(this.fieldToRow(contextField));
}
async _updateContextField(data) {
async update(data) {
return this.db(TABLE)
.where({ name: data.name })
.update(this.fieldToRow(data))
.catch(err =>
this.logger.error(
'Could not update context field, error: ',
err,
),
);
.update(this.fieldToRow(data));
}
async _deleteContextField({ name }) {
async delete(name) {
return this.db(TABLE)
.where({ name })
.del()
.catch(err => {
this.logger.error(
'Could not delete context field, error: ',
err,
);
});
.del();
}
}

View File

@ -43,7 +43,6 @@ module.exports.createStores = (config, eventBus) => {
contextFieldStore: new ContextFieldStore(
db,
config.customContextFields,
eventStore,
getLogger,
),
settingStore: new SettingStore(db, getLogger),

View File

@ -2,17 +2,9 @@
const Controller = require('../controller');
const { contextSchema, nameSchema } = require('./context-schema');
const NameExistsError = require('../../error/name-exists-error');
const { handleErrors } = require('./util');
const extractUser = require('../../extract-user');
const {
CONTEXT_FIELD_CREATED,
CONTEXT_FIELD_UPDATED,
CONTEXT_FIELD_DELETED,
} = require('../../event-type');
const {
CREATE_CONTEXT_FIELD,
UPDATE_CONTEXT_FIELD,
@ -20,11 +12,10 @@ const {
} = require('../../permissions');
class ContextController extends Controller {
constructor(config) {
constructor(config, { contextService }) {
super(config);
this.logger = config.getLogger('/admin-api/feature.js');
this.eventStore = config.stores.eventStore;
this.store = config.stores.contextFieldStore;
this.contextService = contextService;
this.get('/', this.getContextFields);
this.post('/', this.createContextField, CREATE_CONTEXT_FIELD);
@ -43,7 +34,7 @@ class ContextController extends Controller {
}
async getContextFields(req, res) {
const fields = await this.store.getAll();
const fields = await this.contextService.getAll();
res.status(200)
.json(fields)
.end();
@ -52,7 +43,9 @@ class ContextController extends Controller {
async getContextField(req, res) {
try {
const name = req.params.contextField;
const contextField = await this.store.get(name);
const contextField = await this.contextService.getContextField(
name,
);
res.json(contextField).end();
} catch (err) {
res.status(404).json({ error: 'Could not find context field' });
@ -60,17 +53,11 @@ class ContextController extends Controller {
}
async createContextField(req, res) {
const { name } = req.body;
const value = req.body;
const userName = extractUser(req);
try {
await this.validateUniqueName(name);
const value = await contextSchema.validateAsync(req.body);
await this.eventStore.store({
type: CONTEXT_FIELD_CREATED,
createdBy: userName,
data: value,
});
await this.contextService.createContextField(value, userName);
res.status(201).end();
} catch (error) {
handleErrors(res, this.logger, error);
@ -80,21 +67,15 @@ class ContextController extends Controller {
async updateContextField(req, res) {
const name = req.params.contextField;
const userName = extractUser(req);
const updatedContextField = req.body;
const contextField = req.body;
updatedContextField.name = name;
contextField.name = name;
try {
await this.store.get(name);
const value = await contextSchema.validateAsync(
updatedContextField,
await this.contextService.updateContextField(
contextField,
userName,
);
await this.eventStore.store({
type: CONTEXT_FIELD_UPDATED,
createdBy: userName,
data: value,
});
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
@ -103,40 +84,21 @@ class ContextController extends Controller {
async deleteContextField(req, res) {
const name = req.params.contextField;
const userName = extractUser(req);
try {
await this.store.get(name);
await this.eventStore.store({
type: CONTEXT_FIELD_DELETED,
createdBy: extractUser(req),
data: { name },
});
await this.contextService.deleteContextField(name, userName);
res.status(200).end();
} catch (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) {
const { name } = req.body;
try {
await nameSchema.validateAsync({ name });
await this.validateUniqueName(name);
await this.contextService.validateName(name);
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);

View File

@ -4,6 +4,7 @@ const test = require('ava');
const supertest = require('supertest');
const { EventEmitter } = require('events');
const store = require('../../../test/fixtures/store');
const { createServices } = require('../../services');
const getLogger = require('../../../test/fixtures/no-logger');
const getApp = require('../../app');
@ -12,14 +13,17 @@ const eventBus = new EventEmitter();
function getSetup() {
const base = `/random${Math.round(Math.random() * 1000)}`;
const stores = store.createStores();
const app = getApp({
const config = {
baseUriPath: base,
stores,
eventBus,
extendedPermissions: false,
customContextFields: [{ name: 'tenantId' }],
getLogger,
});
};
const services = createServices(stores, config);
const app = getApp(config, services);
return {
base,

View File

@ -1,7 +1,7 @@
'use strict';
const joi = require('joi');
const { nameType } = require('./util');
const { nameType } = require('../routes/admin-api/util');
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 StrategyService = require('./strategy-service');
const AddonService = require('./addon-service');
const ContextService = require('./context-service');
module.exports.createServices = (stores, config) => {
const featureToggleService = new FeatureToggleService(stores, config);
@ -16,6 +17,7 @@ module.exports.createServices = (stores, config) => {
const tagService = new TagService(stores, config);
const clientMetricsService = new ClientMetricsService(stores, config);
const addonService = new AddonService(stores, config, tagTypeService);
const contextService = new ContextService(stores, config);
return {
addonService,
@ -26,5 +28,6 @@ module.exports.createServices = (stores, config) => {
tagTypeService,
tagService,
clientMetricsService,
contextService,
};
};

View File

@ -36,7 +36,7 @@ function createStrategies(store) {
}
function createContextFields(store) {
return dbState.contextFields.map(c => store._createContextField(c));
return dbState.contextFields.map(c => store.create(c));
}
function createApplications(store) {

View File

@ -3,7 +3,7 @@
const NotFoundError = require('../../lib/error/notfound-error');
module.exports = () => {
const _contextFields = [
let _contextFields = [
{ name: 'environment' },
{ name: 'userId' },
{ name: 'appName' },
@ -19,5 +19,13 @@ module.exports = () => {
return Promise.reject(NotFoundError);
},
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);
},
};
};