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:
parent
ce6d2e56bc
commit
3029564304
@ -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
|
||||
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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[]>;
|
||||
}
|
114
src/test/fixtures/fake-suggest-change-store.ts
vendored
114
src/test/fixtures/fake-suggest-change-store.ts
vendored
@ -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);
|
||||
}
|
||||
}
|
2
src/test/fixtures/store.ts
vendored
2
src/test/fixtures/store.ts
vendored
@ -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(),
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user