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:
parent
7d1a5c2012
commit
1a27bffe4d
1
.gitignore
vendored
1
.gitignore
vendored
@ -54,3 +54,4 @@ package-lock.json
|
|||||||
/website/i18n/*
|
/website/i18n/*
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
report.json
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
29
src/lib/openapi/mappers/environment-info.mapper.ts
Normal file
29
src/lib/openapi/mappers/environment-info.mapper.ts
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
29
src/lib/openapi/mappers/feature-strategy.mapper.ts
Normal file
29
src/lib/openapi/mappers/feature-strategy.mapper.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3
src/lib/openapi/mappers/index.ts
Normal file
3
src/lib/openapi/mappers/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './environment-info.mapper';
|
||||||
|
export * from './feature-strategy.mapper';
|
||||||
|
export * from './strategy.mapper';
|
7
src/lib/openapi/mappers/mapper.ts
Normal file
7
src/lib/openapi/mappers/mapper.ts
Normal 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;
|
||||||
|
}
|
33
src/lib/openapi/mappers/strategy.mapper.ts
Normal file
33
src/lib/openapi/mappers/strategy.mapper.ts
Normal 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 || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
12
src/lib/openapi/spec/clone-feature-request.ts
Normal file
12
src/lib/openapi/spec/clone-feature-request.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
19
src/lib/openapi/spec/clone-feature-schema.ts
Normal file
19
src/lib/openapi/spec/clone-feature-schema.ts
Normal 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);
|
@ -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>;
|
||||||
|
@ -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>;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
12
src/lib/openapi/spec/create-tag-request.ts
Normal file
12
src/lib/openapi/spec/create-tag-request.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
11
src/lib/openapi/spec/empty-response-schema.ts
Normal file
11
src/lib/openapi/spec/empty-response-schema.ts
Normal 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);
|
12
src/lib/openapi/spec/empty-response.ts
Normal file
12
src/lib/openapi/spec/empty-response.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
12
src/lib/openapi/spec/feature-environment-info-response.ts
Normal file
12
src/lib/openapi/spec/feature-environment-info-response.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
35
src/lib/openapi/spec/feature-environment-info-schema.ts
Normal file
35
src/lib/openapi/spec/feature-environment-info-schema.ts
Normal 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);
|
@ -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>;
|
||||||
|
58
src/lib/openapi/spec/feature-strategy-schema.ts
Normal file
58
src/lib/openapi/spec/feature-strategy-schema.ts
Normal 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);
|
@ -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>;
|
||||||
|
@ -15,6 +15,7 @@ const schema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'components/schemas': {},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type OverrideSchema = CreateSchemaType<typeof schema>;
|
export type OverrideSchema = CreateSchemaType<typeof schema>;
|
||||||
|
@ -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>;
|
||||||
|
24
src/lib/openapi/spec/patch-operation-schema.ts
Normal file
24
src/lib/openapi/spec/patch-operation-schema.ts
Normal 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);
|
15
src/lib/openapi/spec/patch-request.ts
Normal file
15
src/lib/openapi/spec/patch-request.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
15
src/lib/openapi/spec/strategies-response.ts
Normal file
15
src/lib/openapi/spec/strategies-response.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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);
|
||||||
|
12
src/lib/openapi/spec/tag-response.ts
Normal file
12
src/lib/openapi/spec/tag-response.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
20
src/lib/openapi/spec/tag-schema.ts
Normal file
20
src/lib/openapi/spec/tag-schema.ts
Normal 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);
|
26
src/lib/openapi/spec/tags-response-schema.ts
Normal file
26
src/lib/openapi/spec/tags-response-schema.ts
Normal 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);
|
12
src/lib/openapi/spec/tags-response.ts
Normal file
12
src/lib/openapi/spec/tags-response.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
12
src/lib/openapi/spec/update-feature-request.ts
Normal file
12
src/lib/openapi/spec/update-feature-request.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
12
src/lib/openapi/spec/update-strategy-request.ts
Normal file
12
src/lib/openapi/spec/update-strategy-request.ts
Normal 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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
11
src/lib/openapi/spec/update-strategy-schema.ts
Normal file
11
src/lib/openapi/spec/update-strategy-schema.ts
Normal 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);
|
42
src/lib/openapi/spec/updateFeatureSchema.ts
Normal file
42
src/lib/openapi/spec/updateFeatureSchema.ts
Normal 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);
|
@ -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>;
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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';
|
||||||
|
@ -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
|
||||||
|
4
src/lib/types/allowed-strings.ts
Normal file
4
src/lib/types/allowed-strings.ts
Normal 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;
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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');
|
|
||||||
});
|
|
@ -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);
|
|
||||||
};
|
|
@ -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
@ -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: {},
|
||||||
|
@ -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';
|
||||||
|
13
src/test/fixtures/fake-feature-toggle-store.ts
vendored
13
src/test/fixtures/fake-feature-toggle-store.ts
vendored
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user