mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
This simplifies stores to just be storage interaction, they no longer react to events. Controllers now call services and awaits the result from the call. When the service calls are returned the database is updated. This simplifies testing dramatically, cause you know that your state is updated when returned from a call, rather than hoping the store has picked up the event (which really was a command) and reacted to it. Events are still emitted from eventStore, so other parts of the app can react to events as they're being sent out. As part of the move to services, we now also emit an application-created event when we see a new client application. Fixes: #685 Fixes: #595
173 lines
5.3 KiB
JavaScript
173 lines
5.3 KiB
JavaScript
const { FEATURE_TAGGED, FEATURE_UNTAGGED } = require('../event-type');
|
|
const { featureSchema, nameSchema } = require('./feature-schema');
|
|
const { tagSchema } = require('./tag-schema');
|
|
const NameExistsError = require('../error/name-exists-error');
|
|
const NotFoundError = require('../error/notfound-error');
|
|
const {
|
|
FEATURE_ARCHIVED,
|
|
FEATURE_CREATED,
|
|
FEATURE_REVIVED,
|
|
FEATURE_UPDATED,
|
|
TAG_CREATED,
|
|
} = require('../event-type');
|
|
|
|
class FeatureToggleService {
|
|
constructor(
|
|
{ featureToggleStore, featureTagStore, tagStore, eventStore },
|
|
{ getLogger },
|
|
) {
|
|
this.featureToggleStore = featureToggleStore;
|
|
this.tagStore = tagStore;
|
|
this.eventStore = eventStore;
|
|
this.featureTagStore = featureTagStore;
|
|
this.logger = getLogger('services/feature-toggle-service.js');
|
|
}
|
|
|
|
async getFeatures() {
|
|
return this.featureToggleStore.getFeatures();
|
|
}
|
|
|
|
async getArchivedFeatures() {
|
|
return this.featureToggleStore.getArchivedFeatures();
|
|
}
|
|
|
|
async addArchivedFeature(feature) {
|
|
await this.featureToggleStore.addArchivedFeature(feature);
|
|
}
|
|
|
|
async getFeature(name) {
|
|
return this.featureToggleStore.getFeature(name);
|
|
}
|
|
|
|
async createFeatureToggle(value, userName) {
|
|
await this.validateName(value);
|
|
const feature = await featureSchema.validateAsync(value);
|
|
await this.featureToggleStore.createFeature(feature);
|
|
await this.eventStore.store({
|
|
type: FEATURE_CREATED,
|
|
createdBy: userName,
|
|
data: feature,
|
|
});
|
|
}
|
|
|
|
async updateToggle(updatedFeature, userName) {
|
|
await this.featureToggleStore.getFeature(updatedFeature.name);
|
|
const value = await featureSchema.validateAsync(updatedFeature);
|
|
await this.featureToggleStore.updateFeature(value);
|
|
await this.eventStore.store({
|
|
type: FEATURE_UPDATED,
|
|
createdBy: userName,
|
|
data: updatedFeature,
|
|
});
|
|
}
|
|
|
|
async archiveToggle(name, userName) {
|
|
await this.featureToggleStore.getFeature(name);
|
|
await this.featureToggleStore.archiveFeature(name);
|
|
await this.eventStore.store({
|
|
type: FEATURE_ARCHIVED,
|
|
createdBy: userName,
|
|
data: { name },
|
|
});
|
|
}
|
|
|
|
async reviveToggle(name, userName) {
|
|
await this.featureToggleStore.reviveFeature({ name });
|
|
await this.eventStore.store({
|
|
type: FEATURE_REVIVED,
|
|
createdBy: userName,
|
|
data: { name },
|
|
});
|
|
}
|
|
|
|
async toggle(featureName, userName) {
|
|
const feature = await this.featureToggleStore.getFeature(featureName);
|
|
const toggle = !feature.enabled;
|
|
return this.updateField(feature.name, 'enabled', toggle, userName);
|
|
}
|
|
|
|
/** Tag releated */
|
|
async listTags(featureName) {
|
|
return this.featureTagStore.getAllTagsForFeature(featureName);
|
|
}
|
|
|
|
async addTag(featureName, tag, userName) {
|
|
await nameSchema.validateAsync({ name: featureName });
|
|
const validatedTag = await tagSchema.validateAsync(tag);
|
|
await this.createTagIfNeeded(validatedTag, userName);
|
|
await this.featureTagStore.tagFeature(featureName, validatedTag);
|
|
await this.eventStore.store({
|
|
type: FEATURE_TAGGED,
|
|
createdBy: userName,
|
|
data: {
|
|
featureName,
|
|
tag: validatedTag,
|
|
},
|
|
});
|
|
return validatedTag;
|
|
}
|
|
|
|
async createTagIfNeeded(tag, userName) {
|
|
try {
|
|
await this.tagStore.getTag(tag.type, tag.value);
|
|
} catch (error) {
|
|
if (error instanceof NotFoundError) {
|
|
await this.tagStore.createTag(tag);
|
|
await this.eventStore.store({
|
|
type: TAG_CREATED,
|
|
createdBy: userName,
|
|
data: {
|
|
tag,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
async removeTag(featureName, tag, userName) {
|
|
await this.featureTagStore.untagFeature(featureName, tag);
|
|
await this.eventStore.store({
|
|
type: FEATURE_UNTAGGED,
|
|
createdBy: userName,
|
|
data: {
|
|
featureName,
|
|
tag,
|
|
},
|
|
});
|
|
}
|
|
|
|
/** Validations */
|
|
async validateName({ name }) {
|
|
await nameSchema.validateAsync({ name });
|
|
await this.validateUniqueFeatureName(name);
|
|
return name;
|
|
}
|
|
|
|
async validateUniqueFeatureName(name) {
|
|
let msg;
|
|
try {
|
|
const feature = await this.featureToggleStore.hasFeature(name);
|
|
msg = feature.archived
|
|
? 'An archived toggle with that name already exists'
|
|
: 'A toggle with that name already exists';
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
throw new NameExistsError(msg);
|
|
}
|
|
|
|
async updateField(featureName, field, value, userName) {
|
|
const feature = await this.featureToggleStore.getFeature(featureName);
|
|
feature[field] = value;
|
|
await this.featureToggleStore.updateFeature(feature);
|
|
await this.eventStore.store({
|
|
type: FEATURE_UPDATED,
|
|
createdBy: userName,
|
|
data: feature,
|
|
});
|
|
return feature;
|
|
}
|
|
}
|
|
|
|
module.exports = FeatureToggleService;
|