diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 9fe2894f51..2e05c80838 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -65,7 +65,7 @@ import { UserUnsubscribeStore } from '../features/user-subscriptions/user-unsubs import { UserSubscriptionsReadModel } from '../features/user-subscriptions/user-subscriptions-read-model'; import { UniqueConnectionStore } from '../features/unique-connection/unique-connection-store'; import { UniqueConnectionReadModel } from '../features/unique-connection/unique-connection-read-model'; -import FakeFeatureLinkStore from '../features/feature-links/fake-feature-link-store'; +import { FeatureLinkStore } from '../features/feature-links/feature-link-store'; export const createStores = ( config: IUnleashConfig, @@ -202,7 +202,7 @@ export const createStores = ( releasePlanMilestoneStore: new ReleasePlanMilestoneStore(db, config), releasePlanMilestoneStrategyStore: new ReleasePlanMilestoneStrategyStore(db, config), - featureLinkStore: new FakeFeatureLinkStore(), + featureLinkStore: new FeatureLinkStore(db, config), }; }; diff --git a/src/lib/features/feature-links/createFeatureLinkService.ts b/src/lib/features/feature-links/createFeatureLinkService.ts index b40f2fd921..10b2855c8c 100644 --- a/src/lib/features/feature-links/createFeatureLinkService.ts +++ b/src/lib/features/feature-links/createFeatureLinkService.ts @@ -1,7 +1,24 @@ import type { IUnleashConfig } from '../../types'; import FeatureLinkService from './feature-link-service'; import FakeFeatureLinkStore from './fake-feature-link-store'; -import { createFakeEventsService } from '../events/createEventsService'; +import { + createEventsService, + createFakeEventsService, +} from '../events/createEventsService'; +import type { Db } from '../../db/db'; +import { FeatureLinkStore } from './feature-link-store'; + +export const createFeatureLinkService = + (config: IUnleashConfig) => (db: Db) => { + const eventService = createEventsService(db, config); + const featureLinkStore = new FeatureLinkStore(db, config); + + return new FeatureLinkService( + { featureLinkStore }, + config, + eventService, + ); + }; export const createFakeFeatureLinkService = (config: IUnleashConfig) => { const eventService = createFakeEventsService(config); diff --git a/src/lib/features/feature-links/fake-feature-link-store.ts b/src/lib/features/feature-links/fake-feature-link-store.ts index d9cd2a32c2..4235cc23b5 100644 --- a/src/lib/features/feature-links/fake-feature-link-store.ts +++ b/src/lib/features/feature-links/fake-feature-link-store.ts @@ -7,7 +7,7 @@ import type { export default class FakeFeatureLinkStore implements IFeatureLinkStore { private links: IFeatureLink[] = []; - async create(link: Omit): Promise { + async insert(link: Omit): Promise { const newLink: IFeatureLink = { ...link, id: String(Math.random()), @@ -45,9 +45,13 @@ export default class FakeFeatureLinkStore implements IFeatureLinkStore { return this.links; } - async update(link: IFeatureLink): Promise { - await this.delete(link.id); - this.links.push(link); - return link; + async update( + id: string, + link: Omit, + ): Promise { + await this.delete(id); + const fullLink = { ...link, id }; + this.links.push(fullLink); + return fullLink; } } diff --git a/src/lib/features/feature-links/feature-link-service.ts b/src/lib/features/feature-links/feature-link-service.ts index 9b5e65dfbb..61a8c1246f 100644 --- a/src/lib/features/feature-links/feature-link-service.ts +++ b/src/lib/features/feature-links/feature-link-service.ts @@ -41,7 +41,7 @@ export default class FeatureLinkService { newLink: Omit, auditUser: IAuditUser, ): Promise { - const link = await this.featureLinkStore.create(newLink); + const link = await this.featureLinkStore.insert(newLink); await this.eventService.storeEvent( new FeatureLinkAddedEvent({ @@ -66,10 +66,7 @@ export default class FeatureLinkService { throw new NotFoundError(`Could not find link with id ${linkId}`); } - const link = await this.featureLinkStore.update({ - ...updatedLink, - id: linkId, - }); + const link = await this.featureLinkStore.update(linkId, updatedLink); await this.eventService.storeEvent( new FeatureLinkUpdatedEvent({ diff --git a/src/lib/features/feature-links/feature-link-store-type.ts b/src/lib/features/feature-links/feature-link-store-type.ts index ccc8141c5f..defb86ffb3 100644 --- a/src/lib/features/feature-links/feature-link-store-type.ts +++ b/src/lib/features/feature-links/feature-link-store-type.ts @@ -8,6 +8,6 @@ export interface IFeatureLink { } export interface IFeatureLinkStore extends Store { - create(link: Omit): Promise; - update(link: IFeatureLink): Promise; + insert(link: Omit): Promise; + update(id: string, link: Omit): Promise; } diff --git a/src/lib/features/feature-links/feature-link-store.ts b/src/lib/features/feature-links/feature-link-store.ts new file mode 100644 index 0000000000..8661f5d6f9 --- /dev/null +++ b/src/lib/features/feature-links/feature-link-store.ts @@ -0,0 +1,33 @@ +import type { Db } from '../../db/db'; +import type { IFeatureLinkStore } from '../../types'; +import type { IFeatureLink } from './feature-link-store-type'; +import { CRUDStore, type CrudStoreConfig } from '../../db/crud/crud-store'; +import type { Row } from '../../db/crud/row-type'; +import { ulid } from 'ulidx'; + +export class FeatureLinkStore + extends CRUDStore< + IFeatureLink, + Omit, + Row, + Row>, + string + > + implements IFeatureLinkStore +{ + constructor(db: Db, config: CrudStoreConfig) { + super('feature_link', db, config); + } + + async insert(item: Omit): Promise { + const id = ulid(); + const featureLink = { + id: ulid(), + feature_name: item.featureName, + url: item.url, + title: item.title, + }; + await this.db('feature_link').insert(featureLink); + return { ...item, id }; + } +} diff --git a/src/lib/features/feature-links/feature-link.e2e.test.ts b/src/lib/features/feature-links/feature-link.e2e.test.ts index 08f457a3dd..24a88eaabd 100644 --- a/src/lib/features/feature-links/feature-link.e2e.test.ts +++ b/src/lib/features/feature-links/feature-link.e2e.test.ts @@ -58,8 +58,90 @@ const addLink = async ( .expect(expectedCode); }; -test('should add feature links', async () => { +const updatedLink = async ( + featureName: string, + linkId: string, + link: FeatureLinkSchema, + expectedCode = 204, +) => { + return app.request + .put( + `/api/admin/projects/default/features/${featureName}/link/${linkId}`, + ) + .send(link) + .expect(expectedCode); +}; + +const deleteLink = async ( + featureName: string, + linkId: string, + expectedCode = 204, +) => { + return app.request + .delete( + `/api/admin/projects/default/features/${featureName}/link/${linkId}`, + ) + .expect(expectedCode); +}; + +test('should manage feature links', async () => { await app.createFeature('my_feature'); await addLink('my_feature', { url: 'example.com', title: 'feature link' }); + + const links = await featureLinkStore.getAll(); + expect(links).toMatchObject([ + { + url: 'example.com', + title: 'feature link', + featureName: 'my_feature', + }, + ]); + + await updatedLink('my_feature', links[0].id, { + url: 'example_updated.com', + title: 'feature link updated', + }); + + const updatedLinks = await featureLinkStore.getAll(); + expect(updatedLinks).toMatchObject([ + { + url: 'example_updated.com', + title: 'feature link updated', + featureName: 'my_feature', + }, + ]); + + await deleteLink('my_feature', links[0].id); + + const deletedLinks = await featureLinkStore.getAll(); + expect(deletedLinks).toMatchObject([]); + + const [event1, event2, event3] = await eventStore.getEvents(); + expect([event1, event2, event3]).toMatchObject([ + { + type: 'feature-link-removed', + data: null, + preData: { + url: 'example_updated.com', + title: 'feature link updated', + }, + featureName: 'my_feature', + project: 'default', + }, + { + type: 'feature-link-updated', + data: { url: 'example_updated.com', title: 'feature link updated' }, + preData: { url: 'example.com', title: 'feature link' }, + featureName: 'my_feature', + project: 'default', + }, + { + type: 'feature-link-added', + data: { url: 'example.com', title: 'feature link' }, + preData: null, + featureName: 'my_feature', + project: 'default', + }, + ]); }); diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 0cc8793be4..9fdc822b68 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -72,6 +72,7 @@ import { createFakeProjectService, createFakeUserSubscriptionsService, createFeatureLifecycleService, + createFeatureLinkService, createFeatureToggleService, createProjectService, createUserSubscriptionsService, @@ -425,9 +426,11 @@ export const createServices = ( ? withTransactional(createUserSubscriptionsService(config), db) : withFakeTransactional(createFakeUserSubscriptionsService(config)); - const transactionalFeatureLinkService = withFakeTransactional( - createFakeFeatureLinkService(config).featureLinkService, - ); + const transactionalFeatureLinkService = db + ? withTransactional(createFeatureLinkService(config), db) + : withFakeTransactional( + createFakeFeatureLinkService(config).featureLinkService, + ); return { transactionalAccessService,