1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-04 11:17:02 +02:00

Complete open api schemas for project features controller (#1563)

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* bug fix

* bug fix

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* added emptyResponse, patch feature operation schemas and request

* added emptyResponse, patch feature operation schemas and request

* patch strategy

* patch strategy

* update strategy

* update strategy

* fix pr comment

* fix pr comments

* improvements

* added operationId to schema for better generation

* fix pr comment

* fix pr comment

* fix pr comment

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* Update response types to use inferred types

* Update addTag response status to 201

* refactor: move schema ref destructuring into createSchemaObject

* made serialize date handle deep objects

* made serialize date handle deep objects

* add `name` to IFeatureStrategy nad fix tests

* fix pr comments

* fix pr comments

* Add types to IAuthRequest

* Sync StrategySchema for FE and BE - into the rabbit hole

* Sync model with OAS spec

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* bug fix

* bug fix

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* added emptyResponse, patch feature operation schemas and request

* added emptyResponse, patch feature operation schemas and request

* patch strategy

* patch strategy

* update strategy

* update strategy

* fix pr comment

* fix pr comments

* improvements

* added operationId to schema for better generation

* fix pr comment

* fix pr comment

* fix pr comment

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* Update response types to use inferred types

* Update addTag response status to 201

* refactor: move schema ref destructuring into createSchemaObject

* made serialize date handle deep objects

* made serialize date handle deep objects

* add `name` to IFeatureStrategy nad fix tests

* fix pr comments

* fix pr comments

* Add types to IAuthRequest

* Sync StrategySchema for FE and BE - into the rabbit hole

* Sync model with OAS spec

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* bug fix

* bug fix

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* added emptyResponse, patch feature operation schemas and request

* added emptyResponse, patch feature operation schemas and request

* patch strategy

* patch strategy

* update strategy

* update strategy

* fix pr comment

* fix pr comments

* improvements

* added operationId to schema for better generation

* fix pr comment

* fix pr comment

* fix pr comment

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* Update response types to use inferred types

* Update addTag response status to 201

* refactor: move schema ref destructuring into createSchemaObject

* made serialize date handle deep objects

* made serialize date handle deep objects

* add `name` to IFeatureStrategy nad fix tests

* fix pr comments

* fix pr comments

* Add types to IAuthRequest

* Sync StrategySchema for FE and BE - into the rabbit hole

* Sync model with OAS spec

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* Completed OpenAPI Schemas for ProjectFeatures Controller
Completed OpenAPI Schemas for Feature Controller (tags)

* bug fix

* bug fix

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* fix merge conflicts, some refactoring

* added emptyResponse, patch feature operation schemas and request

* added emptyResponse, patch feature operation schemas and request

* patch strategy

* patch strategy

* update strategy

* update strategy

* fix pr comment

* fix pr comments

* improvements

* added operationId to schema for better generation

* fix pr comment

* fix pr comment

* fix pr comment

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* improvements to generated and dynamic types

* Update response types to use inferred types

* Update addTag response status to 201

* refactor: move schema ref destructuring into createSchemaObject

* made serialize date handle deep objects

* made serialize date handle deep objects

* add `name` to IFeatureStrategy nad fix tests

* fix pr comments

* fix pr comments

* Add types to IAuthRequest

* Sync StrategySchema for FE and BE - into the rabbit hole

* Sync model with OAS spec

* revert

* revert

* revert

* revert

* revert

* mapper

* revert

* revert

* revert

* remove serialize-dates.ts

* remove serialize-dates.ts

* remove serialize-dates.ts

* remove serialize-dates.ts

* remove serialize-dates.ts

* revert

* revert

* add mappers

* add mappers

* fix pr comments

* ignore report.json

* ignore report.json

* Route permission required

Co-authored-by: olav <mail@olav.io>
This commit is contained in:
andreas-unleash 2022-05-18 16:17:09 +03:00 committed by GitHub
parent 7d1a5c2012
commit 1a27bffe4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1916 additions and 539 deletions

1
.gitignore vendored
View File

@ -54,3 +54,4 @@ package-lock.json
/website/i18n/* /website/i18n/*
.env .env
report.json

View File

@ -282,7 +282,9 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
env.strategies = []; env.strategies = [];
} }
if (r.strategy_id) { if (r.strategy_id) {
env.strategies.push(this.getAdminStrategy(r)); env.strategies.push(
FeatureStrategiesStore.getAdminStrategy(r),
);
} }
acc.environments[r.environment] = env; acc.environments[r.environment] = env;
return acc; return acc;
@ -310,7 +312,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private getEnvironment(r: any): IEnvironmentOverview { private static getEnvironment(r: any): IEnvironmentOverview {
return { return {
name: r.environment, name: r.environment,
enabled: r.enabled, enabled: r.enabled,
@ -350,7 +352,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
const overview = rows.reduce((acc, r) => { const overview = rows.reduce((acc, r) => {
if (acc[r.feature_name] !== undefined) { if (acc[r.feature_name] !== undefined) {
acc[r.feature_name].environments.push( acc[r.feature_name].environments.push(
this.getEnvironment(r), FeatureStrategiesStore.getEnvironment(r),
); );
} else { } else {
acc[r.feature_name] = { acc[r.feature_name] = {
@ -359,7 +361,9 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
createdAt: r.created_at, createdAt: r.created_at,
lastSeenAt: r.last_seen_at, lastSeenAt: r.last_seen_at,
stale: r.stale, stale: r.stale,
environments: [this.getEnvironment(r)], environments: [
FeatureStrategiesStore.getEnvironment(r),
],
}; };
} }
return acc; return acc;
@ -399,7 +403,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
return mapRow(row[0]); return mapRow(row[0]);
} }
private getAdminStrategy( private static getAdminStrategy(
r: any, r: any,
includeId: boolean = true, includeId: boolean = true,
): IStrategyConfig { ): IStrategyConfig {

View File

@ -10,8 +10,8 @@ import {
import { IFeatureToggleClientStore } from '../types/stores/feature-toggle-client-store'; import { IFeatureToggleClientStore } from '../types/stores/feature-toggle-client-store';
import { DEFAULT_ENV } from '../util/constants'; import { DEFAULT_ENV } from '../util/constants';
import { PartialDeep } from '../types/partial'; import { PartialDeep } from '../types/partial';
import { EventEmitter } from 'stream';
import { IExperimentalOptions } from '../experimental'; import { IExperimentalOptions } from '../experimental';
import EventEmitter from 'events';
export interface FeaturesTable { export interface FeaturesTable {
name: string; name: string;
@ -149,7 +149,9 @@ export default class FeatureToggleClientStore
strategies: [], strategies: [],
}; };
if (this.isUnseenStrategyRow(feature, r)) { if (this.isUnseenStrategyRow(feature, r)) {
feature.strategies.push(this.rowToStrategy(r)); feature.strategies.push(
FeatureToggleClientStore.rowToStrategy(r),
);
} }
if (inlineSegmentConstraints && r.segment_id) { if (inlineSegmentConstraints && r.segment_id) {
this.addSegmentToStrategy(feature, r); this.addSegmentToStrategy(feature, r);
@ -176,13 +178,13 @@ export default class FeatureToggleClientStore
if (!isAdmin) { if (!isAdmin) {
// We should not send strategy IDs from the client API, // We should not send strategy IDs from the client API,
// as this breaks old versions of the Go SDK (at least). // as this breaks old versions of the Go SDK (at least).
this.removeIdsFromStrategies(features); FeatureToggleClientStore.removeIdsFromStrategies(features);
} }
return features; return features;
} }
private rowToStrategy(row: Record<string, any>): IStrategyConfig { private static rowToStrategy(row: Record<string, any>): IStrategyConfig {
return { return {
id: row.strategy_id, id: row.strategy_id,
name: row.strategy_name, name: row.strategy_name,
@ -191,7 +193,7 @@ export default class FeatureToggleClientStore
}; };
} }
private removeIdsFromStrategies(features: IFeatureToggleClient[]) { private static removeIdsFromStrategies(features: IFeatureToggleClient[]) {
features.forEach((feature) => { features.forEach((feature) => {
feature.strategies.forEach((strategy) => { feature.strategies.forEach((strategy) => {
delete strategy.id; delete strategy.id;

View File

@ -1,13 +1,22 @@
import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIV3 } from 'openapi-types';
import { constraintSchema } from './spec/constraint-schema';
import { createFeatureSchema } from './spec/create-feature-schema';
import { createStrategySchema } from './spec/create-strategy-schema';
import { featureSchema } from './spec/feature-schema';
import { featuresSchema } from './spec/features-schema'; import { featuresSchema } from './spec/features-schema';
import { overrideSchema } from './spec/override-schema'; import { overrideSchema } from './spec/override-schema';
import { parametersSchema } from './spec/parameters-schema';
import { strategySchema } from './spec/strategy-schema'; import { strategySchema } from './spec/strategy-schema';
import { variantSchema } from './spec/variant-schema'; import { variantSchema } from './spec/variant-schema';
import { createFeatureSchema } from './spec/create-feature-schema';
import { constraintSchema } from './spec/constraint-schema';
import { tagSchema } from './spec/tag-schema';
import { tagsResponseSchema } from './spec/tags-response-schema';
import { createStrategySchema } from './spec/create-strategy-schema';
import { featureSchema } from './spec/feature-schema';
import { parametersSchema } from './spec/parameters-schema';
import { featureEnvironmentInfoSchema } from './spec/feature-environment-info-schema';
import { emptyResponseSchema } from './spec/empty-response-schema';
import { patchOperationSchema } from './spec/patch-operation-schema';
import { updateFeatureSchema } from './spec/updateFeatureSchema';
import { updateStrategySchema } from './spec/update-strategy-schema';
import { cloneFeatureSchema } from './spec/clone-feature-schema';
import { featureStrategySchema } from './spec/feature-strategy-schema';
export const createOpenApiSchema = ( export const createOpenApiSchema = (
serverUrl?: string, serverUrl?: string,
@ -34,14 +43,23 @@ export const createOpenApiSchema = (
}, },
schemas: { schemas: {
constraintSchema, constraintSchema,
cloneFeatureSchema,
createFeatureSchema, createFeatureSchema,
createStrategySchema, createStrategySchema,
featureSchema, featureSchema,
featuresSchema, featuresSchema,
featureEnvironmentInfoSchema,
featureStrategySchema,
emptyResponseSchema,
overrideSchema, overrideSchema,
parametersSchema, parametersSchema,
patchOperationSchema,
strategySchema, strategySchema,
updateStrategySchema,
updateFeatureSchema,
variantSchema, variantSchema,
tagSchema,
tagsResponseSchema,
}, },
}, },
}; };

View File

@ -0,0 +1,29 @@
import { SchemaMapper } from './mapper';
import { IFeatureEnvironmentInfo } from '../../types/model';
import { FeatureEnvironmentInfoSchema } from '../spec/feature-environment-info-schema';
import { FeatureStrategyMapper } from './feature-strategy.mapper';
export class EnvironmentInfoMapper
implements
SchemaMapper<
FeatureEnvironmentInfoSchema,
IFeatureEnvironmentInfo,
Partial<FeatureEnvironmentInfoSchema>
>
{
private mapper = new FeatureStrategyMapper();
fromPublic(input: FeatureEnvironmentInfoSchema): IFeatureEnvironmentInfo {
return {
...input,
strategies: input.strategies.map(this.mapper.fromPublic),
};
}
toPublic(input: IFeatureEnvironmentInfo): FeatureEnvironmentInfoSchema {
return {
...input,
strategies: input.strategies.map(this.mapper.toPublic),
};
}
}

View File

@ -0,0 +1,29 @@
import { SchemaMapper } from './mapper';
import { IFeatureStrategy } from '../../types/model';
import { CreateStrategySchema } from '../spec/create-strategy-schema';
import { UpdateStrategySchema } from '../spec/update-strategy-schema';
import { FeatureStrategySchema } from '../spec/feature-strategy-schema';
export class FeatureStrategyMapper
implements
SchemaMapper<
FeatureStrategySchema,
IFeatureStrategy,
CreateStrategySchema | UpdateStrategySchema
>
{
fromPublic(input: FeatureStrategySchema): IFeatureStrategy {
return {
...input,
id: input.id || '',
projectId: input.projectId! || '',
};
}
toPublic(input: IFeatureStrategy): FeatureStrategySchema {
return {
...input,
name: input.strategyName,
};
}
}

View File

@ -0,0 +1,3 @@
export * from './environment-info.mapper';
export * from './feature-strategy.mapper';
export * from './strategy.mapper';

View File

@ -0,0 +1,7 @@
// Convert between public schema types and internal data types.
// Avoids coupling public schemas to internal implementation details.
export interface SchemaMapper<SCHEMA, INTERNAL, INPUT = Partial<SCHEMA>> {
fromPublic(input: SCHEMA): INTERNAL;
toPublic(input: INTERNAL): SCHEMA;
mapInput?(input: INPUT): INTERNAL;
}

View File

@ -0,0 +1,33 @@
import { SchemaMapper } from './mapper';
import { IStrategyConfig } from '../../types/model';
import { StrategySchema } from '../spec/strategy-schema';
import { CreateStrategySchema } from '../spec/create-strategy-schema';
import { UpdateStrategySchema } from '../spec/update-strategy-schema';
export class StrategyMapper
implements
SchemaMapper<
StrategySchema,
IStrategyConfig,
CreateStrategySchema | UpdateStrategySchema
>
{
fromPublic(input: StrategySchema): IStrategyConfig {
return input;
}
toPublic(input: IStrategyConfig): StrategySchema {
return input;
}
mapInput(
input: CreateStrategySchema | UpdateStrategySchema,
): IStrategyConfig {
return {
...input,
name: input.name || '',
parameters: input.parameters || {},
constraints: input.constraints || [],
};
}
}

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const cloneFeatureRequest: OpenAPIV3.RequestBodyObject = {
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/cloneFeatureSchema',
},
},
},
};

View File

@ -0,0 +1,19 @@
import { createSchemaObject, CreateSchemaType } from '../types';
const schema = {
type: 'object',
required: ['name', 'replaceGroupId'],
properties: {
name: {
type: 'string',
},
replaceGroupId: {
type: 'boolean',
},
},
'components/schemas': {},
} as const;
export type CloneFeatureSchema = CreateSchemaType<typeof schema>;
export const cloneFeatureSchema = createSchemaObject(schema);

View File

@ -1,4 +1,5 @@
import { createSchemaObject, CreateSchemaType } from '../types'; import { createSchemaObject, CreateSchemaType } from '../types';
import { ALL_OPERATORS } from '../../util/constants';
const schema = { const schema = {
type: 'object', type: 'object',
@ -10,6 +11,7 @@ const schema = {
}, },
operator: { operator: {
type: 'string', type: 'string',
enum: ALL_OPERATORS,
}, },
caseInsensitive: { caseInsensitive: {
type: 'boolean', type: 'boolean',
@ -27,6 +29,7 @@ const schema = {
type: 'string', type: 'string',
}, },
}, },
'components/schemas': {},
} as const; } as const;
export type ConstraintSchema = CreateSchemaType<typeof schema>; export type ConstraintSchema = CreateSchemaType<typeof schema>;

View File

@ -17,6 +17,7 @@ const schema = {
type: 'boolean', type: 'boolean',
}, },
}, },
'components/schemas': {},
} as const; } as const;
export type CreateFeatureSchema = CreateSchemaType<typeof schema>; export type CreateFeatureSchema = CreateSchemaType<typeof schema>;

View File

@ -14,9 +14,15 @@ const schema = {
}, },
constraints: { constraints: {
type: 'array', type: 'array',
items: constraintSchema, items: { $ref: '#/components/schemas/constraintSchema' },
}, },
parameters: parametersSchema, parameters: {
$ref: '#/components/schemas/parametersSchema',
},
},
'components/schemas': {
constraintSchema,
parametersSchema,
}, },
} as const; } as const;

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const createTagRequest: OpenAPIV3.RequestBodyObject = {
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/tagSchema',
},
},
},
};

View File

@ -0,0 +1,11 @@
import { createSchemaObject, CreateSchemaType } from '../types';
const schema = {
type: 'object',
description: 'OK',
'components/schemas': {},
} as const;
export type EmptyResponseSchema = CreateSchemaType<typeof schema>;
export const emptyResponseSchema = createSchemaObject(schema);

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const emptyResponse: OpenAPIV3.ResponseObject = {
description: 'emptyResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/emptyResponseSchema',
},
},
},
};

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const featureEnvironmentInfoResponse: OpenAPIV3.ResponseObject = {
description: 'featureEnvironmentInfoResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/featureEnvironmentInfoSchema',
},
},
},
};

View File

@ -0,0 +1,35 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { featureStrategySchema } from './feature-strategy-schema';
let schema = {
type: 'object',
additionalProperties: false,
required: ['name', 'environment', 'enabled', 'strategies'],
properties: {
name: {
type: 'string',
},
environment: {
type: 'string',
},
type: {
type: 'string',
},
enabled: {
type: 'boolean',
},
strategies: {
type: 'array',
items: {
$ref: '#/components/schemas/featureStrategySchema',
},
},
},
'components/schemas': {
featureStrategySchema,
},
} as const;
export type FeatureEnvironmentInfoSchema = CreateSchemaType<typeof schema>;
export const featureEnvironmentInfoSchema = createSchemaObject(schema);

View File

@ -1,6 +1,7 @@
import { createSchemaObject, CreateSchemaType } from '../types'; import { createSchemaObject, CreateSchemaType } from '../types';
import { strategySchema } from './strategy-schema'; import { strategySchema } from './strategy-schema';
import { variantSchema } from './variant-schema'; import { variantSchema } from './variant-schema';
import { featureEnvironmentInfoSchema } from './feature-environment-info-schema';
const schema = { const schema = {
type: 'object', type: 'object',
@ -16,6 +17,9 @@ const schema = {
description: { description: {
type: 'string', type: 'string',
}, },
archived: {
type: 'boolean',
},
project: { project: {
type: 'string', type: 'string',
}, },
@ -38,15 +42,28 @@ const schema = {
format: 'date', format: 'date',
nullable: true, nullable: true,
}, },
environments: {
type: 'array',
items: {
$ref: '#/components/schemas/featureEnvironmentInfoSchema',
},
},
strategies: { strategies: {
type: 'array', type: 'array',
items: strategySchema, items: { $ref: '#/components/schemas/strategySchema' },
}, },
variants: { variants: {
items: variantSchema,
type: 'array', type: 'array',
items: {
$ref: '#/components/schemas/variantSchema',
},
}, },
}, },
'components/schemas': {
featureEnvironmentInfoSchema,
strategySchema,
variantSchema,
},
} as const; } as const;
export type FeatureSchema = CreateSchemaType<typeof schema>; export type FeatureSchema = CreateSchemaType<typeof schema>;

View File

@ -0,0 +1,58 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { constraintSchema } from './constraint-schema';
import { parametersSchema } from './parameters-schema';
export const schema = {
type: 'object',
additionalProperties: false,
required: [
'id',
'name',
'featureName',
'strategyName',
'constraints',
'parameters',
'environment',
],
properties: {
id: {
type: 'string',
},
name: {
type: 'string',
},
createdAt: {
type: 'string',
format: 'date',
nullable: true,
},
featureName: {
type: 'string',
},
projectId: {
type: 'string',
},
environment: {
type: 'string',
},
strategyName: {
type: 'string',
},
sortOrder: {
type: 'number',
},
constraints: {
type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' },
},
parameters: { $ref: '#/components/schemas/parametersSchema' },
},
'components/schemas': {
constraintSchema,
parametersSchema,
},
} as const;
export type FeatureStrategySchema = CreateSchemaType<typeof schema>;
export const featureStrategySchema = createSchemaObject(schema);

View File

@ -11,9 +11,12 @@ const schema = {
}, },
features: { features: {
type: 'array', type: 'array',
items: featureSchema, items: { $ref: '#/components/schemas/featureSchema' },
}, },
}, },
'components/schemas': {
featureSchema: { schema: featureSchema },
},
} as const; } as const;
export type FeaturesSchema = CreateSchemaType<typeof schema>; export type FeaturesSchema = CreateSchemaType<typeof schema>;

View File

@ -15,6 +15,7 @@ const schema = {
}, },
}, },
}, },
'components/schemas': {},
} as const; } as const;
export type OverrideSchema = CreateSchemaType<typeof schema>; export type OverrideSchema = CreateSchemaType<typeof schema>;

View File

@ -6,6 +6,7 @@ const schema = {
type: 'string', type: 'string',
maxLength: 100, maxLength: 100,
}, },
'components/schemas': {},
} as const; } as const;
export type ParametersSchema = CreateSchemaType<typeof schema>; export type ParametersSchema = CreateSchemaType<typeof schema>;

View File

@ -0,0 +1,24 @@
import { createSchemaObject, CreateSchemaType } from '../types';
const schema = {
type: 'object',
required: ['path', 'op'],
properties: {
path: {
type: 'string',
},
op: {
type: 'string',
enum: ['add', 'remove', 'replace', 'copy', 'move'],
},
from: {
type: 'string',
},
value: {},
},
'components/schemas': {},
} as const;
export type PatchOperationSchema = CreateSchemaType<typeof schema>;
export const patchOperationSchema = createSchemaObject(schema);

View File

@ -0,0 +1,15 @@
import { OpenAPIV3 } from 'openapi-types';
export const patchRequest: OpenAPIV3.RequestBodyObject = {
required: true,
content: {
'application/json': {
schema: {
type: 'array',
items: {
$ref: '#/components/schemas/patchOperationSchema',
},
},
},
},
};

View File

@ -0,0 +1,15 @@
import { OpenAPIV3 } from 'openapi-types';
export const strategiesResponse: OpenAPIV3.ResponseObject = {
description: 'strategiesResponse',
content: {
'application/json': {
schema: {
type: 'array',
items: {
$ref: '#/components/schemas/strategySchema',
},
},
},
},
};

View File

@ -5,7 +5,7 @@ export const strategyResponse: OpenAPIV3.ResponseObject = {
content: { content: {
'application/json': { 'application/json': {
schema: { schema: {
$ref: '#/components/schemas/strategySchema', $ref: '#/components/schemas/featureStrategySchema',
}, },
}, },
}, },

View File

@ -2,10 +2,10 @@ import { createSchemaObject, CreateSchemaType } from '../types';
import { constraintSchema } from './constraint-schema'; import { constraintSchema } from './constraint-schema';
import { parametersSchema } from './parameters-schema'; import { parametersSchema } from './parameters-schema';
const schema = { export const strategySchemaDefinition = {
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
required: ['id', 'name', 'constraints', 'parameters'], required: ['name', 'constraints', 'parameters'],
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
@ -13,14 +13,21 @@ const schema = {
name: { name: {
type: 'string', type: 'string',
}, },
sortOrder: {
type: 'number',
},
constraints: { constraints: {
type: 'array', type: 'array',
items: constraintSchema, items: { $ref: '#/components/schemas/constraintSchema' },
}, },
parameters: parametersSchema, parameters: { $ref: '#/components/schemas/parametersSchema' },
},
'components/schemas': {
constraintSchema,
parametersSchema,
}, },
} as const; } as const;
export type StrategySchema = CreateSchemaType<typeof schema>; export type StrategySchema = CreateSchemaType<typeof strategySchemaDefinition>;
export const strategySchema = createSchemaObject(schema); export const strategySchema = createSchemaObject(strategySchemaDefinition);

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const tagResponse: OpenAPIV3.ResponseObject = {
description: 'tagResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/tagSchema',
},
},
},
};

View File

@ -0,0 +1,20 @@
import { createSchemaObject, CreateSchemaType } from '../types';
const schema = {
type: 'object',
additionalProperties: false,
required: ['value', 'type'],
properties: {
value: {
type: 'string',
},
type: {
type: 'string',
},
},
'components/schemas': {},
} as const;
export type TagSchema = CreateSchemaType<typeof schema>;
export const tagSchema = createSchemaObject(schema);

View File

@ -0,0 +1,26 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { tagSchema } from './tag-schema';
const schema = {
type: 'object',
additionalProperties: false,
required: ['version', 'tags'],
properties: {
version: {
type: 'integer',
},
tags: {
type: 'array',
items: {
$ref: '#/components/schemas/tagSchema',
},
},
},
'components/schemas': {
tagSchema,
},
} as const;
export type TagsResponseSchema = CreateSchemaType<typeof schema>;
export const tagsResponseSchema = createSchemaObject(schema);

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const tagsResponse: OpenAPIV3.ResponseObject = {
description: 'tagsResponse',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/tagsResponseSchema',
},
},
},
};

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const updateFeatureRequest: OpenAPIV3.RequestBodyObject = {
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/updateFeatureSchema',
},
},
},
};

View File

@ -0,0 +1,12 @@
import { OpenAPIV3 } from 'openapi-types';
export const updateStrategyRequest: OpenAPIV3.RequestBodyObject = {
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/updateStrategySchema',
},
},
},
};

View File

@ -0,0 +1,11 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { strategySchemaDefinition } from './strategy-schema';
const schema = {
...strategySchemaDefinition,
required: [],
} as const;
export type UpdateStrategySchema = CreateSchemaType<typeof schema>;
export const updateStrategySchema = createSchemaObject(schema);

View File

@ -0,0 +1,42 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { constraintSchema } from './constraint-schema';
const schema = {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
},
description: {
type: 'string',
},
type: {
type: 'string',
},
stale: {
type: 'boolean',
},
archived: {
type: 'boolean',
},
createdAt: {
type: 'string',
format: 'date',
},
impressionData: {
type: 'boolean',
},
constraints: {
type: 'array',
items: { $ref: '#/components/schemas/constraintSchema' },
},
},
'components/schemas': {
constraintSchema,
},
} as const;
export type UpdateFeatureSchema = CreateSchemaType<typeof schema>;
export const updateFeatureSchema = createSchemaObject(schema);

View File

@ -20,12 +20,24 @@ const schema = {
}, },
payload: { payload: {
type: 'object', type: 'object',
required: ['type', 'value'],
properties: {
type: {
type: 'string',
},
value: {
type: 'string',
},
},
}, },
overrides: { overrides: {
type: 'array', type: 'array',
items: overrideSchema, items: { $ref: '#/components/schemas/overrideSchema' },
}, },
}, },
'components/schemas': {
overrideSchema,
},
} as const; } as const;
export type VariantSchema = CreateSchemaType<typeof schema>; export type VariantSchema = CreateSchemaType<typeof schema>;

View File

@ -15,7 +15,31 @@ export interface ClientApiOperation
} }
// Create a type from a const schema object. // Create a type from a const schema object.
export type CreateSchemaType<T> = FromSchema<T>; export type CreateSchemaType<T> = FromSchema<
T,
{
definitionsPath: 'components/schemas';
deserialize: [
{
pattern: {
type: 'string';
format: 'date';
};
output: Date;
},
];
}
>;
// Create an OpenAPIV3.SchemaObject from a const schema object. // Create an OpenAPIV3.SchemaObject from a const schema object.
export const createSchemaObject = <T>(schema: T): DeepMutable<T> => schema; // Make sure the schema contains an object of refs for type generation.
// Pass an empty 'components/schemas' object if there are no refs in the schema.
// Note: The order of the refs must match the order they are present in the object
export const createSchemaObject = <
T extends { 'components/schemas': { [key: string]: object } },
>(
schema: T,
): DeepMutable<Omit<T, 'components/schemas'>> => {
const { 'components/schemas': schemas, ...rest } = schema;
return rest;
};

View File

@ -1,17 +1,16 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import Controller from '../controller'; import Controller from '../controller';
import { extractUsername } from '../../util/extract-user'; import { extractUsername } from '../../util/extract-user';
import { DELETE_FEATURE, UPDATE_FEATURE } from '../../types/permissions'; import { DELETE_FEATURE, NONE, UPDATE_FEATURE } from '../../types/permissions';
import FeatureToggleService from '../../services/feature-toggle-service'; import FeatureToggleService from '../../services/feature-toggle-service';
import { IAuthRequest } from '../unleash-types'; import { IAuthRequest } from '../unleash-types';
import { featuresResponse } from '../../openapi/spec/features-response'; import { featuresResponse } from '../../openapi/spec/features-response';
import { FeaturesSchema } from '../../openapi/spec/features-schema'; import { FeaturesSchema } from '../../openapi/spec/features-schema';
import { serializeDates } from '../../util/serialize-dates';
export default class ArchiveController extends Controller { export default class ArchiveController extends Controller {
private readonly logger: Logger; private readonly logger: Logger;
@ -34,6 +33,7 @@ export default class ArchiveController extends Controller {
path: '/features', path: '/features',
acceptAnyContentType: true, acceptAnyContentType: true,
handler: this.getArchivedFeatures, handler: this.getArchivedFeatures,
permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
@ -48,6 +48,7 @@ export default class ArchiveController extends Controller {
path: '/features/:projectId', path: '/features/:projectId',
acceptAnyContentType: true, acceptAnyContentType: true,
handler: this.getArchivedFeaturesByProjectId, handler: this.getArchivedFeaturesByProjectId,
permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
@ -75,7 +76,7 @@ export default class ArchiveController extends Controller {
res.json({ res.json({
version: 2, version: 2,
features: features.map(serializeDates), features: features,
}); });
} }
@ -91,7 +92,7 @@ export default class ArchiveController extends Controller {
); );
res.json({ res.json({
version: 2, version: 2,
features: features.map(serializeDates), features: features,
}); });
} }

View File

@ -5,13 +5,13 @@ import Controller from '../controller';
import { extractUsername } from '../../util/extract-user'; import { extractUsername } from '../../util/extract-user';
import { import {
UPDATE_FEATURE,
DELETE_FEATURE,
CREATE_FEATURE, CREATE_FEATURE,
DELETE_FEATURE,
NONE, NONE,
UPDATE_FEATURE,
} from '../../types/permissions'; } from '../../types/permissions';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types';
import FeatureToggleService from '../../services/feature-toggle-service'; import FeatureToggleService from '../../services/feature-toggle-service';
import { featureSchema, querySchema } from '../../schema/feature-schema'; import { featureSchema, querySchema } from '../../schema/feature-schema';
import { IFeatureToggleQuery } from '../../types/model'; import { IFeatureToggleQuery } from '../../types/model';
@ -20,7 +20,12 @@ import { IAuthRequest } from '../unleash-types';
import { DEFAULT_ENV } from '../../util/constants'; import { DEFAULT_ENV } from '../../util/constants';
import { featuresResponse } from '../../openapi/spec/features-response'; import { featuresResponse } from '../../openapi/spec/features-response';
import { FeaturesSchema } from '../../openapi/spec/features-schema'; import { FeaturesSchema } from '../../openapi/spec/features-schema';
import { serializeDates } from '../../util/serialize-dates'; import { tagsResponse } from '../../openapi/spec/tags-response';
import { tagResponse } from '../../openapi/spec/tag-response';
import { createTagRequest } from '../../openapi/spec/create-tag-request';
import { emptyResponse } from '../../openapi/spec/empty-response';
import { TagSchema } from '../../openapi/spec/tag-schema';
import { TagsResponseSchema } from '../../openapi/spec/tags-response-schema';
const version = 1; const version = 1;
@ -66,23 +71,75 @@ class FeatureController extends Controller {
path: '', path: '',
acceptAnyContentType: true, acceptAnyContentType: true,
handler: this.getAllToggles, handler: this.getAllToggles,
permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'getAllToggles',
responses: { 200: featuresResponse }, responses: { 200: featuresResponse },
deprecated: true, deprecated: true,
}), }),
], ],
}); });
this.post('/validate', this.validate, NONE); this.route({
this.get('/:featureName/tags', this.listTags); method: 'post',
this.post('/:featureName/tags', this.addTag, UPDATE_FEATURE); path: '/validate',
this.delete( handler: this.validate,
'/:featureName/tags/:type/:value', permission: NONE,
this.removeTag, middleware: [
UPDATE_FEATURE, openApiService.validPath({
); tags: ['admin'],
operationId: 'validateFeature',
responses: { 200: emptyResponse },
}),
],
});
this.route({
method: 'get',
path: '/:featureName/tags',
handler: this.listTags,
acceptAnyContentType: true,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'listTags',
responses: { 200: tagsResponse },
}),
],
});
this.route({
method: 'post',
path: '/:featureName/tags',
permission: UPDATE_FEATURE,
handler: this.addTag,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'addTag',
requestBody: createTagRequest,
responses: { 201: tagResponse },
}),
],
});
this.route({
method: 'delete',
path: '/:featureName/tags/:type/:value',
permission: UPDATE_FEATURE,
acceptAnyContentType: true,
handler: this.removeTag,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'removeTag',
responses: { 200: emptyResponse },
}),
],
});
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@ -120,10 +177,9 @@ class FeatureController extends Controller {
): Promise<void> { ): Promise<void> {
const query = await this.prepQuery(req.query); const query = await this.prepQuery(req.query);
const features = await this.service.getFeatureToggles(query); const features = await this.service.getFeatureToggles(query);
res.json({ res.json({
version, version,
features: features.map(serializeDates), features: features,
}); });
} }
@ -136,12 +192,23 @@ class FeatureController extends Controller {
res.json(feature).end(); res.json(feature).end();
} }
async listTags(req: Request, res: Response): Promise<void> { async listTags(
req: Request<{ featureName: string }, any, any, any>,
res: Response<TagsResponseSchema>,
): Promise<void> {
const tags = await this.tagService.listTags(req.params.featureName); const tags = await this.tagService.listTags(req.params.featureName);
res.json({ version, tags }); res.json({ version, tags });
} }
async addTag(req: IAuthRequest, res: Response): Promise<void> { async addTag(
req: IAuthRequest<
{ featureName: string },
Response<TagSchema>,
TagSchema,
any
>,
res: Response<TagSchema>,
): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
const tag = await this.tagService.addTag( const tag = await this.tagService.addTag(
@ -153,14 +220,20 @@ class FeatureController extends Controller {
} }
// TODO // TODO
async removeTag(req: IAuthRequest, res: Response): Promise<void> { async removeTag(
req: IAuthRequest<{ featureName: string; type: string; value: string }>,
res: Response<void>,
): Promise<void> {
const { featureName, type, value } = req.params; const { featureName, type, value } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
await this.tagService.removeTag(featureName, { type, value }, userName); await this.tagService.removeTag(featureName, { type, value }, userName);
res.status(200).end(); res.status(200).end();
} }
async validate(req: Request, res: Response): Promise<void> { async validate(
req: Request<any, any, { name: string }, any>,
res: Response<void>,
): Promise<void> {
const { name } = req.body; const { name } = req.body;
await this.service.validateName(name); await this.service.validateName(name);

View File

@ -2,7 +2,7 @@ import { Request, Response } from 'express';
import { applyPatch, Operation } from 'fast-json-patch'; import { applyPatch, Operation } from 'fast-json-patch';
import Controller from '../../controller'; import Controller from '../../controller';
import { IUnleashConfig } from '../../../types/option'; import { IUnleashConfig } from '../../../types/option';
import { IUnleashServices } from '../../../types/services'; import { IUnleashServices } from '../../../types';
import FeatureToggleService from '../../../services/feature-toggle-service'; import FeatureToggleService from '../../../services/feature-toggle-service';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import { import {
@ -10,23 +10,38 @@ import {
CREATE_FEATURE_STRATEGY, CREATE_FEATURE_STRATEGY,
DELETE_FEATURE, DELETE_FEATURE,
DELETE_FEATURE_STRATEGY, DELETE_FEATURE_STRATEGY,
NONE,
UPDATE_FEATURE, UPDATE_FEATURE,
UPDATE_FEATURE_ENVIRONMENT, UPDATE_FEATURE_ENVIRONMENT,
UPDATE_FEATURE_STRATEGY, UPDATE_FEATURE_STRATEGY,
} from '../../../types/permissions'; } from '../../../types/permissions';
import { FeatureToggleDTO, IStrategyConfig } from '../../../types/model';
import { extractUsername } from '../../../util/extract-user'; import { extractUsername } from '../../../util/extract-user';
import { IAuthRequest } from '../../unleash-types'; import { IAuthRequest } from '../../unleash-types';
import { createFeatureRequest } from '../../../openapi/spec/create-feature-request'; import { createFeatureRequest } from '../../../openapi/spec/create-feature-request';
import { featureResponse } from '../../../openapi/spec/feature-response'; import { featureResponse } from '../../../openapi/spec/feature-response';
import { CreateFeatureSchema } from '../../../openapi/spec/create-feature-schema'; import { CreateFeatureSchema } from '../../../openapi/spec/create-feature-schema';
import { FeatureSchema } from '../../../openapi/spec/feature-schema'; import { FeatureSchema } from '../../../openapi/spec/feature-schema';
import { serializeDates } from '../../../util/serialize-dates';
import { createStrategyRequest } from '../../../openapi/spec/create-strategy-request'; import { createStrategyRequest } from '../../../openapi/spec/create-strategy-request';
import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema';
import { strategyResponse } from '../../../openapi/spec/strategy-response';
import { StrategySchema } from '../../../openapi/spec/strategy-schema'; import { StrategySchema } from '../../../openapi/spec/strategy-schema';
import { featuresResponse } from '../../../openapi/spec/features-response'; import { featuresResponse } from '../../../openapi/spec/features-response';
import { featureEnvironmentInfoResponse } from '../../../openapi/spec/feature-environment-info-response';
import { strategiesResponse } from '../../../openapi/spec/strategies-response';
import { strategyResponse } from '../../../openapi/spec/strategy-response';
import { emptyResponse } from '../../../openapi/spec/empty-response';
import { updateFeatureRequest } from '../../../openapi/spec/update-feature-request';
import { patchRequest } from '../../../openapi/spec/patch-request';
import { updateStrategyRequest } from '../../../openapi/spec/update-strategy-request';
import { cloneFeatureRequest } from '../../../openapi/spec/clone-feature-request';
import { FeatureEnvironmentInfoSchema } from '../../../openapi/spec/feature-environment-info-schema';
import { ParametersSchema } from '../../../openapi/spec/parameters-schema';
import { FeaturesSchema } from '../../../openapi/spec/features-schema';
import { UpdateFeatureSchema } from '../../../openapi/spec/updateFeatureSchema';
import { UpdateStrategySchema } from '../../../openapi/spec/update-strategy-schema';
import { CreateStrategySchema } from '../../../openapi/spec/create-strategy-schema';
import {
EnvironmentInfoMapper,
StrategyMapper,
} from '../../../openapi/mappers';
interface FeatureStrategyParams { interface FeatureStrategyParams {
projectId: string; projectId: string;
@ -62,6 +77,11 @@ type ProjectFeaturesServices = Pick<
export default class ProjectFeaturesController extends Controller { export default class ProjectFeaturesController extends Controller {
private featureService: FeatureToggleService; private featureService: FeatureToggleService;
private strategyMapper: StrategyMapper = new StrategyMapper();
private environmentMapper: EnvironmentInfoMapper =
new EnvironmentInfoMapper();
private readonly logger: Logger; private readonly logger: Logger;
constructor( constructor(
@ -72,21 +92,63 @@ export default class ProjectFeaturesController extends Controller {
this.featureService = featureToggleServiceV2; this.featureService = featureToggleServiceV2;
this.logger = config.getLogger('/admin-api/project/features.ts'); this.logger = config.getLogger('/admin-api/project/features.ts');
this.get(`${PATH_ENV}`, this.getEnvironment); this.route({
method: 'get',
path: PATH_ENV,
acceptAnyContentType: true,
permission: NONE,
handler: this.getEnvironment,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getEnvironment',
responses: { 200: featureEnvironmentInfoResponse },
}),
],
});
this.post( this.route({
`${PATH_ENV}/on`, method: 'post',
this.toggleEnvironmentOn, path: `${PATH_ENV}/off`,
UPDATE_FEATURE_ENVIRONMENT, handler: this.toggleEnvironmentOff,
); permission: UPDATE_FEATURE_ENVIRONMENT,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'toggleEnvironmentOff',
responses: { 200: featureResponse },
}),
],
});
this.post( this.route({
`${PATH_ENV}/off`, method: 'post',
this.toggleEnvironmentOff, path: `${PATH_ENV}/on`,
UPDATE_FEATURE_ENVIRONMENT, handler: this.toggleEnvironmentOn,
); permission: UPDATE_FEATURE_ENVIRONMENT,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'toggleEnvironmentOn',
responses: { 200: featureResponse },
}),
],
});
this.get(`${PATH_STRATEGIES}`, this.getStrategies); this.route({
method: 'get',
path: PATH_STRATEGIES,
handler: this.getStrategies,
acceptAnyContentType: true,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getStrategies',
responses: { 200: strategiesResponse },
}),
],
});
this.route({ this.route({
method: 'post', method: 'post',
@ -96,13 +158,27 @@ export default class ProjectFeaturesController extends Controller {
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'addStrategy',
requestBody: createStrategyRequest, requestBody: createStrategyRequest,
responses: { 200: strategyResponse }, responses: { 200: strategyResponse },
}), }),
], ],
}); });
this.get(`${PATH_STRATEGY}`, this.getStrategy); this.route({
method: 'get',
path: PATH_STRATEGY,
handler: this.getStrategy,
acceptAnyContentType: true,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getStrategy',
responses: { 200: strategyResponse },
}),
],
});
this.route({ this.route({
method: 'put', method: 'put',
@ -112,39 +188,56 @@ export default class ProjectFeaturesController extends Controller {
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
requestBody: createStrategyRequest, operationId: 'updateStrategy',
requestBody: updateStrategyRequest,
responses: { 200: strategyResponse }, responses: { 200: strategyResponse },
}), }),
], ],
}); });
this.route({
this.patch( method: 'patch',
`${PATH_STRATEGY}`, path: PATH_STRATEGY,
this.patchStrategy, handler: this.patchStrategy,
UPDATE_FEATURE_STRATEGY, permission: UPDATE_FEATURE_STRATEGY,
); middleware: [
openApiService.validPath({
this.delete( tags: ['admin'],
`${PATH_STRATEGY}`, operationId: 'patchStrategy',
this.deleteStrategy, requestBody: patchRequest,
DELETE_FEATURE_STRATEGY, responses: { 200: strategyResponse },
); }),
],
});
this.route({
method: 'delete',
path: PATH_STRATEGY,
acceptAnyContentType: true,
handler: this.deleteStrategy,
permission: DELETE_FEATURE_STRATEGY,
middleware: [
openApiService.validPath({
operationId: 'deleteStrategy',
tags: ['admin'],
responses: { 200: emptyResponse },
}),
],
});
this.route({ this.route({
method: 'get', method: 'get',
path: PATH, path: PATH,
acceptAnyContentType: true, acceptAnyContentType: true,
handler: this.getFeatures, handler: this.getFeatures,
permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'getFeatures',
responses: { 200: featuresResponse }, responses: { 200: featuresResponse },
}), }),
], ],
}); });
this.post(PATH_FEATURE_CLONE, this.cloneFeature, CREATE_FEATURE);
this.route({ this.route({
method: 'post', method: 'post',
path: PATH, path: PATH,
@ -153,33 +246,95 @@ export default class ProjectFeaturesController extends Controller {
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'createFeature',
requestBody: createFeatureRequest, requestBody: createFeatureRequest,
responses: { 200: featureResponse }, responses: { 200: featureResponse },
}), }),
], ],
}); });
this.route({
method: 'post',
path: PATH_FEATURE_CLONE,
acceptAnyContentType: true,
handler: this.cloneFeature,
permission: CREATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'cloneFeature',
requestBody: cloneFeatureRequest,
responses: { 200: featureResponse },
}),
],
});
this.route({ this.route({
method: 'get', method: 'get',
path: PATH_FEATURE, path: PATH_FEATURE,
acceptAnyContentType: true, acceptAnyContentType: true,
handler: this.getFeature, handler: this.getFeature,
permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
operationId: 'getFeature',
tags: ['admin'], tags: ['admin'],
responses: { 200: featureResponse }, responses: { 200: featureResponse },
}), }),
], ],
}); });
this.put(PATH_FEATURE, this.updateFeature, UPDATE_FEATURE); this.route({
this.patch(PATH_FEATURE, this.patchFeature, UPDATE_FEATURE); method: 'put',
this.delete(PATH_FEATURE, this.archiveFeature, DELETE_FEATURE); path: PATH_FEATURE,
acceptAnyContentType: true,
handler: this.updateFeature,
permission: UPDATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'updateFeature',
requestBody: updateFeatureRequest,
responses: { 200: featureResponse },
}),
],
});
this.route({
method: 'patch',
path: PATH_FEATURE,
acceptAnyContentType: true,
handler: this.patchFeature,
permission: UPDATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'patchFeature',
requestBody: patchRequest,
responses: { 200: featureResponse },
}),
],
});
this.route({
method: 'delete',
path: PATH_FEATURE,
acceptAnyContentType: true,
handler: this.archiveFeature,
permission: DELETE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'archiveFeature',
responses: { 200: emptyResponse },
}),
],
});
} }
async getFeatures( async getFeatures(
req: Request<ProjectParam, any, any, any>, req: Request<ProjectParam, any, any, any>,
res: Response, res: Response<FeaturesSchema>,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
const features = await this.featureService.getFeatureOverview( const features = await this.featureService.getFeatureOverview(
@ -192,10 +347,10 @@ export default class ProjectFeaturesController extends Controller {
req: IAuthRequest< req: IAuthRequest<
FeatureParams, FeatureParams,
any, any,
{ name: string; replaceGroupId: boolean }, { name: string; replaceGroupId?: boolean },
any any
>, >,
res: Response, res: Response<FeatureSchema>,
): Promise<void> { ): Promise<void> {
const { projectId, featureName } = req.params; const { projectId, featureName } = req.params;
const { name, replaceGroupId } = req.body; const { name, replaceGroupId } = req.body;
@ -204,7 +359,7 @@ export default class ProjectFeaturesController extends Controller {
featureName, featureName,
projectId, projectId,
name, name,
replaceGroupId, Boolean(replaceGroupId),
userName, userName,
); );
res.status(201).json(created); res.status(201).json(created);
@ -223,7 +378,7 @@ export default class ProjectFeaturesController extends Controller {
userName, userName,
); );
res.status(201).json(serializeDates(created)); res.status(201).json(created);
} }
async getFeature( async getFeature(
@ -239,10 +394,10 @@ export default class ProjectFeaturesController extends Controller {
req: IAuthRequest< req: IAuthRequest<
{ projectId: string; featureName: string }, { projectId: string; featureName: string },
any, any,
FeatureToggleDTO, UpdateFeatureSchema,
any any
>, >,
res: Response, res: Response<FeatureSchema>,
): Promise<void> { ): Promise<void> {
const { projectId, featureName } = req.params; const { projectId, featureName } = req.params;
const data = req.body; const data = req.body;
@ -263,7 +418,7 @@ export default class ProjectFeaturesController extends Controller {
Operation[], Operation[],
any any
>, >,
res: Response, res: Response<FeatureSchema>,
): Promise<void> { ): Promise<void> {
const { projectId, featureName } = req.params; const { projectId, featureName } = req.params;
const updated = await this.featureService.patchFeature( const updated = await this.featureService.patchFeature(
@ -283,7 +438,7 @@ export default class ProjectFeaturesController extends Controller {
any, any,
any any
>, >,
res: Response, res: Response<void>,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
@ -293,7 +448,7 @@ export default class ProjectFeaturesController extends Controller {
async getEnvironment( async getEnvironment(
req: Request<FeatureStrategyParams, any, any, any>, req: Request<FeatureStrategyParams, any, any, any>,
res: Response, res: Response<FeatureEnvironmentInfoSchema>,
): Promise<void> { ): Promise<void> {
const { environment, featureName, projectId } = req.params; const { environment, featureName, projectId } = req.params;
const environmentInfo = await this.featureService.getEnvironmentInfo( const environmentInfo = await this.featureService.getEnvironmentInfo(
@ -301,12 +456,12 @@ export default class ProjectFeaturesController extends Controller {
environment, environment,
featureName, featureName,
); );
res.status(200).json(environmentInfo); res.status(200).json(this.environmentMapper.toPublic(environmentInfo));
} }
async toggleEnvironmentOn( async toggleEnvironmentOn(
req: IAuthRequest<FeatureStrategyParams, any, any, any>, req: IAuthRequest<FeatureStrategyParams, any, any, any>,
res: Response, res: Response<void>,
): Promise<void> { ): Promise<void> {
const { featureName, environment, projectId } = req.params; const { featureName, environment, projectId } = req.params;
await this.featureService.updateEnabled( await this.featureService.updateEnabled(
@ -321,7 +476,7 @@ export default class ProjectFeaturesController extends Controller {
async toggleEnvironmentOff( async toggleEnvironmentOff(
req: IAuthRequest<FeatureStrategyParams, any, any, any>, req: IAuthRequest<FeatureStrategyParams, any, any, any>,
res: Response, res: Response<void>,
): Promise<void> { ): Promise<void> {
const { featureName, environment, projectId } = req.params; const { featureName, environment, projectId } = req.params;
await this.featureService.updateEnabled( await this.featureService.updateEnabled(
@ -335,22 +490,22 @@ export default class ProjectFeaturesController extends Controller {
} }
async addStrategy( async addStrategy(
req: IAuthRequest<FeatureStrategyParams, any, IStrategyConfig>, req: IAuthRequest<FeatureStrategyParams, any, CreateStrategySchema>,
res: Response<StrategySchema>, res: Response<StrategySchema>,
): Promise<void> { ): Promise<void> {
const { projectId, featureName, environment } = req.params; const { projectId, featureName, environment } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
const featureStrategy = await this.featureService.createStrategy( const strategy = await this.featureService.createStrategy(
req.body, this.strategyMapper.mapInput(req.body),
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(featureStrategy); res.status(200).json(this.strategyMapper.toPublic(strategy));
} }
async getStrategies( async getStrategies(
req: Request<FeatureStrategyParams, any, any, any>, req: Request<FeatureStrategyParams, any, any, any>,
res: Response, res: Response<StrategySchema[]>,
): Promise<void> { ): Promise<void> {
const { projectId, featureName, environment } = req.params; const { projectId, featureName, environment } = req.params;
const featureStrategies = const featureStrategies =
@ -359,11 +514,13 @@ export default class ProjectFeaturesController extends Controller {
featureName, featureName,
environment, environment,
); );
res.status(200).json(featureStrategies); res.status(200).json(
featureStrategies.map(this.strategyMapper.toPublic),
);
} }
async updateStrategy( async updateStrategy(
req: IAuthRequest<StrategyIdParams, any, CreateStrategySchema>, req: IAuthRequest<StrategyIdParams, any, UpdateStrategySchema>,
res: Response<StrategySchema>, res: Response<StrategySchema>,
): Promise<void> { ): Promise<void> {
const { strategyId, environment, projectId, featureName } = req.params; const { strategyId, environment, projectId, featureName } = req.params;
@ -374,12 +531,12 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(updatedStrategy); res.status(200).json(this.strategyMapper.fromPublic(updatedStrategy));
} }
async patchStrategy( async patchStrategy(
req: IAuthRequest<StrategyIdParams, any, Operation[], any>, req: IAuthRequest<StrategyIdParams, any, Operation[], any>,
res: Response, res: Response<StrategySchema>,
): Promise<void> { ): Promise<void> {
const { strategyId, projectId, environment, featureName } = req.params; const { strategyId, projectId, environment, featureName } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
@ -392,23 +549,23 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(updatedStrategy); res.status(200).json(this.strategyMapper.toPublic(updatedStrategy));
} }
async getStrategy( async getStrategy(
req: IAuthRequest<StrategyIdParams, any, any, any>, req: IAuthRequest<StrategyIdParams, any, any, any>,
res: Response, res: Response<StrategySchema>,
): Promise<void> { ): Promise<void> {
this.logger.info('Getting strategy'); this.logger.info('Getting strategy');
const { strategyId } = req.params; const { strategyId } = req.params;
this.logger.info(strategyId); this.logger.info(strategyId);
const strategy = await this.featureService.getStrategy(strategyId); const strategy = await this.featureService.getStrategy(strategyId);
res.status(200).json(strategy); res.status(200).json(this.strategyMapper.toPublic(strategy));
} }
async deleteStrategy( async deleteStrategy(
req: IAuthRequest<StrategyIdParams, any, any, any>, req: IAuthRequest<StrategyIdParams, any, any, any>,
res: Response, res: Response<void>,
): Promise<void> { ): Promise<void> {
this.logger.info('Deleting strategy'); this.logger.info('Deleting strategy');
const { environment, projectId, featureName } = req.params; const { environment, projectId, featureName } = req.params;
@ -430,7 +587,7 @@ export default class ProjectFeaturesController extends Controller {
{ name: string; value: string | number }, { name: string; value: string | number },
any any
>, >,
res: Response, res: Response<StrategySchema>,
): Promise<void> { ): Promise<void> {
const { strategyId, environment, projectId, featureName } = req.params; const { strategyId, environment, projectId, featureName } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
@ -444,12 +601,12 @@ export default class ProjectFeaturesController extends Controller {
{ environment, projectId, featureName }, { environment, projectId, featureName },
userName, userName,
); );
res.status(200).json(updatedStrategy); res.status(200).json(this.strategyMapper.toPublic(updatedStrategy));
} }
async getStrategyParameters( async getStrategyParameters(
req: Request<StrategyIdParams, any, any, any>, req: Request<StrategyIdParams, any, any, any>,
res: Response, res: Response<ParametersSchema>,
): Promise<void> { ): Promise<void> {
this.logger.info('Getting strategy parameters'); this.logger.info('Getting strategy parameters');
const { strategyId } = req.params; const { strategyId } = req.params;

View File

@ -21,7 +21,7 @@ interface IRequestHandler<
interface IRouteOptions { interface IRouteOptions {
method: 'get' | 'post' | 'put' | 'patch' | 'delete'; method: 'get' | 'post' | 'put' | 'patch' | 'delete';
path: string; path: string;
permission?: string; permission: string;
middleware?: RequestHandler[]; middleware?: RequestHandler[];
handler: IRequestHandler; handler: IRequestHandler;
acceptAnyContentType?: boolean; acceptAnyContentType?: boolean;

View File

@ -1,5 +1,5 @@
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types';
import { Logger } from '../logger'; import { Logger } from '../logger';
import BadDataError from '../error/bad-data-error'; import BadDataError from '../error/bad-data-error';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
@ -424,7 +424,7 @@ class FeatureToggleService {
* } * }
* @param id - strategy id * @param id - strategy id
* @param context - Which context does this strategy live in (projectId, featureName, environment) * @param context - Which context does this strategy live in (projectId, featureName, environment)
* @param environment - Which environment does this strategy belong to * @param createdBy - Which user does this strategy belong to
*/ */
async deleteStrategy( async deleteStrategy(
id: string, id: string,
@ -529,7 +529,6 @@ class FeatureToggleService {
* Used to retrieve metadata of all feature toggles defined in Unleash. * Used to retrieve metadata of all feature toggles defined in Unleash.
* @param query - Allow you to limit search based on criteria such as project, tags, namePrefix. See @IFeatureToggleQuery * @param query - Allow you to limit search based on criteria such as project, tags, namePrefix. See @IFeatureToggleQuery
* @param archived - Return archived or active toggles * @param archived - Return archived or active toggles
* @param includeStrategyId - Include id for strategies
* @returns * @returns
*/ */
async getFeatureToggles( async getFeatureToggles(
@ -1054,7 +1053,7 @@ class FeatureToggleService {
createdBy, createdBy,
tags, tags,
oldVariants, oldVariants,
newVariants: featureToggle.variants, newVariants: featureToggle.variants as IVariant[],
}), }),
); );
return featureToggle; return featureToggle;

View File

@ -6,21 +6,21 @@ import { nameType } from '../routes/util';
import { projectSchema } from './project-schema'; import { projectSchema } from './project-schema';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
import { import {
ProjectUserAddedEvent,
ProjectUserRemovedEvent,
ProjectUserUpdateRoleEvent,
PROJECT_CREATED, PROJECT_CREATED,
PROJECT_DELETED, PROJECT_DELETED,
PROJECT_UPDATED, PROJECT_UPDATED,
ProjectUserAddedEvent,
ProjectUserRemovedEvent,
ProjectUserUpdateRoleEvent,
} from '../types/events'; } from '../types/events';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { import {
FeatureToggle,
IProject, IProject,
IProjectOverview, IProjectOverview,
IProjectWithCount, IProjectWithCount,
IUserWithRole, IUserWithRole,
FeatureToggle,
RoleName, RoleName,
} from '../types/model'; } from '../types/model';
import { IEnvironmentStore } from '../types/stores/environment-store'; import { IEnvironmentStore } from '../types/stores/environment-store';

View File

@ -69,7 +69,7 @@ export class SegmentService {
async create(data: unknown, user: User): Promise<void> { async create(data: unknown, user: User): Promise<void> {
const input = await segmentSchema.validateAsync(data); const input = await segmentSchema.validateAsync(data);
this.validateSegmentValuesLimit(input); SegmentService.validateSegmentValuesLimit(input);
await this.validateName(input.name); await this.validateName(input.name);
const segment = await this.segmentStore.create(input, user); const segment = await this.segmentStore.create(input, user);
@ -83,7 +83,7 @@ export class SegmentService {
async update(id: number, data: unknown, user: User): Promise<void> { async update(id: number, data: unknown, user: User): Promise<void> {
const input = await segmentSchema.validateAsync(data); const input = await segmentSchema.validateAsync(data);
this.validateSegmentValuesLimit(input); SegmentService.validateSegmentValuesLimit(input);
const preData = await this.segmentStore.get(id); const preData = await this.segmentStore.get(id);
if (preData.name !== input.name) { if (preData.name !== input.name) {
@ -147,7 +147,9 @@ export class SegmentService {
} }
} }
private validateSegmentValuesLimit(segment: Omit<ISegment, 'id'>): void { private static validateSegmentValuesLimit(
segment: Omit<ISegment, 'id'>,
): void {
const limit = SEGMENT_VALUES_LIMIT; const limit = SEGMENT_VALUES_LIMIT;
const valuesCount = segment.constraints const valuesCount = segment.constraints

View File

@ -0,0 +1,4 @@
// Create a string with allowed values from a values array. ['A', 'B'] => 'A' | 'B'
export type AllowedStrings<T extends ReadonlyArray<unknown>> =
// eslint-disable-next-line @typescript-eslint/no-shadow
T extends ReadonlyArray<infer AllowedStrings> ? AllowedStrings : never;

View File

@ -2,10 +2,14 @@ import { ITagType } from './stores/tag-type-store';
import { LogProvider } from '../logger'; import { LogProvider } from '../logger';
import { IRole } from './stores/access-store'; import { IRole } from './stores/access-store';
import { IUser } from './user'; import { IUser } from './user';
import { ALL_OPERATORS } from '../util/constants';
import { AllowedStrings } from './allowed-strings';
export type Operator = AllowedStrings<typeof ALL_OPERATORS>;
export interface IConstraint { export interface IConstraint {
contextName: string; contextName: string;
operator: string; operator: Operator;
values?: string[]; values?: string[];
value?: string; value?: string;
inverted?: boolean; inverted?: boolean;

View File

@ -40,7 +40,7 @@ export const ALL_OPERATORS = [
SEMVER_EQ, SEMVER_EQ,
SEMVER_GT, SEMVER_GT,
SEMVER_LT, SEMVER_LT,
]; ] as const;
export const STRING_OPERATORS = [ export const STRING_OPERATORS = [
STR_ENDS_WITH, STR_ENDS_WITH,

View File

@ -1,16 +0,0 @@
import { serializeDates } from './serialize-dates';
test('serializeDates', () => {
const obj = {
a: 1,
b: '2',
c: new Date(),
d: { e: new Date() },
};
expect(serializeDates({})).toEqual({});
expect(serializeDates(obj).a).toEqual(1);
expect(serializeDates(obj).b).toEqual('2');
expect(typeof serializeDates(obj).c).toEqual('string');
expect(typeof serializeDates(obj).d.e).toEqual('object');
});

View File

@ -1,22 +0,0 @@
type SerializedDates<T> = {
[P in keyof T]: T[P] extends Date ? string : T[P];
};
// Disallow array arguments for serializeDates.
// Use `array.map(serializeDates)` instead.
type NotArray<T> = Exclude<T, unknown[]>;
// Serialize top-level date values to strings.
export const serializeDates = <T extends object>(
obj: NotArray<T>,
): SerializedDates<T> => {
const entries = Object.entries(obj).map(([k, v]) => {
if (v instanceof Date) {
return [k, v.toJSON()];
} else {
return [k, v];
}
});
return Object.fromEntries(entries);
};

View File

@ -1,5 +1,4 @@
import faker from 'faker'; import faker from 'faker';
import { FeatureToggleDTO, IStrategyConfig, IVariant } from 'lib/types/model';
import dbInit, { ITestDb } from '../../helpers/database-init'; import dbInit, { ITestDb } from '../../helpers/database-init';
import { import {
IUnleashTest, IUnleashTest,
@ -8,6 +7,9 @@ import {
} from '../../helpers/test-helper'; } from '../../helpers/test-helper';
import getLogger from '../../../fixtures/no-logger'; import getLogger from '../../../fixtures/no-logger';
import { DEFAULT_ENV } from '../../../../lib/util/constants'; import { DEFAULT_ENV } from '../../../../lib/util/constants';
import { StrategySchema } from '../../../../lib/openapi/spec/strategy-schema';
import { FeatureSchema } from '../../../../lib/openapi/spec/feature-schema';
import { VariantSchema } from '../../../../lib/openapi/spec/variant-schema';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
@ -23,8 +25,8 @@ beforeAll(async () => {
app = await setupApp(db.stores); app = await setupApp(db.stores);
const createToggle = async ( const createToggle = async (
toggle: FeatureToggleDTO, toggle: FeatureSchema,
strategy: Omit<IStrategyConfig, 'id'> = defaultStrategy, strategy: Omit<StrategySchema, 'id'> = defaultStrategy,
projectId: string = 'default', projectId: string = 'default',
username: string = 'test', username: string = 'test',
) => { ) => {
@ -41,7 +43,7 @@ beforeAll(async () => {
}; };
const createVariants = async ( const createVariants = async (
featureName: string, featureName: string,
variants: IVariant[], variants: VariantSchema[],
projectId: string = 'default', projectId: string = 'default',
username: string = 'test', username: string = 'test',
) => { ) => {
@ -56,12 +58,14 @@ beforeAll(async () => {
await createToggle({ await createToggle({
name: 'featureX', name: 'featureX',
description: 'the #1 feature', description: 'the #1 feature',
project: 'some-project',
}); });
await createToggle( await createToggle(
{ {
name: 'featureY', name: 'featureY',
description: 'soon to be the #1 feature', description: 'soon to be the #1 feature',
project: 'some-project',
}, },
{ {
name: 'baz', name: 'baz',
@ -76,6 +80,7 @@ beforeAll(async () => {
{ {
name: 'featureZ', name: 'featureZ',
description: 'terrible feature', description: 'terrible feature',
project: 'some-project',
}, },
{ {
name: 'baz', name: 'baz',
@ -90,6 +95,7 @@ beforeAll(async () => {
{ {
name: 'featureArchivedX', name: 'featureArchivedX',
description: 'the #1 feature', description: 'the #1 feature',
project: 'some-project',
}, },
{ {
name: 'default', name: 'default',
@ -107,6 +113,7 @@ beforeAll(async () => {
{ {
name: 'featureArchivedY', name: 'featureArchivedY',
description: 'soon to be the #1 feature', description: 'soon to be the #1 feature',
project: 'some-project',
}, },
{ {
name: 'baz', name: 'baz',
@ -126,13 +133,14 @@ beforeAll(async () => {
{ {
name: 'featureArchivedZ', name: 'featureArchivedZ',
description: 'terrible feature', description: 'terrible feature',
project: 'some-project',
}, },
{ {
name: 'baz', name: 'baz',
constraints: [],
parameters: { parameters: {
foo: 'rab', foo: 'rab',
}, },
constraints: [],
}, },
); );
@ -144,6 +152,7 @@ beforeAll(async () => {
await createToggle({ await createToggle({
name: 'feature.with.variants', name: 'feature.with.variants',
description: 'A feature toggle with variants', description: 'A feature toggle with variants',
project: 'some-project',
}); });
await createVariants('feature.with.variants', [ await createVariants('feature.with.variants', [
{ {

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
import FeatureToggleService from '../../../lib/services/feature-toggle-service'; import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { IStrategyConfig } from '../../../lib/types/model';
import { createTestConfig } from '../../config/test-config'; import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init'; import dbInit from '../helpers/database-init';
import { DEFAULT_ENV } from '../../../lib/util/constants'; import { DEFAULT_ENV } from '../../../lib/util/constants';
import { StrategySchema } from '../../../lib/openapi/spec/strategy-schema';
let stores; let stores;
let db; let db;
@ -25,7 +25,7 @@ afterAll(async () => {
test('Should create feature toggle strategy configuration', async () => { test('Should create feature toggle strategy configuration', async () => {
const projectId = 'default'; const projectId = 'default';
const username = 'feature-toggle'; const username = 'feature-toggle';
const config: Omit<IStrategyConfig, 'id'> = { const config: Omit<StrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
parameters: {}, parameters: {},
@ -53,7 +53,7 @@ test('Should be able to update existing strategy configuration', async () => {
const projectId = 'default'; const projectId = 'default';
const username = 'existing-strategy'; const username = 'existing-strategy';
const featureName = 'update-existing-strategy'; const featureName = 'update-existing-strategy';
const config: Omit<IStrategyConfig, 'id'> = { const config: Omit<StrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
parameters: {}, parameters: {},
@ -88,7 +88,7 @@ test('Should be able to get strategy by id', async () => {
const projectId = 'default'; const projectId = 'default';
const userName = 'strategy'; const userName = 'strategy';
const config: Omit<IStrategyConfig, 'id'> = { const config: Omit<StrategySchema, 'id'> = {
name: 'default', name: 'default',
constraints: [], constraints: [],
parameters: {}, parameters: {},

View File

@ -1,11 +1,11 @@
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { import {
FeatureToggle,
FeatureToggleWithEnvironment, FeatureToggleWithEnvironment,
IFeatureOverview, IFeatureOverview,
IFeatureStrategy,
IFeatureToggleClient, IFeatureToggleClient,
IFeatureToggleQuery, IFeatureToggleQuery,
IFeatureStrategy,
FeatureToggle,
} from '../../lib/types/model'; } from '../../lib/types/model';
import NotFoundError from '../../lib/error/notfound-error'; import NotFoundError from '../../lib/error/notfound-error';
import { IFeatureStrategiesStore } from '../../lib/types/stores/feature-strategies-store'; import { IFeatureStrategiesStore } from '../../lib/types/stores/feature-strategies-store';

View File

@ -2,12 +2,8 @@ import {
IFeatureToggleQuery, IFeatureToggleQuery,
IFeatureToggleStore, IFeatureToggleStore,
} from '../../lib/types/stores/feature-toggle-store'; } from '../../lib/types/stores/feature-toggle-store';
import {
FeatureToggle,
FeatureToggleDTO,
IVariant,
} from '../../lib/types/model';
import NotFoundError from '../../lib/error/notfound-error'; import NotFoundError from '../../lib/error/notfound-error';
import { FeatureToggle, FeatureToggleDTO, IVariant } from 'lib/types/model';
export default class FakeFeatureToggleStore implements IFeatureToggleStore { export default class FakeFeatureToggleStore implements IFeatureToggleStore {
features: FeatureToggle[] = []; features: FeatureToggle[] = [];
@ -49,10 +45,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
}; };
} }
async create( async create(project: string, data: FeatureToggle): Promise<FeatureToggle> {
project: string,
data: FeatureToggleDTO,
): Promise<FeatureToggle> {
const inserted: FeatureToggle = { ...data, project }; const inserted: FeatureToggle = { ...data, project };
this.features.push(inserted); this.features.push(inserted);
return inserted; return inserted;
@ -130,7 +123,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
async getVariants(featureName: string): Promise<IVariant[]> { async getVariants(featureName: string): Promise<IVariant[]> {
const feature = await this.get(featureName); const feature = await this.get(featureName);
return feature.variants; return feature.variants as IVariant[];
} }
async saveVariants( async saveVariants(