unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
159 lines • 6.92 kB
JavaScript
import { contextSchema, legalValueSchema, } from '../../services/context-schema.js';
import { NameExistsError, NotFoundError } from '../../error/index.js';
import { nameSchema } from '../../schema/feature-schema.js';
import { CONTEXT_FIELD_CREATED, CONTEXT_FIELD_UPDATED, CONTEXT_FIELD_DELETED, } from '../../events/index.js';
import ConflictError from '../../error/conflict-error.js';
class ContextService {
constructor({ contextFieldStore, featureStrategiesStore, }, { getLogger, flagResolver, }, eventService, privateProjectChecker) {
this.privateProjectChecker = privateProjectChecker;
this.eventService = eventService;
this.flagResolver = flagResolver;
this.contextFieldStore = contextFieldStore;
this.featureStrategiesStore = featureStrategiesStore;
this.logger = getLogger('services/context-service.js');
}
async getAll() {
return this.contextFieldStore.getAll();
}
async getContextField(name) {
const field = await this.contextFieldStore.get(name);
if (field === undefined) {
throw new NotFoundError(`Could not find context field with name ${name}`);
}
return field;
}
async getStrategiesByContextField(name, userId) {
const strategies = await this.featureStrategiesStore.getStrategiesByContextField(name);
const accessibleProjects = await this.privateProjectChecker.getUserAccessibleProjects(userId);
if (accessibleProjects.mode === 'all') {
return this.mapStrategies(strategies);
}
else {
return this.mapStrategies(strategies.filter((strategy) => accessibleProjects.projects.includes(strategy.projectId)));
}
}
mapStrategies(strategies) {
return {
strategies: strategies.map((strategy) => ({
id: strategy.id,
projectId: strategy.projectId,
featureName: strategy.featureName,
strategyName: strategy.strategyName,
environment: strategy.environment,
})),
};
}
async createContextField(value, auditUser) {
// validations
await this.validateUniqueName(value);
const contextField = await contextSchema.validateAsync(value);
// creations
const createdField = await this.contextFieldStore.create(value);
await this.eventService.storeEvent({
type: CONTEXT_FIELD_CREATED,
createdBy: auditUser.username,
createdByUserId: auditUser.id,
ip: auditUser.ip,
data: contextField,
});
return createdField;
}
async updateContextField(updatedContextField, auditUser) {
const contextField = await this.contextFieldStore.get(updatedContextField.name);
if (contextField === undefined) {
throw new NotFoundError(`Could not find context field with name: ${updatedContextField.name}`);
}
const value = await contextSchema.validateAsync(updatedContextField);
await this.contextFieldStore.update(value);
const { createdAt, sortOrder, ...previousContextField } = contextField;
await this.eventService.storeEvent({
type: CONTEXT_FIELD_UPDATED,
createdBy: auditUser.username,
createdByUserId: auditUser.id,
ip: auditUser.ip,
preData: previousContextField,
data: value,
});
}
async updateLegalValue(contextFieldLegalValue, auditUser) {
const contextField = await this.contextFieldStore.get(contextFieldLegalValue.name);
if (contextField === undefined) {
throw new NotFoundError(`Context field with name ${contextFieldLegalValue.name} was not found`);
}
const validatedLegalValue = await legalValueSchema.validateAsync(contextFieldLegalValue.legalValue);
const legalValues = contextField.legalValues
? [...contextField.legalValues]
: [];
const existingIndex = legalValues.findIndex((legalvalue) => legalvalue.value === validatedLegalValue.value);
if (existingIndex !== -1) {
legalValues[existingIndex] = validatedLegalValue;
}
else {
legalValues.push(validatedLegalValue);
}
const newContextField = { ...contextField, legalValues };
await this.contextFieldStore.update(newContextField);
await this.eventService.storeEvent({
type: CONTEXT_FIELD_UPDATED,
createdBy: auditUser.username,
createdByUserId: auditUser.id,
ip: auditUser.ip,
preData: contextField,
data: newContextField,
});
}
async deleteLegalValue(contextFieldLegalValue, auditUser) {
const contextField = await this.contextFieldStore.get(contextFieldLegalValue.name);
if (contextField === undefined) {
throw new NotFoundError(`Could not find context field with name ${contextFieldLegalValue.name}`);
}
const newContextField = {
...contextField,
legalValues: contextField.legalValues?.filter((legalValue) => legalValue.value !== contextFieldLegalValue.legalValue),
};
await this.contextFieldStore.update(newContextField);
await this.eventService.storeEvent({
type: CONTEXT_FIELD_UPDATED,
createdBy: auditUser.username,
createdByUserId: auditUser.id,
ip: auditUser.ip,
preData: contextField,
data: newContextField,
});
}
async deleteContextField(name, auditUser) {
const contextField = await this.contextFieldStore.get(name);
const strategies = await this.featureStrategiesStore.getStrategiesByContextField(name);
if (strategies.length > 0) {
throw new ConflictError(`This context field is in use by existing flags. To delete it, first remove its usage from all flags.`);
}
// delete
await this.contextFieldStore.delete(name);
await this.eventService.storeEvent({
type: CONTEXT_FIELD_DELETED,
createdBy: auditUser.username,
createdByUserId: auditUser.id,
ip: auditUser.ip,
preData: contextField,
});
}
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 });
}
}
export default ContextService;
//# sourceMappingURL=context-service.js.map