1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

Feat/enterprise stores (#2289)

* feat: add capabilities for stores in enterprise

* fix: remove unused files
This commit is contained in:
Fredrik Strand Oseberg 2022-10-31 10:35:59 +01:00 committed by GitHub
parent ce6d2e56bc
commit 3029564304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 5 additions and 444 deletions

View File

@ -24,12 +24,14 @@ import { loadIndexHTML } from './util/load-index-html';
import { findPublicFolder } from './util/findPublicFolder';
import { conditionalMiddleware } from './middleware/conditional-middleware';
import patMiddleware from './middleware/pat-middleware';
import { Knex } from 'knex';
export default async function getApp(
config: IUnleashConfig,
stores: IUnleashStores,
services: IUnleashServices,
unleashSession?: RequestHandler,
db?: Knex,
): Promise<Application> {
const app = express();
@ -48,7 +50,7 @@ export default async function getApp(
app.use(requestLogger(config));
if (typeof config.preHook === 'function') {
config.preHook(app, config, services);
config.preHook(app, config, services, db);
}
app.use(compression());
@ -137,7 +139,7 @@ export default async function getApp(
);
if (typeof config.preRouterHook === 'function') {
config.preRouterHook(app, config, services, stores);
config.preRouterHook(app, config, services, stores, db);
}
// Setup API routes

View File

@ -32,7 +32,6 @@ import SegmentStore from './segment-store';
import GroupStore from './group-store';
import PatStore from './pat-store';
import { PublicSignupTokenStore } from './public-signup-token-store';
import { SuggestChangeStore } from './suggest-change-store';
export const createStores = (
config: IUnleashConfig,
@ -93,7 +92,6 @@ export const createStores = (
getLogger,
),
patStore: new PatStore(db, getLogger),
suggestChangeStore: new SuggestChangeStore(db, eventBus, getLogger),
};
};

View File

@ -1,277 +0,0 @@
import { ISuggestChangeStore } from '../types/stores/suggest-change-store';
import { Logger, LogProvider } from '../logger';
import EventEmitter from 'events';
import { Knex } from 'knex';
import { PartialSome } from '../types/partial';
import {
ISuggestChange,
ISuggestChangeset,
SuggestChangeAction,
SuggestChangesetState,
} from '../types/model';
const T = {
SUGGEST_CHANGE: 'suggest_change',
SUGGEST_CHANGE_SET: 'suggest_change_set',
};
interface ISuggestChangesetRow {
id: number;
state: SuggestChangesetState;
environment: string;
project: string;
created_at: Date;
created_by: number;
changeSetUsername: string;
changeSetAvatar: string;
changeId: number;
changeFeature: string;
changeAction: SuggestChangeAction;
changePayload: any;
changeCreatedAt: Date;
changeCreatedBy: number;
changeCreatedByUsername: string;
changeCreatedByAvatar: string;
}
const suggestChangeRowReducer = (
acc: Record<number, ISuggestChangeset>,
suggestChangeRow: ISuggestChangesetRow,
): Record<number, ISuggestChangeset> => {
const {
changeId,
changeAction,
changePayload,
changeFeature,
changeCreatedByUsername,
changeCreatedByAvatar,
changeCreatedAt,
changeCreatedBy,
...suggestChangeSet
} = suggestChangeRow;
if (!acc[suggestChangeRow.id]) {
acc[suggestChangeRow.id] = {
id: suggestChangeSet.id,
environment: suggestChangeSet.environment,
state: suggestChangeSet.state,
project: suggestChangeSet.project,
createdBy: {
id: suggestChangeSet.created_by,
username: suggestChangeSet.changeSetUsername,
imageUrl: suggestChangeSet.changeSetAvatar,
},
createdAt: suggestChangeSet.created_at,
features: [],
};
}
const currentSuggestChangeSet = acc[suggestChangeSet.id];
if (changeId) {
const featureObject = currentSuggestChangeSet.features.find(
(feature) => feature.name === changeFeature,
);
const change = {
id: changeId,
action: changeAction,
payload: changePayload,
createdAt: changeCreatedAt,
createdBy: {
id: changeCreatedBy,
username: changeCreatedByUsername,
imageUrl: changeCreatedByAvatar,
},
};
if (featureObject) {
featureObject.changes.push(change);
} else {
currentSuggestChangeSet.features.push({
name: changeFeature,
changes: [change],
});
}
}
return acc;
};
export class SuggestChangeStore implements ISuggestChangeStore {
private logger: Logger;
private eventBus: EventEmitter;
private db: Knex;
constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
this.db = db;
this.eventBus = eventBus;
this.logger = getLogger('lib/db/suggest-change-store.ts');
}
private buildSuggestChangeSetChangesQuery = () => {
return this.db<ISuggestChangesetRow>(
`${T.SUGGEST_CHANGE_SET} as changeSet`,
)
.leftJoin(
`users as changeSetUser`,
'changeSet.created_by',
'changeSetUser.id',
)
.leftJoin(`projects`, 'projects.id', 'changeSet.project')
.leftJoin(
`${T.SUGGEST_CHANGE} as change`,
'changeSet.id',
'change.suggest_change_set_id',
)
.leftJoin(
`users as changeUser`,
'change.created_by',
'changeUser.id',
)
.select(
'changeSet.state',
'changeSet.id',
'changeSet.environment',
'projects.name as project',
'changeSet.created_at',
'changeSet.created_by',
'changeSetUser.username as changeSetUsername',
'changeSetUser.image_url as changeSetAvatar',
'change.id as changeId',
'change.feature as changeFeature',
'change.action as changeAction',
'change.payload as changePayload',
'change.created_at as changeCreatedAt',
'change.created_by as changeCreatedBy',
'changeUser.username as changeCreatedByUsername',
'changeUser.image_url as changeCreatedByAvatar',
);
};
getAll = async (): Promise<ISuggestChangeset[]> => {
const rows = await this.buildSuggestChangeSetChangesQuery();
return this.mapRows(rows);
};
getForProject = async (project: string): Promise<ISuggestChangeset[]> => {
const rows = await this.buildSuggestChangeSetChangesQuery()
.where({
project,
})
.whereNot('state', SuggestChangesetState.DRAFT);
return this.mapRows(rows);
};
getDraftsForUser = async (
userId: number,
project: string,
): Promise<ISuggestChangeset[]> => {
const rows = await this.buildSuggestChangeSetChangesQuery().where({
'changeSet.created_by': userId,
state: SuggestChangesetState.DRAFT,
project: project,
});
const changesets = this.mapRows(rows);
return changesets;
};
getForEnvironment = async (
environment: string,
): Promise<ISuggestChangeset[]> => {
const rows = await this.buildSuggestChangeSetChangesQuery().where({
environment,
});
return this.mapRows(rows);
};
get = async (id: number): Promise<ISuggestChangeset> => {
const rows = await this.buildSuggestChangeSetChangesQuery().where({
'changeSet.id': id,
});
return this.mapRows(rows)[0];
};
create = async (
suggestChangeSet: PartialSome<
ISuggestChangeset,
'id' | 'createdBy' | 'createdAt'
>,
userId: number,
): Promise<ISuggestChangeset> => {
const [{ id }] = await this.db(T.SUGGEST_CHANGE_SET)
.insert({
environment: suggestChangeSet.environment,
state: suggestChangeSet.state,
project: suggestChangeSet.project,
created_by: userId,
})
.returning('id');
suggestChangeSet.features.forEach((feature) => {
feature.changes.forEach((change) => {
this.addChangeToSet(change, feature.name, id, userId);
});
});
return this.get(id);
};
addChangeToSet = async (
change: ISuggestChange,
feature: string,
changeSetID: number,
userId: number,
): Promise<void> => {
await this.db(T.SUGGEST_CHANGE)
.insert({
action: change.action,
feature: feature,
payload: change.payload,
suggest_change_set_id: changeSetID,
created_by: userId,
})
.onConflict(['action', 'suggest_change_set_id', 'feature'])
.merge()
.returning('id');
};
delete = (id: number): Promise<void> => {
return this.db(T.SUGGEST_CHANGE_SET).where({ id }).del();
};
deleteChange = (id: number): Promise<void> => {
return this.db(T.SUGGEST_CHANGE).where({ id }).del();
};
deleteAll = (): Promise<void> => {
return this.db(T.SUGGEST_CHANGE_SET).del();
};
exists = async (id: number): Promise<boolean> => {
const result = await this.db.raw(
`SELECT EXISTS(SELECT 1 FROM ${T.SUGGEST_CHANGE_SET} WHERE id = ?) AS present`,
[id],
);
return result.rows[0].present;
};
mapRows = (rows?: ISuggestChangesetRow[]): ISuggestChangeset[] => {
const suggestChangeSets = rows.reduce<
Record<number, ISuggestChangeset>
>(suggestChangeRowReducer, {});
return Object.values(suggestChangeSets);
};
destroy(): void {}
async updateState(
id: number,
state: SuggestChangesetState,
): Promise<ISuggestChangeset> {
await this.db(T.SUGGEST_CHANGE_SET)
.update('state', state)
.where({ id });
return this.get(id);
}
}

View File

@ -57,7 +57,7 @@ async function createApp(
// eslint-disable-next-line no-param-reassign
config.server.secret = secret;
}
const app = await getApp(config, stores, services, unleashSession);
const app = await getApp(config, stores, services, unleashSession, db);
if (typeof config.eventHook === 'function') {
addEventHook(config.eventHook, stores.eventStore);

View File

@ -28,7 +28,6 @@ import { ISegmentStore } from './stores/segment-store';
import { IGroupStore } from './stores/group-store';
import { IPatStore } from './stores/pat-store';
import { IPublicSignupTokenStore } from './stores/public-signup-token-store';
import { ISuggestChangeStore } from './stores/suggest-change-store';
export interface IUnleashStores {
accessStore: IAccessStore;
@ -61,5 +60,4 @@ export interface IUnleashStores {
segmentStore: ISegmentStore;
patStore: IPatStore;
publicSignupTokenStore: IPublicSignupTokenStore;
suggestChangeStore: ISuggestChangeStore;
}

View File

@ -1,44 +0,0 @@
import { Store } from './store';
import {
ISuggestChange,
ISuggestChangeset,
SuggestChangesetState,
} from '../model';
import { PartialSome } from '../partial';
export interface ISuggestChangeStore extends Store<ISuggestChangeset, number> {
create(
suggestChangeSet: PartialSome<
ISuggestChangeset,
'id' | 'createdBy' | 'createdAt'
>,
userId: number,
): Promise<ISuggestChangeset>;
addChangeToSet(
change: PartialSome<ISuggestChange, 'id' | 'createdBy' | 'createdAt'>,
feature: string,
changeSetID: number,
userId: number,
): Promise<void>;
get(id: number): Promise<ISuggestChangeset>;
deleteChange(changeId: number): Promise<void>;
updateState(
id: number,
state: SuggestChangesetState,
): Promise<ISuggestChangeset>;
getAll(): Promise<ISuggestChangeset[]>;
getForProject(project: string): Promise<ISuggestChangeset[]>;
getDraftsForUser(
userId: number,
project: string,
): Promise<ISuggestChangeset[]>;
getForEnvironment(environment: string): Promise<ISuggestChangeset[]>;
}

View File

@ -1,114 +0,0 @@
import { ISuggestChangeStore } from '../../lib/types/stores/suggest-change-store';
import {
ISuggestChange,
ISuggestChangeset,
SuggestChangesetState,
} from '../../lib/types/model';
import { PartialSome } from '../../lib/types/partial';
export default class FakeSuggestChangeStore implements ISuggestChangeStore {
suggestChanges: ISuggestChangeset[] = [];
async get(id: number): Promise<ISuggestChangeset> {
const change = this.suggestChanges.find((c) => c.id === id);
return Promise.resolve(change);
}
async count(): Promise<number> {
return Promise.resolve(0);
}
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
async delete(id: number): Promise<void> {
return Promise.resolve(undefined);
}
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
async deleteChange(id: number): Promise<void> {
return Promise.resolve(undefined);
}
addChangeToSet(
change: ISuggestChange,
feature: string,
changeSetID: number,
userId: number,
): Promise<void> {
const changeSet = this.suggestChanges.find((s) => s.id === changeSetID);
changeSet.features.push({
name: feature,
changes: [
{
createdBy: { id: userId, username: '', imageUrl: '' },
...change,
},
],
});
return Promise.resolve();
}
getForEnvironment(environment: string): Promise<ISuggestChangeset[]> {
return Promise.resolve(
this.suggestChanges.filter(
(changeSet) => changeSet.environment === environment,
),
);
}
getDraftsForUser(
userId: number,
project: string,
): Promise<ISuggestChangeset[]> {
return Promise.resolve(
this.suggestChanges.filter(
(changeSet) =>
changeSet.project === project &&
changeSet.createdBy.id === userId,
),
);
}
getForProject(project: string): Promise<ISuggestChangeset[]> {
return Promise.resolve(
this.suggestChanges.filter(
(changeSet) => changeSet.project === project,
),
);
}
create(
suggestChangeSet: PartialSome<ISuggestChangeset, 'id'>,
userId: number,
): Promise<ISuggestChangeset> {
this.suggestChanges.push({
id: 1,
...suggestChangeSet,
createdBy: { id: userId, username: '', imageUrl: '' },
});
return Promise.resolve(undefined);
}
getAll(): Promise<ISuggestChangeset[]> {
return Promise.resolve([]);
}
deleteAll(): Promise<void> {
return Promise.resolve(undefined);
}
destroy(): void {}
exists(key: number): Promise<boolean> {
return Promise.resolve(Boolean(key));
}
updateState(
id: number,
state: SuggestChangesetState,
): Promise<ISuggestChangeset> {
const changeSet = this.suggestChanges.find((s) => s.id === id);
changeSet.state = state;
return Promise.resolve(undefined);
}
}

View File

@ -29,7 +29,6 @@ import FakeSegmentStore from './fake-segment-store';
import FakeGroupStore from './fake-group-store';
import FakePatStore from './fake-pat-store';
import FakePublicSignupStore from './fake-public-signup-store';
import FakeSuggestChangeStore from './fake-suggest-change-store';
const createStores: () => IUnleashStores = () => {
const db = {
@ -70,7 +69,6 @@ const createStores: () => IUnleashStores = () => {
groupStore: new FakeGroupStore(),
patStore: new FakePatStore(),
publicSignupTokenStore: new FakePublicSignupStore(),
suggestChangeStore: new FakeSuggestChangeStore(),
};
};