Skip to content

Commit ccc8ab2

Browse files
committed
revert improvised defaults and be clearer in types that these fields are nullable
1 parent 0998c57 commit ccc8ab2

16 files changed

+100
-67
lines changed

src/lib/create-config.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -335,17 +335,22 @@ const parseEnvVarInitialAdminUser = (): UsernameAdminUser | undefined => {
335335
return username && password ? { username, password } : undefined;
336336
};
337337

338-
const defaultAuthentication: IAuthOption = {
339-
demoAllowAdminLogin: parseEnvVarBoolean(
340-
process.env.AUTH_DEMO_ALLOW_ADMIN_LOGIN,
341-
false,
342-
),
343-
enableApiToken: parseEnvVarBoolean(process.env.AUTH_ENABLE_API_TOKEN, true),
344-
type: authTypeFromString(process.env.AUTH_TYPE),
345-
customAuthHandler: defaultCustomAuthDenyAll,
346-
createAdminUser: true,
347-
initialAdminUser: parseEnvVarInitialAdminUser(),
348-
initApiTokens: [],
338+
const buildDefaultAuthOption = () => {
339+
return {
340+
demoAllowAdminLogin: parseEnvVarBoolean(
341+
process.env.AUTH_DEMO_ALLOW_ADMIN_LOGIN,
342+
false,
343+
),
344+
enableApiToken: parseEnvVarBoolean(
345+
process.env.AUTH_ENABLE_API_TOKEN,
346+
true,
347+
),
348+
type: authTypeFromString(process.env.AUTH_TYPE),
349+
customAuthHandler: defaultCustomAuthDenyAll,
350+
createAdminUser: true,
351+
initialAdminUser: parseEnvVarInitialAdminUser(),
352+
initApiTokens: [],
353+
};
349354
};
350355

351356
const defaultImport: WithOptional<IImportOption, 'file'> = {
@@ -563,7 +568,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
563568
const initApiTokens = loadInitApiTokens();
564569

565570
const authentication: IAuthOption = mergeAll([
566-
defaultAuthentication,
571+
buildDefaultAuthOption(),
567572
(options.authentication
568573
? removeUndefinedKeys(options.authentication)
569574
: options.authentication) || {},

src/lib/domain/project-health/project-health.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ const getPotentiallyStaleCount = (
2222
const today = new Date().valueOf();
2323

2424
return features.filter((feature) => {
25-
const diff = today - feature.createdAt!.valueOf();
25+
const diff = feature.createdAt
26+
? today - feature.createdAt.valueOf()
27+
: 0;
2628
const featureTypeExpectedLifetime = featureTypes.find(
2729
(t) => t.id === feature.type,
28-
)!.lifetimeDays;
30+
)?.lifetimeDays;
2931

3032
return (
3133
!feature.stale &&
3234
featureTypeExpectedLifetime !== null &&
35+
featureTypeExpectedLifetime !== undefined &&
3336
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
3437
);
3538
}).length;

src/lib/features/client-feature-toggles/fakes/fake-client-feature-toggle-store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default class FakeClientFeatureToggleStore
3838
...t,
3939
enabled: true,
4040
strategies: [],
41-
description: t.description || '',
41+
description: t.description,
4242
type: t.type || 'Release',
4343
stale: t.stale || false,
4444
variants: [],

src/lib/features/context/context-field-store.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const mapRow = (row: ContextFieldDB): IContextField => ({
5050

5151
interface ICreateContextField {
5252
name: string;
53-
description: string;
53+
description?: string | null;
5454
stickiness: boolean;
5555
sort_order: number;
5656
legal_values?: string;
@@ -80,8 +80,8 @@ class ContextFieldStore implements IContextFieldStore {
8080
return {
8181
name: data.name,
8282
description: data.description,
83-
stickiness: data.stickiness,
84-
sort_order: data.sortOrder, // eslint-disable-line
83+
stickiness: data.stickiness || false,
84+
sort_order: data.sortOrder || 0,
8585
legal_values: JSON.stringify(data.legalValues || []),
8686
};
8787
}

src/lib/features/context/context-service.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
2222
import type EventService from '../events/event-service';
2323
import { contextSchema, legalValueSchema } from '../../services/context-schema';
24-
import { NameExistsError } from '../../error';
24+
import { NameExistsError, NotFoundError } from '../../error';
2525
import { nameSchema } from '../../schema/feature-schema';
2626
import type { LegalValueSchema } from '../../openapi';
2727

@@ -63,7 +63,13 @@ class ContextService {
6363
}
6464

6565
async getContextField(name: string): Promise<IContextField> {
66-
return this.contextFieldStore.get(name);
66+
const field = await this.contextFieldStore.get(name);
67+
if (field === undefined) {
68+
throw new NotFoundError(
69+
`Could not find context field with name ${name}`,
70+
);
71+
}
72+
return field;
6773
}
6874

6975
async getStrategiesByContextField(
@@ -125,6 +131,11 @@ class ContextService {
125131
const contextField = await this.contextFieldStore.get(
126132
updatedContextField.name,
127133
);
134+
if (contextField === undefined) {
135+
throw new NotFoundError(
136+
`Could not find context field with name: ${updatedContextField.name}`,
137+
);
138+
}
128139
const value = await contextSchema.validateAsync(updatedContextField);
129140

130141
await this.contextFieldStore.update(value);
@@ -147,6 +158,11 @@ class ContextService {
147158
const contextField = await this.contextFieldStore.get(
148159
contextFieldLegalValue.name,
149160
);
161+
if (contextField === undefined) {
162+
throw new NotFoundError(
163+
`Context field with name ${contextFieldLegalValue.name} was not found`,
164+
);
165+
}
150166
const validatedLegalValue = await legalValueSchema.validateAsync(
151167
contextFieldLegalValue.legalValue,
152168
);
@@ -186,6 +202,11 @@ class ContextService {
186202
const contextField = await this.contextFieldStore.get(
187203
contextFieldLegalValue.name,
188204
);
205+
if (contextField === undefined) {
206+
throw new NotFoundError(
207+
`Could not find context field with name ${contextFieldLegalValue.name}`,
208+
);
209+
}
189210

190211
const newContextField = {
191212
...contextField,

src/lib/features/export-import-toggles/export-import-permissions.e2e.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ beforeAll(async () => {
262262
contextFieldStore = db.stores.contextFieldStore;
263263

264264
const roles = await accessService.getRootRoles();
265-
adminRole = roles.find((role) => role.name === RoleName.ADMIN);
265+
adminRole = roles.find((role) => role.name === RoleName.ADMIN)!;
266266

267267
await createUserEditorAccess(
268268
regularUserName,

src/lib/features/export-import-toggles/export-import-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ export default class ExportImportService
463463
this.contextService.createContextField(
464464
{
465465
name: contextField.name,
466-
description: contextField.description || '',
466+
description: contextField.description,
467467
legalValues: contextField.legalValues,
468468
stickiness: contextField.stickiness,
469469
},

src/lib/features/export-import-toggles/import-permissions-service.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ export class ImportPermissionsService {
4848
): Promise<ContextFieldSchema[]> {
4949
const availableContextFields = await this.contextService.getAll();
5050

51-
return dto.data.contextFields?.filter(
52-
(contextField) =>
53-
!availableContextFields.some(
54-
(availableField) =>
55-
availableField.name === contextField.name,
56-
),
51+
return (
52+
dto.data.contextFields?.filter(
53+
(contextField) =>
54+
!availableContextFields.some(
55+
(availableField) =>
56+
availableField.name === contextField.name,
57+
),
58+
) || []
5759
);
5860
}
5961

src/lib/features/feature-search/feature-search-store.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
253253

254254
const rankingSql = this.buildRankingSql(
255255
favoritesFirst,
256-
sortBy,
256+
sortBy || '',
257257
validatedSortOrder,
258258
lastSeenQuery,
259259
);
@@ -705,12 +705,14 @@ const applyMultiQueryParams = (
705705
) => (dbSubQuery: Knex.QueryBuilder) => Knex.QueryBuilder,
706706
): void => {
707707
queryParams.forEach((param) => {
708-
const values = param.values.map((val) =>
709-
(Array.isArray(fields)
710-
? val.split(/:(.+)/).filter(Boolean)
711-
: [val]
712-
).map((s) => s.trim()),
713-
);
708+
const values = param.values
709+
.filter((v) => typeof v === 'string')
710+
.map((val) =>
711+
(Array.isArray(fields)
712+
? val!.split(/:(.+)/).filter(Boolean)
713+
: [val]
714+
).map((s) => s.trim()),
715+
);
714716
const baseSubQuery = createBaseQuery(values);
715717

716718
switch (param.operator) {

src/lib/features/feature-toggle/fakes/fake-feature-strategies-store.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export default class FakeFeatureStrategiesStore
6767
return this.featureStrategies.some((s) => s.id === id);
6868
}
6969

70-
async get(id: string): Promise<IFeatureStrategy> {
70+
async get(id: string): Promise<IFeatureStrategy | undefined> {
7171
return this.featureStrategies.find((s) => s.id === id);
7272
}
7373

@@ -180,8 +180,8 @@ export default class FakeFeatureStrategiesStore
180180
archived: boolean = false,
181181
): Promise<IFeatureToggleClient[]> {
182182
const rows = this.featureToggles.filter((toggle) => {
183-
if (featureQuery.namePrefix) {
184-
if (featureQuery.project) {
183+
if (featureQuery?.namePrefix) {
184+
if (featureQuery?.project) {
185185
return (
186186
(toggle.name.startsWith(featureQuery.namePrefix) &&
187187
featureQuery.project.some((project) =>
@@ -192,7 +192,7 @@ export default class FakeFeatureStrategiesStore
192192
}
193193
return toggle.name.startsWith(featureQuery.namePrefix);
194194
}
195-
if (featureQuery.project) {
195+
if (featureQuery?.project) {
196196
return (
197197
featureQuery.project.some((project) =>
198198
project.includes(toggle.project),
@@ -205,7 +205,7 @@ export default class FakeFeatureStrategiesStore
205205
...t,
206206
enabled: true,
207207
strategies: [],
208-
description: t.description || '',
208+
description: t.description,
209209
type: t.type || 'Release',
210210
stale: t.stale || false,
211211
variants: [],
@@ -233,7 +233,7 @@ export default class FakeFeatureStrategiesStore
233233
this.environmentAndFeature.set(environment, []);
234234
}
235235
this.environmentAndFeature
236-
.get(environment)
236+
.get(environment)!
237237
.push({ feature: feature_name, enabled });
238238
return Promise.resolve();
239239
}
@@ -245,7 +245,7 @@ export default class FakeFeatureStrategiesStore
245245
this.environmentAndFeature.set(
246246
environment,
247247
this.environmentAndFeature
248-
.get(environment)
248+
.get(environment)!
249249
.filter((e) => e.featureName !== feature_name),
250250
);
251251
return Promise.resolve();
@@ -271,7 +271,9 @@ export default class FakeFeatureStrategiesStore
271271
}
272272
return f;
273273
});
274-
return Promise.resolve(this.featureStrategies.find((f) => f.id === id));
274+
return Promise.resolve(
275+
this.featureStrategies.find((f) => f.id === id)!,
276+
);
275277
}
276278

277279
async deleteConfigurationsForProjectAndEnvironment(

src/lib/features/feature-toggle/fakes/fake-feature-toggle-store.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
8787
return this.features.filter((f) => names.includes(f.name));
8888
}
8989

90-
async getProjectId(name: string): Promise<string> {
91-
return this.get(name).then((f) => f.project);
90+
async getProjectId(name: string | undefined): Promise<string | undefined> {
91+
if (name === undefined) {
92+
return Promise.resolve(undefined);
93+
}
94+
return Promise.resolve(this.get(name).then((f) => f.project));
9295
}
9396

9497
private getFilterQuery(query: Partial<IFeatureToggleStoreQuery>) {

src/lib/features/feature-toggle/feature-toggle-store.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ const FEATURE_COLUMNS = [
4444

4545
export interface FeaturesTable {
4646
name: string;
47-
description: string;
48-
type: string;
49-
stale: boolean;
47+
description: string | null;
48+
type?: string;
49+
stale?: boolean | null;
5050
project: string;
5151
last_seen_at?: Date;
5252
created_at?: Date;
53-
impression_data: boolean;
53+
impression_data?: boolean | null;
5454
archived?: boolean;
55-
archived_at?: Date;
55+
archived_at?: Date | null;
5656
created_by_user_id?: number;
5757
}
5858

@@ -472,13 +472,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
472472
insertToRow(project: string, data: FeatureToggleInsert): FeaturesTable {
473473
const row = {
474474
name: data.name,
475-
description: data.description,
475+
description: data.description || null,
476476
type: data.type,
477477
project,
478478
archived_at: data.archived ? new Date() : null,
479-
stale: data.stale,
479+
stale: data.stale || false,
480480
created_at: data.createdAt,
481-
impression_data: data.impressionData,
481+
impression_data: data.impressionData || false,
482482
created_by_user_id: data.createdByUserId,
483483
};
484484
if (!row.created_at) {
@@ -494,7 +494,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
494494
): Omit<FeaturesTable, 'created_by_user_id'> {
495495
const row = {
496496
name: data.name,
497-
description: data.description,
497+
description: data.description || null,
498498
type: data.type,
499499
project,
500500
archived_at: data.archived ? new Date() : null,

src/lib/features/feature-toggle/types/feature-toggle-store-type.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
2323

2424
setLastSeen(data: LastSeenInput[]): Promise<void>;
2525

26-
getProjectId(name: string): Promise<string | undefined>;
26+
getProjectId(name: string | undefined): Promise<string | undefined>;
2727

2828
create(project: string, data: FeatureToggleInsert): Promise<FeatureToggle>;
2929

src/lib/features/project/project-service.e2e.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2472,7 +2472,7 @@ test('should not delete project-bound api tokens still bound to project', async
24722472
await projectService.deleteProject(project1, user, auditUser);
24732473
const fetchedToken = await apiTokenService.getToken(token.secret);
24742474
expect(fetchedToken).not.toBeUndefined();
2475-
expect(fetchedToken.project).toBe(project2);
2475+
expect(fetchedToken!.project).toBe(project2);
24762476
});
24772477

24782478
test('should delete project-bound api tokens when all projects they belong to are deleted', async () => {

src/lib/services/api-token-service.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import { FUNCTION_TIME } from '../metric-events';
3636
import type { ResourceLimitsSchema } from '../openapi';
3737
import { throwExceedsLimitError } from '../error/exceeds-limit-error';
3838
import type EventEmitter from 'events';
39-
import { NotFoundError } from '../error';
4039

4140
const resolveTokenPermissions = (tokenType: string) => {
4241
if (tokenType === ApiTokenType.ADMIN) {
@@ -128,12 +127,8 @@ export class ApiTokenService {
128127
}
129128
}
130129

131-
async getToken(secret: string): Promise<IApiToken> {
132-
const token = await this.store.get(secret);
133-
if (token === undefined) {
134-
throw new NotFoundError();
135-
}
136-
return token;
130+
async getToken(secret: string): Promise<IApiToken | undefined> {
131+
return this.store.get(secret);
137132
}
138133

139134
async getTokenWithCache(secret: string): Promise<IApiToken | undefined> {

0 commit comments

Comments
 (0)