Skip to content

Commit

Permalink
feat(project): review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLantukh committed Dec 5, 2023
1 parent 2f8dc58 commit 3359612
Show file tree
Hide file tree
Showing 14 changed files with 89 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ export const DEFAULT_FEATURES = {
};

export const EPG_TYPE = {
JW: 'JW',
JWP: 'JWP',
VIEW_NEXA: 'VIEW_NEXA',
} as const;
6 changes: 3 additions & 3 deletions src/hooks/useLiveChannels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Playlist } from '#types/playlist';
import livePlaylistFixture from '#test/fixtures/livePlaylist.json';
import epgChannelsFixture from '#test/fixtures/epgChannels.json';
import epgChannelsUpdateFixture from '#test/fixtures/epgChannelsUpdate.json';
import EpgClientService from '#src/services/epg/epgClient.service';
import EpgController from '#src/stores/EpgController';

const livePlaylist: Playlist = livePlaylistFixture;
const schedule: EpgChannel[] = epgChannelsFixture;
Expand All @@ -17,9 +17,9 @@ const scheduleUpdate: EpgChannel[] = epgChannelsUpdateFixture;
const mockSchedule = vi.fn();

vi.mock('#src/modules/container', () => ({
getModule: (type: typeof EpgClientService) => {
getModule: (type: typeof EpgController) => {
switch (type) {
case EpgClientService:
case EpgController:
return { getSchedules: mockSchedule };
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useLiveChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { EpgProgram, EpgChannel } from '#types/epg';
import { getLiveProgram, programIsLive } from '#src/utils/epg';
import { LIVE_CHANNELS_REFETCH_INTERVAL } from '#src/config';
import { getModule } from '#src/modules/container';
import EpgClientService from '#src/services/epg/epgClient.service';
import EpgController from '#src/stores/EpgController';

/**
* This hook fetches the schedules for the given list of playlist items and manages the current channel and program.
Expand All @@ -31,9 +31,9 @@ const useLiveChannels = ({
initialChannelId: string | undefined;
enableAutoUpdate?: boolean;
}) => {
const epgService = getModule(EpgClientService);
const epgController = getModule(EpgController);

const { data: channels = [] } = useQuery(['schedules', ...playlist.map(({ mediaid }) => mediaid)], () => epgService.getSchedules(playlist), {
const { data: channels = [] } = useQuery(['schedules', ...playlist.map(({ mediaid }) => mediaid)], () => epgController.getSchedules(playlist), {
refetchInterval: LIVE_CHANNELS_REFETCH_INTERVAL,
});

Expand Down
14 changes: 7 additions & 7 deletions src/modules/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T
return module;
}

export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, integration: string | null, required: false): T | undefined;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, integration: string | null, required: true): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, integration: string | null): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, integration: string | null, required = true): T | undefined {
if (!integration) {
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: false): T | undefined;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: true): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required = true): T | undefined {
if (!name) {
return;
}

let module;

try {
module = container.getAllNamed(constructorFunction, integration)[0];
module = container.getAllNamed(constructorFunction, name)[0];

return module;
} catch (err: unknown) {
if (required) {
throw new Error(`Service not found '${String(constructorFunction)}' with name '${integration}'`);
throw new Error(`Service not found '${String(constructorFunction)}' with name '${name}'`);
}
}
}
Expand Down
26 changes: 10 additions & 16 deletions src/modules/register.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// To organize imports in a better way
/* eslint-disable import/order */
import 'reflect-metadata'; // include once in the app for inversify (see: https://github.com/inversify/InversifyJS/blob/master/README.md#-installation)
import { EPG_TYPE, INTEGRATION } from '#src/config';
import { INTEGRATION } from '#src/config';
import { container } from '#src/modules/container';

import ApiService from '#src/services/api.service';
Expand All @@ -13,17 +13,17 @@ import ConfigService from '#src/services/config.service';
import SettingsService from '#src/services/settings.service';

// Epg services
import EpgClientService from '#src/services/epg/epgClient.service';
import EpgProviderService from '#src/services/epg/epgProvider.service';
import ViewNexaEpgService from '#src/services/epg/viewNexaEpg.service';
import JWEpgService from '#src/services/epg/jwEpg.service';
import EpgService from '#src/services/epg/epg.service';
import ViewNexaEpgService from '#src/services/epg/viewNexa.epg.service';
import JWEpgService from '#src/services/epg/jw.epg.service';

import WatchHistoryController from '#src/stores/WatchHistoryController';
import CheckoutController from '#src/stores/CheckoutController';
import AccountController from '#src/stores/AccountController';
import ProfileController from '#src/stores/ProfileController';
import FavoritesController from '#src/stores/FavoritesController';
import AppController from '#src/stores/AppController';
import EpgController from '#src/stores/EpgController';

// Integration interfaces
import AccountService from '#src/services/account.service';
Expand Down Expand Up @@ -51,6 +51,10 @@ container.bind(GenericEntitlementService).toSelf();
container.bind(ApiService).toSelf();
container.bind(SettingsService).toSelf();

// EPG services
container.bind(EpgService).to(JWEpgService);
container.bind(EpgService).to(ViewNexaEpgService);

// Common controllers
container.bind(AppController).toSelf();
container.bind(WatchHistoryController).toSelf();
Expand All @@ -60,6 +64,7 @@ container.bind(FavoritesController).toSelf();
container.bind(AccountController).toSelf();
container.bind(CheckoutController).toSelf();
container.bind(ProfileController).toSelf();
container.bind(EpgController).toSelf();

container.bind('INTEGRATION_TYPE').toDynamicValue((context) => {
return context.container.get(AppController).getIntegrationType();
Expand All @@ -77,14 +82,3 @@ container.bind(AccountService).to(InplayerAccountService).whenTargetNamed(INTEGR
container.bind(CheckoutService).to(InplayerCheckoutService).whenTargetNamed(INTEGRATION.JWP);
container.bind(SubscriptionService).to(InplayerSubscriptionService).whenTargetNamed(INTEGRATION.JWP);
container.bind(ProfileService).to(InplayerProfileService).whenTargetNamed(INTEGRATION.JWP);

// EPG integration
container.bind(EpgClientService).toSelf();
container
.bind(EpgProviderService)
.to(ViewNexaEpgService)
.when((request) => request.target.name.equals(EPG_TYPE.VIEW_NEXA));
container
.bind(EpgProviderService)
.to(JWEpgService)
.when((request) => request.target.name.equals(EPG_TYPE.JW));
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { EpgProgram } from '#types/epg';
import type { EpgProgram, EpgScheduleType } from '#types/epg';
import type { PlaylistItem } from '#types/playlist';

export default abstract class EpgProviderService {
readonly type: EpgScheduleType;

protected constructor(type: EpgScheduleType) {
this.type = type;
}

/**
* Fetch the schedule data for the given PlaylistItem
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect } from 'vitest';
import { mockFetch, mockGet } from 'vi-fetch';
import { unregister } from 'timezone-mock';

import JWEpgService from './jwEpg.service';
import JWEpgService from './jw.epg.service';

import livePlaylistFixture from '#test/fixtures/livePlaylist.json';
import type { Playlist } from '#types/playlist';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { array, object, string } from 'yup';
import { isValid } from 'date-fns';
import { injectable } from 'inversify';

import EpgProviderService from './epgProvider.service';
import EpgService from './epg.service';

import type { PlaylistItem } from '#types/playlist';
import { getDataOrThrow } from '#src/utils/api';
import { logDev } from '#src/utils/common';
import type { EpgProgram } from '#types/epg';
import { EPG_TYPE } from '#src/config';

const AUTHENTICATION_HEADER = 'API-KEY';

Expand All @@ -29,7 +30,11 @@ const jwEpgProgramSchema = object().shape({
});

@injectable()
export default class JWEpgService extends EpgProviderService {
export default class JWEpgService extends EpgService {
constructor() {
super(EPG_TYPE.JWP);
}

transformProgram = async (data: unknown): Promise<EpgProgram> => {
const program = await jwEpgProgramSchema.validate(data);
const image = program.chapterPointCustomProperties?.find((item) => item.key === 'image')?.value || undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect } from 'vitest';
import { mockFetch, mockGet } from 'vi-fetch';
import { unregister } from 'timezone-mock';

import ViewNexaEpgService from './viewNexaEpg.service';
import ViewNexaEpgService from './viewNexa.epg.service';

import viewNexaChannel from '#test/epg/viewNexaChannel.xml?raw';
import livePlaylistFixture from '#test/fixtures/livePlaylist.json';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { object, string } from 'yup';
import { parse } from 'date-fns';
import { injectable } from 'inversify';

import EpgProviderService from './epgProvider.service';
import EpgService from './epg.service';

import type { PlaylistItem } from '#types/playlist';
import { logDev } from '#src/utils/common';
import type { EpgProgram } from '#types/epg';
import { EPG_TYPE } from '#src/config';

const viewNexaEpgProgramSchema = object().shape({
'episode-num': object().shape({
Expand All @@ -28,7 +29,11 @@ const viewNexaEpgProgramSchema = object().shape({
const parseData = (date: string): string => parse(date, 'yyyyMdHms xxxx', new Date()).toISOString();

@injectable()
export default class ViewNexaEpgService extends EpgProviderService {
export default class ViewNexaEpgService extends EpgService {
constructor() {
super(EPG_TYPE.VIEW_NEXA);
}

/**
* Validate the given data with the viewNexaProgramSchema and transform it into an EpgProgram
*/
Expand All @@ -38,11 +43,11 @@ export default class ViewNexaEpgService extends EpgProviderService {
return {
id: program['episode-num']['#text'],
title: program['title']['#text'],
description: program['desc']['#text'],
startTime: parseData(program['start']),
endTime: parseData(program['stop']),
cardImage: program['icon']['src'],
backgroundImage: program['icon']['src'],
description: program?.['desc']?.['#text'],
cardImage: program?.['icon']?.['src'],
backgroundImage: program?.['icon']?.['src'],
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { afterEach, beforeEach, describe, expect } from 'vitest';
import { unregister } from 'timezone-mock';

import EpgClientService from './epgClient.service';
import type EpgProviderService from './epgProvider.service';
import EpgController from './EpgController';

import channel1 from '#test/epg/jwChannel.json';
import livePlaylistFixture from '#test/fixtures/livePlaylist.json';
Expand All @@ -14,8 +13,18 @@ const livePlaylist = livePlaylistFixture as Playlist;
const transformProgram = vi.fn();
const fetchSchedule = vi.fn();

const epgProvider = { transformProgram, fetchSchedule };
const epgService = new EpgClientService(epgProvider, epgProvider);
const epgService = { transformProgram, fetchSchedule };
const jwpEpgService = {
...epgService,
type: EPG_TYPE.JWP,
};

const viewNexaEpgService = {
...epgService,
type: EPG_TYPE.VIEW_NEXA,
};

const epgController = new EpgController([jwpEpgService, viewNexaEpgService]);

const mockProgram1 = {
id: 'test',
Expand All @@ -37,19 +46,6 @@ const mockProgram2 = {
description: 'Description',
};

vi.mock('#src/modules/container', () => ({
getNamedModule: (_service: EpgProviderService, type: string) => {
switch (type) {
case EPG_TYPE.JW:
case EPG_TYPE.VIEW_NEXA:
return {
transformProgram,
fetchSchedule,
};
}
},
}));

describe('epgService', () => {
beforeEach(() => {
vi.useFakeTimers();
Expand All @@ -76,7 +72,7 @@ describe('epgService', () => {
fetchSchedule.mockResolvedValue(channel1);
transformProgram.mockResolvedValue(mockProgram);

const schedule = await epgService.getSchedule(livePlaylist.playlist[0]);
const schedule = await epgController.getSchedule(livePlaylist.playlist[0]);

expect(schedule.title).toEqual('Channel 1');
expect(schedule.programs.length).toEqual(33);
Expand All @@ -94,7 +90,7 @@ describe('epgService', () => {
const item = Object.assign({}, livePlaylist.playlist[0]);
item.scheduleDemo = '1';

const schedule = await epgService.getSchedule(item);
const schedule = await epgController.getSchedule(item);

expect(schedule.title).toEqual('Channel 1');
expect(schedule.programs[0].startTime).toEqual('2036-06-03T23:50:00.000Z');
Expand All @@ -116,7 +112,7 @@ describe('epgService', () => {
transformProgram.mockRejectedValueOnce(undefined);
transformProgram.mockResolvedValueOnce(mockProgram2);

const schedule = await epgService.parseSchedule([scheduleItem, scheduleItem, scheduleItem, scheduleItem], livePlaylist.playlist[0]);
const schedule = await epgController.parseSchedule([scheduleItem, scheduleItem, scheduleItem, scheduleItem], livePlaylist.playlist[0]);

expect(schedule.length).toEqual(2);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { addDays, differenceInDays } from 'date-fns';
import { injectable, targetName } from 'inversify';

import EpgProviderService from './epgProvider.service';
import { injectable, multiInject } from 'inversify';

import EpgService from '#src/services/epg/epg.service';
import { logDev } from '#src/utils/common';
import { EPG_TYPE } from '#src/config';
import type { PlaylistItem } from '#types/playlist';
import type { EpgProgram, EpgChannel } from '#types/epg';
import { EPG_TYPE } from '#src/config';

export const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => {
if (input.status === 'fulfilled') {
Expand All @@ -18,13 +17,11 @@ export const isFulfilled = <T>(input: PromiseSettledResult<T>): input is Promise
};

@injectable()
export default class EpgClientService {
private viewNexaProvider: EpgProviderService;
private jwProvider: EpgProviderService;
export default class EpgController {
private epgServices: EpgService[];

public constructor(@targetName(EPG_TYPE.JW) jwProvider: EpgProviderService, @targetName(EPG_TYPE.VIEW_NEXA) viewNexaProvider: EpgProviderService) {
this.viewNexaProvider = viewNexaProvider;
this.jwProvider = jwProvider;
public constructor(@multiInject(EpgService) epgServices: EpgService[]) {
this.epgServices = epgServices || [];
}

/**
Expand Down Expand Up @@ -55,7 +52,7 @@ export default class EpgClientService {
parseSchedule = async (data: unknown, item: PlaylistItem) => {
const demo = !!item.scheduleDemo || false;

const epgService = this.getScheduleProvider(item);
const epgService = this.getEpgService(item);

if (!Array.isArray(data)) return [];

Expand Down Expand Up @@ -84,7 +81,7 @@ export default class EpgClientService {
* When there is no program (empty schedule) or the request fails, it returns a static program.
*/
getSchedule = async (item: PlaylistItem) => {
const epgService = this.getScheduleProvider(item);
const epgService = this.getEpgService(item);

const schedule = await epgService.fetchSchedule(item);
const programs = await this.parseSchedule(schedule, item);
Expand All @@ -102,8 +99,15 @@ export default class EpgClientService {
} as EpgChannel;
};

getScheduleProvider = (item: PlaylistItem) => {
return item?.scheduleType === EPG_TYPE.VIEW_NEXA ? this.viewNexaProvider : this.jwProvider;
getEpgService = (item: PlaylistItem) => {
const scheduleType = item?.scheduleType || EPG_TYPE.JWP;
const service = this.epgServices.find((service) => service.type === scheduleType);

if (!service) {
throw Error(`No epg service was added for the ${scheduleType} schedule type`);
}

return service;
};

/**
Expand Down
Loading

0 comments on commit 3359612

Please sign in to comment.