UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

159 lines • 6.92 kB
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